255 lines
9.2 KiB
Solidity
255 lines
9.2 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol";
|
|
import {IERC20Metadata} from "@openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
|
import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.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 {GhostAccessControlled} from "./types/GhostAccessControlled.sol";
|
|
import {Babylonian} from "./libraries/FixedPoint.sol";
|
|
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 {IGhostAuthority} from "./interfaces/IGhostAuthority.sol";
|
|
|
|
contract GhostTreasury is GhostAccessControlled, ITreasury {
|
|
using SafeERC20 for IERC20;
|
|
|
|
address public immutable ftso; // forge-lint: disable-line(screaming-snake-case-immutable)
|
|
uint256 public immutable blocksNeededForQueue; // forge-lint: disable-line(screaming-snake-case-immutable)
|
|
|
|
uint256 public totalReserves;
|
|
uint256 public totalDebt;
|
|
uint256 public ftsoDebt;
|
|
|
|
mapping(STATUS => address[]) public registry;
|
|
mapping(STATUS => mapping(address => bool)) public permissions;
|
|
mapping(address => address) public bondCalculator;
|
|
|
|
constructor(
|
|
address _ftso,
|
|
uint256 _timelock,
|
|
address _authority
|
|
) GhostAccessControlled(IGhostAuthority(_authority)) {
|
|
ftso = _ftso;
|
|
blocksNeededForQueue = _timelock;
|
|
}
|
|
|
|
function deposit(
|
|
address token,
|
|
uint256 amount,
|
|
uint256 profit
|
|
) external override returns (uint256 send) {
|
|
if (permissions[STATUS.RESERVETOKEN][token]) {
|
|
if (!permissions[STATUS.RESERVEDEPOSITOR][msg.sender]) revert NotApproved();
|
|
} else if (permissions[STATUS.LIQUIDITYTOKEN][token]) {
|
|
if (!permissions[STATUS.LIQUIDITYDEPOSITOR][msg.sender]) revert NotApproved();
|
|
} else revert InvalidToken();
|
|
|
|
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
|
|
uint256 value = tokenValue(token, amount);
|
|
send = value - profit;
|
|
IFTSO(ftso).mint(msg.sender, send);
|
|
|
|
totalReserves = totalReserves + value;
|
|
emit Deposit(token, amount, value);
|
|
}
|
|
|
|
function mint(address recipient, uint256 amount) external override {
|
|
if (!permissions[STATUS.REWARDMANAGER][msg.sender]) revert NotApproved();
|
|
if (amount > excessReserves()) revert InsufficientReserves();
|
|
IFTSO(ftso).mint(recipient, amount);
|
|
emit Minted(msg.sender, recipient, amount);
|
|
}
|
|
|
|
function withdraw(address token, uint256 amount) external onlyGovernor override {
|
|
if (!permissions[STATUS.RESERVETOKEN][token]) revert NotAccepted();
|
|
|
|
uint256 value = tokenValue(token, amount);
|
|
totalReserves = totalReserves - value;
|
|
|
|
IERC20(token).safeTransfer(msg.sender, amount);
|
|
emit Withdrawal(token, amount, value);
|
|
}
|
|
|
|
function auditReserves() external {
|
|
if (
|
|
msg.sender != authority.governor() &&
|
|
msg.sender != authority.guardian()
|
|
) revert NotApproved();
|
|
|
|
uint256 reserves;
|
|
address[] memory reserveTokens = registry[STATUS.RESERVETOKEN];
|
|
|
|
uint256 i;
|
|
for (; i < reserveTokens.length;) {
|
|
address reserveToken = reserveTokens[i];
|
|
if (permissions[STATUS.RESERVETOKEN][reserveToken]) {
|
|
reserves = reserves + tokenValue(
|
|
reserveToken,
|
|
IERC20(reserveToken).balanceOf(address(this))
|
|
);
|
|
}
|
|
unchecked { ++i; }
|
|
}
|
|
|
|
address[] memory liquidityTokens = registry[STATUS.LIQUIDITYTOKEN];
|
|
i = 0;
|
|
for (; i < liquidityTokens.length;) {
|
|
address liquidityToken = liquidityTokens[i];
|
|
if (permissions[STATUS.LIQUIDITYTOKEN][liquidityToken]) {
|
|
reserves = reserves + tokenValue(
|
|
liquidityToken,
|
|
IERC20(liquidityToken).balanceOf(address(this))
|
|
);
|
|
}
|
|
unchecked { ++i; }
|
|
}
|
|
|
|
totalReserves = reserves;
|
|
emit ReservesAudited(reserves);
|
|
}
|
|
|
|
function enable(
|
|
STATUS status,
|
|
address someAddress,
|
|
address calculatorAddress
|
|
) external onlyGovernor {
|
|
permissions[status][someAddress] = true;
|
|
(bool registered, ) = indexInRegistry(someAddress, status);
|
|
if (!registered && (status == STATUS.LIQUIDITYTOKEN || status == STATUS.RESERVETOKEN)) {
|
|
assert(calculatorAddress != address(0));
|
|
registry[status].push(someAddress);
|
|
bondCalculator[someAddress] = calculatorAddress;
|
|
}
|
|
emit Permissioned(someAddress, status, true);
|
|
}
|
|
|
|
function disable(STATUS status, address toDisable) external {
|
|
if (
|
|
msg.sender != authority.governor() &&
|
|
msg.sender != authority.guardian()
|
|
) revert NotApproved();
|
|
permissions[status][toDisable] = false;
|
|
emit Permissioned(toDisable, status, false);
|
|
}
|
|
|
|
function forfeitReserves(
|
|
address router,
|
|
uint256 liquidity,
|
|
bool destroyerMode
|
|
) external onlyGovernor {
|
|
address weth = IUniswapV2Router01(router).WETH();
|
|
address pair = IUniswapV2Factory(IUniswapV2Router01(router).factory()).getPair(ftso, weth);
|
|
|
|
IERC20(pair).safeTransfer(pair, liquidity);
|
|
(uint256 amount0, uint256 amount1) = IUniswapV2Pair(pair).burn(address(this));
|
|
|
|
if (destroyerMode) {
|
|
uint256 amountToDestroy;
|
|
address token0 = IUniswapV2Pair(pair).token0();
|
|
address token1 = IUniswapV2Pair(pair).token1();
|
|
|
|
if (token0 == ftso) {
|
|
amountToDestroy = amount0;
|
|
}
|
|
|
|
if (token1 == ftso) {
|
|
amountToDestroy = amount1;
|
|
}
|
|
|
|
IFTSO(ftso).burn(amountToDestroy);
|
|
}
|
|
}
|
|
|
|
function redeemReserve(
|
|
address router,
|
|
uint256 amount
|
|
) external onlyGovernor {
|
|
address weth = IUniswapV2Router01(router).WETH();
|
|
address pair = IUniswapV2Factory(IUniswapV2Router01(router).factory()).getPair(ftso, weth);
|
|
|
|
IERC20(weth).approve(router, amount);
|
|
|
|
(uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pair).getReserves();
|
|
address[] memory path = new address[](2);
|
|
path[0] = weth;
|
|
path[1] = ftso;
|
|
|
|
if (ftso < weth) {
|
|
reserve0 = reserve1 ^ reserve0;
|
|
reserve1 = reserve1 ^ reserve0;
|
|
reserve0 = reserve1 ^ reserve0;
|
|
}
|
|
|
|
uint256 amountIn = _quantityToBeSwapped(amount, reserve0);
|
|
uint256[] memory amounts = IUniswapV2Router02(router).getAmountsOut(amountIn, path);
|
|
IUniswapV2Router02(router).swapExactTokensForTokens(
|
|
amountIn,
|
|
amounts[1],
|
|
path,
|
|
address(this),
|
|
block.timestamp
|
|
);
|
|
|
|
amountIn = amount - amountIn;
|
|
|
|
IERC20(weth).safeTransfer(pair, amountIn);
|
|
IERC20(ftso).safeTransfer(pair, amounts[1]);
|
|
IUniswapV2Pair(pair).mint(address(this));
|
|
}
|
|
|
|
function indexInRegistry(
|
|
address someAddress,
|
|
STATUS status
|
|
) public view override returns (bool, uint256) {
|
|
address[] memory entries = registry[status];
|
|
uint256 i;
|
|
for (; i < entries.length; ) {
|
|
if (someAddress == entries[i]) {
|
|
return (true, i);
|
|
}
|
|
unchecked { ++i; }
|
|
}
|
|
return (false, 0);
|
|
}
|
|
|
|
function originalCoefficient() external view returns (uint256) {
|
|
address[] memory reserveTokens = registry[STATUS.RESERVETOKEN];
|
|
return IBondingCalculator(bondCalculator[reserveTokens[0]]).fraction();
|
|
}
|
|
|
|
function excessReserves() public view override returns (uint256) {
|
|
return totalReserves - (IFTSO(ftso).totalSupply() - totalDebt);
|
|
}
|
|
|
|
function tokenValue(
|
|
address token,
|
|
uint256 amount
|
|
) public view override returns (uint256 value) {
|
|
if (permissions[STATUS.LIQUIDITYTOKEN][token]) {
|
|
value = IBondingCalculator(bondCalculator[token]).valuation(token, amount);
|
|
} else if (permissions[STATUS.RESERVETOKEN][token]) {
|
|
value = FullMath.mulDiv(amount, 1e9, 10**IERC20Metadata(token).decimals());
|
|
value = FullMath.mulDiv(value, IBondingCalculator(bondCalculator[token]).fraction(), 1e18);
|
|
}
|
|
}
|
|
|
|
function baseSupply() external view override returns (uint256) {
|
|
return IFTSO(ftso).totalSupply() - ftsoDebt;
|
|
}
|
|
|
|
function _quantityToBeSwapped(uint256 xa, uint256 x1) internal pure returns (uint256) {
|
|
uint256 y1 = x1 * 1994 / 1000;
|
|
uint256 y2 = Babylonian.sqrt(y1**2 + 4 * xa * x1 * 997 / 1000);
|
|
return (y2 - y1) * 1000 / 1994;
|
|
}
|
|
}
|