From 53928fa63ced763e5d64647d3a7f525cedbceca0 Mon Sep 17 00:00:00 2001 From: Uncle Fatso Date: Tue, 17 Mar 2026 20:38:00 +0300 Subject: [PATCH] make gatekeeper to store historical bridging information Signed-off-by: Uncle Fatso --- .env.template | 10 ++++- foundry.toml | 4 +- src/Gatekeeper.sol | 22 +++++++++- src/interfaces/IGatekeeperMetadata.sol | 12 ++++++ test/gatekeeper/Gatekeeper.t.sol | 4 +- test/gatekeeper/GatekeeperMetadata.t.sol | 55 ++++++++++++++++++++++++ test/staking/Staking.t.sol | 2 +- 7 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 src/interfaces/IGatekeeperMetadata.sol create mode 100644 test/gatekeeper/GatekeeperMetadata.t.sol diff --git a/.env.template b/.env.template index 5ad0039..91a5d01 100644 --- a/.env.template +++ b/.env.template @@ -102,8 +102,16 @@ GOVERNOR_VOTING_PERIOD= GOVERNOR_PROPOSAL_THRESHOLD= GOVERNOR_QUORUM_FRACTION= -## Initial ghosted supply on gatekeeper +###################### Initial ghosted supply on gatekeeper ########################## +## ghostedSupply - supply that is currently locked inside the gatekeeper ## +## ghostedIn - historical supply that was bridged in (max value is 2^104) ## +## ghostedOut - historical supply that was bridged out (max value is 2^104) ## +## deployedAt - time when gatekeepers was deployed, could be inherited if needed ## +###################################################################################### INITIAL_GHOSTED_SUPPLY= +INITIAL_GHOSTED_IN= +INITIAL_GHOSTED_OUT= +GATEKEEPER_DEPLOYED_AT= SEPOLIA_TEST_RPC_URL= SEPOLIA_TEST_API_KEY= diff --git a/foundry.toml b/foundry.toml index 722d7c8..e0452d9 100644 --- a/foundry.toml +++ b/foundry.toml @@ -43,6 +43,4 @@ mordor-testnet = "${MORDOR_TEST_RPC_URL}" [etherscan] sepolia-testnet = { key = "${SEPOLIA_TEST_API_KEY}", url = "${SEPOLIA_TEST_ENDPOINT}" } hoodi-testnet = { key = "${HOODI_TEST_API_KEY}", url = "${HOODI_TEST_ENDPOINT}" } - -[verifiers] -mordor-testnet = { verifier = "blockscout", url = "${MORDOR_TEST_ENDPOINT}" } +mordor-testnet = { key = "${MORDOR_TEST_API_KEY}", url = "${MORDOR_TEST_ENDPOINT}", verifier = "blockscout" } diff --git a/src/Gatekeeper.sol b/src/Gatekeeper.sol index e32fb63..cec5e36 100644 --- a/src/Gatekeeper.sol +++ b/src/Gatekeeper.sol @@ -2,25 +2,42 @@ pragma solidity ^0.8.20; import {IGatekeeper} from "./interfaces/IGatekeeper.sol"; +import {IGatekeeperMetadata} from "./interfaces/IGatekeeperMetadata.sol"; + +contract Gatekeeper is IGatekeeper, IGatekeeperMetadata { + uint256 public constant DIVISOR = 1e6; -contract Gatekeeper is IGatekeeper { uint256 public override ghostedSupply; address public immutable staking; // forge-lint: disable-line(screaming-snake-case-immutable) + GatekeeperMetadata private _metadata; uint256 private _aggregatedPublicKey; mapping(uint256 => mapping(uint256 => bool)) private _executedTransaction; constructor( address _staking, - uint256 _ghostedSupply + uint256 _ghostedSupply, + uint48 _deployedAt, + uint104 _amountIn, + uint104 _amountOut ) { ghostedSupply = _ghostedSupply; staking = _staking; + _metadata = GatekeeperMetadata({ + deployedAt: _deployedAt, + amountIn: _amountIn, + amountOut: _amountOut + }); + } + + function metadata() external view returns (GatekeeperMetadata memory) { + return _metadata; } function ghost(bytes32 receiver, uint256 amount) external override { if (msg.sender != staking) revert NotStaking(); ghostedSupply += amount; + _metadata.amountIn += uint104(amount); // forge-lint: disable-line(unsafe-typecast) emit Ghosted(receiver, amount); } @@ -45,6 +62,7 @@ contract Gatekeeper is IGatekeeper { if (_incorrectSignature(rx, s, message)) revert WrongSignature(); ghostedSupply -= amount; + _metadata.amountIn += uint104(amount); // forge-lint: disable-line(unsafe-typecast) emit Materialized(receiver, amount); } diff --git a/src/interfaces/IGatekeeperMetadata.sol b/src/interfaces/IGatekeeperMetadata.sol new file mode 100644 index 0000000..f277da2 --- /dev/null +++ b/src/interfaces/IGatekeeperMetadata.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +interface IGatekeeperMetadata { + struct GatekeeperMetadata { + uint48 deployedAt; + uint104 amountIn; + uint104 amountOut; + } + + function metadata() external view returns (GatekeeperMetadata memory); +} diff --git a/test/gatekeeper/Gatekeeper.t.sol b/test/gatekeeper/Gatekeeper.t.sol index 7682cb5..4e4cf55 100644 --- a/test/gatekeeper/Gatekeeper.t.sol +++ b/test/gatekeeper/Gatekeeper.t.sol @@ -13,14 +13,14 @@ contract GatekeeperTest is Test { event Ghosted(bytes32 indexed receiver, uint256 indexed amount); function setUp() public { - gatekeeper = new Gatekeeper(ALICE, 0); + gatekeeper = new Gatekeeper(ALICE, 0, 0, 0, 0); } function test_correctInitialization() public { assertEq(gatekeeper.staking(), ALICE); assertEq(gatekeeper.ghostedSupply(), 0); - Gatekeeper anotherGatekeeper = new Gatekeeper(BOB, INIT_AMOUNT); + Gatekeeper anotherGatekeeper = new Gatekeeper(BOB, INIT_AMOUNT, 0, 0, 0); assertEq(anotherGatekeeper.staking(), BOB); assertEq(anotherGatekeeper.ghostedSupply(), INIT_AMOUNT); } diff --git a/test/gatekeeper/GatekeeperMetadata.t.sol b/test/gatekeeper/GatekeeperMetadata.t.sol new file mode 100644 index 0000000..9719b4b --- /dev/null +++ b/test/gatekeeper/GatekeeperMetadata.t.sol @@ -0,0 +1,55 @@ +pragma solidity 0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {Gatekeeper} from "../../src/Gatekeeper.sol"; + +contract GatekeeperMetadataTest is Test { + address constant ALICE = 0x0000000000000000000000000000000000000001; + uint256 constant INIT_AMOUNT = 69 * 1e18; + uint256 constant INIT_GHOSTED = type(uint104).max / 2; + uint48 constant DEPLOYED_AT = 1337; + uint104 constant AMOUNT_IN = 69; + uint104 constant AMOUNT_OUT = 420; + + Gatekeeper gatekeeper; + + function setUp() public { + gatekeeper = new Gatekeeper(ALICE, INIT_GHOSTED, DEPLOYED_AT, AMOUNT_IN, AMOUNT_OUT); + } + + function test_correctMetadataInitialization() public view { + Gatekeeper.GatekeeperMetadata memory metadata = gatekeeper.metadata(); + assertEq(metadata.deployedAt, DEPLOYED_AT); + assertEq(metadata.amountIn, AMOUNT_IN); + assertEq(metadata.amountOut, AMOUNT_OUT); + assertEq(gatekeeper.ghostedSupply(), INIT_GHOSTED); + } + + function test_historicalAmountsOnlyIncrease(uint256 ghostAmount) public { + vm.assume(ghostAmount > 0 && ghostAmount < INIT_GHOSTED / 2); + bytes32 receiver = bytes32(abi.encodePacked(ALICE)); + uint256 ghostedSupply = gatekeeper.ghostedSupply(); + + Gatekeeper.GatekeeperMetadata memory metadata = gatekeeper.metadata(); + uint104 amountIn = metadata.amountIn; + uint104 amountOut = metadata.amountOut; + + if (ghostAmount % 2 == 0) { + vm.prank(ALICE); + gatekeeper.ghost(receiver, ghostAmount); + amountIn += uint104(ghostAmount); // forge-lint: disable-line(unsafe-typecast) + ghostedSupply += ghostAmount; + } else { + vm.expectRevert(); + vm.prank(ALICE); + gatekeeper.materialize(ALICE, ghostAmount, 0, 0); + } + + Gatekeeper.GatekeeperMetadata memory newMetadata = gatekeeper.metadata(); + assertEq(newMetadata.amountIn, amountIn); + assertEq(newMetadata.amountOut, amountOut); + assertEq(gatekeeper.ghostedSupply(), ghostedSupply); + } + +} diff --git a/test/staking/Staking.t.sol b/test/staking/Staking.t.sol index 810a795..95b432b 100644 --- a/test/staking/Staking.t.sol +++ b/test/staking/Staking.t.sol @@ -73,7 +73,7 @@ contract StakingTest is Test { treasury = new GhostTreasury(address(ftso), 69, address(authority)); stnk.initialize(address(staking), address(treasury), address(ghst)); ghst.initialize(address(staking)); - gatekeeper = new Gatekeeper(address(staking), 0); + gatekeeper = new Gatekeeper(address(staking), 0, 0, 0, 0); calculator = new GhostBondingCalculator(address(ftso), 1, 1); vm.stopPrank(); }