avoid DOS for validators; limit bridging to existential deposit

Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
Uncle Fatso 2026-03-23 14:11:43 +03:00
parent f2df4f600b
commit 6a47e18b35
Signed by: f4ts0
GPG Key ID: 565F4F2860226EBB
6 changed files with 30 additions and 6 deletions

View File

@ -106,11 +106,13 @@ GOVERNOR_QUORUM_FRACTION=
###################### Initial ghosted supply on gatekeeper ########################## ###################### Initial ghosted supply on gatekeeper ##########################
## ghostedSupply - supply that is currently locked inside the gatekeeper ## ## ghostedSupply - supply that is currently locked inside the gatekeeper ##
## existential - minimum amount that could be reflected inside other chain ##
## ghostedIn - historical supply that was bridged in (max value is 2^104) ## ## ghostedIn - historical supply that was bridged in (max value is 2^104) ##
## ghostedOut - historical supply that was bridged out (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 ## ## deployedAt - time when gatekeepers was deployed, could be inherited if needed ##
###################################################################################### ######################################################################################
INITIAL_GHOSTED_SUPPLY= INITIAL_GHOSTED_SUPPLY=
INITIAL_EXISTENTIAL_DEPOSIT=
INITIAL_GHOSTED_IN= INITIAL_GHOSTED_IN=
INITIAL_GHOSTED_OUT= INITIAL_GHOSTED_OUT=
GATEKEEPER_DEPLOYED_AT= GATEKEEPER_DEPLOYED_AT=

View File

@ -7,6 +7,7 @@ import {IGatekeeperMetadata} from "./interfaces/IGatekeeperMetadata.sol";
contract Gatekeeper is IGatekeeper, IGatekeeperMetadata { contract Gatekeeper is IGatekeeper, IGatekeeperMetadata {
uint256 public constant DIVISOR = 1e6; uint256 public constant DIVISOR = 1e6;
uint256 public override existentialDeposit;
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)
@ -17,10 +18,12 @@ contract Gatekeeper is IGatekeeper, IGatekeeperMetadata {
constructor( constructor(
address _staking, address _staking,
uint256 _ghostedSupply, uint256 _ghostedSupply,
uint256 _existential,
uint48 _deployedAt, uint48 _deployedAt,
uint104 _amountIn, uint104 _amountIn,
uint104 _amountOut uint104 _amountOut
) { ) {
existentialDeposit = _existential;
ghostedSupply = _ghostedSupply; ghostedSupply = _ghostedSupply;
staking = _staking; staking = _staking;
_metadata = GatekeeperMetadata({ _metadata = GatekeeperMetadata({
@ -36,6 +39,7 @@ contract Gatekeeper is IGatekeeper, IGatekeeperMetadata {
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();
if (amount < existentialDeposit) revert NonExistentAmount();
ghostedSupply += amount; ghostedSupply += amount;
_metadata.amountIn += uint104(amount); // forge-lint: disable-line(unsafe-typecast) _metadata.amountIn += uint104(amount); // forge-lint: disable-line(unsafe-typecast)
emit Ghosted(receiver, amount); emit Ghosted(receiver, amount);

View File

@ -5,12 +5,14 @@ interface IGatekeeper {
error NotStaking(); error NotStaking();
error WrongSignature(); error WrongSignature();
error AlreadyExecuted(); error AlreadyExecuted();
error NonExistentAmount();
event Ghosted(bytes32 indexed receiver, uint256 indexed amount); event Ghosted(bytes32 indexed receiver, uint256 indexed amount);
event Materialized(address indexed receiver, uint256 indexed amount); event Materialized(address indexed receiver, uint256 indexed amount);
event Rotated(uint256 indexed aggregatedPublicKey); event Rotated(uint256 indexed aggregatedPublicKey);
function ghostedSupply() external view returns (uint256); function ghostedSupply() external view returns (uint256);
function existentialDeposit() external view returns (uint256);
function ghost(bytes32 receiver, uint256 amount) external; function ghost(bytes32 receiver, uint256 amount) external;
function materialize(address receiver, uint256 amount, uint256 rx, uint256 s) external; function materialize(address receiver, uint256 amount, uint256 rx, uint256 s) external;
function rotate(uint256 aggregatedPublicKey, uint256 rx, uint256 s) external; function rotate(uint256 aggregatedPublicKey, uint256 rx, uint256 s) external;

View File

@ -7,26 +7,29 @@ import {Gatekeeper} from "../../src/Gatekeeper.sol";
contract GatekeeperTest is Test { contract GatekeeperTest is Test {
address constant ALICE = 0x0000000000000000000000000000000000000001; address constant ALICE = 0x0000000000000000000000000000000000000001;
address constant BOB = 0x0000000000000000000000000000000000000002; address constant BOB = 0x0000000000000000000000000000000000000002;
uint256 constant EXISTENTIAL = 1337;
uint256 constant INIT_AMOUNT = 69 * 1e18; uint256 constant INIT_AMOUNT = 69 * 1e18;
Gatekeeper gatekeeper; Gatekeeper gatekeeper;
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, 0, 0, 0); gatekeeper = new Gatekeeper(ALICE, 0, EXISTENTIAL, 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, 0, 0, 0); Gatekeeper anotherGatekeeper = new Gatekeeper(BOB, INIT_AMOUNT, EXISTENTIAL, 0, 0, 0);
assertEq(anotherGatekeeper.staking(), BOB); assertEq(anotherGatekeeper.staking(), BOB);
assertEq(anotherGatekeeper.ghostedSupply(), INIT_AMOUNT); assertEq(anotherGatekeeper.ghostedSupply(), INIT_AMOUNT);
assertEq(anotherGatekeeper.existentialDeposit(), EXISTENTIAL);
assertEq(anotherGatekeeper.ghostedSupply(), INIT_AMOUNT);
} }
function test_ghostTokensWork(uint256 ghostAmount) public { function test_ghostTokensWork(uint256 ghostAmount) public {
vm.assume(ghostAmount > 0); vm.assume(ghostAmount >= EXISTENTIAL);
bytes32 receiver = bytes32(abi.encodePacked(ALICE)); bytes32 receiver = bytes32(abi.encodePacked(ALICE));
uint256 ghostedSupply = gatekeeper.ghostedSupply(); uint256 ghostedSupply = gatekeeper.ghostedSupply();
@ -46,7 +49,7 @@ contract GatekeeperTest is Test {
} }
function test_ghostTokensEmitsEvent(uint256 ghostAmount) public { function test_ghostTokensEmitsEvent(uint256 ghostAmount) public {
vm.assume(ghostAmount > 0); vm.assume(ghostAmount >= EXISTENTIAL);
bytes32 receiver = bytes32(abi.encodePacked(ALICE)); bytes32 receiver = bytes32(abi.encodePacked(ALICE));
vm.expectEmit(true, true, true, false, address(gatekeeper)); vm.expectEmit(true, true, true, false, address(gatekeeper));
@ -64,4 +67,16 @@ contract GatekeeperTest is Test {
vm.expectRevert(); vm.expectRevert();
gatekeeper.rotate(aggregatedPublicKey, 0, 0); gatekeeper.rotate(aggregatedPublicKey, 0, 0);
} }
function test_couldNotBridgeBelowExistential(uint256 amount) public {
vm.assume(amount < EXISTENTIAL);
bytes32 receiver = bytes32(abi.encodePacked(ALICE));
vm.expectRevert();
vm.prank(ALICE);
gatekeeper.ghost(receiver, amount);
assertEq(gatekeeper.ghostedSupply(), 0);
}
} }

View File

@ -8,6 +8,7 @@ contract GatekeeperMetadataTest is Test {
address constant ALICE = 0x0000000000000000000000000000000000000001; address constant ALICE = 0x0000000000000000000000000000000000000001;
uint256 constant INIT_AMOUNT = 69 * 1e18; uint256 constant INIT_AMOUNT = 69 * 1e18;
uint256 constant INIT_GHOSTED = type(uint104).max / 2; uint256 constant INIT_GHOSTED = type(uint104).max / 2;
uint256 constant EXISTENTIAL = 0;
uint48 constant DEPLOYED_AT = 1337; uint48 constant DEPLOYED_AT = 1337;
uint104 constant AMOUNT_IN = 69; uint104 constant AMOUNT_IN = 69;
uint104 constant AMOUNT_OUT = 420; uint104 constant AMOUNT_OUT = 420;
@ -15,7 +16,7 @@ contract GatekeeperMetadataTest is Test {
Gatekeeper gatekeeper; Gatekeeper gatekeeper;
function setUp() public { function setUp() public {
gatekeeper = new Gatekeeper(ALICE, INIT_GHOSTED, DEPLOYED_AT, AMOUNT_IN, AMOUNT_OUT); gatekeeper = new Gatekeeper(ALICE, INIT_GHOSTED, EXISTENTIAL, DEPLOYED_AT, AMOUNT_IN, AMOUNT_OUT);
} }
function test_correctMetadataInitialization() public view { function test_correctMetadataInitialization() public view {

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, 0, 0, 0); gatekeeper = new Gatekeeper(address(staking), 0, 0, 0, 0, 0);
calculator = new GhostBondingCalculator(address(ftso), 1, 1); calculator = new GhostBondingCalculator(address(ftso), 1, 1);
vm.stopPrank(); vm.stopPrank();
} }