implement phantom refund after verification

Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
Uncle Fatso 2026-09-10 14:16:14 +03:00
parent 28cf9d6de8
commit 8d94ab0dd1
Signed by: f4ts0
GPG Key ID: 565F4F2860226EBB
10 changed files with 149 additions and 143 deletions

View File

@ -21,13 +21,10 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
using RequestPacking for RequestPacking.RequestPayload; using RequestPacking for RequestPacking.RequestPayload;
using GovernancePacking for GovernancePacking.GovernancePayload; using GovernancePacking for GovernancePacking.GovernancePayload;
uint256 private constant BOUNTY_DIVISOR = type(uint32).max; uint256 public constant BOUNTY_DIVISOR = type(uint32).max;
uint256 private constant SECP256K1_N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; uint256 public constant MAX_ALLOWED_GAS_PRICE = 3e9; // 3 gwei
uint256 private constant SECP256K1_Q = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F;
uint256 private constant SHIFT_FACTOR = (2**128) % SECP256K1_N;
uint256 public constant GAS_RECALL_SPENDING = 329188;
uint256 public constant EXISTENTIAL_DEPOSIT = 500 * 1e12; uint256 public constant EXISTENTIAL_DEPOSIT = 500 * 1e12;
uint256 public constant GAS_EXECUTION_BUFFER = 33805;
uint256 public constant REGISTRY_INDEX = 0; uint256 public constant REGISTRY_INDEX = 0;
address public override staking; address public override staking;
@ -112,21 +109,6 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
return (publicKey, state.parity, state.session); return (publicKey, state.parity, state.session);
} }
function verify(
bytes calldata call,
uint256 rx,
uint256 s
) external nonReentrant returns (bytes memory) {
uint256 px = _extractPublicKey(call);
bool validSignature = Verifier.verifyGhost(call, px, rx, s);
if (!validSignature) revert BadSignature();
(bool success, bytes memory data) = address(this).call(call);
if (!success) revert ExecutionReverted();
return data;
}
function ghost(bytes32 receiver, uint256 amount) external override returns (uint256) { function ghost(bytes32 receiver, uint256 amount) external override returns (uint256) {
if (msg.sender != staking) revert NotStaking(); if (msg.sender != staking) revert NotStaking();
if (amount < EXISTENTIAL_DEPOSIT) revert NonExistentAmount(); if (amount < EXISTENTIAL_DEPOSIT) revert NonExistentAmount();
@ -137,36 +119,56 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
return _insertTreeNode(receiver, amount); return _insertTreeNode(receiver, amount);
} }
function verify(
bytes calldata call,
uint256 rx,
uint256 s
) external nonReentrant returns (bytes memory) {
uint256 gasStart = gasleft();
uint256 px = _extractPublicKey(call);
bool validSignature = Verifier.verifyGhost(call, px, rx, s);
if (!validSignature) revert BadSignature();
(bool success, bytes memory data) = address(this).call(call);
if (!success) revert ExecutionReverted();
uint256 gasPrice = MAX_ALLOWED_GAS_PRICE < tx.gasprice ? MAX_ALLOWED_GAS_PRICE : tx.gasprice;
uint256 gasSpent = (gasStart - gasleft() + GAS_EXECUTION_BUFFER) * gasPrice;
try IStaking(staking).phantomRefund(msg.sender, gasSpent, REGISTRY_INDEX) {}
catch {
emit VoluntaryVerification(msg.sender, gasSpent);
}
return data;
}
function recall( function recall(
uint256 exodusSession, uint256 exodusSession,
uint256 amount, uint256 amount,
uint256 packed uint256 packed
) external override { ) external {
if (msg.sender != address(this)) revert NotGatekeeper(); if (msg.sender != address(this)) revert NotGatekeeper();
RequestPacking.RequestPayload memory payload = RequestPacking.unpack(packed); RequestPacking.RequestPayload memory payload = RequestPacking.unpack(packed);
if (payload.chainId != block.chainid) revert WrongChainId(); if (payload.chainId != block.chainid) revert WrongChainId();
StorageHistory(storageHistory).trySetTransactionExecuted(exodusSession); uint256 bountyAmount = FullMath.mulDiv(amount, uint256(payload.bounty), BOUNTY_DIVISOR);
uint256 bountyAmount = FullMath.mulDiv(amount, BOUNTY_DIVISOR - uint256(payload.bounty), BOUNTY_DIVISOR);
uint256 receiverAmount = amount - bountyAmount; uint256 receiverAmount = amount - bountyAmount;
uint256 bridgeOutImbalance = receiverAmount;
StorageHistory(storageHistory).trySetTransactionExecuted(exodusSession);
IStorageHistory(storageHistory).tryIncreaseBridgeOut(receiverAmount);
IStaking(staking).recall(payload.receiver, receiverAmount); IStaking(staking).recall(payload.receiver, receiverAmount);
if (bountyAmount > 0) { if (bountyAmount > 0) {
uint256 gasSpent = GAS_RECALL_SPENDING * tx.gasprice; (address token, uint256 sent) = IStaking(staking).recall(bountyAmount, REGISTRY_INDEX);
(address token, uint256 forGas, uint256 sent) = IStaking(staking).recall(bountyAmount, gasSpent, REGISTRY_INDEX);
bridgeOutImbalance += forGas;
IWETH9(token).withdraw(sent); IWETH9(token).withdraw(sent);
(bool sentSuccess,) = payload.receiver.call{ value: sent }(""); (bool sentSuccess,) = payload.receiver.call{ value: sent }("");
if (!sentSuccess) revert SendFailed(); if (!sentSuccess) revert SendFailed();
} }
IStorageHistory(storageHistory).tryIncreaseBridgeOut(bridgeOutImbalance);
emit Recalled(payload.receiver, amount); emit Recalled(payload.receiver, amount);
} }
@ -174,7 +176,7 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
uint256 exodusSession, uint256 exodusSession,
bytes32 newPublicKey, bytes32 newPublicKey,
uint8 newParity uint8 newParity
) external override { ) external {
if (msg.sender != address(this)) revert NotGatekeeper(); if (msg.sender != address(this)) revert NotGatekeeper();
if (newParity % 2 != 0) revert InvalidPublicKey(); if (newParity % 2 != 0) revert InvalidPublicKey();
@ -192,7 +194,7 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
uint256 exodusSession, uint256 exodusSession,
uint256 packed, uint256 packed,
bytes calldata bytes calldata
) external override returns (bytes memory) { ) external returns (bytes memory) {
if (msg.sender != address(this)) revert NotGatekeeper(); if (msg.sender != address(this)) revert NotGatekeeper();
GovernancePacking.GovernancePayload memory payload = GovernancePacking.unpack(packed); GovernancePacking.GovernancePayload memory payload = GovernancePacking.unpack(packed);

View File

@ -167,6 +167,16 @@ contract GhostStaking is IStaking, GhostAccessControlled {
IGatekeeper(gatekeeper).ghost(receiver, amount); IGatekeeper(gatekeeper).ghost(receiver, amount);
} }
function phantomRefund(address receiver, uint256 amount, uint256 registryIndex) external override {
if (gatekeeper != msg.sender) revert NotGatekeeper();
address treasury = ISTNK(stnk).treasury();
uint256 gasFtsoAmount = ITreasury(treasury).phantomRefund(amount, registryIndex);
uint256 gasGhstAmount = IGHST(ghst).balanceTo(gasFtsoAmount);
IGHST(ghst).mint(receiver, gasGhstAmount);
}
function recall(address receiver, uint256 amount) external override { function recall(address receiver, uint256 amount) external override {
if (gatekeeper != msg.sender) revert NotGatekeeper(); if (gatekeeper != msg.sender) revert NotGatekeeper();
IGHST(ghst).mint(receiver, amount); IGHST(ghst).mint(receiver, amount);
@ -174,25 +184,20 @@ contract GhostStaking is IStaking, GhostAccessControlled {
function recall( function recall(
uint256 amount, uint256 amount,
uint256 gasSpent,
uint256 registryIndex uint256 registryIndex
) external override returns (address reserveToken, uint256 gasGhstAmount, uint256 value) { ) external override returns (address reserveToken, uint256 value) {
if (gatekeeper != msg.sender) revert NotGatekeeper(); if (gatekeeper != msg.sender) revert NotGatekeeper();
address treasury = ISTNK(stnk).treasury(); address treasury = ISTNK(stnk).treasury();
uint256 ftsoAmount = IGHST(ghst).balanceFrom(amount); uint256 ftsoAmount = IGHST(ghst).balanceFrom(amount);
uint256 gasFtsoAmount; (reserveToken, value) = ITreasury(treasury).buyBack(
(reserveToken, value, gasFtsoAmount) = ITreasury(treasury).buyBack(
msg.sender, msg.sender,
ftsoAmount, ftsoAmount,
gasSpent,
registryIndex registryIndex
); );
gasGhstAmount = IGHST(ghst).balanceTo(gasFtsoAmount); IFTSO(ftso).burn(ftsoAmount);
IFTSO(ftso).burn(ftsoAmount - gasFtsoAmount);
IGHST(ghst).mint(tx.origin, gasGhstAmount);
} }
function rebase() public override returns (uint256 bounty) { function rebase() public override returns (uint256 bounty) {

View File

@ -77,23 +77,35 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
emit Withdrawal(token, amount, value); emit Withdrawal(token, amount, value);
} }
function phantomRefund(
uint256 gasSpent,
uint256 index
) external override returns (uint256 gasValue) {
if (!_permissions[STATUS.STAKING][msg.sender]) revert NotApproved();
address reserveToken = _registry[STATUS.RESERVETOKEN].at(index);
if (!_permissions[STATUS.RESERVETOKEN][reserveToken]) revert NotApproved();
uint256 totalSupply = IERC20(FTSO).totalSupply();
gasValue = tokenValue(reserveToken, gasSpent);
gasValue = FullMath.mulDiv(gasValue, totalSupply, totalReserves);
if (gasValue > excessReserves()) revert InsufficientReserves();
IFTSO(FTSO).mint(msg.sender, gasValue);
}
function buyBack( function buyBack(
address receiver, address receiver,
uint256 amount, uint256 amount,
uint256 gasSpent,
uint256 index uint256 index
) external override returns (address reserveToken, uint256 value, uint256 gasValue) { ) external override returns (address reserveToken, uint256 value) {
if (!_permissions[STATUS.STAKING][msg.sender]) revert NotApproved(); if (!_permissions[STATUS.STAKING][msg.sender]) revert NotApproved();
reserveToken = _registry[STATUS.RESERVETOKEN].at(index); reserveToken = _registry[STATUS.RESERVETOKEN].at(index);
if (!_permissions[STATUS.RESERVETOKEN][reserveToken]) revert NotApproved(); if (!_permissions[STATUS.RESERVETOKEN][reserveToken]) revert NotApproved();
uint256 totalSupply = IERC20(FTSO).totalSupply(); uint256 totalSupply = IERC20(FTSO).totalSupply();
gasValue = tokenValue(reserveToken, gasSpent); uint256 reservesToSend = FullMath.mulDiv(amount, totalReserves, totalSupply);
gasValue = FullMath.mulDiv(gasValue, totalSupply, totalReserves);
if (amount <= gasValue) revert GasExceedsBounty();
uint256 reservesToSend = FullMath.mulDiv(amount - gasValue, totalReserves, totalSupply);
totalReserves = totalReserves - reservesToSend; totalReserves = totalReserves - reservesToSend;
value = FullMath.mulDiv(reservesToSend, 1e18, IBondingCalculator(bondCalculator[reserveToken]).fraction()); value = FullMath.mulDiv(reservesToSend, 1e18, IBondingCalculator(bondCalculator[reserveToken]).fraction());

View File

@ -17,6 +17,7 @@ interface IGatekeeper {
event Ghosted(bytes32 indexed receiver, uint256 indexed amount); event Ghosted(bytes32 indexed receiver, uint256 indexed amount);
event Recalled(address indexed receiver, uint256 indexed amount); event Recalled(address indexed receiver, uint256 indexed amount);
event Rotated(bytes32 indexed aggregatedPublicKey, uint8 indexed parity); event Rotated(bytes32 indexed aggregatedPublicKey, uint8 indexed parity);
event VoluntaryVerification(address indexed sender, uint256 indexed gasSpent);
function staking() external view returns (address); function staking() external view returns (address);
function deployer() external view returns (address); function deployer() external view returns (address);

View File

@ -69,7 +69,8 @@ interface IStaking {
function unwrap(address _to, uint256 _amount) external returns (uint256 sBalance_); function unwrap(address _to, uint256 _amount) external returns (uint256 sBalance_);
function ghost(bytes32 receiver, uint256 amount) external; function ghost(bytes32 receiver, uint256 amount) external;
function recall(address receiver, uint256 amount) external; function recall(address receiver, uint256 amount) external;
function recall(uint256 amount, uint256 gasSpent, uint256 registryIndex) external returns (address, uint256, uint256); function recall(uint256 amount, uint256 registryIndex) external returns (address, uint256);
function phantomRefund(address receiver, uint256 amount, uint256 registryIndex) external;
function rebase() external returns (uint256); function rebase() external returns (uint256);
function index() external view returns (uint256); function index() external view returns (uint256);

View File

@ -29,7 +29,8 @@ interface ITreasury {
uint256 _profit uint256 _profit
) external returns (uint256); ) external returns (uint256);
function buyBack(address receiver, uint256 amount, uint256 gasSpent, uint256 index) external returns (address, uint256, uint256); function phantomRefund(uint256 gasSpent, uint256 amount) external returns (uint256);
function buyBack(address receiver, uint256 amount, uint256 index) external returns (address, uint256);
function withdraw(address token, uint256 amount) external; function withdraw(address token, uint256 amount) external;
function mint(address _recipient, uint256 _amount) external; function mint(address _recipient, uint256 _amount) external;
function tokenValue(address _token, uint256 _amount) external view returns (uint256 value_); function tokenValue(address _token, uint256 _amount) external view returns (uint256 value_);

View File

@ -22,12 +22,7 @@ library Verifier {
) internal view returns (bool) { ) internal view returns (bool) {
if (px >= P || rx >= P || s >= N || s == 0) { return false; } if (px >= P || rx >= P || s >= N || s == 0) { return false; }
if (rx < N) { return verifySpectre(call, px, rx, s); } if (rx < N) { return verifySpectre(call, px, rx, s); }
return verifyBanshee(call, px, rx, s);
(uint256 py, bool success1) = liftPoint(px);
(uint256 ry, bool success2) = liftPoint(rx);
if (!success1 || !success2) { return false; }
return verifyBanshee(call, px, py, rx, ry, s);
} }
function verifySpectre( function verifySpectre(
@ -52,14 +47,12 @@ library Verifier {
function verifyBanshee( function verifyBanshee(
bytes memory call, bytes memory call,
uint256 px, uint256 px,
uint256 py,
uint256 rx, uint256 rx,
uint256 ry,
uint256 s uint256 s
) internal pure returns (bool) { ) internal view returns (bool) {
// TODO: because I lift it before, do I need to check is it on curve? (uint256 py, bool success1) = liftPoint(px);
// both of them Px,Py and Rx,Ry (uint256 ry, bool success2) = liftPoint(rx);
if (!isOnCurve(rx, ry) || !isOnCurve(px, py)) { return false; } if (!success1 || !success2) { return false; }
uint256 e = computeChallenge(call, rx, px); uint256 e = computeChallenge(call, rx, px);
if (e == 0) return false; // Cheap and safe if (e == 0) return false; // Cheap and safe
@ -96,9 +89,6 @@ library Verifier {
} }
function expModPrecompile(uint256 base) internal view returns (uint256 result) { function expModPrecompile(uint256 base) internal view returns (uint256 result) {
uint256 localEXP = EXP;
uint256 localP = P;
assembly { assembly {
let pointer := mload(0x40) let pointer := mload(0x40)
@ -107,8 +97,8 @@ library Verifier {
mstore(add(pointer, 0x40), 0x20) mstore(add(pointer, 0x40), 0x20)
mstore(add(pointer, 0x60), base) mstore(add(pointer, 0x60), base)
mstore(add(pointer, 0x80), localEXP) mstore(add(pointer, 0x80), EXP)
mstore(add(pointer, 0xa0), localP) mstore(add(pointer, 0xa0), P)
// Modular Exponentiation Precompile (modexp) // Modular Exponentiation Precompile (modexp)
let success := staticcall(gas(), 0x05, pointer, 0xc0, pointer, 0x20) let success := staticcall(gas(), 0x05, pointer, 0xc0, pointer, 0x20)

View File

@ -4,7 +4,6 @@ import {Test} from "forge-std/Test.sol";
import {Gatekeeper} from "../../src/Gatekeeper.sol"; import {Gatekeeper} from "../../src/Gatekeeper.sol";
import {FullMath} from "../../src/libraries/FullMath.sol"; import {FullMath} from "../../src/libraries/FullMath.sol";
import {Verifier} from "../../src/libraries/Verifier.sol";
import {RequestPacking} from "../../src/libraries/Packing.sol"; import {RequestPacking} from "../../src/libraries/Packing.sol";
import {IStorageHistory} from "../../src/interfaces/IStorageHistory.sol"; import {IStorageHistory} from "../../src/interfaces/IStorageHistory.sol";
import {StorageHistory} from "../../src/types/StorageHistory.sol"; import {StorageHistory} from "../../src/types/StorageHistory.sol";
@ -142,6 +141,7 @@ contract MockStaking is Test {
WETH9 public mockReserve; WETH9 public mockReserve;
mapping(address => uint256) private _recalledAmounts; mapping(address => uint256) private _recalledAmounts;
mapping(address => uint256) private _phantomedAmounts;
constructor() { constructor() {
mockReserve = new WETH9(); mockReserve = new WETH9();
@ -169,9 +169,13 @@ contract MockStaking is Test {
_recalledAmounts[receiver] += amount; _recalledAmounts[receiver] += amount;
} }
function recall(uint256 amount, uint256, uint256) external returns (address, uint256, uint256){ function recall(uint256 amount, uint256) external returns (address, uint256){
_recalledAmounts[tx.origin] += amount; _recalledAmounts[tx.origin] += amount;
return (address(mockReserve), 0, 0); return (address(mockReserve), 0);
}
function phantomRefund(address who, uint256 amount) external {
_phantomedAmounts[who] += amount;
} }
function recalledAmount(address who) external view returns (uint256) { function recalledAmount(address who) external view returns (uint256) {
@ -387,7 +391,6 @@ contract GatekeeperTest is Test {
aliceBalanceBefore = ALICE.balance; aliceBalanceBefore = ALICE.balance;
bobBalanceBefore = BOB.balance; bobBalanceBefore = BOB.balance;
// TODO: revisit
// execute bridge out, happened on DKG #2 // execute bridge out, happened on DKG #2
// amount: 210; commission: 50%; BOB // amount: 210; commission: 50%; BOB
// exodus session #12 // exodus session #12

View File

@ -14,6 +14,7 @@ contract MockStaking is Test {
WETH9 public mockReserve; WETH9 public mockReserve;
mapping(address => uint256) private _recalledAmounts; mapping(address => uint256) private _recalledAmounts;
mapping(address => uint256) private _phantomedAmounts;
constructor() { constructor() {
mockReserve = new WETH9(); mockReserve = new WETH9();
@ -46,14 +47,22 @@ contract MockStaking is Test {
_recalledAmounts[receiver] += amount; _recalledAmounts[receiver] += amount;
} }
function recall(uint256 amount, uint256, uint256) external returns (address, uint256, uint256){ function recall(uint256 amount, uint256) external returns (address, uint256){
_recalledAmounts[tx.origin] += amount; _recalledAmounts[tx.origin] += amount;
return (address(mockReserve), amount, 0); return (address(mockReserve), amount);
} }
function recalledAmount(address who) external view returns (uint256) { function phantomRefund(address who, uint256 amount) external {
_phantomedAmounts[who] += amount;
}
function recalledAmounts(address who) external view returns (uint256) {
return _recalledAmounts[who]; return _recalledAmounts[who];
} }
function phantomedAmounts(address who) external view returns (uint256) {
return _phantomedAmounts[who];
}
} }
contract GatekeeperStorageHistoryTest is Test { contract GatekeeperStorageHistoryTest is Test {
@ -62,9 +71,11 @@ contract GatekeeperStorageHistoryTest is Test {
address constant ALICE = 0x0000000000000000000000000000000000000001; address constant ALICE = 0x0000000000000000000000000000000000000001;
address constant BOB = 0x0000000000000000000000000000000000000002; address constant BOB = 0x0000000000000000000000000000000000000002;
uint256 constant INIT_AMOUNT = 1337 * 1e18; uint256 constant INIT_AMOUNT = 1337 * 1e18;
uint256 private constant BIG_VALUE = 420 * 1e20;
Gatekeeper gatekeeper; Gatekeeper gatekeeper;
MockStaking staking; MockStaking staking;
WETH9 mockReserve;
function setUp() public { function setUp() public {
vm.prank(ALICE); vm.prank(ALICE);
@ -72,6 +83,14 @@ contract GatekeeperStorageHistoryTest is Test {
staking.runGhost(bytes32(abi.encodePacked(ALICE)), INIT_AMOUNT); staking.runGhost(bytes32(abi.encodePacked(ALICE)), INIT_AMOUNT);
gatekeeper = staking.gatekeeper(); gatekeeper = staking.gatekeeper();
mockReserve = staking.mockReserve();
vm.deal(ALICE, BIG_VALUE);
vm.startPrank(ALICE);
mockReserve.deposit{ value: BIG_VALUE }();
assertTrue(mockReserve.transfer(address(gatekeeper), BIG_VALUE));
vm.stopPrank();
} }
function test_correctStorageHistoryInitialization() public view { function test_correctStorageHistoryInitialization() public view {
@ -104,11 +123,10 @@ contract GatekeeperStorageHistoryTest is Test {
} else { } else {
// forge-lint: disable-next-line(unsafe-typecast) // forge-lint: disable-next-line(unsafe-typecast)
uint256 packed = RequestPacking.pack(0, uint64(block.chainid), BOB); uint256 packed = RequestPacking.pack(0, uint64(block.chainid), BOB);
uint256 previousAmount = staking.recalledAmount(BOB); uint256 previousAmount = staking.recalledAmounts(BOB);
vm.prank(BOB, BOB);
staking.runRecall(exodusSession, amountToMaterialize, packed); staking.runRecall(exodusSession, amountToMaterialize, packed);
assertEq(previousAmount + amountToMaterialize, staking.recalledAmount(BOB)); assertEq(previousAmount + amountToMaterialize, staking.recalledAmounts(BOB));
// forge-lint: disable-next-line(unsafe-typecast) // forge-lint: disable-next-line(unsafe-typecast)
amountOut += uint104(amountToMaterialize); amountOut += uint104(amountToMaterialize);
@ -123,6 +141,7 @@ contract GatekeeperStorageHistoryTest is Test {
function test_inheritanceWorksForHistoricalStorge() public { function test_inheritanceWorksForHistoricalStorge() public {
uint256 exodusSession = 69; uint256 exodusSession = 69;
uint256 packed = RequestPacking.pack(0, uint64(block.chainid), BOB); uint256 packed = RequestPacking.pack(0, uint64(block.chainid), BOB);
staking.runRecall(exodusSession, INIT_AMOUNT, packed); staking.runRecall(exodusSession, INIT_AMOUNT, packed);
staking.runGhost(bytes32(abi.encodePacked(ALICE)), INIT_AMOUNT); staking.runGhost(bytes32(abi.encodePacked(ALICE)), INIT_AMOUNT);

View File

@ -95,8 +95,7 @@ contract GatekeeperRecallTest is Test {
function test_recallChainWorks() public { function test_recallChainWorks() public {
vm.startPrank(INITIALIZER); vm.startPrank(INITIALIZER);
Gatekeeper gatekeeper = Gatekeeper(payable(staking.gatekeeper())); Gatekeeper gatekeeper = Gatekeeper(payable(staking.gatekeeper()));
gatekeeper.updatePublicKeyMetadata(0, 0x36ff5b7f0fc50100b563c6072e499980d6e6aa8528ea0ae6d776cf7e8e96c374, 0); gatekeeper.updatePublicKeyMetadata(0, 0xb5cd0d028a5e1b6a4eecb113b7e49c8176385eb77c8f134bc1db4bddd7654de6, 0);
vm.stopPrank(); vm.stopPrank();
vm.startPrank(ALICE); vm.startPrank(ALICE);
@ -108,13 +107,12 @@ contract GatekeeperRecallTest is Test {
staking.ghost(bytes32(abi.encodePacked(ALICE)), ghostBalance); staking.ghost(bytes32(abi.encodePacked(ALICE)), ghostBalance);
vm.stopPrank(); vm.stopPrank();
uint256 bountyPercent = type(uint32).max / 2; uint256 actualAmount = 105000000000000000;
uint256 amountToBridge = 42 * 1e15; address actualReceiver = address(0x0000000000000000000000000000000000000002);
address evmReceiver = address(0x0000000000000000000000000000000000000002);
uint256 bobGhstBefore = ghst.balanceOf(BOB); uint256 bobGhstBefore = ghst.balanceOf(BOB);
uint256 receiverEthBefore = evmReceiver.balance; uint256 receiverEthBefore = actualReceiver.balance;
uint256 receiverGhstBefore = ghst.balanceOf(evmReceiver); uint256 receiverGhstBefore = ghst.balanceOf(actualReceiver);
uint256 totalReservesBefore = treasury.totalReserves(); uint256 totalReservesBefore = treasury.totalReserves();
uint256 totalSupplyBefore = ftso.totalSupply(); uint256 totalSupplyBefore = ftso.totalSupply();
uint256 ghostedSupplyBefore = gatekeeper.ghostedSupply(); uint256 ghostedSupplyBefore = gatekeeper.ghostedSupply();
@ -122,78 +120,52 @@ contract GatekeeperRecallTest is Test {
vm.txGasPrice(2 gwei); vm.txGasPrice(2 gwei);
vm.startPrank(BOB, BOB); vm.startPrank(BOB, BOB);
gatekeeper.verify( gatekeeper.verify(
hex"bf06188a0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000007a919134c0400000000000000000000000000000000000000000010000000000007a6900000000", hex"bf06188a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000017508f1956a800000000000000000000000000000000000000000020000000000007a6980000000",
0x938640689e6f52929acf2714c5c785e8e725b5df616c449a19bca8a8b300ec8b, 0x883302783b7f3d253d5bdeb17f39117641acdf061bf3fe457cb505df6b17dfc1,
0x15b8b8c2934ee96d6a83a24055d83ea4074d251c207584e66d9caf8d653cabf2 0x8c3fc03b5cb738777ceec396ee8647f782d6670992f8f973de2dfc83379978d5
); );
vm.stopPrank(); vm.stopPrank();
{ {
uint256 bobEarnedGhst = ghst.balanceOf(BOB) - bobGhstBefore; uint256 bobGhstAfter = ghst.balanceOf(BOB);
assertTrue(bobEarnedGhst > 0); uint256 receiverGhstAfter = ghst.balanceOf(actualReceiver);
uint256 bobEarnedFtso = ghst.balanceFrom(bobEarnedGhst); assertTrue(
ghst.balanceFrom(bobGhstAfter - bobGhstBefore) * totalReservesBefore / totalSupplyBefore
uint256 totalReservesAfter = treasury.totalReserves(); > gatekeeper.GAS_EXECUTION_BUFFER()
uint256 totalSupplyAfter = ftso.totalSupply(); );
assertTrue(bobGhstAfter > bobGhstBefore);
uint256 gasUsed = 2 * 1e9 * 329188; assertApproxEqAbs(receiverGhstAfter - receiverGhstBefore, actualAmount / 2, 1e18);
uint256 gasReserveValue = treasury.tokenValue(address(reserve), gasUsed);
uint256 expectedGasFtso = gasReserveValue * totalSupplyAfter / totalReservesAfter;
uint256 simulatedGhst = ghst.balanceTo(expectedGasFtso);
uint256 dynamicGasPriceEstimation = ghst.balanceFrom(simulatedGhst);
assertApproxEqAbs(bobEarnedFtso, dynamicGasPriceEstimation, 1);
} }
{ {
uint256 expectedBounty = amountToBridge * bountyPercent / type(uint32).max; uint256 ftsoAmount = ghst.balanceFrom(actualAmount / 2);
uint256 expectedMintToReceiver = amountToBridge - expectedBounty; uint256 expectedReservesToSend = ftsoAmount * totalReservesBefore / totalSupplyBefore;
uint256 actualMintToReceiver = ghst.balanceOf(evmReceiver) - receiverGhstBefore; uint256 expectedEthRefund = expectedReservesToSend * 1e18 / calculator.fraction();
assertApproxEqAbs(actualMintToReceiver, expectedMintToReceiver, 1);
expectedEthRefund = expectedEthRefund * 1e18 / 1e9;
assertApproxEqAbs(actualReceiver.balance, receiverEthBefore + expectedEthRefund, 1);
} }
{ {
uint256 receiverEthAfter = evmReceiver.balance; actualAmount = treasury.tokenValue(address(reserve), actualReceiver.balance);
uint256 receivedEth = receiverEthAfter - receiverEthBefore;
assertTrue(receivedEth > 0);
uint256 totalReservesAfter = treasury.totalReserves();
uint256 totalSupplyAfter = ftso.totalSupply();
uint256 expectedBounty = amountToBridge * bountyPercent / type(uint32).max;
uint256 totalBountyFtso = ghst.balanceFrom(expectedBounty);
uint256 totalBountyReserveValue = totalBountyFtso * totalReservesAfter / totalSupplyAfter;
uint256 fraction = calculator.fraction();
uint256 totalBountyInEth = totalBountyReserveValue * 1e18 / fraction;
totalBountyInEth = totalBountyInEth * 1e18 / 1e9;
uint256 physicalGasSpentEth = 2 * 1e9 * 329188; // 2 gwei * gas Used
assertApproxEqAbs(receivedEth, totalBountyInEth - physicalGasSpentEth, 1);
}
{
uint256 totalReservesAfter = treasury.totalReserves();
uint256 totalSupplyAfter = ftso.totalSupply();
uint256 backingRatioBefore = totalReservesBefore * 1e18 / totalSupplyBefore; uint256 backingRatioBefore = totalReservesBefore * 1e18 / totalSupplyBefore;
uint256 backingRatioAfter = totalReservesAfter * 1e18 / totalSupplyAfter; uint256 backingRatioAfter = treasury.totalReserves() * 1e18 / ftso.totalSupply();
uint256 receivedValue = treasury.tokenValue(address(reserve), evmReceiver.balance);
assertEq(backingRatioAfter, backingRatioBefore); uint256 gasFtsoMinted = ghst.balanceFrom(ghst.balanceOf(BOB) - bobGhstBefore);
assertApproxEqAbs(totalReservesAfter + receivedValue, totalReservesBefore, 2000); // 2e-16% uint256 expectedDelta = (backingRatioBefore * gasFtsoMinted) / totalSupplyBefore;
assertApproxEqAbs(backingRatioAfter, backingRatioBefore - expectedDelta, 1000);
vm.prank(GOVERNOR); vm.prank(GOVERNOR);
treasury.auditReserves(); treasury.auditReserves();
assertEq(treasury.totalReserves() + receivedValue, totalReservesBefore); assertApproxEqAbs(treasury.totalReserves() * 1e18 / ftso.totalSupply(), backingRatioBefore - expectedDelta, 2000);
} }
{ {
uint256 expectedMintToReceiver = amountToBridge * (type(uint32).max - bountyPercent) / type(uint32).max; uint256 receiverGhstAfter = ghst.balanceOf(actualReceiver);
uint256 bobGhstAfter = ghst.balanceOf(BOB); uint256 receiverImbalance = receiverGhstAfter - receiverGhstBefore;
assertApproxEqAbs(ghostedSupplyBefore - bobGhstAfter - expectedMintToReceiver, gatekeeper.ghostedSupply(), 1); assertEq(ghostedSupplyBefore - receiverImbalance, staking.ghostedSupply());
} }
} }
} }