make gatekeeper to store historical bridging information

Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
Uncle Fatso 2026-03-17 20:38:00 +03:00
parent 6350f8cfba
commit 53928fa63c
Signed by: f4ts0
GPG Key ID: 565F4F2860226EBB
7 changed files with 100 additions and 9 deletions

View File

@ -102,8 +102,16 @@ GOVERNOR_VOTING_PERIOD=
GOVERNOR_PROPOSAL_THRESHOLD= GOVERNOR_PROPOSAL_THRESHOLD=
GOVERNOR_QUORUM_FRACTION= 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_SUPPLY=
INITIAL_GHOSTED_IN=
INITIAL_GHOSTED_OUT=
GATEKEEPER_DEPLOYED_AT=
SEPOLIA_TEST_RPC_URL= SEPOLIA_TEST_RPC_URL=
SEPOLIA_TEST_API_KEY= SEPOLIA_TEST_API_KEY=

View File

@ -43,6 +43,4 @@ mordor-testnet = "${MORDOR_TEST_RPC_URL}"
[etherscan] [etherscan]
sepolia-testnet = { key = "${SEPOLIA_TEST_API_KEY}", url = "${SEPOLIA_TEST_ENDPOINT}" } sepolia-testnet = { key = "${SEPOLIA_TEST_API_KEY}", url = "${SEPOLIA_TEST_ENDPOINT}" }
hoodi-testnet = { key = "${HOODI_TEST_API_KEY}", url = "${HOODI_TEST_ENDPOINT}" } hoodi-testnet = { key = "${HOODI_TEST_API_KEY}", url = "${HOODI_TEST_ENDPOINT}" }
mordor-testnet = { key = "${MORDOR_TEST_API_KEY}", url = "${MORDOR_TEST_ENDPOINT}", verifier = "blockscout" }
[verifiers]
mordor-testnet = { verifier = "blockscout", url = "${MORDOR_TEST_ENDPOINT}" }

View File

@ -2,25 +2,42 @@
pragma solidity ^0.8.20; pragma solidity ^0.8.20;
import {IGatekeeper} from "./interfaces/IGatekeeper.sol"; 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; uint256 public override ghostedSupply;
address public immutable staking; // forge-lint: disable-line(screaming-snake-case-immutable) address public immutable staking; // forge-lint: disable-line(screaming-snake-case-immutable)
GatekeeperMetadata private _metadata;
uint256 private _aggregatedPublicKey; uint256 private _aggregatedPublicKey;
mapping(uint256 => mapping(uint256 => bool)) private _executedTransaction; mapping(uint256 => mapping(uint256 => bool)) private _executedTransaction;
constructor( constructor(
address _staking, address _staking,
uint256 _ghostedSupply uint256 _ghostedSupply,
uint48 _deployedAt,
uint104 _amountIn,
uint104 _amountOut
) { ) {
ghostedSupply = _ghostedSupply; ghostedSupply = _ghostedSupply;
staking = _staking; 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 { function ghost(bytes32 receiver, uint256 amount) external override {
if (msg.sender != staking) revert NotStaking(); if (msg.sender != staking) revert NotStaking();
ghostedSupply += amount; ghostedSupply += amount;
_metadata.amountIn += uint104(amount); // forge-lint: disable-line(unsafe-typecast)
emit Ghosted(receiver, amount); emit Ghosted(receiver, amount);
} }
@ -45,6 +62,7 @@ contract Gatekeeper is IGatekeeper {
if (_incorrectSignature(rx, s, message)) revert WrongSignature(); if (_incorrectSignature(rx, s, message)) revert WrongSignature();
ghostedSupply -= amount; ghostedSupply -= amount;
_metadata.amountIn += uint104(amount); // forge-lint: disable-line(unsafe-typecast)
emit Materialized(receiver, amount); emit Materialized(receiver, amount);
} }

View File

@ -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);
}

View File

@ -13,14 +13,14 @@ contract GatekeeperTest is Test {
event Ghosted(bytes32 indexed receiver, uint256 indexed amount); event Ghosted(bytes32 indexed receiver, uint256 indexed amount);
function setUp() public { function setUp() public {
gatekeeper = new Gatekeeper(ALICE, 0); gatekeeper = new Gatekeeper(ALICE, 0, 0, 0, 0);
} }
function test_correctInitialization() public { function test_correctInitialization() public {
assertEq(gatekeeper.staking(), ALICE); assertEq(gatekeeper.staking(), ALICE);
assertEq(gatekeeper.ghostedSupply(), 0); 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.staking(), BOB);
assertEq(anotherGatekeeper.ghostedSupply(), INIT_AMOUNT); assertEq(anotherGatekeeper.ghostedSupply(), INIT_AMOUNT);
} }

View File

@ -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);
}
}

View File

@ -73,7 +73,7 @@ contract StakingTest is Test {
treasury = new GhostTreasury(address(ftso), 69, address(authority)); treasury = new GhostTreasury(address(ftso), 69, address(authority));
stnk.initialize(address(staking), address(treasury), address(ghst)); stnk.initialize(address(staking), address(treasury), address(ghst));
ghst.initialize(address(staking)); 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); calculator = new GhostBondingCalculator(address(ftso), 1, 1);
vm.stopPrank(); vm.stopPrank();
} }