use TWAP price from DEX for phantom refunds
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
parent
ec29d7e18d
commit
f79b32554d
2
dependencies/uniswap-v2-contracts
vendored
2
dependencies/uniswap-v2-contracts
vendored
@ -1 +1 @@
|
||||
Subproject commit 5f4365a3a872a0df3f6026402bba7ac71baa9b33
|
||||
Subproject commit b131a064dbcfe6b2750d0f4c3d576b23c561fdb9
|
||||
@ -26,7 +26,7 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
|
||||
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 GAS_EXECUTION_BUFFER = 21000 + 5969 + 67585;
|
||||
uint256 public constant REGISTRY_INDEX = 0;
|
||||
|
||||
address public override staking;
|
||||
@ -117,6 +117,7 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
|
||||
}
|
||||
|
||||
function ghost(bytes32 receiver, uint256 amount) external override {
|
||||
// TODO: if (!_initialized) revert SomeCoolError();
|
||||
if (msg.sender != staking) revert NotStaking();
|
||||
if (amount < EXISTENTIAL_DEPOSIT) revert NonExistentAmount();
|
||||
|
||||
@ -129,6 +130,7 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
|
||||
uint256 rx,
|
||||
uint256 s
|
||||
) external nonReentrant {
|
||||
// TODO: if (!_initialized) revert SomeCoolError();
|
||||
uint256 gasStart = gasleft();
|
||||
uint256 px = _extractPublicKey(call);
|
||||
|
||||
@ -151,7 +153,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, PHANTOM_EXPIRY) {}
|
||||
try IStaking(staking).phantomRefund(msg.sender, gasSpent, PHANTOM_EXPIRY) {}
|
||||
catch {
|
||||
emit VoluntaryVerification(msg.sender, gasSpent);
|
||||
}
|
||||
|
||||
@ -178,16 +178,17 @@ contract GhostStaking is IStaking, GhostAccessControlled {
|
||||
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)
|
||||
uint96 routerIndex = uint96(dexRouterIndex);
|
||||
// forge-lint: disable-next-line(unsafe-typecast)
|
||||
uint48 phantomRelease = uint48(block.timestamp + phantomExpiry);
|
||||
address treasury = ISTNK(stnk).treasury();
|
||||
|
||||
ITreasury(treasury).phantomRefund(receiver, phantomRelease, amount, registryIndex);
|
||||
ITreasury(treasury).phantomRefund(receiver, phantomRelease, routerIndex, amount);
|
||||
}
|
||||
|
||||
function recall(address receiver, uint256 amount) external override {
|
||||
|
||||
@ -88,19 +88,23 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
|
||||
function phantomRefund(
|
||||
address receiver,
|
||||
uint48 phantomRelease,
|
||||
uint256 gasSpent,
|
||||
uint256 registryIndex
|
||||
uint96 dexRouterIndex,
|
||||
uint256 gasSpent
|
||||
) external override {
|
||||
if (!_permissions[STATUS.STAKING][msg.sender]) revert NotApproved();
|
||||
|
||||
address reserveToken = _registry[STATUS.RESERVETOKEN].at(registryIndex);
|
||||
if (!_permissions[STATUS.RESERVETOKEN][reserveToken]) revert NotApproved();
|
||||
address dexRouter = _registry[STATUS.DEXROUTER].at(dexRouterIndex);
|
||||
if (!_permissions[STATUS.DEXROUTER][dexRouter] || dexRouter == address(0)) {
|
||||
revert NotApproved();
|
||||
}
|
||||
|
||||
uint256 totalSupply = IERC20(FTSO).totalSupply();
|
||||
uint256 gasValue = tokenValue(reserveToken, gasSpent);
|
||||
gasValue = FullMath.mulDiv(gasValue, totalSupply, totalReserves);
|
||||
address wethAddress = IUniswapV2Router01(dexRouter).WETH();
|
||||
address factory = IUniswapV2Router01(dexRouter).factory();
|
||||
address pairAddress = IUniswapV2Factory(factory).getPair(FTSO, wethAddress);
|
||||
|
||||
uint256 gasValue = (capturedPriceSnapshots[pairAddress] * gasSpent) >> 112;
|
||||
uint256 currentExcessReserves = excessReserves();
|
||||
|
||||
if (gasValue > 0 && gasValue <= currentExcessReserves) {
|
||||
IBondDepository(bondDepository).addNote(receiver, gasValue, phantomRelease);
|
||||
} else {
|
||||
|
||||
@ -71,7 +71,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, uint256 phantomExpiry) external;
|
||||
function phantomRefund(address receiver, uint256 amount, uint256 phantomExpiry) external;
|
||||
function rebase() external returns (uint256);
|
||||
|
||||
function index() external view returns (uint256);
|
||||
|
||||
@ -40,7 +40,7 @@ interface ITreasury {
|
||||
function registered(STATUS status, address token) external view returns (bool);
|
||||
function permissioned(STATUS status, address token) external view returns (bool);
|
||||
function registeredAt(STATUS status, uint256 index) external view returns (address);
|
||||
function phantomRefund(address receiver, uint48 phantomExpiry, uint256 gasSpent, uint256 amount) external;
|
||||
function phantomRefund(address receiver, uint48 phantomExpiry, uint96 dexRouterIndex, uint256 gasSpent) external;
|
||||
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;
|
||||
|
||||
@ -13,6 +13,12 @@ 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 {UniswapV2Router} from "@uniswap-v2-contracts/UniswapV2Router.sol";
|
||||
import {UniswapV2Factory} from "@uniswap-v2-contracts/UniswapV2Factory.sol";
|
||||
|
||||
import {IUniswapV2Pair} from "@uniswap-v2-contracts/interfaces/IUniswapV2Pair.sol";
|
||||
|
||||
contract GatekeeperRecallTest is Test {
|
||||
uint256 public constant TOTAL_INITIAL_SUPPLY = 5000000000000000;
|
||||
@ -50,9 +56,14 @@ contract GatekeeperRecallTest is Test {
|
||||
GhostBondDepository bondDepository;
|
||||
WETH9 reserve;
|
||||
|
||||
UniswapV2Factory factory;
|
||||
UniswapV2Router router;
|
||||
|
||||
function setUp() public {
|
||||
vm.startPrank(INITIALIZER, INITIALIZER);
|
||||
reserve = new WETH9();
|
||||
factory = new UniswapV2Factory(address(0));
|
||||
router = new UniswapV2Router(address(factory), address(reserve));
|
||||
authority = new GhostAuthority(
|
||||
GOVERNOR,
|
||||
GUARDIAN,
|
||||
@ -91,24 +102,77 @@ contract GatekeeperRecallTest is Test {
|
||||
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.enable(ITreasury.STATUS.DEXROUTER, address(router), address(0));
|
||||
treasury.setBondDepository(address(bondDepository));
|
||||
vm.stopPrank();
|
||||
|
||||
vm.deal(ALICE, INITIAL_MINT);
|
||||
vm.deal(ALICE, INITIAL_MINT * 2);
|
||||
|
||||
uint256 tokenValue = treasury.tokenValue(address(reserve), INITIAL_MINT / 2);
|
||||
|
||||
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);
|
||||
treasury.deposit(address(reserve), INITIAL_MINT, tokenValue);
|
||||
assertEq(ftso.totalSupply(), treasury.baseSupply());
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 ftsoAmount = ftso.balanceOf(ALICE) / 2;
|
||||
uint256 wethAmount = ftsoAmount / 2000;
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
ftso.approve(address(router), ftso.balanceOf(ALICE));
|
||||
router.addLiquidityETH{value: INITIAL_MINT}(
|
||||
address(ftso),
|
||||
ftsoAmount,
|
||||
ftsoAmount,
|
||||
wethAmount,
|
||||
ALICE,
|
||||
1234
|
||||
);
|
||||
vm.stopPrank();
|
||||
|
||||
address pair = factory.getPair(address(reserve), address(ftso));
|
||||
vm.prank(GOVERNOR);
|
||||
treasury.enable(ITreasury.STATUS.LIQUIDITYTOKEN, pair, address(calculator));
|
||||
|
||||
(,, uint48 endBefore,) = staking.epoch();
|
||||
vm.warp(endBefore);
|
||||
vm.roll(block.number + 1);
|
||||
|
||||
IUniswapV2Pair(pair).sync();
|
||||
staking.rebase();
|
||||
assertEq(treasury.cummulativePriceSnapshots(pair), IUniswapV2Pair(pair).price0CumulativeLast());
|
||||
}
|
||||
|
||||
function test_phantomRefundBenchmark() public {
|
||||
vm.startPrank(INITIALIZER);
|
||||
Gatekeeper newGatekeeper = new Gatekeeper(
|
||||
IGatekeeper(staking.gatekeeper()).storageHistory(),
|
||||
address(staking)
|
||||
);
|
||||
vm.stopPrank();
|
||||
|
||||
vm.prank(GOVERNOR);
|
||||
staking.updateGatekeeperAddress(address(newGatekeeper));
|
||||
|
||||
// needed only for GAS_EXECUTION_BUFFER benchmarking with --gas-report flag
|
||||
vm.prank(address(newGatekeeper));
|
||||
staking.phantomRefund(ALICE, 420, 432000);
|
||||
|
||||
vm.prank(address(newGatekeeper));
|
||||
staking.phantomRefund(ALICE, 1337, 432000);
|
||||
|
||||
vm.prank(address(newGatekeeper));
|
||||
staking.phantomRefund(ALICE, 69, 432000);
|
||||
}
|
||||
|
||||
function test_gasPriceCorrectDuringRecall() public {
|
||||
vm.startPrank(INITIALIZER);
|
||||
Gatekeeper gatekeeper = Gatekeeper(payable(staking.gatekeeper()));
|
||||
address gatekeeperAddress = staking.gatekeeper();
|
||||
Gatekeeper gatekeeper = Gatekeeper(payable(gatekeeperAddress));
|
||||
|
||||
vm.prank(INITIALIZER);
|
||||
gatekeeper.updatePublicKeyMetadata(0xb5cd0d028a5e1b6a4eecb113b7e49c8176385eb77c8f134bc1db4bddd7654de6);
|
||||
vm.stopPrank();
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
uint256 aliceBalance = ftso.balanceOf(ALICE);
|
||||
@ -169,10 +233,11 @@ contract GatekeeperRecallTest is Test {
|
||||
}
|
||||
|
||||
function test_recallChainWorks() public {
|
||||
vm.startPrank(INITIALIZER);
|
||||
Gatekeeper gatekeeper = Gatekeeper(payable(staking.gatekeeper()));
|
||||
address gatekeeperAddress = staking.gatekeeper();
|
||||
Gatekeeper gatekeeper = Gatekeeper(payable(gatekeeperAddress));
|
||||
|
||||
vm.prank(INITIALIZER);
|
||||
gatekeeper.updatePublicKeyMetadata(0xb5cd0d028a5e1b6a4eecb113b7e49c8176385eb77c8f134bc1db4bddd7654de6);
|
||||
vm.stopPrank();
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
uint256 aliceBalance = ftso.balanceOf(ALICE);
|
||||
|
||||
88
test/treasury/TreasuryCumulativePrice.t.soll
Normal file
88
test/treasury/TreasuryCumulativePrice.t.soll
Normal file
@ -0,0 +1,88 @@
|
||||
pragma solidity 0.8.20;
|
||||
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
|
||||
import {Fatso} from "../../src/FatsoERC20.sol";
|
||||
import {GhostAuthority} from "../../src/GhostAuthority.sol";
|
||||
import {GhostTreasury} from "../../src/Treasury.sol";
|
||||
import {GhostBondingCalculator} from "../../src/StandardBondingCalculator.sol";
|
||||
import {WETH9} from "../../src/mocks/WETH9.sol";
|
||||
|
||||
import {ITreasury} from "../../src/interfaces/ITreasury.sol";
|
||||
import {UniswapV2Pair} from "@uniswap-v2-contracts/UniswapV2Pair.sol";
|
||||
import {UniswapV2Factory} from "@uniswap-v2-contracts/UniswapV2Factory.sol";
|
||||
import {UniswapV2Router} from "@uniswap-v2-contracts/UniswapV2Router.sol";
|
||||
import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol";
|
||||
|
||||
// TODO: remove fucking old uniswap dependencies
|
||||
|
||||
contract GhostTreasuryCumulativePriceTest is Test {
|
||||
address public constant OWNER = 0x0000000000000000000000000000000000000001;
|
||||
address public constant GOVERNOR = 0x0000000000000000000000000000000000000002;
|
||||
address public constant GUARDIAN = 0x0000000000000000000000000000000000000003;
|
||||
address public constant ALICE = 0x0000000000000000000000000000000000000004;
|
||||
|
||||
uint256 public constant AMOUNT = 2000 ether;
|
||||
|
||||
Fatso ftso;
|
||||
WETH9 weth;
|
||||
UniswapV2Factory factory;
|
||||
UniswapV2Router router;
|
||||
GhostTreasury treasury;
|
||||
GhostAuthority authority;
|
||||
GhostBondingCalculator calculator;
|
||||
|
||||
function setUp() public {
|
||||
vm.startPrank(OWNER);
|
||||
authority = new GhostAuthority(GOVERNOR, GUARDIAN, OWNER, OWNER);
|
||||
weth = new WETH9();
|
||||
ftso = new Fatso(address(authority), "Fatso", "FTSO");
|
||||
treasury = new GhostTreasury(address(ftso), address(authority));
|
||||
calculator = new GhostBondingCalculator(address(ftso), 2000, 1);
|
||||
factory = new UniswapV2Factory(OWNER);
|
||||
router = new UniswapV2Router(address(factory), address(weth));
|
||||
vm.stopPrank();
|
||||
|
||||
vm.startPrank(GOVERNOR);
|
||||
authority.pushVault(address(treasury));
|
||||
treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, ALICE, address(0));
|
||||
treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(weth), address(calculator));
|
||||
treasury.enable(ITreasury.STATUS.DEXROUTER, address(router), address(0));
|
||||
vm.stopPrank();
|
||||
|
||||
vm.deal(ALICE, 4 ether);
|
||||
vm.startPrank(ALICE);
|
||||
weth.deposit{value: 2 ether}();
|
||||
weth.approve(address(treasury), type(uint256).max);
|
||||
treasury.deposit(address(weth), 1 ether, 0);
|
||||
vm.stopPrank();
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
ftso.approve(address(router), type(uint256).max);
|
||||
(,, uint256 liquidity) = router.addLiquidityETH{value: 1 ether}(
|
||||
address(ftso), AMOUNT, AMOUNT, 1 ether, ALICE, 69
|
||||
);
|
||||
vm.stopPrank();
|
||||
|
||||
address lpToken = factory.getPair(address(ftso), address(weth));
|
||||
require(lpToken != address(0), "pair not created");
|
||||
|
||||
vm.startPrank(GOVERNOR);
|
||||
treasury.enable(ITreasury.STATUS.LIQUIDITYTOKEN, lpToken, address(calculator));
|
||||
vm.stopPrank();
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(lpToken).approve(address(treasury), type(uint256).max);
|
||||
treasury.deposit(lpToken, liquidity, treasury.tokenValue(lpToken, liquidity));
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function test_initialTreasuryPricingEmpty() public view {
|
||||
assertEq(treasury.blockTimestampLast(), 0);
|
||||
assertEq(treasury.cummulativePriceLast(), 0);
|
||||
assertEq(treasury.reservesValuationPrice(), 0);
|
||||
}
|
||||
|
||||
// TODO: after creation price should be correct but Treasury does not know anything
|
||||
// TODO: change price as hard as possible, and then check the treasury cummulative
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user