ghost-dao-contracts/src/Treasury.sol
Uncle Fatso 2623451103
make everyone happy about the phantomRefund with help of maturity
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-10-15 15:03:08 +03:00

303 lines
11 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 {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 {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 {IBondDepository} from "./interfaces/IBondDepository.sol";
import {IGhostAuthority} from "./interfaces/IGhostAuthority.sol";
contract GhostTreasury is GhostAccessControlled, ITreasury {
using SafeERC20 for IERC20;
using EnumerableSet for EnumerableSet.AddressSet;
address public immutable FTSO;
address public bondDepository;
uint256 public totalReserves;
mapping(STATUS => EnumerableSet.AddressSet) private _registry;
mapping(STATUS => mapping(address => bool)) private _permissions;
mapping(address => address) public bondCalculator;
constructor(
address _ftso,
address _authority
) GhostAccessControlled(IGhostAuthority(_authority)) {
FTSO = _ftso;
}
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()) {
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 phantomRefund(
address receiver,
uint48 phantomRelease,
uint256 gasSpent,
uint256 registryIndex
) external override returns (uint256 gasValue) {
if (!_permissions[STATUS.STAKING][msg.sender]) revert NotApproved();
address reserveToken = _registry[STATUS.RESERVETOKEN].at(registryIndex);
if (!_permissions[STATUS.RESERVETOKEN][reserveToken]) revert NotApproved();
uint256 totalSupply = IERC20(FTSO).totalSupply();
gasValue = tokenValue(reserveToken, gasSpent);
gasValue = FullMath.mulDiv(gasValue, totalSupply, totalReserves);
if (gasValue > 0 && gasValue <= excessReserves()) {
IBondDepository(bondDepository).addNote(receiver, gasValue, phantomRelease);
}
}
function buyBack(
address receiver,
uint256 amount,
uint256 index
) external override returns (address reserveToken, uint256 value) {
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();
uint256 reservesToSend = FullMath.mulDiv(amount, 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 {
if (
msg.sender != authority.governor() &&
msg.sender != authority.guardian()
) revert NotApproved();
uint256 reserves;
uint256 reserveTokensLength = _registry[STATUS.RESERVETOKEN].length();
uint256 i;
for (; i < reserveTokensLength;) {
address reserveToken = _registry[STATUS.RESERVETOKEN].at(i);
if (_permissions[STATUS.RESERVETOKEN][reserveToken]) {
reserves = reserves + tokenValue(
reserveToken,
IERC20(reserveToken).balanceOf(address(this))
);
}
unchecked { ++i; }
}
uint256 liquidityTokensLength = _registry[STATUS.LIQUIDITYTOKEN].length();
i = 0;
for (; i < liquidityTokensLength;) {
address liquidityToken = _registry[STATUS.LIQUIDITYTOKEN].at(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 alreadyRegistered = _registry[status].contains(someAddress);
if (!alreadyRegistered && (status == STATUS.LIQUIDITYTOKEN || status == STATUS.RESERVETOKEN)) {
if (calculatorAddress == address(0)) revert();
_registry[status].add(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 setBondDepository(address newBondDepository) external {
if (
msg.sender != authority.governor() &&
msg.sender != authority.guardian()
) revert NotApproved();
bondDepository = newBondDepository;
emit BondDepositoryUpdated(newBondDepository);
}
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 permissioned(STATUS status, address someAddress) external view returns (bool) {
return _permissions[status][someAddress];
}
function registered(STATUS status, address someAddress) external override view returns (bool) {
return _registry[status].contains(someAddress);
}
function originalCoefficient() external view returns (uint256) {
address reserveToken = _registry[STATUS.RESERVETOKEN].at(0);
return IBondingCalculator(bondCalculator[reserveToken]).fraction();
}
function excessReserves() public view override returns (uint256) {
uint256 ftsoSupply = IFTSO(FTSO).totalSupply();
uint256 currentTotalReserves = totalReserves;
return currentTotalReserves > ftsoSupply
? currentTotalReserves - ftsoSupply
: 0;
}
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();
}
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;
}
}