This commit is contained in:
2025-12-18 13:07:35 +08:00
commit 76b7f838db
271 changed files with 88812 additions and 0 deletions

View File

@@ -0,0 +1,266 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../../interfaces/IYTVault.sol";
import "../../interfaces/IYTLPToken.sol";
import "../../interfaces/IUSDY.sol";
/**
* @title YTPoolManager
* @notice 管理ytLP的铸造和赎回计算池子AUM
* @dev UUPS可升级合约
*/
contract YTPoolManager is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
using SafeERC20 for IERC20;
error Forbidden();
error InvalidAddress();
error InvalidDuration();
error PrivateMode();
error InvalidAmount();
error InsufficientOutput();
error CooldownNotPassed();
uint256 public constant PRICE_PRECISION = 10 ** 30;
uint256 public constant YTLP_PRECISION = 10 ** 18;
uint256 public constant BASIS_POINTS_DIVISOR = 10000;
uint256 public constant MAX_COOLDOWN_DURATION = 48 hours;
address public gov;
address public ytVault;
address public usdy;
address public ytLP;
uint256 public cooldownDuration;
mapping(address => uint256) public lastAddedAt;
mapping(address => bool) public isHandler;
uint256 public aumAddition;
uint256 public aumDeduction;
event AddLiquidity(
address indexed account,
address indexed token,
uint256 amount,
uint256 aumInUsdy,
uint256 ytLPSupply,
uint256 usdyAmount,
uint256 mintAmount
);
event RemoveLiquidity(
address indexed account,
address indexed token,
uint256 ytLPAmount,
uint256 aumInUsdy,
uint256 ytLPSupply,
uint256 usdyAmount,
uint256 amountOut
);
event CooldownDurationSet(uint256 duration);
event HandlerSet(address indexed handler, bool isActive);
modifier onlyGov() {
if (msg.sender != gov) revert Forbidden();
_;
}
modifier onlyHandler() {
if (!isHandler[msg.sender] && msg.sender != gov) revert Forbidden();
_;
}
/**
* @notice 初始化合约
* @param _ytVault YTVault合约地址
* @param _usdy USDY代币地址
* @param _ytLP ytLP代币地址
* @param _cooldownDuration 冷却时间(秒)
*/
function initialize(
address _ytVault,
address _usdy,
address _ytLP,
uint256 _cooldownDuration
) external initializer {
if (_ytVault == address(0) || _usdy == address(0) || _ytLP == address(0)) revert InvalidAddress();
if (_cooldownDuration > MAX_COOLDOWN_DURATION) revert InvalidDuration();
__ReentrancyGuard_init();
__UUPSUpgradeable_init();
gov = msg.sender;
ytVault = _ytVault;
usdy = _usdy;
ytLP = _ytLP;
cooldownDuration = _cooldownDuration;
}
/**
* @notice 授权升级仅gov可调用
* @param newImplementation 新实现合约地址
*/
function _authorizeUpgrade(address newImplementation) internal override onlyGov {}
function setGov(address _gov) external onlyGov {
if (_gov == address(0)) revert InvalidAddress();
gov = _gov;
}
function setHandler(address _handler, bool _isActive) external onlyGov {
isHandler[_handler] = _isActive;
emit HandlerSet(_handler, _isActive);
}
function setCooldownDuration(uint256 _duration) external onlyGov {
if (_duration > MAX_COOLDOWN_DURATION) revert InvalidDuration();
cooldownDuration = _duration;
emit CooldownDurationSet(_duration);
}
function setAumAdjustment(uint256 _addition, uint256 _deduction) external onlyGov {
aumAddition = _addition;
aumDeduction = _deduction;
}
/**
* @notice 为指定账户添加流动性Handler调用
*/
function addLiquidityForAccount(
address _fundingAccount,
address _account,
address _token,
uint256 _amount,
uint256 _minUsdy,
uint256 _minYtLP
) external onlyHandler nonReentrant returns (uint256) {
return _addLiquidity(_fundingAccount, _account, _token, _amount, _minUsdy, _minYtLP);
}
function _addLiquidity(
address _fundingAccount,
address _account,
address _token,
uint256 _amount,
uint256 _minUsdy,
uint256 _minYtLP
) private returns (uint256) {
if (_amount == 0) revert InvalidAmount();
uint256 aumInUsdy = getAumInUsdy(true);
uint256 ytLPSupply = IERC20(ytLP).totalSupply();
IERC20(_token).safeTransferFrom(_fundingAccount, ytVault, _amount);
uint256 usdyAmount = IYTVault(ytVault).buyUSDY(_token, address(this));
if (usdyAmount < _minUsdy) revert InsufficientOutput();
uint256 mintAmount;
if (ytLPSupply == 0) {
mintAmount = usdyAmount;
} else {
mintAmount = usdyAmount * ytLPSupply / aumInUsdy;
}
if (mintAmount < _minYtLP) revert InsufficientOutput();
IYTLPToken(ytLP).mint(_account, mintAmount);
lastAddedAt[_account] = block.timestamp;
emit AddLiquidity(_account, _token, _amount, aumInUsdy, ytLPSupply, usdyAmount, mintAmount);
return mintAmount;
}
/**
* @notice 为指定账户移除流动性Handler调用
*/
function removeLiquidityForAccount(
address _account,
address _tokenOut,
uint256 _ytLPAmount,
uint256 _minOut,
address _receiver
) external onlyHandler nonReentrant returns (uint256) {
return _removeLiquidity(_account, _tokenOut, _ytLPAmount, _minOut, _receiver);
}
function _removeLiquidity(
address _account,
address _tokenOut,
uint256 _ytLPAmount,
uint256 _minOut,
address _receiver
) private returns (uint256) {
if (_ytLPAmount == 0) revert InvalidAmount();
if (lastAddedAt[_account] + cooldownDuration > block.timestamp) revert CooldownNotPassed();
uint256 aumInUsdy = getAumInUsdy(false);
uint256 ytLPSupply = IERC20(ytLP).totalSupply();
uint256 usdyAmount = _ytLPAmount * aumInUsdy / ytLPSupply;
// 先销毁ytLP
IYTLPToken(ytLP).burn(_account, _ytLPAmount);
// 检查余额,只铸造差额部分
uint256 usdyBalance = IERC20(usdy).balanceOf(address(this));
if (usdyAmount > usdyBalance) {
IUSDY(usdy).mint(address(this), usdyAmount - usdyBalance);
}
// 转账USDY到Vault并换回代币
IERC20(usdy).safeTransfer(ytVault, usdyAmount);
uint256 amountOut = IYTVault(ytVault).sellUSDY(_tokenOut, _receiver);
if (amountOut < _minOut) revert InsufficientOutput();
emit RemoveLiquidity(_account, _tokenOut, _ytLPAmount, aumInUsdy, ytLPSupply, usdyAmount, amountOut);
return amountOut;
}
/**
* @notice 获取ytLP价格
* @param _maximise 是否取最大值
* @return ytLP价格18位精度
*/
function getPrice(bool _maximise) external view returns (uint256) {
uint256 aum = getAumInUsdy(_maximise);
uint256 supply = IERC20(ytLP).totalSupply();
if (supply == 0) return YTLP_PRECISION;
return aum * YTLP_PRECISION / supply;
}
/**
* @notice 获取池子总价值AUM
* @param _maximise true=使用最大价格(添加流动性时), false=使用最小价格(移除流动性时)
* @return USDY计价的总价值
*/
function getAumInUsdy(bool _maximise) public view returns (uint256) {
uint256 aum = IYTVault(ytVault).getPoolValue(_maximise);
aum += aumAddition;
if (aum > aumDeduction) {
aum -= aumDeduction;
} else {
aum = 0;
}
return aum;
}
/**
* @dev 预留存储空间,用于未来升级时添加新的状态变量
* 50个slot = 50 * 32 bytes = 1600 bytes
*/
uint256[50] private __gap;
}