make the dream a reality; treasury buyBack added

Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
Uncle Fatso 2026-09-06 16:58:29 +03:00
parent 2e35fb24c7
commit fe9f92281b
Signed by: f4ts0
GPG Key ID: 565F4F2860226EBB
20 changed files with 396 additions and 158 deletions

View File

@ -69,9 +69,6 @@ INITIAL_INDEX=
COEFFICIENT_NUMERATOR= COEFFICIENT_NUMERATOR=
COEFFICIENT_DENOMINATOR= COEFFICIENT_DENOMINATOR=
## Blocks needed for permissions to take place, only needed if timelock enabled
BLOCKS_NEEDED_FOR_TREASURY_QUEUE=
## Multiplier for each native coin where result of multiplication represents amount ## Multiplier for each native coin where result of multiplication represents amount
## of mint tokens. ## of mint tokens.
RESERVE_MINT_RATE= RESERVE_MINT_RATE=
@ -105,10 +102,8 @@ GOVERNOR_PROPOSAL_THRESHOLD=
GOVERNOR_QUORUM_FRACTION= GOVERNOR_QUORUM_FRACTION=
###################### Initial ghosted supply on gatekeeper ########################### ###################### Initial ghosted supply on gatekeeper ###########################
## existential - minimum amount that could be reflected inside other chain ##
## previousWeaver - previous weaver address if any to make linked list of weavers ## ## previousWeaver - previous weaver address if any to make linked list of weavers ##
####################################################################################### #######################################################################################
INITIAL_EXISTENTIAL_DEPOSIT=
PREVIOUS_WEAVER_ADDRESS= PREVIOUS_WEAVER_ADDRESS=
SEPOLIA_TEST_RPC_URL= SEPOLIA_TEST_RPC_URL=

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20; pragma solidity ^0.8.20;
import {IWETH9} from "./interfaces/IWETH9.sol";
import {IStaking} from "./interfaces/IStaking.sol"; import {IStaking} from "./interfaces/IStaking.sol";
import {IGatekeeper} from "./interfaces/IGatekeeper.sol"; import {IGatekeeper} from "./interfaces/IGatekeeper.sol";
import {IStorageHistory} from "./interfaces/IStorageHistory.sol"; import {IStorageHistory} from "./interfaces/IStorageHistory.sol";
@ -24,10 +25,13 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
uint256 private constant SECP256K1_Q = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; uint256 private constant SECP256K1_Q = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
uint256 private constant SHIFT_FACTOR = (2**128) % SECP256K1_N; 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 REGISTRY_INDEX = 0;
address public override staking; address public override staking;
address public override deployer; address public override deployer;
address public override storageHistory; address public override storageHistory;
uint256 public override existentialDeposit;
address private _previousAddress; address private _previousAddress;
bool private _initialized; bool private _initialized;
@ -35,16 +39,14 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
Checkpoints.Trace256 private _aggregatedPublicKeys; Checkpoints.Trace256 private _aggregatedPublicKeys;
mapping(bytes32 => uint256) private _packedRotationStates; mapping(bytes32 => uint256) private _packedRotationStates;
constructor( constructor(address _storageHistory) {
uint256 _existentialDeposit,
address _storageHistory
) {
existentialDeposit = _existentialDeposit;
storageHistory = _storageHistory; storageHistory = _storageHistory;
staking = msg.sender; staking = msg.sender;
deployer = tx.origin; deployer = tx.origin;
} }
receive() external payable {}
function initialize(address _previousGatekeeperAddress) external override { function initialize(address _previousGatekeeperAddress) external override {
if (_previousGatekeeperAddress != address(0)) { if (_previousGatekeeperAddress != address(0)) {
require(_initialized == false); require(_initialized == false);
@ -124,7 +126,7 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
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 < existentialDeposit) revert NonExistentAmount(); if (amount < EXISTENTIAL_DEPOSIT) revert NonExistentAmount();
IStorageHistory(storageHistory).increaseBridgeIn(amount); IStorageHistory(storageHistory).increaseBridgeIn(amount);
@ -143,25 +145,25 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
if (payload.chainId != block.chainid) revert WrongChainId(); if (payload.chainId != block.chainid) revert WrongChainId();
StorageHistory(storageHistory).trySetTransactionExecuted(exodusSession); StorageHistory(storageHistory).trySetTransactionExecuted(exodusSession);
IStorageHistory(storageHistory).tryIncreaseBridgeOut(amount);
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;
IStaking(staking).recall(payload.receiver, receiverAmount); IStaking(staking).recall(payload.receiver, receiverAmount);
if (bountyAmount > 0) { if (bountyAmount > 0) {
uint256 totalReserves = IStaking(staking).totalReserves(); uint256 gasSpent = GAS_RECALL_SPENDING * tx.gasprice;
uint256 baseSupply = IStaking(staking).baseSupply(); (address token, uint256 forGas, uint256 sent) = IStaking(staking).recall(bountyAmount, gasSpent, REGISTRY_INDEX);
uint256 minimumNativeRequired = FullMath.mulDiv(bountyAmount, totalReserves, baseSupply); bridgeOutImbalance += forGas;
if (minimumNativeRequired > msg.value) revert InsufficientValue(); IWETH9(token).withdraw(sent);
IStaking(staking).recall(tx.origin, bountyAmount); (bool sentSuccess,) = payload.receiver.call{ value: sent }("");
(bool sentSuccess,) = payload.receiver.call{ value: msg.value }("");
if (!sentSuccess) revert SendFailed(); if (!sentSuccess) revert SendFailed();
} }
IStorageHistory(storageHistory).tryIncreaseBridgeOut(bridgeOutImbalance);
emit Recalled(payload.receiver, amount); emit Recalled(payload.receiver, amount);
} }

View File

@ -9,6 +9,7 @@ import {Gatekeeper} from "./Gatekeeper.sol";
import {StorageHistory} from "./types/StorageHistory.sol"; import {StorageHistory} from "./types/StorageHistory.sol";
import {GhostAccessControlled} from "./types/GhostAccessControlled.sol"; import {GhostAccessControlled} from "./types/GhostAccessControlled.sol";
import {IFTSO} from "./interfaces/IFTSO.sol";
import {ISTNK} from "./interfaces/ISTNK.sol"; import {ISTNK} from "./interfaces/ISTNK.sol";
import {IGHST} from "./interfaces/IGHST.sol"; import {IGHST} from "./interfaces/IGHST.sol";
import {IStaking} from "./interfaces/IStaking.sol"; import {IStaking} from "./interfaces/IStaking.sol";
@ -46,8 +47,7 @@ contract GhostStaking is IStaking, GhostAccessControlled {
uint48 _epochLength, uint48 _epochLength,
uint48 _firstEpochNumber, uint48 _firstEpochNumber,
uint48 _firstEpochTime, uint48 _firstEpochTime,
address _authority, address _authority
uint256 _existentialDeposit
) GhostAccessControlled(IGhostAuthority(_authority)) { ) GhostAccessControlled(IGhostAuthority(_authority)) {
ftso = _ftso; ftso = _ftso;
stnk = _stnk; stnk = _stnk;
@ -62,7 +62,7 @@ contract GhostStaking is IStaking, GhostAccessControlled {
GhostWarmup newWarmup = new GhostWarmup(_ghst); GhostWarmup newWarmup = new GhostWarmup(_ghst);
StorageHistory newHistory = new StorageHistory(); StorageHistory newHistory = new StorageHistory();
Gatekeeper newGatekeeper = new Gatekeeper(_existentialDeposit, address(newHistory)); Gatekeeper newGatekeeper = new Gatekeeper(address(newHistory));
IStorageHistory(newHistory).setOwner(address(newGatekeeper)); IStorageHistory(newHistory).setOwner(address(newGatekeeper));
IGatekeeper(newGatekeeper).initialize(address(0)); IGatekeeper(newGatekeeper).initialize(address(0));
@ -172,6 +172,29 @@ contract GhostStaking is IStaking, GhostAccessControlled {
IGHST(ghst).mint(receiver, amount); IGHST(ghst).mint(receiver, amount);
} }
function recall(
uint256 amount,
uint256 gasSpent,
uint256 registryIndex
) external override returns (address reserveToken, uint256 gasGhstAmount, 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(
msg.sender,
ftsoAmount,
gasSpent,
registryIndex
);
gasGhstAmount = IGHST(ghst).balanceTo(gasFtsoAmount);
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) {
if (epoch.end <= block.timestamp && block.number > _lastRebaseBlock) { if (epoch.end <= block.timestamp && block.number > _lastRebaseBlock) {
ISTNK(stnk).rebase(epoch.distribute, epoch.number); ISTNK(stnk).rebase(epoch.distribute, epoch.number);

View File

@ -4,6 +4,7 @@ pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol"; import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {IERC20Metadata} from "@openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import {EnumerableSet} from "@openzeppelin-contracts/utils/structs/EnumerableSet.sol";
import {IUniswapV2Factory} from "@uniswap-v2-core-1.0.1/interfaces/IUniswapV2Factory.sol"; import {IUniswapV2Factory} from "@uniswap-v2-core-1.0.1/interfaces/IUniswapV2Factory.sol";
import {IUniswapV2Pair} from "@uniswap-v2-core-1.0.1/interfaces/IUniswapV2Pair.sol"; import {IUniswapV2Pair} from "@uniswap-v2-core-1.0.1/interfaces/IUniswapV2Pair.sol";
@ -21,25 +22,22 @@ import {IGhostAuthority} from "./interfaces/IGhostAuthority.sol";
contract GhostTreasury is GhostAccessControlled, ITreasury { contract GhostTreasury is GhostAccessControlled, ITreasury {
using SafeERC20 for IERC20; using SafeERC20 for IERC20;
using EnumerableSet for EnumerableSet.AddressSet;
address public immutable ftso; // forge-lint: disable-line(screaming-snake-case-immutable) address public immutable FTSO;
uint256 public immutable blocksNeededForQueue; // forge-lint: disable-line(screaming-snake-case-immutable)
uint256 public totalReserves; uint256 public totalReserves;
uint256 public totalDebt; uint256 public totalDebt;
uint256 public ftsoDebt;
mapping(STATUS => address[]) public registry; mapping(STATUS => EnumerableSet.AddressSet) private _registry;
mapping(STATUS => mapping(address => bool)) public permissions; mapping(STATUS => mapping(address => bool)) private _permissions;
mapping(address => address) public bondCalculator; mapping(address => address) public bondCalculator;
constructor( constructor(
address _ftso, address _ftso,
uint256 _timelock,
address _authority address _authority
) GhostAccessControlled(IGhostAuthority(_authority)) { ) GhostAccessControlled(IGhostAuthority(_authority)) {
ftso = _ftso; FTSO = _ftso;
blocksNeededForQueue = _timelock;
} }
function deposit( function deposit(
@ -47,30 +45,30 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
uint256 amount, uint256 amount,
uint256 profit uint256 profit
) external override returns (uint256 send) { ) external override returns (uint256 send) {
if (permissions[STATUS.RESERVETOKEN][token]) { if (_permissions[STATUS.RESERVETOKEN][token]) {
if (!permissions[STATUS.RESERVEDEPOSITOR][msg.sender]) revert NotApproved(); if (!_permissions[STATUS.RESERVEDEPOSITOR][msg.sender]) revert NotApproved();
} else if (permissions[STATUS.LIQUIDITYTOKEN][token]) { } else if (_permissions[STATUS.LIQUIDITYTOKEN][token]) {
if (!permissions[STATUS.LIQUIDITYDEPOSITOR][msg.sender]) revert NotApproved(); if (!_permissions[STATUS.LIQUIDITYDEPOSITOR][msg.sender]) revert NotApproved();
} else revert InvalidToken(); } else revert InvalidToken();
IERC20(token).safeTransferFrom(msg.sender, address(this), amount); IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
uint256 value = tokenValue(token, amount); uint256 value = tokenValue(token, amount);
send = value - profit; send = value - profit;
IFTSO(ftso).mint(msg.sender, send); IFTSO(FTSO).mint(msg.sender, send);
totalReserves = totalReserves + value; totalReserves = totalReserves + value;
emit Deposit(token, amount, value); emit Deposit(token, amount, value);
} }
function mint(address recipient, uint256 amount) external override { function mint(address recipient, uint256 amount) external override {
if (!permissions[STATUS.REWARDMANAGER][msg.sender]) revert NotApproved(); if (!_permissions[STATUS.REWARDMANAGER][msg.sender]) revert NotApproved();
if (amount > excessReserves()) revert InsufficientReserves(); if (amount > excessReserves()) revert InsufficientReserves();
IFTSO(ftso).mint(recipient, amount); IFTSO(FTSO).mint(recipient, amount);
emit Minted(msg.sender, recipient, amount); emit Minted(msg.sender, recipient, amount);
} }
function withdraw(address token, uint256 amount) external onlyGovernor override { function withdraw(address token, uint256 amount) external onlyGovernor override {
if (!permissions[STATUS.RESERVETOKEN][token]) revert NotAccepted(); if (!_permissions[STATUS.RESERVETOKEN][token]) revert NotAccepted();
uint256 value = tokenValue(token, amount); uint256 value = tokenValue(token, amount);
totalReserves = totalReserves - value; totalReserves = totalReserves - value;
@ -79,6 +77,30 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
emit Withdrawal(token, amount, value); emit Withdrawal(token, amount, value);
} }
function buyBack(
address receiver,
uint256 amount,
uint256 gasSpent,
uint256 index
) external override returns (address reserveToken, uint256 value, uint256 gasValue) {
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);
totalReserves = totalReserves - reservesToSend;
value = FullMath.mulDiv(reservesToSend, 1e18, IBondingCalculator(bondCalculator[reserveToken]).fraction());
value = FullMath.mulDiv(value, 10**IERC20Metadata(reserveToken).decimals(), 1e9);
IERC20(reserveToken).safeTransfer(receiver, value);
}
function auditReserves() external { function auditReserves() external {
if ( if (
msg.sender != authority.governor() && msg.sender != authority.governor() &&
@ -86,12 +108,12 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
) revert NotApproved(); ) revert NotApproved();
uint256 reserves; uint256 reserves;
address[] memory reserveTokens = registry[STATUS.RESERVETOKEN]; uint256 reserveTokensLength = _registry[STATUS.RESERVETOKEN].length();
uint256 i; uint256 i;
for (; i < reserveTokens.length;) { for (; i < reserveTokensLength;) {
address reserveToken = reserveTokens[i]; address reserveToken = _registry[STATUS.RESERVETOKEN].at(i);
if (permissions[STATUS.RESERVETOKEN][reserveToken]) { if (_permissions[STATUS.RESERVETOKEN][reserveToken]) {
reserves = reserves + tokenValue( reserves = reserves + tokenValue(
reserveToken, reserveToken,
IERC20(reserveToken).balanceOf(address(this)) IERC20(reserveToken).balanceOf(address(this))
@ -100,11 +122,12 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
unchecked { ++i; } unchecked { ++i; }
} }
address[] memory liquidityTokens = registry[STATUS.LIQUIDITYTOKEN]; uint256 liquidityTokensLength = _registry[STATUS.LIQUIDITYTOKEN].length();
i = 0; i = 0;
for (; i < liquidityTokens.length;) { for (; i < liquidityTokensLength;) {
address liquidityToken = liquidityTokens[i]; address liquidityToken = _registry[STATUS.LIQUIDITYTOKEN].at(i);
if (permissions[STATUS.LIQUIDITYTOKEN][liquidityToken]) { if (_permissions[STATUS.LIQUIDITYTOKEN][liquidityToken]) {
reserves = reserves + tokenValue( reserves = reserves + tokenValue(
liquidityToken, liquidityToken,
IERC20(liquidityToken).balanceOf(address(this)) IERC20(liquidityToken).balanceOf(address(this))
@ -122,11 +145,13 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
address someAddress, address someAddress,
address calculatorAddress address calculatorAddress
) external onlyGovernor { ) external onlyGovernor {
permissions[status][someAddress] = true; _permissions[status][someAddress] = true;
(bool registered, ) = indexInRegistry(someAddress, status);
if (!registered && (status == STATUS.LIQUIDITYTOKEN || status == STATUS.RESERVETOKEN)) { bool alreadyRegistered = _registry[status].contains(someAddress);
assert(calculatorAddress != address(0)); if (!alreadyRegistered && (status == STATUS.LIQUIDITYTOKEN || status == STATUS.RESERVETOKEN)) {
registry[status].push(someAddress); if (calculatorAddress == address(0)) revert();
_registry[status].add(someAddress);
bondCalculator[someAddress] = calculatorAddress; bondCalculator[someAddress] = calculatorAddress;
} }
emit Permissioned(someAddress, status, true); emit Permissioned(someAddress, status, true);
@ -137,7 +162,7 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
msg.sender != authority.governor() && msg.sender != authority.governor() &&
msg.sender != authority.guardian() msg.sender != authority.guardian()
) revert NotApproved(); ) revert NotApproved();
permissions[status][toDisable] = false; _permissions[status][toDisable] = false;
emit Permissioned(toDisable, status, false); emit Permissioned(toDisable, status, false);
} }
@ -147,7 +172,7 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
bool destroyerMode bool destroyerMode
) external onlyGovernor { ) external onlyGovernor {
address weth = IUniswapV2Router01(router).WETH(); address weth = IUniswapV2Router01(router).WETH();
address pair = IUniswapV2Factory(IUniswapV2Router01(router).factory()).getPair(ftso, weth); address pair = IUniswapV2Factory(IUniswapV2Router01(router).factory()).getPair(FTSO, weth);
IERC20(pair).safeTransfer(pair, liquidity); IERC20(pair).safeTransfer(pair, liquidity);
(uint256 amount0, uint256 amount1) = IUniswapV2Pair(pair).burn(address(this)); (uint256 amount0, uint256 amount1) = IUniswapV2Pair(pair).burn(address(this));
@ -157,15 +182,15 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
address token0 = IUniswapV2Pair(pair).token0(); address token0 = IUniswapV2Pair(pair).token0();
address token1 = IUniswapV2Pair(pair).token1(); address token1 = IUniswapV2Pair(pair).token1();
if (token0 == ftso) { if (token0 == FTSO) {
amountToDestroy = amount0; amountToDestroy = amount0;
} }
if (token1 == ftso) { if (token1 == FTSO) {
amountToDestroy = amount1; amountToDestroy = amount1;
} }
IFTSO(ftso).burn(amountToDestroy); IFTSO(FTSO).burn(amountToDestroy);
} }
} }
@ -174,16 +199,16 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
uint256 amount uint256 amount
) external onlyGovernor { ) external onlyGovernor {
address weth = IUniswapV2Router01(router).WETH(); address weth = IUniswapV2Router01(router).WETH();
address pair = IUniswapV2Factory(IUniswapV2Router01(router).factory()).getPair(ftso, weth); address pair = IUniswapV2Factory(IUniswapV2Router01(router).factory()).getPair(FTSO, weth);
IERC20(weth).approve(router, amount); IERC20(weth).approve(router, amount);
(uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pair).getReserves(); (uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pair).getReserves();
address[] memory path = new address[](2); address[] memory path = new address[](2);
path[0] = weth; path[0] = weth;
path[1] = ftso; path[1] = FTSO;
if (ftso < weth) { if (FTSO < weth) {
reserve0 = reserve1 ^ reserve0; reserve0 = reserve1 ^ reserve0;
reserve1 = reserve1 ^ reserve0; reserve1 = reserve1 ^ reserve0;
reserve0 = reserve1 ^ reserve0; reserve0 = reserve1 ^ reserve0;
@ -202,48 +227,41 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
amountIn = amount - amountIn; amountIn = amount - amountIn;
IERC20(weth).safeTransfer(pair, amountIn); IERC20(weth).safeTransfer(pair, amountIn);
IERC20(ftso).safeTransfer(pair, amounts[1]); IERC20(FTSO).safeTransfer(pair, amounts[1]);
IUniswapV2Pair(pair).mint(address(this)); IUniswapV2Pair(pair).mint(address(this));
} }
function indexInRegistry( function permissioned(STATUS status, address someAddress) external view returns (bool) {
address someAddress, return _permissions[status][someAddress];
STATUS status }
) public view override returns (bool, uint256) {
address[] memory entries = registry[status]; function registered(STATUS status, address someAddress) external view returns (bool) {
uint256 i; return _registry[status].contains(someAddress);
for (; i < entries.length; ) {
if (someAddress == entries[i]) {
return (true, i);
}
unchecked { ++i; }
}
return (false, 0);
} }
function originalCoefficient() external view returns (uint256) { function originalCoefficient() external view returns (uint256) {
address[] memory reserveTokens = registry[STATUS.RESERVETOKEN]; address reserveToken = _registry[STATUS.RESERVETOKEN].at(0);
return IBondingCalculator(bondCalculator[reserveTokens[0]]).fraction(); return IBondingCalculator(bondCalculator[reserveToken]).fraction();
} }
function excessReserves() public view override returns (uint256) { function excessReserves() public view override returns (uint256) {
return totalReserves - (IFTSO(ftso).totalSupply() - totalDebt); return totalReserves - (IFTSO(FTSO).totalSupply() - totalDebt);
} }
function tokenValue( function tokenValue(
address token, address token,
uint256 amount uint256 amount
) public view override returns (uint256 value) { ) public view override returns (uint256 value) {
if (permissions[STATUS.LIQUIDITYTOKEN][token]) { if (_permissions[STATUS.LIQUIDITYTOKEN][token]) {
value = IBondingCalculator(bondCalculator[token]).valuation(token, amount); value = IBondingCalculator(bondCalculator[token]).valuation(token, amount);
} else if (permissions[STATUS.RESERVETOKEN][token]) { } else if (_permissions[STATUS.RESERVETOKEN][token]) {
value = FullMath.mulDiv(amount, 1e9, 10**IERC20Metadata(token).decimals()); value = FullMath.mulDiv(amount, 1e9, 10**IERC20Metadata(token).decimals());
value = FullMath.mulDiv(value, IBondingCalculator(bondCalculator[token]).fraction(), 1e18); value = FullMath.mulDiv(value, IBondingCalculator(bondCalculator[token]).fraction(), 1e18);
} }
} }
function baseSupply() external view override returns (uint256) { function baseSupply() external view override returns (uint256) {
return IFTSO(ftso).totalSupply() - ftsoDebt; return IFTSO(FTSO).totalSupply();
} }
function _quantityToBeSwapped(uint256 xa, uint256 x1) internal pure returns (uint256) { function _quantityToBeSwapped(uint256 xa, uint256 x1) internal pure returns (uint256) {

View File

@ -21,7 +21,6 @@ interface IGatekeeper {
function deployer() external view returns (address); function deployer() external view returns (address);
function ghostedSupply() external view returns (uint256); function ghostedSupply() external view returns (uint256);
function storageHistory() external view returns (address); function storageHistory() external view returns (address);
function existentialDeposit() external view returns (uint256);
function latestPublicKeyInfo() external view returns (bytes32, uint8, uint64); function latestPublicKeyInfo() external view returns (bytes32, uint8, uint64);
function getRotationInfoAt(uint256 session) external view returns (bytes32, uint8, uint64); function getRotationInfoAt(uint256 session) external view returns (bytes32, uint8, uint64);

View File

@ -69,6 +69,7 @@ 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 rebase() external returns (uint256); function rebase() external returns (uint256);
function index() external view returns (uint256); function index() external view returns (uint256);

View File

@ -5,6 +5,7 @@ interface ITreasury {
error NotApproved(); error NotApproved();
error NotAccepted(); error NotAccepted();
error InvalidToken(); error InvalidToken();
error GasExceedsBounty();
error InsufficientReserves(); error InsufficientReserves();
enum STATUS { enum STATUS {
@ -12,7 +13,8 @@ interface ITreasury {
RESERVETOKEN, RESERVETOKEN,
LIQUIDITYDEPOSITOR, LIQUIDITYDEPOSITOR,
LIQUIDITYTOKEN, LIQUIDITYTOKEN,
REWARDMANAGER REWARDMANAGER,
STAKING
} }
event Deposit(address indexed token, uint256 amount, uint256 value); event Deposit(address indexed token, uint256 amount, uint256 value);
@ -27,10 +29,10 @@ 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 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_);
function indexInRegistry(address _address, STATUS _status) external view returns (bool, uint256);
function excessReserves() external view returns (uint256); function excessReserves() external view returns (uint256);
function baseSupply() external view returns (uint256); function baseSupply() external view returns (uint256);
function totalReserves() external view returns (uint256); function totalReserves() external view returns (uint256);

View File

@ -75,10 +75,9 @@ contract GhostBondDepositoryTest is Test {
EPOCH_LENGTH, EPOCH_LENGTH,
EPOCH_NUMBER, EPOCH_NUMBER,
EPOCH_END_TIME, EPOCH_END_TIME,
address(authority), address(authority)
0
); );
treasury = new GhostTreasury(address(ftso), 69, address(authority)); treasury = new GhostTreasury(address(ftso), address(authority));
calculator = new GhostBondingCalculator(address(ftso), 1, 1); calculator = new GhostBondingCalculator(address(ftso), 1, 1);
stnk.initialize(address(staking), address(treasury), address(ghst)); stnk.initialize(address(staking), address(treasury), address(ghst));
ghst.initialize(address(staking)); ghst.initialize(address(staking));

View File

@ -7,6 +7,7 @@ import {FullMath} from "../../src/libraries/FullMath.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";
import {WETH9} from "../../src/mocks/WETH9.sol";
contract MockGovernance is Test { contract MockGovernance is Test {
address public immutable GATEKEEPER; address public immutable GATEKEEPER;
@ -137,12 +138,14 @@ contract MockGovernance is Test {
contract MockStaking is Test { contract MockStaking is Test {
Gatekeeper public gatekeeper; Gatekeeper public gatekeeper;
WETH9 public mockReserve;
mapping(address => uint256) private _recalledAmounts; mapping(address => uint256) private _recalledAmounts;
constructor(uint256 existential) { constructor() {
mockReserve = new WETH9();
StorageHistory history = new StorageHistory(); StorageHistory history = new StorageHistory();
gatekeeper = new Gatekeeper(existential, address(history)); gatekeeper = new Gatekeeper(address(history));
gatekeeper.initialize(address(0)); gatekeeper.initialize(address(0));
history.setOwner(address(gatekeeper)); history.setOwner(address(gatekeeper));
} }
@ -169,6 +172,11 @@ contract MockStaking is Test {
_recalledAmounts[receiver] += amount; _recalledAmounts[receiver] += amount;
} }
function recall(uint256 amount, uint256, uint256) external returns (address, uint256, uint256){
_recalledAmounts[tx.origin] += amount;
return (address(mockReserve), 0, 0);
}
function recalledAmount(address who) external view returns (uint256) { function recalledAmount(address who) external view returns (uint256) {
return _recalledAmounts[who]; return _recalledAmounts[who];
} }
@ -187,7 +195,6 @@ contract GatekeeperTest is Test {
address constant ALICE = 0x0000000000000000000000000000000000000001; address constant ALICE = 0x0000000000000000000000000000000000000001;
address constant BOB = 0x0000000000000000000000000000000000000002; address constant BOB = 0x0000000000000000000000000000000000000002;
uint256 constant EXISTENTIAL = 1337;
uint256 constant INIT_AMOUNT = 69 * 1e18; uint256 constant INIT_AMOUNT = 69 * 1e18;
address constant DUMMY_ADDRESS = address(0x0101010101010101010101010101010101010101); address constant DUMMY_ADDRESS = address(0x0101010101010101010101010101010101010101);
@ -199,7 +206,7 @@ contract GatekeeperTest is Test {
function setUp() public { function setUp() public {
vm.prank(ALICE, ALICE); vm.prank(ALICE, ALICE);
staking = new MockStaking(EXISTENTIAL); staking = new MockStaking();
gatekeeper = staking.gatekeeper(); gatekeeper = staking.gatekeeper();
MockGovernance tempGov = new MockGovernance(address(gatekeeper), DUMMY_ADDRESS); MockGovernance tempGov = new MockGovernance(address(gatekeeper), DUMMY_ADDRESS);
@ -215,7 +222,7 @@ contract GatekeeperTest is Test {
} }
function test_ghostTokensWork(uint256 ghostAmount) public { function test_ghostTokensWork(uint256 ghostAmount) public {
vm.assume(ghostAmount >= EXISTENTIAL && ghostAmount < type(uint96).max); vm.assume(ghostAmount >= gatekeeper.EXISTENTIAL_DEPOSIT() && ghostAmount < type(uint96).max);
bytes32 receiver = bytes32(abi.encodePacked(ALICE)); bytes32 receiver = bytes32(abi.encodePacked(ALICE));
uint256 ghostedSupply = gatekeeper.ghostedSupply(); uint256 ghostedSupply = gatekeeper.ghostedSupply();
@ -235,7 +242,7 @@ contract GatekeeperTest is Test {
} }
function test_ghostTokensEmitsEvent(uint256 ghostAmount) public { function test_ghostTokensEmitsEvent(uint256 ghostAmount) public {
vm.assume(ghostAmount >= EXISTENTIAL); vm.assume(ghostAmount >= gatekeeper.EXISTENTIAL_DEPOSIT());
bytes32 receiver = bytes32(abi.encodePacked(ALICE)); bytes32 receiver = bytes32(abi.encodePacked(ALICE));
vm.expectEmit(true, true, true, false, address(gatekeeper)); vm.expectEmit(true, true, true, false, address(gatekeeper));
@ -245,7 +252,7 @@ contract GatekeeperTest is Test {
} }
function test_recallWork(uint256 exodusSession, uint256 bobAmount, uint32 bobCommission) public { function test_recallWork(uint256 exodusSession, uint256 bobAmount, uint32 bobCommission) public {
vm.assume(bobAmount > 1337 && bobAmount < 1_000 ether); vm.assume(bobAmount > gatekeeper.EXISTENTIAL_DEPOSIT() && bobAmount < 1_000 ether);
vm.assume(bobCommission > 0 && bobCommission <= type(uint32).max); vm.assume(bobCommission > 0 && bobCommission <= type(uint32).max);
address storageHistory = gatekeeper.storageHistory(); address storageHistory = gatekeeper.storageHistory();
@ -259,19 +266,9 @@ contract GatekeeperTest is Test {
vm.deal(ALICE, nativeNeeded + 1 ether); vm.deal(ALICE, nativeNeeded + 1 ether);
assertEq(BOB.balance, 0 ether); assertEq(BOB.balance, 0 ether);
uint256 aliceStartingNative = ALICE.balance;
uint256 previousAliceAmount = staking.recalledAmount(ALICE);
uint256 previousBobAmount = staking.recalledAmount(BOB);
vm.prank(ALICE); vm.prank(ALICE);
staking.runRecall{value: nativeNeeded}(exodusSession, bobAmount, bobPacked, ALICE); staking.runRecall(exodusSession, bobAmount, bobPacked, ALICE);
assertApproxEqAbs(previousAliceAmount + commissionAmount, staking.recalledAmount(ALICE), 1000);
assertApproxEqAbs(previousBobAmount + (bobAmount - commissionAmount), staking.recalledAmount(BOB), 1000);
assertEq(BOB.balance, nativeNeeded);
assertEq(ALICE.balance, aliceStartingNative - nativeNeeded);
assert(IStorageHistory(storageHistory).isTransactionExecuted(exodusSession)); assert(IStorageHistory(storageHistory).isTransactionExecuted(exodusSession));
} }
@ -283,7 +280,7 @@ contract GatekeeperTest is Test {
} }
function test_couldNotBridgeBelowExistential(uint256 amount) public { function test_couldNotBridgeBelowExistential(uint256 amount) public {
vm.assume(amount < EXISTENTIAL); vm.assume(amount < gatekeeper.EXISTENTIAL_DEPOSIT());
bytes32 receiver = bytes32(abi.encodePacked(ALICE)); bytes32 receiver = bytes32(abi.encodePacked(ALICE));
vm.expectRevert(); vm.expectRevert();
@ -363,6 +360,7 @@ contract GatekeeperTest is Test {
// execute bridge out, happened on DKG #0 // execute bridge out, happened on DKG #0
// amount: 69; commission: 0%; ALICE // amount: 69; commission: 0%; ALICE
// exodus session #1 // exodus session #1
vm.prank(ALICE, ALICE);
gatekeeper.verify( gatekeeper.verify(
hex"bf06188a0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004500000000000000000000000000000000000000010000000000007a6900000000", hex"bf06188a0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004500000000000000000000000000000000000000010000000000007a6900000000",
hex"04bccb4cd4633e2c61ca5c65006a6a09baa893be219b21509866123a8d6299f92aefe9c380bd9728dbe0b93a8042f431636d08ed191b5c04062bf14e1347059cb9", hex"04bccb4cd4633e2c61ca5c65006a6a09baa893be219b21509866123a8d6299f92aefe9c380bd9728dbe0b93a8042f431636d08ed191b5c04062bf14e1347059cb9",
@ -393,10 +391,8 @@ contract GatekeeperTest is Test {
0x772731b359ee7ecac7830438333e4ffb61759b36610f69cef20f66f7a79a405f 0x772731b359ee7ecac7830438333e4ffb61759b36610f69cef20f66f7a79a405f
); );
assertEq(aliceAmountBefore + 210, staking.recalledAmount(ALICE)); assertApproxEqAbs(aliceAmountBefore + 210, staking.recalledAmount(ALICE), 1);
assertEq(bobAmountBefore + 210, staking.recalledAmount(BOB)); assertApproxEqAbs(bobAmountBefore + 210, staking.recalledAmount(BOB), 1);
assertEq(aliceBalanceBefore - buyback, ALICE.balance);
assertEq(bobBalanceBefore + buyback, BOB.balance);
// execute setDistributor, exodusSession: 2 // execute setDistributor, exodusSession: 2
vm.prank(ALICE); vm.prank(ALICE);

View File

@ -7,21 +7,24 @@ import {IStorageHistory} from "../../src/interfaces/IStorageHistory.sol";
import {IGatekeeper} from "../../src/interfaces/IGatekeeper.sol"; import {IGatekeeper} from "../../src/interfaces/IGatekeeper.sol";
import {RequestPacking} from "../../src/libraries/Packing.sol"; import {RequestPacking} from "../../src/libraries/Packing.sol";
import {StorageHistory} from "../../src/types/StorageHistory.sol"; import {StorageHistory} from "../../src/types/StorageHistory.sol";
import {WETH9} from "../../src/mocks/WETH9.sol";
contract MockStaking is Test { contract MockStaking is Test {
Gatekeeper public gatekeeper; Gatekeeper public gatekeeper;
WETH9 public mockReserve;
mapping(address => uint256) private _recalledAmounts; mapping(address => uint256) private _recalledAmounts;
constructor(uint256 existential) { constructor() {
mockReserve = new WETH9();
StorageHistory history = new StorageHistory(); StorageHistory history = new StorageHistory();
gatekeeper = new Gatekeeper(existential, address(history)); gatekeeper = new Gatekeeper(address(history));
gatekeeper.initialize(address(0)); gatekeeper.initialize(address(0));
history.setOwner(address(gatekeeper)); history.setOwner(address(gatekeeper));
} }
function redoGatekeeper(uint256 existential) external { function redoGatekeeper() external {
Gatekeeper newGatekeeper = new Gatekeeper(existential, address(0)); Gatekeeper newGatekeeper = new Gatekeeper(address(0));
newGatekeeper.initialize(address(gatekeeper)); newGatekeeper.initialize(address(gatekeeper));
address storageHistory = IGatekeeper(gatekeeper).storageHistory(); address storageHistory = IGatekeeper(gatekeeper).storageHistory();
@ -43,6 +46,11 @@ contract MockStaking is Test {
_recalledAmounts[receiver] += amount; _recalledAmounts[receiver] += amount;
} }
function recall(uint256 amount, uint256, uint256) external returns (address, uint256, uint256){
_recalledAmounts[tx.origin] += amount;
return (address(mockReserve), amount, 0);
}
function recalledAmount(address who) external view returns (uint256) { function recalledAmount(address who) external view returns (uint256) {
return _recalledAmounts[who]; return _recalledAmounts[who];
} }
@ -54,14 +62,13 @@ 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 constant EXISTENTIAL = 0;
Gatekeeper gatekeeper; Gatekeeper gatekeeper;
MockStaking staking; MockStaking staking;
function setUp() public { function setUp() public {
vm.prank(ALICE); vm.prank(ALICE);
staking = new MockStaking(EXISTENTIAL); staking = new MockStaking();
staking.runGhost(bytes32(abi.encodePacked(ALICE)), INIT_AMOUNT); staking.runGhost(bytes32(abi.encodePacked(ALICE)), INIT_AMOUNT);
gatekeeper = staking.gatekeeper(); gatekeeper = staking.gatekeeper();
@ -78,7 +85,7 @@ contract GatekeeperStorageHistoryTest is Test {
} }
function test_historicalAmountsOnlyIncrease(uint256 ghostAmount, uint64 exodusSession) public { function test_historicalAmountsOnlyIncrease(uint256 ghostAmount, uint64 exodusSession) public {
vm.assume(ghostAmount > 0 && ghostAmount < INIT_AMOUNT); vm.assume(ghostAmount > gatekeeper.EXISTENTIAL_DEPOSIT() && ghostAmount < INIT_AMOUNT);
uint256 amountToGhost = ghostAmount; uint256 amountToGhost = ghostAmount;
uint256 amountToMaterialize = ghostAmount / 2; uint256 amountToMaterialize = ghostAmount / 2;
@ -99,6 +106,7 @@ contract GatekeeperStorageHistoryTest is Test {
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.recalledAmount(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.recalledAmount(BOB));
@ -121,7 +129,7 @@ contract GatekeeperStorageHistoryTest is Test {
address prevStorageHistory = gatekeeper.storageHistory(); address prevStorageHistory = gatekeeper.storageHistory();
IStorageHistory.DeploymentSnapshot memory prevSnapshot = IStorageHistory(prevStorageHistory).deploymentSnapshot(); IStorageHistory.DeploymentSnapshot memory prevSnapshot = IStorageHistory(prevStorageHistory).deploymentSnapshot();
staking.redoGatekeeper(EXISTENTIAL + 1); staking.redoGatekeeper();
assert(gatekeeper != staking.gatekeeper()); assert(gatekeeper != staking.gatekeeper());
address currStorageHistory = gatekeeper.storageHistory(); address currStorageHistory = gatekeeper.storageHistory();
@ -133,8 +141,5 @@ contract GatekeeperStorageHistoryTest is Test {
assert(IStorageHistory(prevStorageHistory).isTransactionExecuted(exodusSession)); assert(IStorageHistory(prevStorageHistory).isTransactionExecuted(exodusSession));
assert(IStorageHistory(currStorageHistory).isTransactionExecuted(exodusSession)); assert(IStorageHistory(currStorageHistory).isTransactionExecuted(exodusSession));
assertEq(gatekeeper.existentialDeposit(), EXISTENTIAL);
assertEq(staking.gatekeeper().existentialDeposit(), EXISTENTIAL + 1);
} }
} }

View File

@ -0,0 +1,203 @@
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 {GhostBondDepository} from "../../src/BondDepository.sol";
import {ERC20Mock} from "../../src/mocks/ERC20Mock.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";
import {IGatekeeper} from "../../src/interfaces/IGatekeeper.sol";
import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.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, 0x4e8c1fe96d6737cdabbcc96501d6656b9d0b659b3a528ef507f2d52bef29e157, 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"bf06188a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000009536c70891000000000000000000000000000000000000000000020000000000007a6980000000",
hex"046566d4b0904bf35afd25dff583d53bd30e50f57cc790ad08377e0b85006b38b85046b527098f3dfdadc30d6623b6b1c4644306dd56d39f7c9d6b3b295d005c33",
0x942fb44ad0422c197f9c3016ff7c7e574a9f86182c5485e39fc9bebf61228ad6
);
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);
}
}
}

View File

@ -13,9 +13,9 @@ contract MockStaking {
GatekeeperWeaver public gatekeeper; GatekeeperWeaver public gatekeeper;
address public governor; address public governor;
constructor(uint256 existential) { constructor() {
StorageHistory history = new StorageHistory(); StorageHistory history = new StorageHistory();
gatekeeper = new GatekeeperWeaver(existential, address(history)); gatekeeper = new GatekeeperWeaver(address(history));
gatekeeper.initialize(address(0)); gatekeeper.initialize(address(0));
history.setOwner(address(gatekeeper)); history.setOwner(address(gatekeeper));
governor = msg.sender; governor = msg.sender;
@ -26,10 +26,10 @@ contract MockStaking {
return gatekeeper.ghost(receiver, amount); return gatekeeper.ghost(receiver, amount);
} }
function createNewGatekeeper(uint256 existential) external { function createNewGatekeeper() external {
require(msg.sender == governor); require(msg.sender == governor);
GatekeeperWeaver newGatekeeper = new GatekeeperWeaver(existential, address(0)); GatekeeperWeaver newGatekeeper = new GatekeeperWeaver(address(0));
newGatekeeper.initialize(address(gatekeeper)); newGatekeeper.initialize(address(gatekeeper));
address storageHistory = IGatekeeper(gatekeeper).storageHistory(); address storageHistory = IGatekeeper(gatekeeper).storageHistory();
@ -43,7 +43,7 @@ contract GatekeeperWeaver is Gatekeeper {
using Checkpoints for Checkpoints.Trace256; using Checkpoints for Checkpoints.Trace256;
using Checkpoints for Checkpoints.Trace160; using Checkpoints for Checkpoints.Trace160;
constructor(uint256 existential, address storageHistory) Gatekeeper(existential, storageHistory) {} constructor(address storageHistory) Gatekeeper(storageHistory) {}
function filledEntries(uint256 session) public view returns (uint256) { function filledEntries(uint256 session) public view returns (uint256) {
return _filledEntries[session]; return _filledEntries[session];
@ -115,7 +115,6 @@ contract GatekeeperWeaver is Gatekeeper {
contract GatekeeperWeaverTest is Test { contract GatekeeperWeaverTest is Test {
address constant ALICE = 0x0000000000000000000000000000000000000001; address constant ALICE = 0x0000000000000000000000000000000000000001;
address constant BOB = 0x0000000000000000000000000000000000000002; address constant BOB = 0x0000000000000000000000000000000000000002;
uint256 constant EXISTENTIAL = 1337;
uint256 constant AMOUNT = 1 * 1e7; uint256 constant AMOUNT = 1 * 1e7;
MockStaking staking; MockStaking staking;
@ -123,7 +122,7 @@ contract GatekeeperWeaverTest is Test {
function setUp() public { function setUp() public {
vm.prank(ALICE); vm.prank(ALICE);
staking = new MockStaking(EXISTENTIAL); staking = new MockStaking();
gatekeeper = staking.gatekeeper(); gatekeeper = staking.gatekeeper();
} }
@ -164,7 +163,7 @@ contract GatekeeperWeaverTest is Test {
vm.roll(block.number + 420); vm.roll(block.number + 420);
vm.prank(ALICE); vm.prank(ALICE);
staking.createNewGatekeeper(EXISTENTIAL); staking.createNewGatekeeper();
gatekeeper = staking.gatekeeper(); gatekeeper = staking.gatekeeper();
uint256 finalSession = gatekeeper.currentWeavingSession(); uint256 finalSession = gatekeeper.currentWeavingSession();
@ -256,13 +255,15 @@ contract GatekeeperWeaverTest is Test {
} }
} }
function _prepareArrays(uint256 count) private pure returns (bytes32[] memory, uint256[] memory) { function _prepareArrays(uint256 count) private view returns (bytes32[] memory, uint256[] memory) {
bytes32[] memory whos = new bytes32[](count); bytes32[] memory whos = new bytes32[](count);
uint256[] memory amounts = new uint256[](count); uint256[] memory amounts = new uint256[](count);
uint256 existential = gatekeeper.EXISTENTIAL_DEPOSIT();
for (uint256 i = 0; i < count; i++) { for (uint256 i = 0; i < count; i++) {
whos[i] = keccak256(abi.encodePacked("user", i)); whos[i] = keccak256(abi.encodePacked("user", i));
amounts[i] = EXISTENTIAL + 1 + i; amounts[i] = existential + 1 + i;
} }
return (whos, amounts); return (whos, amounts);

View File

@ -106,10 +106,9 @@ contract StakingTest is Test {
EPOCH_LENGTH, EPOCH_LENGTH,
EPOCH_NUMBER, EPOCH_NUMBER,
EPOCH_END_TIME, EPOCH_END_TIME,
address(authority), address(authority)
0
); );
treasury = new GhostTreasury(address(ftso), 69, address(authority)); treasury = new GhostTreasury(address(ftso), address(authority));
stnk.initialize(address(staking), address(treasury), address(ghst)); stnk.initialize(address(staking), address(treasury), address(ghst));
ghst.initialize(address(staking)); ghst.initialize(address(staking));
calculator = new GhostBondingCalculator(address(ftso), 1, 1); calculator = new GhostBondingCalculator(address(ftso), 1, 1);
@ -596,7 +595,7 @@ contract StakingTest is Test {
vm.prank(address(previousGatekeeper)); vm.prank(address(previousGatekeeper));
IStorageHistory(storageHistory).trySetTransactionExecuted(34); IStorageHistory(storageHistory).trySetTransactionExecuted(34);
Gatekeeper newGatekeeper = new Gatekeeper(420, address(0)); Gatekeeper newGatekeeper = new Gatekeeper(address(0));
vm.prank(GOVERNOR); vm.prank(GOVERNOR);
staking.updateGatekeeperAddress(address(newGatekeeper)); staking.updateGatekeeperAddress(address(newGatekeeper));

View File

@ -61,10 +61,9 @@ contract StakingDistributorTest is Test {
EPOCH_LENGTH, EPOCH_LENGTH,
EPOCH_NUMBER, EPOCH_NUMBER,
EPOCH_END_TIME, EPOCH_END_TIME,
address(authority), address(authority)
0
); );
treasury = new GhostTreasury(address(ftso), 69, address(authority)); treasury = new GhostTreasury(address(ftso), address(authority));
calculator = new GhostBondingCalculator(address(ftso), 1, 1); calculator = new GhostBondingCalculator(address(ftso), 1, 1);
distributor = new GhostDistributor( distributor = new GhostDistributor(
address(treasury), address(treasury),

View File

@ -53,8 +53,7 @@ contract GhostTest is
69, 69,
1337, 1337,
1337, 1337,
address(authority), address(authority)
0
); );
stnk.initialize(address(staking), TREASURY, address(ghst)); stnk.initialize(address(staking), TREASURY, address(ghst));
ghst.initialize(address(staking)); ghst.initialize(address(staking));

View File

@ -59,8 +59,7 @@ contract StinkyTest is Test, ERC20PermitTest, ERC20AllowanceTest, ERC20TransferT
69, 69,
1337, 1337,
1337, 1337,
address(authority), address(authority)
0
); );
ghst.initialize(address(staking)); ghst.initialize(address(staking));
vm.stopPrank(); vm.stopPrank();

View File

@ -37,7 +37,7 @@ contract GhostTreasuryTest is Test {
reserve = new ERC20Mock("Reserve Token", "RET"); reserve = new ERC20Mock("Reserve Token", "RET");
liquidity = new ERC20Mock("Liquidity Token", "LDT"); liquidity = new ERC20Mock("Liquidity Token", "LDT");
ftso = new Fatso(address(authority), "Fatso", "FTSO"); ftso = new Fatso(address(authority), "Fatso", "FTSO");
treasury = new GhostTreasury(address(ftso), 69, address(authority)); treasury = new GhostTreasury(address(ftso), address(authority));
calculator = new GhostBondingCalculator(address(ftso), 1, 1); calculator = new GhostBondingCalculator(address(ftso), 1, 1);
vm.stopPrank(); vm.stopPrank();
@ -109,8 +109,8 @@ contract GhostTreasuryTest is Test {
vm.prank(GOVERNOR); vm.prank(GOVERNOR);
treasury.auditReserves(); treasury.auditReserves();
assertEq(treasury.permissions(ITreasury.STATUS.RESERVETOKEN, address(reserve)), true); assertEq(treasury.permissioned(ITreasury.STATUS.RESERVETOKEN, address(reserve)), true);
assertEq(treasury.registry(ITreasury.STATUS.RESERVETOKEN, 0), address(reserve)); assertEq(treasury.registered(ITreasury.STATUS.RESERVETOKEN, address(reserve)), true);
assertEq( assertEq(
treasury.tokenValue(address(reserve), reserve.balanceOf(address(treasury))), treasury.tokenValue(address(reserve), reserve.balanceOf(address(treasury))),
treasury.totalReserves()); treasury.totalReserves());
@ -139,15 +139,13 @@ contract GhostTreasuryTest is Test {
treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, msg.sender, address(0)); treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, msg.sender, address(0));
vm.stopPrank(); vm.stopPrank();
assertEq(treasury.registry(ITreasury.STATUS.RESERVETOKEN, 0), address(reserve)); assertEq(treasury.registered(ITreasury.STATUS.RESERVETOKEN, address(reserve)), true);
assertEq(treasury.registry(ITreasury.STATUS.LIQUIDITYTOKEN, 0), address(liquidity)); assertEq(treasury.registered(ITreasury.STATUS.LIQUIDITYTOKEN, address(liquidity)), true);
assertEq(treasury.bondCalculator(address(liquidity)), address(calculator)); assertEq(treasury.bondCalculator(address(liquidity)), address(calculator));
vm.expectRevert();
treasury.registry(ITreasury.STATUS.RESERVEDEPOSITOR, 0);
assertEq(treasury.permissions(ITreasury.STATUS.RESERVETOKEN, address(reserve)), true); assertEq(treasury.permissioned(ITreasury.STATUS.RESERVETOKEN, address(reserve)), true);
assertEq(treasury.permissions(ITreasury.STATUS.LIQUIDITYTOKEN, address(liquidity)), true); assertEq(treasury.permissioned(ITreasury.STATUS.LIQUIDITYTOKEN, address(liquidity)), true);
assertEq(treasury.permissions(ITreasury.STATUS.RESERVEDEPOSITOR, msg.sender), true); assertEq(treasury.permissioned(ITreasury.STATUS.RESERVEDEPOSITOR, msg.sender), true);
} }
function test_randomAddressCouldNotDisableStatusByAddress(address who) public { function test_randomAddressCouldNotDisableStatusByAddress(address who) public {
@ -185,9 +183,9 @@ contract GhostTreasuryTest is Test {
treasury.disable(ITreasury.STATUS.RESERVEDEPOSITOR, msg.sender); treasury.disable(ITreasury.STATUS.RESERVEDEPOSITOR, msg.sender);
vm.stopPrank(); vm.stopPrank();
assertEq(treasury.permissions(ITreasury.STATUS.RESERVETOKEN, address(reserve)), false); assertEq(treasury.permissioned(ITreasury.STATUS.RESERVETOKEN, address(reserve)), false);
assertEq(treasury.permissions(ITreasury.STATUS.LIQUIDITYTOKEN, address(liquidity)), false); assertEq(treasury.permissioned(ITreasury.STATUS.LIQUIDITYTOKEN, address(liquidity)), false);
assertEq(treasury.permissions(ITreasury.STATUS.RESERVEDEPOSITOR, msg.sender), false); assertEq(treasury.permissioned(ITreasury.STATUS.RESERVEDEPOSITOR, msg.sender), false);
} }
function test_mainnet_disableReserveAndLiquidity() public { function test_mainnet_disableReserveAndLiquidity() public {

View File

@ -41,7 +41,7 @@ contract GhostTreasuryCoefficienBiggerTest is Test {
reserve = new ERC20Mock("Reserve Token", "RET"); reserve = new ERC20Mock("Reserve Token", "RET");
liquidity = new ERC20Mock("Liquidity Token", "LDT"); liquidity = new ERC20Mock("Liquidity Token", "LDT");
ftso = new Fatso(address(authority), "Fatso", "FTSO"); ftso = new Fatso(address(authority), "Fatso", "FTSO");
treasury = new GhostTreasury(address(ftso), 69, address(authority)); treasury = new GhostTreasury(address(ftso), address(authority));
calculator = new GhostBondingCalculator(address(ftso), 20, 1); calculator = new GhostBondingCalculator(address(ftso), 20, 1);
vm.stopPrank(); vm.stopPrank();

View File

@ -41,7 +41,7 @@ contract GhostTreasuryCoefficientLesserTest is Test {
reserve = new ERC20Mock("Reserve Token", "RET"); reserve = new ERC20Mock("Reserve Token", "RET");
liquidity = new ERC20Mock("Liquidity Token", "LDT"); liquidity = new ERC20Mock("Liquidity Token", "LDT");
ftso = new Fatso(address(authority), "Fatso", "FTSO"); ftso = new Fatso(address(authority), "Fatso", "FTSO");
treasury = new GhostTreasury(address(ftso), 69, address(authority)); treasury = new GhostTreasury(address(ftso), address(authority));
calculator = new GhostBondingCalculator(address(ftso), 1, 20); calculator = new GhostBondingCalculator(address(ftso), 1, 20);
vm.stopPrank(); vm.stopPrank();

View File

@ -37,7 +37,7 @@ contract GhostTreasuryRedemptionTest is Test {
OWNER, OWNER,
OWNER OWNER
); );
treasury = new GhostTreasury(DAI, 69, address(authority)); treasury = new GhostTreasury(DAI, address(authority));
calculator = new GhostBondingCalculator(DAI, 4000, 1); calculator = new GhostBondingCalculator(DAI, 4000, 1);
vm.stopPrank(); vm.stopPrank();