pragma solidity 0.8.20; import {Test} from "forge-std/Test.sol"; import {Gatekeeper} from "../../src/Gatekeeper.sol"; import {FullMath} from "../../src/libraries/FullMath.sol"; import {RequestPacking} from "../../src/libraries/Packing.sol"; import {IStorageHistory} from "../../src/interfaces/IStorageHistory.sol"; contract MockStaking is Test { Gatekeeper public gatekeeper; mapping(address => uint256) private _materializedAmounts; constructor(uint256 existential) { gatekeeper = new Gatekeeper(existential, address(0)); } function runGhost(bytes32 receiver, uint256 amount) external { gatekeeper.ghost(receiver, amount); } function runMaterialize( uint256 exodusSession, uint256 amount, uint256 packed, address caller ) external payable { if (msg.value > 0) { vm.deal(address(gatekeeper), address(gatekeeper).balance + msg.value); } vm.prank(address(gatekeeper), caller); gatekeeper.materialize{value: msg.value}(exodusSession, amount, packed); } function materialize(address receiver, uint256 amount) external { _materializedAmounts[receiver] += amount; } function materializedAmount(address who) external view returns (uint256) { return _materializedAmounts[who]; } function totalReserves() external pure returns (uint256) { return type(uint256).max; } function baseSupply() external pure returns (uint256) { return type(uint256).max; } } contract GatekeeperTest is Test { using RequestPacking for RequestPacking.RequestPayload; address constant ALICE = 0x0000000000000000000000000000000000000001; address constant BOB = 0x0000000000000000000000000000000000000002; uint256 constant EXISTENTIAL = 1337; uint256 constant INIT_AMOUNT = 69 * 1e18; Gatekeeper gatekeeper; MockStaking staking; event Ghosted(bytes32 indexed receiver, uint256 indexed amount); function setUp() public { vm.prank(ALICE, ALICE); staking = new MockStaking(EXISTENTIAL); gatekeeper = staking.gatekeeper(); } function test_correctInitialization() public view { assertEq(gatekeeper.staking(), address(staking)); assertEq(gatekeeper.ghostedSupply(), 0); } function test_ghostTokensWork(uint256 ghostAmount) public { vm.assume(ghostAmount >= EXISTENTIAL && ghostAmount < type(uint96).max); bytes32 receiver = bytes32(abi.encodePacked(ALICE)); uint256 ghostedSupply = gatekeeper.ghostedSupply(); vm.prank(ALICE); staking.runGhost(receiver, ghostAmount); assertEq(gatekeeper.ghostedSupply(), ghostedSupply + ghostAmount); } function test_couldNotGhostTokensFromArbitraryAddress(address someone) public { vm.assume(someone != ALICE); bytes32 receiver = bytes32(abi.encodePacked(ALICE)); vm.expectRevert(); vm.prank(someone); staking.runGhost(receiver, 69); assertEq(gatekeeper.ghostedSupply(), 0); } function test_ghostTokensEmitsEvent(uint256 ghostAmount) public { vm.assume(ghostAmount >= EXISTENTIAL); bytes32 receiver = bytes32(abi.encodePacked(ALICE)); vm.expectEmit(true, true, true, false, address(gatekeeper)); emit Ghosted(receiver, ghostAmount); vm.prank(ALICE); staking.runGhost(receiver, ghostAmount); } function test_materializeWork(uint256 exodusSession, uint256 bobAmount, uint32 bobCommission) public { vm.assume(bobAmount > 1337 && bobAmount < 1_000 ether); vm.assume(bobCommission > 0 && bobCommission <= type(uint32).max); address storageHistory = gatekeeper.storageHistory(); uint256 commissionAmount = FullMath.mulDiv(bobAmount, uint256(bobCommission), type(uint32).max); uint256 bobPacked = RequestPacking.pack(uint32(bobCommission), uint64(block.chainid), BOB); vm.prank(ALICE); staking.runGhost(bytes32(abi.encodePacked(ALICE)), bobAmount); uint256 nativeNeeded = FullMath.mulDiv(commissionAmount, staking.totalReserves(), staking.baseSupply()); vm.deal(ALICE, nativeNeeded + 1 ether); assertEq(BOB.balance, 0 ether); uint256 aliceStartingNative = ALICE.balance; uint256 previousAliceAmount = staking.materializedAmount(ALICE); uint256 previousBobAmount = staking.materializedAmount(BOB); vm.prank(ALICE); staking.runMaterialize{value: nativeNeeded}(exodusSession, bobAmount, bobPacked, ALICE); assertApproxEqAbs(previousAliceAmount + commissionAmount, staking.materializedAmount(ALICE), 1000); assertApproxEqAbs(previousBobAmount + (bobAmount - commissionAmount), staking.materializedAmount(BOB), 1000); assertEq(BOB.balance, nativeNeeded); assertEq(ALICE.balance, aliceStartingNative - nativeNeeded); assert(IStorageHistory(storageHistory).isTransactionExecuted(exodusSession)); } function test_rotateWork(bytes32 aggregatedPublicKey) public { vm.warp(block.timestamp + 1); vm.prank(address(gatekeeper)); gatekeeper.rotate(block.timestamp, aggregatedPublicKey, 0); } function test_couldNotBridgeBelowExistential(uint256 amount) public { vm.assume(amount < EXISTENTIAL); bytes32 receiver = bytes32(abi.encodePacked(ALICE)); vm.expectRevert(); vm.prank(ALICE); staking.runGhost(receiver, amount); assertEq(gatekeeper.ghostedSupply(), 0); } function test_signatureVerificationsWorks() public { assertEq(gatekeeper.deployer(), ALICE); vm.prank(ALICE); staking.runGhost(bytes32(abi.encodePacked(ALICE)), type(uint104).max); vm.prank(ALICE); gatekeeper.updatePublicKeyMetadata(0, 0x875cdcba4ae5494518fa2602791e667ca0998402b6a69e5b7cb4c72dba4e4669, 0); (bytes32 prevPublicKey, uint8 prevParity,) = gatekeeper.latestPublicKeyInfo(); // genesis validators 4 validators, DKG #0 // exodus session #0 gatekeeper.verify( hex"49f03a670000000000000000000000000000000000000000000000000000000000000000875cdcba4ae5494518fa2602791e667ca0998402b6a69e5b7cb4c72dba4e46690000000000000000000000000000000000000000000000000000000000000000", hex"044d9fa18df9da04381ed256981570a6ef6a0893496ded966fc994662df931ed423b29e2737394b31d8498594caad7b77e36ab31ac79df3796a1b6f26baa5552ae", 0x24029a571fcaeadd14f4afe8be62afe3d07876ae270c3caf446ecb6cfc7c08f2 ); (bytes32 publicKey, uint8 parity, uint64 session) = gatekeeper.latestPublicKeyInfo(); assertEq(publicKey, prevPublicKey); assertEq(parity, prevParity); assertEq(session, 0); // could not apply signature from DKG #2 vm.expectRevert(); gatekeeper.verify( hex"49f03a6700000000000000000000000000000000000000000000000000000000000000043492bc8bb10848f1f788496ccadde7d458eac4ac2c1eca2dd2c7a506d8fa2c5b0000000000000000000000000000000000000000000000000000000000000000", hex"0496bacdcc4260a610c9ef465cc1d9f973dda7d4794a65c1d1ea8ea2bb8aa3ddb08d171f5aa267c1ddc64343b4f054424af0c61b57e1f359cd4e23876a0fa4a704", 0x34383aacbe014a6fed8b5976ccfcdcbb93cef7e41a6634f934432d9ede912c14 ); // genesis validators 7 validators, DKG #1 // exodus session #2 gatekeeper.verify( hex"49f03a6700000000000000000000000000000000000000000000000000000000000000024e8c1fe96d6737cdabbcc96501d6656b9d0b659b3a528ef507f2d52bef29e1570000000000000000000000000000000000000000000000000000000000000000", hex"04460e7dce1ebd4cc7527ba80852b6fcdbf8810da3b2173c2f3b3da63206a44407f79d5b5b4c3adcdcce2404c7488240873babb856f065fa551caa6dddea68ca06", 0x7f6ac70e1e3f3ddd7101a439e7da6f8622a0c8cc38ce42ce81d911d8ad23af90 ); (publicKey, parity, session) = gatekeeper.latestPublicKeyInfo(); assert(publicKey != prevPublicKey); assertEq(parity, prevParity); assertEq(session, 2); prevPublicKey = publicKey; prevParity = parity; // genesis validators 5 validators, DKG #2 // exodus session #4 gatekeeper.verify( hex"49f03a6700000000000000000000000000000000000000000000000000000000000000043492bc8bb10848f1f788496ccadde7d458eac4ac2c1eca2dd2c7a506d8fa2c5b0000000000000000000000000000000000000000000000000000000000000000", hex"0496bacdcc4260a610c9ef465cc1d9f973dda7d4794a65c1d1ea8ea2bb8aa3ddb08d171f5aa267c1ddc64343b4f054424af0c61b57e1f359cd4e23876a0fa4a704", 0x34383aacbe014a6fed8b5976ccfcdcbb93cef7e41a6634f934432d9ede912c14 ); (publicKey, parity, session) = gatekeeper.latestPublicKeyInfo(); assert(publicKey != prevPublicKey); assertEq(parity, prevParity); assertEq(session, 4); // could not apply DKG #0 no more vm.expectRevert(); gatekeeper.verify( hex"49f03a670000000000000000000000000000000000000000000000000000000000000000875cdcba4ae5494518fa2602791e667ca0998402b6a69e5b7cb4c72dba4e46690000000000000000000000000000000000000000000000000000000000000000", hex"044d9fa18df9da04381ed256981570a6ef6a0893496ded966fc994662df931ed423b29e2737394b31d8498594caad7b77e36ab31ac79df3796a1b6f26baa5552ae", 0x24029a571fcaeadd14f4afe8be62afe3d07876ae270c3caf446ecb6cfc7c08f2 ); // could not apply DKG #1 no more vm.expectRevert(); gatekeeper.verify( hex"49f03a6700000000000000000000000000000000000000000000000000000000000000024e8c1fe96d6737cdabbcc96501d6656b9d0b659b3a528ef507f2d52bef29e1570000000000000000000000000000000000000000000000000000000000000000", hex"04460e7dce1ebd4cc7527ba80852b6fcdbf8810da3b2173c2f3b3da63206a44407f79d5b5b4c3adcdcce2404c7488240873babb856f065fa551caa6dddea68ca06", 0x7f6ac70e1e3f3ddd7101a439e7da6f8622a0c8cc38ce42ce81d911d8ad23af90 ); // could not apply DKG #2 no more vm.expectRevert(); gatekeeper.verify( hex"49f03a6700000000000000000000000000000000000000000000000000000000000000043492bc8bb10848f1f788496ccadde7d458eac4ac2c1eca2dd2c7a506d8fa2c5b0000000000000000000000000000000000000000000000000000000000000000", hex"0496bacdcc4260a610c9ef465cc1d9f973dda7d4794a65c1d1ea8ea2bb8aa3ddb08d171f5aa267c1ddc64343b4f054424af0c61b57e1f359cd4e23876a0fa4a704", 0x34383aacbe014a6fed8b5976ccfcdcbb93cef7e41a6634f934432d9ede912c14 ); uint256 aliceAmountBefore = staking.materializedAmount(ALICE); uint256 bobAmountBefore = staking.materializedAmount(BOB); uint256 aliceBalanceBefore = ALICE.balance; uint256 bobBalanceBefore = BOB.balance; // execute bridge out, happened on DKG #0 // amount: 69; commission: 0%; ALICE // exodus session #1 gatekeeper.verify( hex"39cd99050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004500000000000000000000000000000000000000010000000000007a6900000000", hex"04bf0bcb3d42d8187c543a82ee201a221525047020ff0ac03e4d60e322d3eb6d8ab5bfdae43ea0fbf59128d201b8076727c3c63fe8a5c2b890fdb3c97a7da5f207", 0x08f121a1615782c83cce41d7fc2064be57fdeeae8b45f716211b3c8c873f3e4c ); assertEq(aliceAmountBefore + 69, staking.materializedAmount(ALICE)); assertEq(bobAmountBefore, staking.materializedAmount(BOB)); assertEq(aliceBalanceBefore, ALICE.balance); assertEq(bobBalanceBefore, BOB.balance); vm.deal(ALICE, 220); aliceAmountBefore = staking.materializedAmount(ALICE); bobAmountBefore = staking.materializedAmount(BOB); aliceBalanceBefore = ALICE.balance; bobBalanceBefore = BOB.balance; uint256 buyback = 220; // execute bridge out, happened on DKG #1 // amount: 420; commission: 50%; BOB // exodus session #3 vm.prank(ALICE, ALICE); gatekeeper.verify{value: buyback}( hex"39cd9905000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000001a400000000000000000000000000000000000000020000000000007a6980000000", hex"04089940ed9bb8d7f3a77adfafb0d5ebc45af558ade46844856c049aa912b9d8401826e17f24c0aa625524ff4a65b430a19422b4f9754cec3b862641f17f99b1a9", 0x9d2cf40ab0b1820858fe6f8950e2f511b4acb57b00b37bb93760978e2add4243 ); assertEq(aliceAmountBefore + 210, staking.materializedAmount(ALICE)); assertEq(bobAmountBefore + 210, staking.materializedAmount(BOB)); assertEq(aliceBalanceBefore - buyback, ALICE.balance); assertEq(bobBalanceBefore + buyback, BOB.balance); } }