pragma solidity 0.8.20; import {Test} from "forge-std/Test.sol"; import {Fatso} from "../../src/FatsoERC20.sol"; import {Stinky} from "../../src/StinkyERC20.sol"; import {Ghost} from "../../src/GhstERC20.sol"; import {GhostAuthority} from "../../src/GhostAuthority.sol"; import {GhostTreasury} from "../../src/Treasury.sol"; import {GhostStaking} from "../../src/Staking.sol"; import {WETH9} from "../../src/mocks/WETH9.sol"; import {GhostBondingCalculator} from "../../src/StandardBondingCalculator.sol"; import {Gatekeeper} from "../../src/Gatekeeper.sol"; import {ITreasury} from "../../src/interfaces/ITreasury.sol"; contract GatekeeperRecallTest is Test { uint256 public constant TOTAL_INITIAL_SUPPLY = 5000000000000000; uint256 public constant LARGE_APPROVAL = 100000000000000000000000000000000; uint256 public constant INITIAL_INDEX = 10819917194513808e56; uint48 public constant EPOCH_LENGTH = 2200; uint48 public constant EPOCH_NUMBER = 1; uint48 public constant EPOCH_END_TIME = 1337; uint256 public constant INITIAL_MINT = 1000000000000000000000000; uint256 public constant CAPACITY = 10000e9; uint256 public constant INITIAL_PRICE = 400e9; uint256 public constant BUFFER = 2e5; address constant INITIALIZER = 0x0000000000000000000000000000000000000001; address constant GOVERNOR = 0x0000000000000000000000000000000000000003; address constant GUARDIAN = 0x0000000000000000000000000000000000000004; address constant POLICY = 0x0000000000000000000000000000000000000005; address constant VAULT = 0x0000000000000000000000000000000000000006; address constant ALICE = 0x0000000000000000000000000000000000000007; address constant BOB = 0x0000000000000000000000000000000000000008; uint256 public constant VESTING = 100; uint256 public constant TIME_TO_CONCLUSION = 60 * 60 * 24; uint256 public constant DEPOSIT_INTERVAL = 60 * 60 * 4; uint256 public constant TUNE_INTERVAL = 60 * 60; Fatso ftso; Stinky stnk; Ghost ghst; GhostStaking staking; GhostTreasury treasury; GhostAuthority authority; GhostBondingCalculator calculator; WETH9 reserve; function setUp() public { vm.startPrank(INITIALIZER, INITIALIZER); reserve = new WETH9(); authority = new GhostAuthority( GOVERNOR, GUARDIAN, POLICY, VAULT ); ftso = new Fatso(address(authority), "Fatso", "FTSO"); stnk = new Stinky(INITIAL_INDEX, "Stinky", "STNK"); ghst = new Ghost(address(stnk), "Ghost", "GHST"); staking = new GhostStaking( address(ftso), address(stnk), address(ghst), EPOCH_LENGTH, EPOCH_NUMBER, EPOCH_END_TIME, address(authority) ); treasury = new GhostTreasury(address(ftso), address(authority)); calculator = new GhostBondingCalculator(address(ftso), 6000, 3); stnk.initialize(address(staking), address(treasury), address(ghst)); ghst.initialize(address(staking)); vm.stopPrank(); vm.startPrank(GOVERNOR); authority.pushVault(address(treasury)); treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, ALICE, address(0)); treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator)); treasury.enable(ITreasury.STATUS.STAKING, address(staking), address(0)); vm.stopPrank(); vm.deal(ALICE, INITIAL_MINT); vm.startPrank(ALICE); reserve.deposit{value: INITIAL_MINT}(); reserve.approve(address(treasury), type(uint256).max); treasury.deposit(address(reserve), INITIAL_MINT, treasury.tokenValue(address(reserve), INITIAL_MINT) / 2); assertEq(ftso.totalSupply(), treasury.baseSupply()); vm.stopPrank(); } function test_recallChainWorks() public { vm.startPrank(INITIALIZER); Gatekeeper gatekeeper = Gatekeeper(payable(staking.gatekeeper())); gatekeeper.updatePublicKeyMetadata(0, 0x36ff5b7f0fc50100b563c6072e499980d6e6aa8528ea0ae6d776cf7e8e96c374, 0); vm.stopPrank(); vm.startPrank(ALICE); uint256 aliceBalance = ftso.balanceOf(ALICE); ftso.approve(address(staking), type(uint256).max); staking.stake(aliceBalance, ALICE, false, true); uint256 ghostBalance = ghst.balanceOf(ALICE); staking.ghost(bytes32(abi.encodePacked(ALICE)), ghostBalance); vm.stopPrank(); uint256 bountyPercent = type(uint32).max / 2; uint256 amountToBridge = 42 * 1e15; address evmReceiver = address(0x0000000000000000000000000000000000000002); uint256 bobGhstBefore = ghst.balanceOf(BOB); uint256 receiverEthBefore = evmReceiver.balance; uint256 receiverGhstBefore = ghst.balanceOf(evmReceiver); uint256 totalReservesBefore = treasury.totalReserves(); uint256 totalSupplyBefore = ftso.totalSupply(); uint256 ghostedSupplyBefore = gatekeeper.ghostedSupply(); vm.txGasPrice(2 gwei); vm.startPrank(BOB, BOB); gatekeeper.verify( hex"bf06188a0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000007a919134c0400000000000000000000000000000000000000000010000000000007a6900000000", 0x938640689e6f52929acf2714c5c785e8e725b5df616c449a19bca8a8b300ec8b, 0x15b8b8c2934ee96d6a83a24055d83ea4074d251c207584e66d9caf8d653cabf2 ); vm.stopPrank(); { uint256 bobEarnedGhst = ghst.balanceOf(BOB) - bobGhstBefore; assertTrue(bobEarnedGhst > 0); uint256 bobEarnedFtso = ghst.balanceFrom(bobEarnedGhst); uint256 totalReservesAfter = treasury.totalReserves(); uint256 totalSupplyAfter = ftso.totalSupply(); uint256 gasUsed = 2 * 1e9 * 329188; 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 expectedMintToReceiver = amountToBridge - expectedBounty; uint256 actualMintToReceiver = ghst.balanceOf(evmReceiver) - receiverGhstBefore; assertApproxEqAbs(actualMintToReceiver, expectedMintToReceiver, 1); } { uint256 receiverEthAfter = evmReceiver.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 backingRatioAfter = totalReservesAfter * 1e18 / totalSupplyAfter; uint256 receivedValue = treasury.tokenValue(address(reserve), evmReceiver.balance); assertEq(backingRatioAfter, backingRatioBefore); assertApproxEqAbs(totalReservesAfter + receivedValue, totalReservesBefore, 2000); // 2e-16% vm.prank(GOVERNOR); treasury.auditReserves(); assertEq(treasury.totalReserves() + receivedValue, totalReservesBefore); } { uint256 expectedMintToReceiver = amountToBridge * (type(uint32).max - bountyPercent) / type(uint32).max; uint256 bobGhstAfter = ghst.balanceOf(BOB); assertApproxEqAbs(ghostedSupplyBefore - bobGhstAfter - expectedMintToReceiver, gatekeeper.ghostedSupply(), 1); } } }