ghost-dao-contracts/src/Treasury.sol
Uncle Fatso 0860792f12
make governance great again
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-09-04 11:22:22 +03:00

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;
}
}