From a7529774473b950f880a0eda972c673221e181bd Mon Sep 17 00:00:00 2001 From: Uncle Fatso Date: Tue, 25 Aug 2026 20:23:32 +0300 Subject: [PATCH] tests added for buyback logic based on the real signatures Signed-off-by: Uncle Fatso --- test/gatekeeper/Gatekeeper.t.sol | 208 +++++++++++++++++++----- test/gatekeeper/GatekeeperHistory.t.sol | 99 +++++++++-- 2 files changed, 249 insertions(+), 58 deletions(-) diff --git a/test/gatekeeper/Gatekeeper.t.sol b/test/gatekeeper/Gatekeeper.t.sol index 54fa39e..cf1afa8 100644 --- a/test/gatekeeper/Gatekeeper.t.sol +++ b/test/gatekeeper/Gatekeeper.t.sol @@ -3,34 +3,76 @@ 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); - gatekeeper = new Gatekeeper(EXISTENTIAL, address(0)); + staking = new MockStaking(EXISTENTIAL); + gatekeeper = staking.gatekeeper(); } - function test_correctInitialization() public { - assertEq(gatekeeper.staking(), ALICE); + function test_correctInitialization() public view { + assertEq(gatekeeper.staking(), address(staking)); assertEq(gatekeeper.ghostedSupply(), 0); - - bytes32 receiver = bytes32(abi.encodePacked(ALICE)); - vm.prank(ALICE); - gatekeeper.ghost(receiver, INIT_AMOUNT); - - vm.prank(BOB); - Gatekeeper anotherGatekeeper = new Gatekeeper(EXISTENTIAL, address(gatekeeper)); - assertEq(anotherGatekeeper.staking(), ALICE); - assertEq(anotherGatekeeper.ghostedSupply(), INIT_AMOUNT); - assertEq(anotherGatekeeper.existentialDeposit(), EXISTENTIAL); } function test_ghostTokensWork(uint256 ghostAmount) public { @@ -39,7 +81,7 @@ contract GatekeeperTest is Test { uint256 ghostedSupply = gatekeeper.ghostedSupply(); vm.prank(ALICE); - gatekeeper.ghost(receiver, ghostAmount); + staking.runGhost(receiver, ghostAmount); assertEq(gatekeeper.ghostedSupply(), ghostedSupply + ghostAmount); } @@ -49,7 +91,7 @@ contract GatekeeperTest is Test { vm.expectRevert(); vm.prank(someone); - gatekeeper.ghost(receiver, 69); + staking.runGhost(receiver, 69); assertEq(gatekeeper.ghostedSupply(), 0); } @@ -60,14 +102,40 @@ contract GatekeeperTest is Test { vm.expectEmit(true, true, true, false, address(gatekeeper)); emit Ghosted(receiver, ghostAmount); vm.prank(ALICE); - gatekeeper.ghost(receiver, ghostAmount); + staking.runGhost(receiver, ghostAmount); } - // TODO: revisit with actual signatures from the cargo test - // function test_materializeWork(uint256 ghostAmount) public { - // vm.prank(ALICE); - // gatekeeper.materialize(0, ghostAmount, 0, ALICE); - // } + 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); @@ -81,18 +149,24 @@ contract GatekeeperTest is Test { vm.expectRevert(); vm.prank(ALICE); - gatekeeper.ghost(receiver, amount); + 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, uint64 prevSession) = gatekeeper.latestPublicKeyInfo(); + (bytes32 prevPublicKey, uint8 prevParity,) = gatekeeper.latestPublicKeyInfo(); + // genesis validators 4 validators, DKG #0 + // exodus session #0 gatekeeper.verify( hex"49f03a670000000000000000000000000000000000000000000000000000000000000000875cdcba4ae5494518fa2602791e667ca0998402b6a69e5b7cb4c72dba4e46690000000000000000000000000000000000000000000000000000000000000000", hex"044d9fa18df9da04381ed256981570a6ef6a0893496ded966fc994662df931ed423b29e2737394b31d8498594caad7b77e36ab31ac79df3796a1b6f26baa5552ae", @@ -102,41 +176,46 @@ contract GatekeeperTest is Test { (bytes32 publicKey, uint8 parity, uint64 session) = gatekeeper.latestPublicKeyInfo(); assertEq(publicKey, prevPublicKey); assertEq(parity, prevParity); - assertEq(session, prevSession); + assertEq(session, 0); + // could not apply signature from DKG #2 vm.expectRevert(); gatekeeper.verify( - hex"49f03a6700000000000000000000000000000000000000000000000000000000000000023492bc8bb10848f1f788496ccadde7d458eac4ac2c1eca2dd2c7a506d8fa2c5b0000000000000000000000000000000000000000000000000000000000000000", - hex"042b45a8909a137e7bab5caf4b2870ece0469c6a4149c10074cfdaf2c114a65daefe14179830c95d07fea439ce78d2ad11898578e5e678dbcb98a7576da9e57c77", - 0x5a61b59cededac4010b89bc6935e0cb7d975f5e2db457227ee473203de5247ed + hex"49f03a6700000000000000000000000000000000000000000000000000000000000000043492bc8bb10848f1f788496ccadde7d458eac4ac2c1eca2dd2c7a506d8fa2c5b0000000000000000000000000000000000000000000000000000000000000000", + hex"0496bacdcc4260a610c9ef465cc1d9f973dda7d4794a65c1d1ea8ea2bb8aa3ddb08d171f5aa267c1ddc64343b4f054424af0c61b57e1f359cd4e23876a0fa4a704", + 0x34383aacbe014a6fed8b5976ccfcdcbb93cef7e41a6634f934432d9ede912c14 ); + // genesis validators 7 validators, DKG #1 + // exodus session #2 gatekeeper.verify( - hex"49f03a6700000000000000000000000000000000000000000000000000000000000000014e8c1fe96d6737cdabbcc96501d6656b9d0b659b3a528ef507f2d52bef29e1570000000000000000000000000000000000000000000000000000000000000000", - hex"04d4c4daeb68bf8e40db4b516ddf3be9dd0f56a81809945e30bf06b7e5b26d1b27c83367de842f00c915f06ab1755dc511a3a3fe213308b5e65c6832d9d9bb3b38", - 0xd93d1b1613277ddb27df66bdea16d7b91adf975eb150e27a872ce035655ec13d + hex"49f03a6700000000000000000000000000000000000000000000000000000000000000024e8c1fe96d6737cdabbcc96501d6656b9d0b659b3a528ef507f2d52bef29e1570000000000000000000000000000000000000000000000000000000000000000", + hex"04460e7dce1ebd4cc7527ba80852b6fcdbf8810da3b2173c2f3b3da63206a44407f79d5b5b4c3adcdcce2404c7488240873babb856f065fa551caa6dddea68ca06", + 0x7f6ac70e1e3f3ddd7101a439e7da6f8622a0c8cc38ce42ce81d911d8ad23af90 ); (publicKey, parity, session) = gatekeeper.latestPublicKeyInfo(); assert(publicKey != prevPublicKey); assertEq(parity, prevParity); - assertEq(session, prevSession + 1); + assertEq(session, 2); prevPublicKey = publicKey; prevParity = parity; - prevSession = session; + // genesis validators 5 validators, DKG #2 + // exodus session #4 gatekeeper.verify( - hex"49f03a6700000000000000000000000000000000000000000000000000000000000000023492bc8bb10848f1f788496ccadde7d458eac4ac2c1eca2dd2c7a506d8fa2c5b0000000000000000000000000000000000000000000000000000000000000000", - hex"042b45a8909a137e7bab5caf4b2870ece0469c6a4149c10074cfdaf2c114a65daefe14179830c95d07fea439ce78d2ad11898578e5e678dbcb98a7576da9e57c77", - 0x5a61b59cededac4010b89bc6935e0cb7d975f5e2db457227ee473203de5247ed + hex"49f03a6700000000000000000000000000000000000000000000000000000000000000043492bc8bb10848f1f788496ccadde7d458eac4ac2c1eca2dd2c7a506d8fa2c5b0000000000000000000000000000000000000000000000000000000000000000", + hex"0496bacdcc4260a610c9ef465cc1d9f973dda7d4794a65c1d1ea8ea2bb8aa3ddb08d171f5aa267c1ddc64343b4f054424af0c61b57e1f359cd4e23876a0fa4a704", + 0x34383aacbe014a6fed8b5976ccfcdcbb93cef7e41a6634f934432d9ede912c14 ); (publicKey, parity, session) = gatekeeper.latestPublicKeyInfo(); assert(publicKey != prevPublicKey); assertEq(parity, prevParity); - assertEq(session, prevSession + 1); + assertEq(session, 4); + // could not apply DKG #0 no more vm.expectRevert(); gatekeeper.verify( hex"49f03a670000000000000000000000000000000000000000000000000000000000000000875cdcba4ae5494518fa2602791e667ca0998402b6a69e5b7cb4c72dba4e46690000000000000000000000000000000000000000000000000000000000000000", @@ -144,18 +223,63 @@ contract GatekeeperTest is Test { 0x24029a571fcaeadd14f4afe8be62afe3d07876ae270c3caf446ecb6cfc7c08f2 ); + // could not apply DKG #1 no more vm.expectRevert(); gatekeeper.verify( - hex"49f03a6700000000000000000000000000000000000000000000000000000000000000014e8c1fe96d6737cdabbcc96501d6656b9d0b659b3a528ef507f2d52bef29e1570000000000000000000000000000000000000000000000000000000000000000", - hex"04d4c4daeb68bf8e40db4b516ddf3be9dd0f56a81809945e30bf06b7e5b26d1b27c83367de842f00c915f06ab1755dc511a3a3fe213308b5e65c6832d9d9bb3b38", - 0xd93d1b1613277ddb27df66bdea16d7b91adf975eb150e27a872ce035655ec13d + hex"49f03a6700000000000000000000000000000000000000000000000000000000000000024e8c1fe96d6737cdabbcc96501d6656b9d0b659b3a528ef507f2d52bef29e1570000000000000000000000000000000000000000000000000000000000000000", + hex"04460e7dce1ebd4cc7527ba80852b6fcdbf8810da3b2173c2f3b3da63206a44407f79d5b5b4c3adcdcce2404c7488240873babb856f065fa551caa6dddea68ca06", + 0x7f6ac70e1e3f3ddd7101a439e7da6f8622a0c8cc38ce42ce81d911d8ad23af90 ); + // could not apply DKG #2 no more vm.expectRevert(); gatekeeper.verify( - hex"49f03a6700000000000000000000000000000000000000000000000000000000000000023492bc8bb10848f1f788496ccadde7d458eac4ac2c1eca2dd2c7a506d8fa2c5b0000000000000000000000000000000000000000000000000000000000000000", - hex"042b45a8909a137e7bab5caf4b2870ece0469c6a4149c10074cfdaf2c114a65daefe14179830c95d07fea439ce78d2ad11898578e5e678dbcb98a7576da9e57c77", - 0x5a61b59cededac4010b89bc6935e0cb7d975f5e2db457227ee473203de5247ed + 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); } } diff --git a/test/gatekeeper/GatekeeperHistory.t.sol b/test/gatekeeper/GatekeeperHistory.t.sol index 15fccf1..df8e8aa 100644 --- a/test/gatekeeper/GatekeeperHistory.t.sol +++ b/test/gatekeeper/GatekeeperHistory.t.sol @@ -4,19 +4,56 @@ import {Test} from "forge-std/Test.sol"; import {Gatekeeper} from "../../src/Gatekeeper.sol"; import {IStorageHistory} from "../../src/interfaces/IStorageHistory.sol"; +import {RequestPacking} from "../../src/libraries/Packing.sol"; + +contract MockStaking is Test { + Gatekeeper public gatekeeper; + + mapping(address => uint256) private _materializedAmounts; + + constructor(uint256 existential) { + gatekeeper = new Gatekeeper(existential, address(0)); + } + + function redoGatekeeper(uint256 existential) external { + gatekeeper = new Gatekeeper(existential, address(gatekeeper)); + } + + function runGhost(bytes32 receiver, uint256 amount) external { + gatekeeper.ghost(receiver, amount); + } + + function runMaterialize(uint256 exodusSession, uint256 amount, uint256 packed) external { + vm.prank(address(gatekeeper)); + gatekeeper.materialize(exodusSession, amount, packed); + } + + function materialize(address receiver, uint256 amount) external { + _materializedAmounts[receiver] += amount; + } + + function materializedAmount(address who) external view returns (uint256) { + return _materializedAmounts[who]; + } +} contract GatekeeperStorageHistoryTest is Test { + using RequestPacking for RequestPacking.RequestPayload; + address constant ALICE = 0x0000000000000000000000000000000000000001; address constant BOB = 0x0000000000000000000000000000000000000002; - uint256 constant INIT_AMOUNT = 69 * 1e18; - uint256 constant INIT_GHOSTED = type(uint104).max / 2; + uint256 constant INIT_AMOUNT = 1337 * 1e18; uint256 constant EXISTENTIAL = 0; Gatekeeper gatekeeper; + MockStaking staking; function setUp() public { vm.prank(ALICE); - gatekeeper = new Gatekeeper(EXISTENTIAL, address(0)); + staking = new MockStaking(EXISTENTIAL); + + staking.runGhost(bytes32(abi.encodePacked(ALICE)), INIT_AMOUNT); + gatekeeper = staking.gatekeeper(); } function test_correctStorageHistoryInitialization() public view { @@ -24,13 +61,16 @@ contract GatekeeperStorageHistoryTest is Test { IStorageHistory.DeploymentSnapshot memory snapshot = IStorageHistory(storageHistory).deploymentSnapshot(); assertEq(snapshot.deployedAt, 0); - assertEq(snapshot.amountIn, 0); + assertEq(snapshot.amountIn, INIT_AMOUNT); assertEq(snapshot.amountOut, 0); - assertEq(gatekeeper.ghostedSupply(), 0); + assertEq(gatekeeper.ghostedSupply(), INIT_AMOUNT); } - function test_historicalAmountsOnlyIncrease(uint256 ghostAmount) public { - vm.assume(ghostAmount > 0 && ghostAmount < INIT_GHOSTED / 2); + function test_historicalAmountsOnlyIncrease(uint256 ghostAmount, uint64 exodusSession) public { + vm.assume(ghostAmount > 0 && ghostAmount < INIT_AMOUNT); + + uint256 amountToGhost = ghostAmount; + uint256 amountToMaterialize = ghostAmount / 2; bytes32 receiver = bytes32(abi.encodePacked(ALICE)); address storageHistory = gatekeeper.storageHistory(); @@ -40,17 +80,19 @@ contract GatekeeperStorageHistoryTest is Test { uint104 amountOut = snapshot.amountOut; if (ghostAmount % 2 == 0) { - vm.prank(ALICE); - gatekeeper.ghost(receiver, ghostAmount); + staking.runGhost(receiver, amountToGhost); // forge-lint: disable-next-line(unsafe-typecast) - amountIn += uint104(ghostAmount); + amountIn += uint104(amountToGhost); } else { - if (IStorageHistory(storageHistory).bridgeImbalance() >= ghostAmount) { - vm.prank(ALICE); - gatekeeper.materialize(0, ghostAmount, 0, BOB); - // forge-lint: disable-next-line(unsafe-typecast) - amountOut += uint104(ghostAmount); - } + // forge-lint: disable-next-line(unsafe-typecast) + uint256 packed = RequestPacking.pack(0, uint64(block.chainid), BOB); + uint256 previousAmount = staking.materializedAmount(BOB); + + staking.runMaterialize(exodusSession, amountToMaterialize, packed); + assertEq(previousAmount + amountToMaterialize, staking.materializedAmount(BOB)); + + // forge-lint: disable-next-line(unsafe-typecast) + amountOut += uint104(amountToMaterialize); } IStorageHistory.DeploymentSnapshot memory newSnapshot = IStorageHistory(storageHistory).deploymentSnapshot(); @@ -59,4 +101,29 @@ contract GatekeeperStorageHistoryTest is Test { assertEq(gatekeeper.ghostedSupply(), amountIn - amountOut); } + function test_inheritanceWorksForHistoricalStorge() public { + uint256 exodusSession = 69; + uint256 packed = RequestPacking.pack(0, uint64(block.chainid), BOB); + staking.runMaterialize(exodusSession, INIT_AMOUNT, packed); + staking.runGhost(bytes32(abi.encodePacked(ALICE)), INIT_AMOUNT); + + address prevStorageHistory = gatekeeper.storageHistory(); + IStorageHistory.DeploymentSnapshot memory prevSnapshot = IStorageHistory(prevStorageHistory).deploymentSnapshot(); + + staking.redoGatekeeper(EXISTENTIAL + 1); + assert(gatekeeper != staking.gatekeeper()); + + address currStorageHistory = gatekeeper.storageHistory(); + IStorageHistory.DeploymentSnapshot memory currSnapshot = IStorageHistory(currStorageHistory).deploymentSnapshot(); + + assertEq(prevSnapshot.amountIn, currSnapshot.amountIn); + assertEq(prevSnapshot.amountOut, currSnapshot.amountOut); + assertEq(prevSnapshot.deployedAt, currSnapshot.deployedAt); + + assert(IStorageHistory(prevStorageHistory).isTransactionExecuted(exodusSession)); + assert(IStorageHistory(currStorageHistory).isTransactionExecuted(exodusSession)); + + assertEq(gatekeeper.existentialDeposit(), EXISTENTIAL); + assertEq(staking.gatekeeper().existentialDeposit(), EXISTENTIAL + 1); + } }