diff --git a/src/Gatekeeper.sol b/src/Gatekeeper.sol index 50ea531..3dc4bec 100644 --- a/src/Gatekeeper.sol +++ b/src/Gatekeeper.sol @@ -21,13 +21,10 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard { using RequestPacking for RequestPacking.RequestPayload; using GovernancePacking for GovernancePacking.GovernancePayload; - uint256 private constant BOUNTY_DIVISOR = type(uint32).max; - uint256 private constant SECP256K1_N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; - 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 BOUNTY_DIVISOR = type(uint32).max; + uint256 public constant MAX_ALLOWED_GAS_PRICE = 3e9; // 3 gwei + uint256 public constant EXISTENTIAL_DEPOSIT = 500 * 1e12; + uint256 public constant GAS_EXECUTION_BUFFER = 33805; uint256 public constant REGISTRY_INDEX = 0; address public override staking; @@ -112,21 +109,6 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard { 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) { if (msg.sender != staking) revert NotStaking(); if (amount < EXISTENTIAL_DEPOSIT) revert NonExistentAmount(); @@ -137,36 +119,56 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard { 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( uint256 exodusSession, uint256 amount, uint256 packed - ) external override { + ) external { if (msg.sender != address(this)) revert NotGatekeeper(); RequestPacking.RequestPayload memory payload = RequestPacking.unpack(packed); if (payload.chainId != block.chainid) revert WrongChainId(); - StorageHistory(storageHistory).trySetTransactionExecuted(exodusSession); - - uint256 bountyAmount = FullMath.mulDiv(amount, BOUNTY_DIVISOR - uint256(payload.bounty), BOUNTY_DIVISOR); + uint256 bountyAmount = FullMath.mulDiv(amount, uint256(payload.bounty), BOUNTY_DIVISOR); uint256 receiverAmount = amount - bountyAmount; - uint256 bridgeOutImbalance = receiverAmount; + StorageHistory(storageHistory).trySetTransactionExecuted(exodusSession); + IStorageHistory(storageHistory).tryIncreaseBridgeOut(receiverAmount); IStaking(staking).recall(payload.receiver, receiverAmount); if (bountyAmount > 0) { - uint256 gasSpent = GAS_RECALL_SPENDING * tx.gasprice; - (address token, uint256 forGas, uint256 sent) = IStaking(staking).recall(bountyAmount, gasSpent, REGISTRY_INDEX); - bridgeOutImbalance += forGas; - + (address token, uint256 sent) = IStaking(staking).recall(bountyAmount, REGISTRY_INDEX); IWETH9(token).withdraw(sent); + (bool sentSuccess,) = payload.receiver.call{ value: sent }(""); if (!sentSuccess) revert SendFailed(); } - IStorageHistory(storageHistory).tryIncreaseBridgeOut(bridgeOutImbalance); - emit Recalled(payload.receiver, amount); } @@ -174,7 +176,7 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard { uint256 exodusSession, bytes32 newPublicKey, uint8 newParity - ) external override { + ) external { if (msg.sender != address(this)) revert NotGatekeeper(); if (newParity % 2 != 0) revert InvalidPublicKey(); @@ -192,7 +194,7 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard { uint256 exodusSession, uint256 packed, bytes calldata - ) external override returns (bytes memory) { + ) external returns (bytes memory) { if (msg.sender != address(this)) revert NotGatekeeper(); GovernancePacking.GovernancePayload memory payload = GovernancePacking.unpack(packed); diff --git a/src/Staking.sol b/src/Staking.sol index ee01321..faff22e 100644 --- a/src/Staking.sol +++ b/src/Staking.sol @@ -167,6 +167,16 @@ contract GhostStaking is IStaking, GhostAccessControlled { 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 { if (gatekeeper != msg.sender) revert NotGatekeeper(); IGHST(ghst).mint(receiver, amount); @@ -174,25 +184,20 @@ contract GhostStaking is IStaking, GhostAccessControlled { function recall( uint256 amount, - uint256 gasSpent, uint256 registryIndex - ) external override returns (address reserveToken, uint256 gasGhstAmount, uint256 value) { + ) external override returns (address reserveToken, uint256 value) { if (gatekeeper != msg.sender) revert NotGatekeeper(); address treasury = ISTNK(stnk).treasury(); uint256 ftsoAmount = IGHST(ghst).balanceFrom(amount); - uint256 gasFtsoAmount; - (reserveToken, value, gasFtsoAmount) = ITreasury(treasury).buyBack( + (reserveToken, value) = ITreasury(treasury).buyBack( msg.sender, ftsoAmount, - gasSpent, registryIndex ); - - gasGhstAmount = IGHST(ghst).balanceTo(gasFtsoAmount); - IFTSO(ftso).burn(ftsoAmount - gasFtsoAmount); - IGHST(ghst).mint(tx.origin, gasGhstAmount); + + IFTSO(ftso).burn(ftsoAmount); } function rebase() public override returns (uint256 bounty) { diff --git a/src/Treasury.sol b/src/Treasury.sol index 47211c7..c809d01 100644 --- a/src/Treasury.sol +++ b/src/Treasury.sol @@ -77,23 +77,35 @@ contract GhostTreasury is GhostAccessControlled, ITreasury { 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( address receiver, uint256 amount, - uint256 gasSpent, 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(); 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 (amount <= gasValue) revert GasExceedsBounty(); - - uint256 reservesToSend = FullMath.mulDiv(amount - gasValue, totalReserves, totalSupply); + uint256 reservesToSend = FullMath.mulDiv(amount, totalReserves, totalSupply); totalReserves = totalReserves - reservesToSend; value = FullMath.mulDiv(reservesToSend, 1e18, IBondingCalculator(bondCalculator[reserveToken]).fraction()); diff --git a/src/interfaces/IGatekeeper.sol b/src/interfaces/IGatekeeper.sol index ee7000a..8cfb6f6 100644 --- a/src/interfaces/IGatekeeper.sol +++ b/src/interfaces/IGatekeeper.sol @@ -17,6 +17,7 @@ interface IGatekeeper { event Ghosted(bytes32 indexed receiver, uint256 indexed amount); event Recalled(address indexed receiver, uint256 indexed amount); event Rotated(bytes32 indexed aggregatedPublicKey, uint8 indexed parity); + event VoluntaryVerification(address indexed sender, uint256 indexed gasSpent); function staking() external view returns (address); function deployer() external view returns (address); diff --git a/src/interfaces/IStaking.sol b/src/interfaces/IStaking.sol index 43b0df7..61a33e8 100644 --- a/src/interfaces/IStaking.sol +++ b/src/interfaces/IStaking.sol @@ -69,7 +69,8 @@ interface IStaking { function unwrap(address _to, uint256 _amount) external returns (uint256 sBalance_); function ghost(bytes32 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 index() external view returns (uint256); diff --git a/src/interfaces/ITreasury.sol b/src/interfaces/ITreasury.sol index 4092ec8..9c47031 100644 --- a/src/interfaces/ITreasury.sol +++ b/src/interfaces/ITreasury.sol @@ -29,7 +29,8 @@ interface ITreasury { uint256 _profit ) 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 mint(address _recipient, uint256 _amount) external; function tokenValue(address _token, uint256 _amount) external view returns (uint256 value_); diff --git a/src/libraries/Verifier.sol b/src/libraries/Verifier.sol index fca116b..b34e247 100644 --- a/src/libraries/Verifier.sol +++ b/src/libraries/Verifier.sol @@ -22,12 +22,7 @@ library Verifier { ) internal view returns (bool) { if (px >= P || rx >= P || s >= N || s == 0) { return false; } if (rx < N) { return verifySpectre(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); + return verifyBanshee(call, px, rx, s); } function verifySpectre( @@ -52,14 +47,12 @@ library Verifier { function verifyBanshee( bytes memory call, uint256 px, - uint256 py, uint256 rx, - uint256 ry, uint256 s - ) internal pure returns (bool) { - // TODO: because I lift it before, do I need to check is it on curve? - // both of them Px,Py and Rx,Ry - if (!isOnCurve(rx, ry) || !isOnCurve(px, py)) { return false; } + ) internal view returns (bool) { + (uint256 py, bool success1) = liftPoint(px); + (uint256 ry, bool success2) = liftPoint(rx); + if (!success1 || !success2) { return false; } uint256 e = computeChallenge(call, rx, px); if (e == 0) return false; // Cheap and safe @@ -96,9 +89,6 @@ library Verifier { } function expModPrecompile(uint256 base) internal view returns (uint256 result) { - uint256 localEXP = EXP; - uint256 localP = P; - assembly { let pointer := mload(0x40) @@ -107,8 +97,8 @@ library Verifier { mstore(add(pointer, 0x40), 0x20) mstore(add(pointer, 0x60), base) - mstore(add(pointer, 0x80), localEXP) - mstore(add(pointer, 0xa0), localP) + mstore(add(pointer, 0x80), EXP) + mstore(add(pointer, 0xa0), P) // Modular Exponentiation Precompile (modexp) let success := staticcall(gas(), 0x05, pointer, 0xc0, pointer, 0x20) diff --git a/test/gatekeeper/Gatekeeper.t.sol b/test/gatekeeper/Gatekeeper.t.sol index 882a169..efa83ec 100644 --- a/test/gatekeeper/Gatekeeper.t.sol +++ b/test/gatekeeper/Gatekeeper.t.sol @@ -4,7 +4,6 @@ import {Test} from "forge-std/Test.sol"; import {Gatekeeper} from "../../src/Gatekeeper.sol"; import {FullMath} from "../../src/libraries/FullMath.sol"; -import {Verifier} from "../../src/libraries/Verifier.sol"; import {RequestPacking} from "../../src/libraries/Packing.sol"; import {IStorageHistory} from "../../src/interfaces/IStorageHistory.sol"; import {StorageHistory} from "../../src/types/StorageHistory.sol"; @@ -142,6 +141,7 @@ contract MockStaking is Test { WETH9 public mockReserve; mapping(address => uint256) private _recalledAmounts; + mapping(address => uint256) private _phantomedAmounts; constructor() { mockReserve = new WETH9(); @@ -169,9 +169,13 @@ contract MockStaking is Test { _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; - 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) { @@ -387,7 +391,6 @@ contract GatekeeperTest is Test { aliceBalanceBefore = ALICE.balance; bobBalanceBefore = BOB.balance; - // TODO: revisit // execute bridge out, happened on DKG #2 // amount: 210; commission: 50%; BOB // exodus session #12 diff --git a/test/gatekeeper/GatekeeperHistory.t.sol b/test/gatekeeper/GatekeeperHistory.t.sol index c1c1172..fbc72af 100644 --- a/test/gatekeeper/GatekeeperHistory.t.sol +++ b/test/gatekeeper/GatekeeperHistory.t.sol @@ -14,6 +14,7 @@ contract MockStaking is Test { WETH9 public mockReserve; mapping(address => uint256) private _recalledAmounts; + mapping(address => uint256) private _phantomedAmounts; constructor() { mockReserve = new WETH9(); @@ -46,25 +47,35 @@ contract MockStaking is Test { _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; - 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]; } + + function phantomedAmounts(address who) external view returns (uint256) { + return _phantomedAmounts[who]; + } } contract GatekeeperStorageHistoryTest is Test { using RequestPacking for RequestPacking.RequestPayload; - address constant ALICE = 0x0000000000000000000000000000000000000001; - address constant BOB = 0x0000000000000000000000000000000000000002; - uint256 constant INIT_AMOUNT = 1337 * 1e18; + address constant ALICE = 0x0000000000000000000000000000000000000001; + address constant BOB = 0x0000000000000000000000000000000000000002; + uint256 constant INIT_AMOUNT = 1337 * 1e18; + uint256 private constant BIG_VALUE = 420 * 1e20; Gatekeeper gatekeeper; MockStaking staking; + WETH9 mockReserve; function setUp() public { vm.prank(ALICE); @@ -72,6 +83,14 @@ contract GatekeeperStorageHistoryTest is Test { staking.runGhost(bytes32(abi.encodePacked(ALICE)), INIT_AMOUNT); 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 { @@ -104,11 +123,10 @@ contract GatekeeperStorageHistoryTest is Test { } else { // forge-lint: disable-next-line(unsafe-typecast) 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); - assertEq(previousAmount + amountToMaterialize, staking.recalledAmount(BOB)); + assertEq(previousAmount + amountToMaterialize, staking.recalledAmounts(BOB)); // forge-lint: disable-next-line(unsafe-typecast) amountOut += uint104(amountToMaterialize); @@ -123,6 +141,7 @@ contract GatekeeperStorageHistoryTest is Test { function test_inheritanceWorksForHistoricalStorge() public { uint256 exodusSession = 69; uint256 packed = RequestPacking.pack(0, uint64(block.chainid), BOB); + staking.runRecall(exodusSession, INIT_AMOUNT, packed); staking.runGhost(bytes32(abi.encodePacked(ALICE)), INIT_AMOUNT); diff --git a/test/gatekeeper/GatekeeperRecall.t.sol b/test/gatekeeper/GatekeeperRecall.t.sol index cac373d..a48083e 100644 --- a/test/gatekeeper/GatekeeperRecall.t.sol +++ b/test/gatekeeper/GatekeeperRecall.t.sol @@ -95,8 +95,7 @@ contract GatekeeperRecallTest is Test { function test_recallChainWorks() public { vm.startPrank(INITIALIZER); Gatekeeper gatekeeper = Gatekeeper(payable(staking.gatekeeper())); - gatekeeper.updatePublicKeyMetadata(0, 0x36ff5b7f0fc50100b563c6072e499980d6e6aa8528ea0ae6d776cf7e8e96c374, 0); - + gatekeeper.updatePublicKeyMetadata(0, 0xb5cd0d028a5e1b6a4eecb113b7e49c8176385eb77c8f134bc1db4bddd7654de6, 0); vm.stopPrank(); vm.startPrank(ALICE); @@ -108,13 +107,12 @@ contract GatekeeperRecallTest is Test { staking.ghost(bytes32(abi.encodePacked(ALICE)), ghostBalance); vm.stopPrank(); - uint256 bountyPercent = type(uint32).max / 2; - uint256 amountToBridge = 42 * 1e15; - address evmReceiver = address(0x0000000000000000000000000000000000000002); + uint256 actualAmount = 105000000000000000; + address actualReceiver = address(0x0000000000000000000000000000000000000002); uint256 bobGhstBefore = ghst.balanceOf(BOB); - uint256 receiverEthBefore = evmReceiver.balance; - uint256 receiverGhstBefore = ghst.balanceOf(evmReceiver); + uint256 receiverEthBefore = actualReceiver.balance; + uint256 receiverGhstBefore = ghst.balanceOf(actualReceiver); uint256 totalReservesBefore = treasury.totalReserves(); uint256 totalSupplyBefore = ftso.totalSupply(); uint256 ghostedSupplyBefore = gatekeeper.ghostedSupply(); @@ -122,78 +120,52 @@ contract GatekeeperRecallTest is Test { vm.txGasPrice(2 gwei); vm.startPrank(BOB, BOB); gatekeeper.verify( - hex"bf06188a0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000007a919134c0400000000000000000000000000000000000000000010000000000007a6900000000", - 0x938640689e6f52929acf2714c5c785e8e725b5df616c449a19bca8a8b300ec8b, - 0x15b8b8c2934ee96d6a83a24055d83ea4074d251c207584e66d9caf8d653cabf2 + hex"bf06188a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000017508f1956a800000000000000000000000000000000000000000020000000000007a6980000000", + 0x883302783b7f3d253d5bdeb17f39117641acdf061bf3fe457cb505df6b17dfc1, + 0x8c3fc03b5cb738777ceec396ee8647f782d6670992f8f973de2dfc83379978d5 ); vm.stopPrank(); { - uint256 bobEarnedGhst = ghst.balanceOf(BOB) - bobGhstBefore; - assertTrue(bobEarnedGhst > 0); + uint256 bobGhstAfter = ghst.balanceOf(BOB); + uint256 receiverGhstAfter = ghst.balanceOf(actualReceiver); - 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); + assertTrue( + ghst.balanceFrom(bobGhstAfter - bobGhstBefore) * totalReservesBefore / totalSupplyBefore + > gatekeeper.GAS_EXECUTION_BUFFER() + ); + assertTrue(bobGhstAfter > bobGhstBefore); + assertApproxEqAbs(receiverGhstAfter - receiverGhstBefore, actualAmount / 2, 1e18); } { - uint256 expectedBounty = amountToBridge * bountyPercent / type(uint32).max; - uint256 expectedMintToReceiver = amountToBridge - expectedBounty; - uint256 actualMintToReceiver = ghst.balanceOf(evmReceiver) - receiverGhstBefore; - assertApproxEqAbs(actualMintToReceiver, expectedMintToReceiver, 1); + uint256 ftsoAmount = ghst.balanceFrom(actualAmount / 2); + uint256 expectedReservesToSend = ftsoAmount * totalReservesBefore / totalSupplyBefore; + uint256 expectedEthRefund = expectedReservesToSend * 1e18 / calculator.fraction(); + + expectedEthRefund = expectedEthRefund * 1e18 / 1e9; + assertApproxEqAbs(actualReceiver.balance, receiverEthBefore + expectedEthRefund, 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(); - + actualAmount = treasury.tokenValue(address(reserve), actualReceiver.balance); uint256 backingRatioBefore = totalReservesBefore * 1e18 / totalSupplyBefore; - uint256 backingRatioAfter = totalReservesAfter * 1e18 / totalSupplyAfter; - uint256 receivedValue = treasury.tokenValue(address(reserve), evmReceiver.balance); + uint256 backingRatioAfter = treasury.totalReserves() * 1e18 / ftso.totalSupply(); - assertEq(backingRatioAfter, backingRatioBefore); - assertApproxEqAbs(totalReservesAfter + receivedValue, totalReservesBefore, 2000); // 2e-16% + uint256 gasFtsoMinted = ghst.balanceFrom(ghst.balanceOf(BOB) - bobGhstBefore); + uint256 expectedDelta = (backingRatioBefore * gasFtsoMinted) / totalSupplyBefore; + + assertApproxEqAbs(backingRatioAfter, backingRatioBefore - expectedDelta, 1000); vm.prank(GOVERNOR); 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 bobGhstAfter = ghst.balanceOf(BOB); - assertApproxEqAbs(ghostedSupplyBefore - bobGhstAfter - expectedMintToReceiver, gatekeeper.ghostedSupply(), 1); + uint256 receiverGhstAfter = ghst.balanceOf(actualReceiver); + uint256 receiverImbalance = receiverGhstAfter - receiverGhstBefore; + assertEq(ghostedSupplyBefore - receiverImbalance, staking.ghostedSupply()); } } }