// 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"; contract YTPoolManager is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable { using SafeERC20 for IERC20; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } 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); event GovChanged(address indexed oldGov, address indexed newGov); event AumAdjustmentChanged(uint256 addition, uint256 deduction); modifier onlyGov() { if (msg.sender != gov) revert Forbidden(); _; } modifier onlyHandler() { if (!isHandler[msg.sender] && msg.sender != gov) revert Forbidden(); _; } 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; } function _authorizeUpgrade(address newImplementation) internal override onlyGov {} function setGov(address _gov) external onlyGov { if (_gov == address(0)) revert InvalidAddress(); address oldGov = gov; gov = _gov; emit GovChanged(oldGov, _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; emit AumAdjustmentChanged(_addition, _deduction); } 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).transferFrom(_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; } 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; } 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; } function getAumInUsdy(bool _maximise) public view returns (uint256) { uint256 aum = IYTVault(ytVault).getPoolValue(_maximise); aum += aumAddition; // aumAddition是协议额外增加的AUM,用来“预留风险缓冲 / 扣除潜在负债” if (aum > aumDeduction) { aum -= aumDeduction; } else { aum = 0; } return aum; } uint256[50] private __gap; }