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

View File

@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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

View File

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

View File

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