implement TWAP through cumulative price from the DEX pair
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
parent
b06f6c115f
commit
6d64494797
@ -65,6 +65,11 @@ RESERVE_PROFIT=
|
||||
|
||||
INITIAL_INDEX=
|
||||
|
||||
#######################################################
|
||||
## Address of Uniswap V2 ONLY! UniswapRouterV2 ONLY! ##
|
||||
#######################################################
|
||||
DEX_ROUTER=
|
||||
|
||||
## Network specific native coefficient
|
||||
COEFFICIENT_NUMERATOR=
|
||||
COEFFICIENT_DENOMINATOR=
|
||||
|
||||
@ -35,6 +35,7 @@ contract GhostStaking is IStaking, GhostAccessControlled {
|
||||
address public distributor;
|
||||
address public gatekeeper;
|
||||
address public warmup;
|
||||
uint96 public dexRouterIndex;
|
||||
|
||||
uint256 private _lastRebaseBlock;
|
||||
|
||||
@ -233,9 +234,27 @@ contract GhostStaking is IStaking, GhostAccessControlled {
|
||||
|
||||
_lastRebaseBlock = block.number;
|
||||
epoch.distribute = balance > extra ? balance - extra : 0;
|
||||
|
||||
address treasury = ISTNK(stnk).treasury();
|
||||
// forge-lint: disable-next-line(unsafe-typecast)
|
||||
ITreasury(treasury).capturePriceSnapshot(uint256(dexRouterIndex));
|
||||
}
|
||||
}
|
||||
|
||||
function setRouterRegistryIndex(uint256 _dexRouterIndex) external onlyGovernor {
|
||||
address treasury = ISTNK(stnk).treasury();
|
||||
address dexRouter = ITreasury(treasury).registeredAt(ITreasury.STATUS.DEXROUTER, _dexRouterIndex);
|
||||
|
||||
bool updated = false;
|
||||
if (ITreasury(treasury).permissioned(ITreasury.STATUS.DEXROUTER, dexRouter)) {
|
||||
// forge-lint: disable-next-line(unsafe-typecast)
|
||||
dexRouterIndex = uint96(_dexRouterIndex);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
emit RouterRegistryIndexSet(dexRouter, _dexRouterIndex, updated);
|
||||
}
|
||||
|
||||
function setDistributor(address _distributor) external onlyGovernor {
|
||||
distributor = _distributor;
|
||||
emit DistributorSet(_distributor);
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol";
|
||||
import {IUniswapV2Pair} from "@uniswap-v2-core/interfaces/IUniswapV2Pair.sol";
|
||||
import {IUniswapV2Pair} from "@uniswap-v2-contracts/interfaces/IUniswapV2Pair.sol";
|
||||
|
||||
import {GhostAccessControlled} from "./types/GhostAccessControlled.sol";
|
||||
import {FullMath} from "./libraries/FullMath.sol";
|
||||
|
||||
@ -4,7 +4,7 @@ pragma solidity ^0.8.20;
|
||||
import {FixedPoint} from "./libraries/FixedPoint.sol";
|
||||
|
||||
import {IERC20Metadata} from "@openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
||||
import {IUniswapV2Pair} from "@uniswap-v2-core/interfaces/IUniswapV2Pair.sol";
|
||||
import {IUniswapV2Pair} from "@uniswap-v2-contracts/interfaces/IUniswapV2Pair.sol";
|
||||
|
||||
import {IBondingCalculator} from "./interfaces/IBondingCalculator.sol";
|
||||
import {FullMath} from "./libraries/FullMath.sol";
|
||||
|
||||
@ -6,10 +6,10 @@ import {IERC20Metadata} from "@openzeppelin-contracts/token/ERC20/extensions/IER
|
||||
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 {IUniswapV2Pair} from "@uniswap-v2-core-1.0.1/interfaces/IUniswapV2Pair.sol";
|
||||
import {IUniswapV2Router02} from "@uniswap-v2-periphery-1.1.0-beta.0/interfaces/IUniswapV2Router02.sol";
|
||||
import {IUniswapV2Router01} from "@uniswap-v2-periphery-1.1.0-beta.0/interfaces/IUniswapV2Router01.sol";
|
||||
import {IUniswapV2Pair} from "@uniswap-v2-contracts/interfaces/IUniswapV2Pair.sol";
|
||||
import {IUniswapV2Factory} from "@uniswap-v2-contracts/interfaces/IUniswapV2Factory.sol";
|
||||
import {IUniswapV2Router} from "@uniswap-v2-contracts/interfaces/IUniswapV2Router.sol";
|
||||
import {IUniswapV2Router01} from "@uniswap-v2-contracts/interfaces/IUniswapV2Router01.sol";
|
||||
|
||||
import {GhostAccessControlled} from "./types/GhostAccessControlled.sol";
|
||||
import {Babylonian} from "./libraries/FixedPoint.sol";
|
||||
@ -29,6 +29,10 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
|
||||
address public bondDepository;
|
||||
uint256 public totalReserves;
|
||||
|
||||
mapping(address => uint32) public blockTimestampSnapshots;
|
||||
mapping(address => uint256) public cummulativePriceSnapshots;
|
||||
mapping(address => uint256) public capturedPriceSnapshots;
|
||||
|
||||
mapping(STATUS => EnumerableSet.AddressSet) private _registry;
|
||||
mapping(STATUS => mapping(address => bool)) private _permissions;
|
||||
mapping(address => address) public bondCalculator;
|
||||
@ -123,6 +127,39 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
|
||||
IERC20(reserveToken).safeTransfer(receiver, value);
|
||||
}
|
||||
|
||||
function capturePriceSnapshot(uint256 index) external override {
|
||||
if (!_permissions[STATUS.STAKING][msg.sender]) revert NotApproved();
|
||||
if (index >= _registry[STATUS.DEXROUTER].length()) return;
|
||||
|
||||
address dexRouter = _registry[STATUS.DEXROUTER].at(index);
|
||||
if (!_permissions[STATUS.DEXROUTER][dexRouter] || dexRouter == address(0)) return;
|
||||
|
||||
address wethAddress = IUniswapV2Router01(dexRouter).WETH();
|
||||
address factory = IUniswapV2Router01(dexRouter).factory();
|
||||
if (wethAddress == address(0) || factory == address(0) || wethAddress == FTSO) return;
|
||||
|
||||
address pairAddress = IUniswapV2Factory(factory).getPair(FTSO, wethAddress);
|
||||
if (!_permissions[STATUS.LIQUIDITYTOKEN][pairAddress] || pairAddress == address(0)) return;
|
||||
|
||||
(uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast) =
|
||||
IUniswapV2Pair(pairAddress).getReserves();
|
||||
|
||||
uint256 cummulativePrice;
|
||||
if (wethAddress < FTSO) { cummulativePrice = IUniswapV2Pair(pairAddress).price0CumulativeLast(); }
|
||||
else { cummulativePrice = IUniswapV2Pair(pairAddress).price1CumulativeLast(); }
|
||||
|
||||
unchecked {
|
||||
// overflow is desired
|
||||
uint32 timeElapsed = blockTimestampLast - blockTimestampSnapshots[pairAddress];
|
||||
if (timeElapsed > 0 && reserve0 != 0 && reserve1 != 0) {
|
||||
capturedPriceSnapshots[pairAddress] =
|
||||
(cummulativePrice - cummulativePriceSnapshots[pairAddress]) / timeElapsed;
|
||||
blockTimestampSnapshots[pairAddress] = blockTimestampLast;
|
||||
cummulativePriceSnapshots[pairAddress] = cummulativePrice;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function auditReserves() external {
|
||||
if (
|
||||
msg.sender != authority.governor() &&
|
||||
@ -169,13 +206,22 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
|
||||
) external onlyGovernor {
|
||||
_permissions[status][someAddress] = true;
|
||||
|
||||
bool alreadyRegistered = _registry[status].contains(someAddress);
|
||||
if (!alreadyRegistered && (status == STATUS.LIQUIDITYTOKEN || status == STATUS.RESERVETOKEN)) {
|
||||
if (calculatorAddress == address(0)) revert();
|
||||
if (_registry[status].contains(someAddress)) {
|
||||
emit Permissioned(someAddress, status, true);
|
||||
return;
|
||||
}
|
||||
|
||||
_registry[status].add(someAddress);
|
||||
bool isToken = status == STATUS.LIQUIDITYTOKEN || status == STATUS.RESERVETOKEN;
|
||||
|
||||
if (isToken) {
|
||||
if (calculatorAddress == address(0)) revert InvalidCalculator();
|
||||
bondCalculator[someAddress] = calculatorAddress;
|
||||
}
|
||||
|
||||
if (isToken || status == STATUS.DEXROUTER) {
|
||||
_registry[status].add(someAddress);
|
||||
}
|
||||
|
||||
emit Permissioned(someAddress, status, true);
|
||||
}
|
||||
|
||||
@ -202,6 +248,9 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
|
||||
uint256 liquidity,
|
||||
bool destroyerMode
|
||||
) external onlyGovernor {
|
||||
if (!_registry[STATUS.DEXROUTER].contains(router)) return;
|
||||
if (!_permissions[STATUS.DEXROUTER][router]) return;
|
||||
|
||||
address weth = IUniswapV2Router01(router).WETH();
|
||||
address pair = IUniswapV2Factory(IUniswapV2Router01(router).factory()).getPair(FTSO, weth);
|
||||
|
||||
@ -229,6 +278,9 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
|
||||
address router,
|
||||
uint256 amount
|
||||
) external onlyGovernor {
|
||||
if (!_registry[STATUS.DEXROUTER].contains(router)) return;
|
||||
if (!_permissions[STATUS.DEXROUTER][router]) return;
|
||||
|
||||
address weth = IUniswapV2Router01(router).WETH();
|
||||
address pair = IUniswapV2Factory(IUniswapV2Router01(router).factory()).getPair(FTSO, weth);
|
||||
|
||||
@ -246,8 +298,8 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
|
||||
}
|
||||
|
||||
uint256 amountIn = _quantityToBeSwapped(amount, reserve0);
|
||||
uint256[] memory amounts = IUniswapV2Router02(router).getAmountsOut(amountIn, path);
|
||||
IUniswapV2Router02(router).swapExactTokensForTokens(
|
||||
uint256[] memory amounts = IUniswapV2Router(router).getAmountsOut(amountIn, path);
|
||||
IUniswapV2Router(router).swapExactTokensForTokens(
|
||||
amountIn,
|
||||
amounts[1],
|
||||
path,
|
||||
@ -262,14 +314,18 @@ contract GhostTreasury is GhostAccessControlled, ITreasury {
|
||||
IUniswapV2Pair(pair).mint(address(this));
|
||||
}
|
||||
|
||||
function permissioned(STATUS status, address someAddress) external view returns (bool) {
|
||||
function permissioned(STATUS status, address someAddress) external override view returns (bool) {
|
||||
return _permissions[status][someAddress];
|
||||
}
|
||||
|
||||
function registered(STATUS status, address someAddress) external override view returns (bool) {
|
||||
function registered(STATUS status, address someAddress) external override view returns (bool isRegistered) {
|
||||
return _registry[status].contains(someAddress);
|
||||
}
|
||||
|
||||
function registeredAt(STATUS status, uint256 index) external override view returns (address) {
|
||||
return _registry[status].at(index);
|
||||
}
|
||||
|
||||
function originalCoefficient() external view returns (uint256) {
|
||||
address reserveToken = _registry[STATUS.RESERVETOKEN].at(0);
|
||||
return IBondingCalculator(bondCalculator[reserveToken]).fraction();
|
||||
|
||||
@ -23,6 +23,7 @@ interface IStaking {
|
||||
bool isRebase
|
||||
);
|
||||
|
||||
event RouterRegistryIndexSet(address router, uint256 index, bool changed);
|
||||
event DistributorSet(address distributor);
|
||||
event GatekeeperSet(address gatekeeper);
|
||||
event WarmupSet(uint256 warmup);
|
||||
|
||||
@ -6,6 +6,7 @@ interface ITreasury {
|
||||
error NotAccepted();
|
||||
error InvalidToken();
|
||||
error GasExceedsBounty();
|
||||
error InvalidCalculator();
|
||||
|
||||
enum STATUS {
|
||||
RESERVEDEPOSITOR,
|
||||
@ -13,7 +14,8 @@ interface ITreasury {
|
||||
LIQUIDITYDEPOSITOR,
|
||||
LIQUIDITYTOKEN,
|
||||
REWARDMANAGER,
|
||||
STAKING
|
||||
STAKING,
|
||||
DEXROUTER
|
||||
}
|
||||
|
||||
event Deposit(address indexed token, uint256 amount, uint256 value);
|
||||
@ -35,11 +37,14 @@ interface ITreasury {
|
||||
uint256 _profit
|
||||
) external returns (uint256);
|
||||
|
||||
function registered(STATUS status, address token) external view returns(bool);
|
||||
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 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 capturePriceSnapshot(uint256 index) external;
|
||||
function tokenValue(address _token, uint256 _amount) external view returns (uint256 value_);
|
||||
function excessReserves() external view returns (uint256);
|
||||
function baseSupply() external view returns (uint256);
|
||||
|
||||
@ -100,6 +100,7 @@ contract GhostBondDepositoryTest is Test {
|
||||
treasury.enable(ITreasury.STATUS.REWARDMANAGER, address(depository), 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));
|
||||
vm.stopPrank();
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
|
||||
@ -113,6 +113,9 @@ contract StakingTest is Test {
|
||||
calculator = new GhostBondingCalculator(address(ftso), 1, 1);
|
||||
vm.stopPrank();
|
||||
vm.roll(block.number + 1);
|
||||
|
||||
vm.prank(GOVERNOR);
|
||||
treasury.enable(ITreasury.STATUS.STAKING, address(staking), address(0));
|
||||
}
|
||||
|
||||
function test_correctAfterConstruction() public view {
|
||||
@ -157,7 +160,7 @@ contract StakingTest is Test {
|
||||
assertEq(staking.warmupPeriod(), maybeWarmupPeriod);
|
||||
}
|
||||
|
||||
function test_emitsWarmupEvent(uint48 maybeWarmupPeriod) public {
|
||||
function test_emitsWarmupEvent(uint256 maybeWarmupPeriod) public {
|
||||
vm.expectEmit(true, true, true, false, address(staking));
|
||||
emit WarmupSet(maybeWarmupPeriod);
|
||||
vm.prank(GOVERNOR);
|
||||
|
||||
@ -79,6 +79,7 @@ contract StakingDistributorTest is Test {
|
||||
vm.startPrank(GOVERNOR);
|
||||
staking.setDistributor(address(distributor));
|
||||
authority.pushVault(address(treasury));
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
@ -241,6 +242,7 @@ contract StakingDistributorTest is Test {
|
||||
treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, ALICE, address(0));
|
||||
treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, BOB, address(0));
|
||||
treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator));
|
||||
treasury.enable(ITreasury.STATUS.STAKING, address(staking), address(0));
|
||||
vm.stopPrank();
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
|
||||
@ -9,7 +9,7 @@ import {GhostBondingCalculator} from "../../src/StandardBondingCalculator.sol";
|
||||
import {ERC20Mock} from "../../src/mocks/ERC20Mock.sol";
|
||||
|
||||
import {ITreasury} from "../../src/interfaces/ITreasury.sol";
|
||||
import {IUniswapV2Pair} from "@uniswap-v2-core/interfaces/IUniswapV2Pair.sol";
|
||||
import {IUniswapV2Pair} from "@uniswap-v2-contracts/interfaces/IUniswapV2Pair.sol";
|
||||
|
||||
contract GhostTreasuryTest is Test {
|
||||
address public constant OWNER = 0x0000000000000000000000000000000000000001;
|
||||
|
||||
@ -10,7 +10,7 @@ import {ITreasury} from "../../src/interfaces/ITreasury.sol";
|
||||
import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol";
|
||||
import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
|
||||
import {IWETH} from "@uniswap-v2-periphery-1.1.0-beta.0/interfaces/IWETH.sol";
|
||||
import {IWETH} from "@uniswap-v2-contracts/interfaces/IWETH.sol";
|
||||
|
||||
contract GhostTreasuryRedemptionTest is Test {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user