make everyone happy about the phantomRefund with help of maturity

Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
Uncle Fatso 2026-10-15 15:03:08 +03:00
parent ef598ae702
commit 2623451103
Signed by: f4ts0
GPG Key ID: 565F4F2860226EBB
9 changed files with 88 additions and 25 deletions

View File

@ -41,6 +41,11 @@ contract GhostBondDepository is IBondDepository, NoteKeeper {
WETH = wrapped;
}
function addNote(address user, uint256 payout, uint48 expiry) external returns (uint256) {
if (msg.sender != TREASURY) revert NotTreasury();
return _addNote(user, payout, expiry, type(uint48).max);
}
function deposit(
uint256 id,
uint256 amount,
@ -77,7 +82,7 @@ contract GhostBondDepository is IBondDepository, NoteKeeper {
emit Bond(id, amount, price);
index = addNote(user, payout, uint48(expiry), uint48(id)); // forge-lint: disable-line(unsafe-typecast)
index = _addNote(user, payout, uint48(expiry), uint48(id)); // forge-lint: disable-line(unsafe-typecast)
IWETH9(WETH).deposit{value: msg.value}();
IERC20(WETH).safeTransfer(TREASURY, msg.value);

View File

@ -25,6 +25,7 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
uint256 public constant EXISTENTIAL_DEPOSIT = 500 * 1e12; // 0.005
uint256 public constant FULL_GAS_RESTORATION_TIME = 3600; // 1 hour
uint256 public constant MAX_ALLOWED_GAS_PRICE = 3e9; // 3 gwei
uint256 public constant PHANTOM_EXPIRY = 432000; // seconds, 5 days
uint256 public constant GAS_EXECUTION_BUFFER = 21000 + 5969 + 50955;
uint256 public constant REGISTRY_INDEX = 0;
@ -160,7 +161,7 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
_lastVerificationTime = block.timestamp;
uint256 gasSpent = (gasStart - gasleft() + GAS_EXECUTION_BUFFER) * currentGasPrice;
try IStaking(staking).phantomRefund(msg.sender, gasSpent, REGISTRY_INDEX) {}
try IStaking(staking).phantomRefund(msg.sender, gasSpent, REGISTRY_INDEX, PHANTOM_EXPIRY) {}
catch {
emit VoluntaryVerification(msg.sender, gasSpent);
}

View File

@ -171,14 +171,19 @@ contract GhostStaking is IStaking, GhostAccessControlled {
IGatekeeper(gatekeeper).ghost(receiver, amount);
}
function phantomRefund(address receiver, uint256 amount, uint256 registryIndex) external override {
function phantomRefund(
address receiver,
uint256 amount,
uint256 registryIndex,
uint256 phantomExpiry
) external override {
if (gatekeeper != msg.sender) revert NotGatekeeper();
// forge-lint: disable-next-line(unsafe-typecast)
uint48 phantomRelease = uint48(block.timestamp + phantomExpiry);
address treasury = ISTNK(stnk).treasury();
uint256 gasFtsoAmount = ITreasury(treasury).phantomRefund(amount, registryIndex);
uint256 gasGhstAmount = IGHST(ghst).balanceTo(gasFtsoAmount);
IGHST(ghst).mint(receiver, gasGhstAmount);
ITreasury(treasury).phantomRefund(receiver, phantomRelease, amount, registryIndex);
}
function recall(address receiver, uint256 amount) external override {

View File

@ -18,6 +18,7 @@ import {FullMath} from "./libraries/FullMath.sol";
import {IFTSO} from "./interfaces/IFTSO.sol";
import {IBondingCalculator} from "./interfaces/IBondingCalculator.sol";
import {ITreasury} from "./interfaces/ITreasury.sol";
import {IBondDepository} from "./interfaces/IBondDepository.sol";
import {IGhostAuthority} from "./interfaces/IGhostAuthority.sol";
contract GhostTreasury is GhostAccessControlled, ITreasury {
@ -25,6 +26,7 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
using EnumerableSet for EnumerableSet.AddressSet;
address public immutable FTSO;
address public bondDepository;
uint256 public totalReserves;
mapping(STATUS => EnumerableSet.AddressSet) private _registry;
@ -77,20 +79,22 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
}
function phantomRefund(
address receiver,
uint48 phantomRelease,
uint256 gasSpent,
uint256 index
uint256 registryIndex
) external override returns (uint256 gasValue) {
if (!_permissions[STATUS.STAKING][msg.sender]) revert NotApproved();
address reserveToken = _registry[STATUS.RESERVETOKEN].at(index);
address reserveToken = _registry[STATUS.RESERVETOKEN].at(registryIndex);
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()) {
IFTSO(FTSO).mint(msg.sender, gasValue);
if (gasValue > 0 && gasValue <= excessReserves()) {
IBondDepository(bondDepository).addNote(receiver, gasValue, phantomRelease);
}
}
@ -178,6 +182,15 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
emit Permissioned(toDisable, status, false);
}
function setBondDepository(address newBondDepository) external {
if (
msg.sender != authority.governor() &&
msg.sender != authority.guardian()
) revert NotApproved();
bondDepository = newBondDepository;
emit BondDepositoryUpdated(newBondDepository);
}
function forfeitReserves(
address router,
uint256 liquidity,

View File

@ -2,6 +2,7 @@
pragma solidity ^0.8.20;
interface IBondDepository {
error NotTreasury();
error DepositoryConcluded(uint48 concludedTime);
error DepositoryMaxPrice(uint256 price, uint256 maxPrice);
error DepositoryMaxSize(uint256 payout, uint256 maxPayout);
@ -75,6 +76,7 @@ interface IBondDepository {
) external returns (uint256 id_);
function close(uint256 _id) external;
function addNote(address user, uint256 payout, uint48 expiry) external returns (uint256);
function isLive(uint256 _bid) external view returns (bool);
function liveMarkets() external view returns (uint256[] memory);
function liveMarketsFor(address _quoteToken) external view returns (uint256[] memory);

View File

@ -70,7 +70,7 @@ interface IStaking {
function ghost(bytes32 receiver, uint256 amount) external;
function recall(address receiver, uint256 amount) external;
function recall(uint256 amount, uint256 registryIndex) external returns (address, uint256);
function phantomRefund(address receiver, uint256 amount, uint256 registryIndex) external;
function phantomRefund(address receiver, uint256 amount, uint256 registryIndex, uint256 phantomExpiry) external;
function rebase() external returns (uint256);
function index() external view returns (uint256);

View File

@ -21,6 +21,7 @@ interface ITreasury {
event Minted(address indexed caller, address indexed recipient, uint256 amount);
event Permissioned(address addr, STATUS indexed status, bool result);
event Withdrawal(address indexed token, uint256 indexed amount, uint256 value);
event BondDepositoryUpdated(address bondDepository);
function deposit(
address _token,
@ -29,7 +30,7 @@ interface ITreasury {
) external returns (uint256);
function registered(STATUS status, address token) external view returns(bool);
function phantomRefund(uint256 gasSpent, uint256 amount) external returns (uint256);
function phantomRefund(address receiver, uint48 phantomExpiry, 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;

View File

@ -27,7 +27,7 @@ abstract contract NoteKeeper is INoteKeeper {
_TREASURY = ITreasury(_treasuryAddress);
}
function addNote(
function _addNote(
address user,
uint256 payout,
uint48 expiry,

View File

@ -5,6 +5,7 @@ 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 {GhostBondDepository} from "../../src/BondDepository.sol";
import {GhostAuthority} from "../../src/GhostAuthority.sol";
import {GhostTreasury} from "../../src/Treasury.sol";
import {GhostStaking} from "../../src/Staking.sol";
@ -46,6 +47,7 @@ contract GatekeeperRecallTest is Test {
GhostTreasury treasury;
GhostAuthority authority;
GhostBondingCalculator calculator;
GhostBondDepository bondDepository;
WETH9 reserve;
function setUp() public {
@ -71,15 +73,25 @@ contract GatekeeperRecallTest is Test {
);
treasury = new GhostTreasury(address(ftso), address(authority));
calculator = new GhostBondingCalculator(address(ftso), 6000, 3);
bondDepository = new GhostBondDepository(
address(authority),
address(ftso),
address(ghst),
address(staking),
address(treasury),
address(reserve)
);
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.REWARDMANAGER, address(bondDepository), address(0));
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));
treasury.setBondDepository(address(bondDepository));
vm.stopPrank();
vm.deal(ALICE, INITIAL_MINT);
@ -109,11 +121,11 @@ contract GatekeeperRecallTest is Test {
vm.warp(block.timestamp + gatekeeper.FULL_GAS_RESTORATION_TIME() * 69);
uint256 bobBalanceBeforeFirst = ghst.balanceOf(BOB);
uint256 priceBeforeFirst = gatekeeper.getCurrentGasPrice();
assertEq(priceBeforeFirst, gatekeeper.MAX_ALLOWED_GAS_PRICE());
assertEq(ghst.balanceOf(BOB), 0);
vm.txGasPrice(2 gwei);
vm.prank(BOB, BOB);
gatekeeper.verify(
hex"bf06188a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000017508f1956a800000000000000000000000000000000000000000020000000000007a6980000000",
@ -121,10 +133,12 @@ contract GatekeeperRecallTest is Test {
0x8c3fc03b5cb738777ceec396ee8647f782d6670992f8f973de2dfc83379978d5
);
uint256 bobBalanceAfterFirst = ghst.balanceOf(BOB);
assertGt(bobBalanceAfterFirst, bobBalanceBeforeFirst);
(uint256 pendingPayout, bool matured) = bondDepository.pendingFor(BOB, 0);
assertGt(pendingPayout, 0); // 19273751457699280
assertFalse(matured);
assertEq(gatekeeper.getCurrentGasPrice(), 0);
vm.txGasPrice(2 gwei);
vm.prank(BOB, BOB);
gatekeeper.verify(
hex"3137142600000000000000000000000000000000000000000000000000000000000000120000000000007a69010101010101010101010101010101010101010100000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000042b7ce50000000000000000000000000000000000000000000000000000000000",
@ -132,13 +146,12 @@ contract GatekeeperRecallTest is Test {
0xdbeed3eb94a829710147595c1b10f2ac6ec761c9721d8cc4711842c212ac517e
);
assertEq(ghst.balanceOf(BOB), bobBalanceAfterFirst);
assertEq(gatekeeper.getCurrentGasPrice(), 0);
vm.warp(block.timestamp + gatekeeper.FULL_GAS_RESTORATION_TIME() / 2);
uint256 gasPriceRestored = gatekeeper.getCurrentGasPrice();
assertEq(gasPriceRestored, gatekeeper.MAX_ALLOWED_GAS_PRICE() / 2);
vm.txGasPrice(2 gwei);
vm.prank(BOB, BOB);
gatekeeper.verify(
hex"31371426000000000000000000000000000000000000000000000000000000000000000d0000000000007a6901010101010101010101010101010101010101010000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064e4e33ef800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000101010101010101010101010101010101010101000000000000000000000000010101010101010101010101010101010101010100000000000000000000000000000000000000000000000000000000",
@ -146,8 +159,13 @@ contract GatekeeperRecallTest is Test {
0xc2da93948f869c5102992bd1cbd70ef5f821ff522305c69d113faf2e89570ce7
);
assertGt(ghst.balanceOf(BOB), bobBalanceAfterFirst);
(pendingPayout, matured) = bondDepository.pendingFor(BOB, 1);
assertGt(pendingPayout, 69);
assertFalse(matured);
assertEq(gatekeeper.getCurrentGasPrice(), 0);
uint256[] memory indexes = bondDepository.indexesFor(BOB);
assertEq(indexes.length, 2);
}
function test_recallChainWorks() public {
@ -168,7 +186,6 @@ contract GatekeeperRecallTest is Test {
uint256 actualAmount = 105000000000000000;
address actualReceiver = address(0x0000000000000000000000000000000000000002);
uint256 bobGhstBefore = ghst.balanceOf(BOB);
uint256 receiverEthBefore = actualReceiver.balance;
uint256 receiverGhstBefore = ghst.balanceOf(actualReceiver);
uint256 totalReservesBefore = treasury.totalReserves();
@ -185,14 +202,15 @@ contract GatekeeperRecallTest is Test {
vm.stopPrank();
{
uint256 bobGhstAfter = ghst.balanceOf(BOB);
(uint256 bobPhantomClaimAfter, bool maturedAfter) = bondDepository.pendingFor(BOB, 0);
uint256 receiverGhstAfter = ghst.balanceOf(actualReceiver);
assertFalse(maturedAfter);
assertTrue(
ghst.balanceFrom(bobGhstAfter - bobGhstBefore) * totalReservesBefore / totalSupplyBefore
ghst.balanceFrom(bobPhantomClaimAfter) * totalReservesBefore / totalSupplyBefore
> gatekeeper.GAS_EXECUTION_BUFFER()
);
assertTrue(bobGhstAfter > bobGhstBefore);
assertApproxEqAbs(receiverGhstAfter - receiverGhstBefore, actualAmount / 2, 1e18);
}
@ -211,7 +229,8 @@ contract GatekeeperRecallTest is Test {
uint256 backingRatioBefore = totalReservesBefore * 1e18 / totalSupplyBefore;
uint256 backingRatioAfter = treasury.totalReserves() * 1e18 / ftso.totalSupply();
uint256 gasFtsoMinted = ghst.balanceFrom(ghst.balanceOf(BOB) - bobGhstBefore);
(uint256 bobPhantomClaim,) = bondDepository.pendingFor(BOB, 0);
uint256 gasFtsoMinted = ghst.balanceFrom(bobPhantomClaim);
uint256 expectedDelta = (backingRatioBefore * gasFtsoMinted) / totalSupplyBefore;
assertApproxEqAbs(backingRatioAfter, backingRatioBefore - expectedDelta, 1000);
@ -220,5 +239,22 @@ contract GatekeeperRecallTest is Test {
treasury.auditReserves();
assertApproxEqAbs(treasury.totalReserves() * 1e18 / ftso.totalSupply(), backingRatioBefore - expectedDelta, 2000);
}
{
(uint256 phantomClaim, bool claimMatured) = bondDepository.pendingFor(BOB, 0);
assertFalse(claimMatured);
vm.expectRevert();
bondDepository.redeemAll(BOB, true);
vm.warp(block.timestamp + gatekeeper.PHANTOM_EXPIRY() + 69);
(phantomClaim, claimMatured) = bondDepository.pendingFor(BOB, 0);
assertTrue(claimMatured);
vm.prank(BOB);
uint256 actualPayout = bondDepository.redeemAll(BOB, true);
assertEq(phantomClaim, actualPayout);
assertEq(actualPayout, ghst.balanceOf(BOB));
}
}
}