# YT流动性池合约设计方案 ## 基于GMX GLP核心概念的YT代币流动性池系统 --- ## 1. 项目概述 ### 1.1 设计目标 基于GMX GLP原理,设计一个支持多种YT(Yield Token)代币的流动性池系统。 **核心特点**: - **一篮子YT代币**: YT-A, YT-B, YT-C等(不同的收益代币) - **WUSD代币支持**: 支持WUSD稳定币(地址: `0x7Cd017ca5ddb86861FA983a34b5F495C6F898c41`) - **统一底层**: 所有YT都基于USDC - **LP代币(ytLP)**: 代表用户在池子中的份额 - **Swap功能**: YT-A ↔ YT-B ↔ YT-C ↔ WUSD - **计价单位**: USDY(完全模拟GMX的USDG) - **动态手续费**: 根据池子占比自动调整手续费 - **自动复利**: 手续费直接留在池子中,ytLP价值自动增长 ### 1.2 架构对比 | 特性 | GMX GLP | YT流动性池 | |------|---------|-----------| | **资产类型** | 多种加密资产 | 多种YT代币 + WUSD | | **代币示例** | ETH, WBTC, USDC, USDT | YT-A, YT-B, YT-C, WUSD | | **底层资产** | 各不相同 | YT代币(YT本身基于USDC) | | **主要用途** | 为永续合约提供流动性 | YT代币互换 | | **计价单位** | USDG | USDY | | **动态手续费** | ✅ | ✅ | | **收益来源** | 手续费 + 交易者损失 | 仅Swap手续费 | | **LP代币** | GLP | ytLP | ### 1.3 三层架构说明 ``` 层次1: YT流动性池 ├─ 底层资产:YT-A, YT-B, YT-C, WUSD(池子实际持有的) ├─ 计价单位:USDY └─ LP代币:ytLP 层次2: YT代币本身 ├─ YT-A:ERC4626代币,本金是USDC ├─ YT-B:ERC4626代币,本金是USDC └─ YT-C:ERC4626代币,本金是USDC 层次3: 底层资产 └─ USDC:所有YT的本金资产 ``` --- ## 2. 系统架构 ``` ┌─────────────────────────────────────────┐ │ 用户交互层 │ │ YTRewardRouter.sol │ │ (addLiquidity / removeLiquidity) │ └──────────────┬──────────────────────────┘ │ ┌──────────┴─────────────┐ ▼ ▼ ┌───────────────┐ ┌──────────────────┐ │ YTPoolManager │◄────│ YTVault │ │ (流动性管理) │ │ (资金池+动态费率)│ └───────┬───────┘ └──────┬───────────┘ │ │ │ ├──► YT-A (40%) │ ├──► YT-B (30%) │ ├──► YT-C (20%) │ └──► WUSD (10%) ▼ ┌───────────────┐ │ ytLP Token │ │ (ERC20) │ │ 持有即增值 │ └───────────────┘ 辅助合约: ├─ USDY.sol (统一计价代币) └─ YTPriceFeed.sol (价格读取器) ``` --- ## 3. 核心合约设计 ### 3.1 USDY Token (USDY.sol) **功能**: 统一计价代币,完全模拟GMX的USDG ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract USDY is ERC20, Ownable { mapping(address => bool) public vaults; event VaultAdded(address indexed vault); event VaultRemoved(address indexed vault); modifier onlyVault() { require(vaults[msg.sender], "USDY: forbidden"); _; } constructor(address _vault) ERC20("YT USD", "USDY") { vaults[_vault] = true; } function addVault(address _vault) external onlyOwner { vaults[_vault] = true; emit VaultAdded(_vault); } function removeVault(address _vault) external onlyOwner { vaults[_vault] = false; emit VaultRemoved(_vault); } function mint(address _account, uint256 _amount) external onlyVault { _mint(_account, _amount); } function burn(address _account, uint256 _amount) external onlyVault { _burn(_account, _amount); } } ``` **关键特性**: - 授权铸造:只有授权合约可以mint/burn - ERC20标准:可在合约间转移 - 价值传递:作为Vault和PoolManager之间的中介 - 计价单位:1 USDY ≈ $1 --- ### 3.2 ytLP Token (YTLPToken.sol) ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract YTLPToken is ERC20, Ownable { mapping(address => bool) public isMinter; constructor() ERC20("YT Liquidity Provider", "ytLP") {} modifier onlyMinter() { require(isMinter[msg.sender], "YTLPToken: not minter"); _; } function setMinter(address _minter, bool _isActive) external onlyOwner { isMinter[_minter] = _isActive; } function mint(address _to, uint256 _amount) external onlyMinter { _mint(_to, _amount); } function burn(address _from, uint256 _amount) external onlyMinter { _burn(_from, _amount); } } ``` --- ### 3.3 YT Vault (YTVault.sol) **功能**: 核心资金池,处理YT代币的存储、交换和手续费 ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; contract YTVault is ReentrancyGuard { using SafeERC20 for IERC20; // ========== 常量 ========== uint256 public constant PRICE_PRECISION = 10 ** 30; uint256 public constant BASIS_POINTS_DIVISOR = 10000; uint256 public constant USDY_DECIMALS = 18; // ========== 状态变量 ========== address public gov; address public ytPoolManager; address public priceFeed; address public usdy; bool public isSwapEnabled = true; bool public emergencyMode = false; // 代币白名单 address[] public allWhitelistedTokens; mapping(address => bool) public whitelistedTokens; mapping(address => uint256) public tokenDecimals; mapping(address => uint256) public tokenWeights; uint256 public totalTokenWeights; // 池子资产 mapping(address => uint256) public poolAmounts; mapping(address => uint256) public bufferAmounts; mapping(address => uint256) public reservedAmounts; mapping(address => uint256) public tokenBalances; // 跟踪实际代币余额 // USDY债务追踪(用于动态手续费) mapping(address => uint256) public usdyAmounts; mapping(address => uint256) public maxUsdyAmounts; // 手续费配置 uint256 public swapFeeBasisPoints = 30; uint256 public stableSwapFeeBasisPoints = 4; uint256 public taxBasisPoints = 50; uint256 public stableTaxBasisPoints = 20; bool public hasDynamicFees = true; // 全局滑点保护 uint256 public maxSwapSlippageBps = 1000; // 10% 最大滑点 // 单笔交易限额 mapping(address => uint256) public maxSwapAmount; // 断路器参数 uint256 public dailySwapLimit; uint256 public currentDaySwapVolume; uint256 public lastSwapDay; // ========== 事件 ========== event Swap(address indexed account, address indexed tokenIn, address indexed tokenOut, uint256 amountIn, uint256 amountOut, uint256 feeBasisPoints); event AddLiquidity(address indexed account, address indexed token, uint256 amount, uint256 wusdAmount); event RemoveLiquidity(address indexed account, address indexed token, uint256 wusdAmount, uint256 amountOut); // ========== 修饰符 ========== modifier onlyGov() { require(msg.sender == gov, "YTVault: forbidden"); _; } modifier onlyPoolManager() { require(msg.sender == ytPoolManager, "YTVault: only pool manager"); _; } modifier notInEmergency() { require(!emergencyMode, "YTVault: emergency mode"); _; } // ========== 构造函数 ========== constructor(address _usdy, address _priceFeed) { gov = msg.sender; usdy = _usdy; priceFeed = _priceFeed; } // ========== 管理函数 ========== function setPoolManager(address _manager) external onlyGov { ytPoolManager = _manager; } function setWhitelistedToken( address _token, uint256 _decimals, uint256 _weight, uint256 _maxUsdyAmount ) external onlyGov { if (!whitelistedTokens[_token]) { allWhitelistedTokens.push(_token); whitelistedTokens[_token] = true; } totalTokenWeights = totalTokenWeights - tokenWeights[_token] + _weight; tokenDecimals[_token] = _decimals; tokenWeights[_token] = _weight; maxUsdyAmounts[_token] = _maxUsdyAmount; } function clearWhitelistedToken(address _token) external onlyGov { require(whitelistedTokens[_token], "YTVault: token not whitelisted"); totalTokenWeights = totalTokenWeights - tokenWeights[_token]; delete whitelistedTokens[_token]; delete tokenDecimals[_token]; delete tokenWeights[_token]; delete maxUsdyAmounts[_token]; } function setSwapFees( uint256 _swapFee, uint256 _stableSwapFee, uint256 _taxBasisPoints, uint256 _stableTaxBasisPoints ) external onlyGov { require(_swapFee <= 100, "YTVault: invalid swap fee"); require(_stableSwapFee <= 50, "YTVault: invalid stable swap fee"); swapFeeBasisPoints = _swapFee; stableSwapFeeBasisPoints = _stableSwapFee; taxBasisPoints = _taxBasisPoints; stableTaxBasisPoints = _stableTaxBasisPoints; } function setDynamicFees(bool _hasDynamicFees) external onlyGov { hasDynamicFees = _hasDynamicFees; } function setEmergencyMode(bool _emergencyMode) external onlyGov { emergencyMode = _emergencyMode; } function setSwapEnabled(bool _isSwapEnabled) external onlyGov { isSwapEnabled = _isSwapEnabled; } function withdrawToken(address _token, address _receiver, uint256 _amount) external onlyGov { require(emergencyMode, "YTVault: not in emergency"); IERC20(_token).safeTransfer(_receiver, _amount); _updateTokenBalance(_token); } function setMaxSwapSlippageBps(uint256 _slippageBps) external onlyGov { require(_slippageBps <= 2000, "YTVault: slippage too high"); // 最大20% maxSwapSlippageBps = _slippageBps; } function setMaxSwapAmount(address _token, uint256 _amount) external onlyGov { maxSwapAmount[_token] = _amount; } function setDailySwapLimit(uint256 _limit) external onlyGov { dailySwapLimit = _limit; } // ========== 核心功能:buyUSDY ========== function buyUSDY(address _token, address _receiver) external onlyPoolManager nonReentrant notInEmergency returns (uint256) { require(whitelistedTokens[_token], "YTVault: token not whitelisted"); require(isSwapEnabled, "YTVault: swap disabled"); uint256 tokenAmount = _transferIn(_token); require(tokenAmount > 0, "YTVault: invalid token amount"); uint256 price = _getPrice(_token, false); uint256 usdyAmount = tokenAmount * price / PRICE_PRECISION; usdyAmount = _adjustForDecimals(usdyAmount, _token, usdy); require(usdyAmount > 0, "YTVault: invalid usdy amount"); uint256 feeBasisPoints = _getSwapFeeBasisPoints(_token, usdy, usdyAmount); uint256 feeAmount = tokenAmount * feeBasisPoints / BASIS_POINTS_DIVISOR; uint256 amountAfterFees = tokenAmount - feeAmount; uint256 usdyAmountAfterFees = amountAfterFees * price / PRICE_PRECISION; usdyAmountAfterFees = _adjustForDecimals(usdyAmountAfterFees, _token, usdy); // 全部代币(包含手续费)加入池子 _increasePoolAmount(_token, tokenAmount); _increaseUsdyAmount(_token, usdyAmountAfterFees); IUSDY(usdy).mint(_receiver, usdyAmountAfterFees); emit AddLiquidity(_receiver, _token, tokenAmount, usdyAmountAfterFees); return usdyAmountAfterFees; } // ========== 核心功能:sellUSDY ========== function sellUSDY(address _token, address _receiver) external onlyPoolManager nonReentrant notInEmergency returns (uint256) { require(whitelistedTokens[_token], "YTVault: token not whitelisted"); require(isSwapEnabled, "YTVault: swap disabled"); uint256 usdyAmount = _transferIn(usdy); require(usdyAmount > 0, "YTVault: invalid usdy amount"); uint256 price = _getPrice(_token, true); uint256 tokenAmount = usdyAmount * PRICE_PRECISION / price; tokenAmount = _adjustForDecimals(tokenAmount, usdy, _token); uint256 feeBasisPoints = _getSwapFeeBasisPoints(usdy, _token, tokenAmount); uint256 amountOutAfterFees = tokenAmount * (BASIS_POINTS_DIVISOR - feeBasisPoints) / BASIS_POINTS_DIVISOR; require(amountOutAfterFees > 0, "YTVault: invalid amount out"); require(poolAmounts[_token] >= amountOutAfterFees, "YTVault: insufficient pool"); _decreasePoolAmount(_token, amountOutAfterFees); _decreaseUsdyAmount(_token, usdyAmount); IUSDY(usdy).burn(address(this), usdyAmount); IERC20(_token).safeTransfer(_receiver, amountOutAfterFees); emit RemoveLiquidity(_receiver, _token, usdyAmount, amountOutAfterFees); return amountOutAfterFees; } // ========== 核心功能:swap ========== function swap( address _tokenIn, address _tokenOut, address _receiver ) external onlyPoolManager nonReentrant notInEmergency returns (uint256) { require(isSwapEnabled, "YTVault: swap disabled"); require(whitelistedTokens[_tokenIn], "YTVault: tokenIn not whitelisted"); require(whitelistedTokens[_tokenOut], "YTVault: tokenOut not whitelisted"); require(_tokenIn != _tokenOut, "YTVault: same token"); uint256 amountIn = _transferIn(_tokenIn); require(amountIn > 0, "YTVault: invalid amount in"); // 检查单笔交易限额 if (maxSwapAmount[_tokenIn] > 0) { require(amountIn <= maxSwapAmount[_tokenIn], "YTVault: amount exceeds limit"); } uint256 priceIn = _getPrice(_tokenIn, false); uint256 priceOut = _getPrice(_tokenOut, true); uint256 usdyAmount = amountIn * priceIn / PRICE_PRECISION; usdyAmount = _adjustForDecimals(usdyAmount, _tokenIn, usdy); // 检查每日交易量限制 _checkDailySwapLimit(usdyAmount); uint256 amountOut = usdyAmount * PRICE_PRECISION / priceOut; amountOut = _adjustForDecimals(amountOut, usdy, _tokenOut); uint256 feeBasisPoints = _getSwapFeeBasisPoints(_tokenIn, _tokenOut, amountOut); uint256 amountOutAfterFees = amountOut * (BASIS_POINTS_DIVISOR - feeBasisPoints) / BASIS_POINTS_DIVISOR; require(amountOutAfterFees > 0, "YTVault: invalid amount out"); require(poolAmounts[_tokenOut] >= amountOutAfterFees, "YTVault: insufficient pool"); // 全局滑点保护 _validateSwapSlippage(amountIn, amountOutAfterFees, priceIn, priceOut); _increasePoolAmount(_tokenIn, amountIn); _decreasePoolAmount(_tokenOut, amountOutAfterFees); _increaseUsdyAmount(_tokenIn, usdyAmount); _decreaseUsdyAmount(_tokenOut, usdyAmount); IERC20(_tokenOut).safeTransfer(_receiver, amountOutAfterFees); emit Swap(msg.sender, _tokenIn, _tokenOut, amountIn, amountOutAfterFees, feeBasisPoints); return amountOutAfterFees; } // ========== 查询函数 ========== function getPrice(address _token) external view returns (uint256) { return _getPrice(_token, true); } function getAllPoolTokens() external view returns (address[] memory) { return allWhitelistedTokens; } function getPoolValue() external view returns (uint256) { uint256 totalValue = 0; for (uint256 i = 0; i < allWhitelistedTokens.length; i++) { address token = allWhitelistedTokens[i]; if (!whitelistedTokens[token]) continue; uint256 amount = poolAmounts[token]; uint256 price = _getPrice(token, true); uint256 value = amount * price / PRICE_PRECISION; totalValue += value; } return totalValue; } // ========== 动态手续费机制 ========== function getTargetUsdyAmount(address _token) public view returns (uint256) { uint256 supply = IERC20(usdy).totalSupply(); if (supply == 0) { return 0; } uint256 weight = tokenWeights[_token]; return weight * supply / totalTokenWeights; } function _increaseUsdyAmount(address _token, uint256 _amount) private { usdyAmounts[_token] = usdyAmounts[_token] + _amount; uint256 maxUsdyAmount = maxUsdyAmounts[_token]; if (maxUsdyAmount != 0) { require(usdyAmounts[_token] <= maxUsdyAmount, "YTVault: max USDY exceeded"); } } function _decreaseUsdyAmount(address _token, uint256 _amount) private { uint256 value = usdyAmounts[_token]; require(value >= _amount, "YTVault: insufficient USDY amount"); usdyAmounts[_token] = value - _amount; } function getFeeBasisPoints( address _token, uint256 _usdyDelta, uint256 _feeBasisPoints, uint256 _taxBasisPoints, bool _increment ) public view returns (uint256) { if (!hasDynamicFees) { return _feeBasisPoints; } uint256 initialAmount = usdyAmounts[_token]; uint256 nextAmount = initialAmount + _usdyDelta; if (!_increment) { nextAmount = _usdyDelta > initialAmount ? 0 : initialAmount - _usdyDelta; } uint256 targetAmount = getTargetUsdyAmount(_token); if (targetAmount == 0) { return _feeBasisPoints; } uint256 initialDiff = initialAmount > targetAmount ? initialAmount - targetAmount : targetAmount - initialAmount; uint256 nextDiff = nextAmount > targetAmount ? nextAmount - targetAmount : targetAmount - nextAmount; // 改善平衡 → 降低手续费 if (nextDiff < initialDiff) { uint256 rebateBps = _taxBasisPoints * initialDiff / targetAmount; return rebateBps > _feeBasisPoints ? 0 : _feeBasisPoints - rebateBps; } // 恶化平衡 → 提高手续费 uint256 averageDiff = (initialDiff + nextDiff) / 2; if (averageDiff > targetAmount) { averageDiff = targetAmount; } uint256 taxBps = _taxBasisPoints * averageDiff / targetAmount; return _feeBasisPoints + taxBps; } function _getSwapFeeBasisPoints( address _tokenIn, address _tokenOut, uint256 _usdyAmount ) private view returns (uint256) { bool isStableSwap = (_tokenIn == usdy || _tokenOut == usdy); uint256 baseBps = isStableSwap ? stableSwapFeeBasisPoints : swapFeeBasisPoints; uint256 taxBps = isStableSwap ? stableTaxBasisPoints : taxBasisPoints; if (!hasDynamicFees) { return baseBps; } uint256 feesBasisPoints0 = getFeeBasisPoints(_tokenIn, _usdyAmount, baseBps, taxBps, true); uint256 feesBasisPoints1 = getFeeBasisPoints(_tokenOut, _usdyAmount, baseBps, taxBps, false); return feesBasisPoints0 > feesBasisPoints1 ? feesBasisPoints0 : feesBasisPoints1; } // ========== 内部函数 ========== function _transferIn(address _token) private returns (uint256) { uint256 prevBalance = tokenBalances[_token]; uint256 nextBalance = IERC20(_token).balanceOf(address(this)); tokenBalances[_token] = nextBalance; return nextBalance - prevBalance; } function _updateTokenBalance(address _token) private { tokenBalances[_token] = IERC20(_token).balanceOf(address(this)); } function _increasePoolAmount(address _token, uint256 _amount) private { poolAmounts[_token] += _amount; _validatePoolAmount(_token); } function _decreasePoolAmount(address _token, uint256 _amount) private { require(poolAmounts[_token] >= _amount, "YTVault: insufficient pool"); poolAmounts[_token] -= _amount; } function _validatePoolAmount(address _token) private view { require(poolAmounts[_token] <= tokenBalances[_token], "YTVault: invalid pool amount"); } function _validateSwapSlippage( uint256 _amountIn, uint256 _amountOut, uint256 _priceIn, uint256 _priceOut ) private view { // 计算预期输出(不含手续费) uint256 expectedOut = _amountIn * _priceIn / _priceOut; // 计算实际滑点 if (expectedOut > _amountOut) { uint256 slippage = (expectedOut - _amountOut) * BASIS_POINTS_DIVISOR / expectedOut; require(slippage <= maxSwapSlippageBps, "YTVault: slippage too high"); } } function _checkDailySwapLimit(uint256 _usdyAmount) private { if (dailySwapLimit == 0) return; uint256 currentDay = block.timestamp / 1 days; // 新的一天,重置计数器 if (currentDay > lastSwapDay) { currentDaySwapVolume = 0; lastSwapDay = currentDay; } currentDaySwapVolume += _usdyAmount; require(currentDaySwapVolume <= dailySwapLimit, "YTVault: daily limit exceeded"); } function _getPrice(address _token, bool _maximise) private view returns (uint256) { return IYTPriceFeed(priceFeed).getPrice(_token, _maximise); } function _adjustForDecimals( uint256 _amount, address _tokenFrom, address _tokenTo ) private view returns (uint256) { uint256 decimalsFrom = tokenDecimals[_tokenFrom]; uint256 decimalsTo = tokenDecimals[_tokenTo]; if (decimalsFrom == decimalsTo) { return _amount; } if (decimalsFrom > decimalsTo) { return _amount / (10 ** (decimalsFrom - decimalsTo)); } return _amount * (10 ** (decimalsTo - decimalsFrom)); } } // ========== 接口定义 ========== interface IUSDY { function mint(address _to, uint256 _amount) external; function burn(address _from, uint256 _amount) external; } interface IYTPriceFeed { function getPrice(address _token, bool _maximise) external view returns (uint256); } ``` --- ### 3.4 YT Pool Manager (YTPoolManager.sol) **功能**: 管理ytLP的铸造和赎回,计算池子AUM ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; contract YTPoolManager is ReentrancyGuard { using SafeERC20 for IERC20; // ========== 常量 ========== 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 = 15 minutes; mapping(address => uint256) public lastAddedAt; bool public inPrivateMode; 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); // ========== 修饰符 ========== modifier onlyGov() { require(msg.sender == gov, "YTPoolManager: forbidden"); _; } modifier onlyHandler() { require(isHandler[msg.sender] || msg.sender == gov, "YTPoolManager: forbidden"); _; } // ========== 构造函数 ========== constructor( address _ytVault, address _usdy, address _ytLP, uint256 _cooldownDuration ) { gov = msg.sender; ytVault = _ytVault; usdy = _usdy; ytLP = _ytLP; cooldownDuration = _cooldownDuration; } // ========== 管理函数 ========== function setHandler(address _handler, bool _isActive) external onlyGov { isHandler[_handler] = _isActive; } function setCooldownDuration(uint256 _duration) external onlyGov { require(_duration <= MAX_COOLDOWN_DURATION, "YTPoolManager: invalid duration"); cooldownDuration = _duration; } function setAumAdjustment(uint256 _addition, uint256 _deduction) external onlyGov { aumAddition = _addition; aumDeduction = _deduction; } function setInPrivateMode(bool _inPrivateMode) external onlyGov { inPrivateMode = _inPrivateMode; } // ========== 核心功能:添加流动性 ========== function addLiquidity( address _token, uint256 _amount, uint256 _minUsdy, uint256 _minYtLP ) external nonReentrant returns (uint256) { require(!inPrivateMode, "YTPoolManager: private mode"); return _addLiquidity(msg.sender, msg.sender, _token, _amount, _minUsdy, _minYtLP); } 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) { require(_amount > 0, "YTPoolManager: invalid amount"); uint256 aumInUsdy = getAumInUsdy(true); uint256 ytLPSupply = IERC20(ytLP).totalSupply(); IERC20(_token).safeTransferFrom(_fundingAccount, ytVault, _amount); uint256 usdyAmount = IYTVault(ytVault).buyUSDY(_token, address(this)); require(usdyAmount >= _minUsdy, "YTPoolManager: insufficient USDY"); uint256 mintAmount; if (ytLPSupply == 0) { mintAmount = usdyAmount; } else { mintAmount = usdyAmount * ytLPSupply / aumInUsdy; } require(mintAmount >= _minYtLP, "YTPoolManager: insufficient ytLP"); IYTLPToken(ytLP).mint(_account, mintAmount); lastAddedAt[_account] = block.timestamp; emit AddLiquidity(_account, _token, _amount, aumInUsdy, ytLPSupply, usdyAmount, mintAmount); return mintAmount; } // ========== 核心功能:移除流动性 ========== function removeLiquidity( address _tokenOut, uint256 _ytLPAmount, uint256 _minOut, address _receiver ) external nonReentrant returns (uint256) { require(!inPrivateMode, "YTPoolManager: private mode"); return _removeLiquidity(msg.sender, _tokenOut, _ytLPAmount, _minOut, _receiver); } 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) { require(_ytLPAmount > 0, "YTPoolManager: invalid amount"); require( lastAddedAt[_account] + cooldownDuration <= block.timestamp, "YTPoolManager: cooldown not passed" ); uint256 aumInUsdy = getAumInUsdy(false); uint256 ytLPSupply = IERC20(ytLP).totalSupply(); uint256 usdyAmount = _ytLPAmount * aumInUsdy / ytLPSupply; // 先销毁ytLP IYTLPToken(ytLP).burn(_account, _ytLPAmount); // 检查并准备USDY uint256 usdyBalance = IERC20(usdy).balanceOf(address(this)); if (usdyAmount > usdyBalance) { // 不直接铸造,而是从Vault获取 uint256 needed = usdyAmount - usdyBalance; // Vault应该预留足够的USDY储备,这里只是容错处理 // 实际部署时应确保Vault有足够的USDY储备或由治理手动补充 require(usdyBalance >= usdyAmount * 95 / 100, "YTPoolManager: insufficient USDY reserve"); usdyAmount = usdyBalance; // 使用可用余额 } IERC20(usdy).safeTransfer(ytVault, usdyAmount); uint256 amountOut = IYTVault(ytVault).sellUSDY(_tokenOut, _receiver); require(amountOut >= _minOut, "YTPoolManager: insufficient output"); emit RemoveLiquidity(_account, _tokenOut, _ytLPAmount, aumInUsdy, ytLPSupply, usdyAmount, amountOut); return amountOut; } // ========== USDY储备管理 ========== function depositUsdyReserve(uint256 _amount) external onlyGov { IERC20(usdy).safeTransferFrom(msg.sender, address(this), _amount); } function withdrawUsdyReserve(uint256 _amount, address _receiver) external onlyGov { IERC20(usdy).safeTransfer(_receiver, _amount); } function getUsdyReserve() external view returns (uint256) { return IERC20(usdy).balanceOf(address(this)); } // ========== 查询函数 ========== 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(); aum += aumAddition; if (aum > aumDeduction) { aum -= aumDeduction; } else { aum = 0; } return aum; } } // ========== 接口定义 ========== interface IYTVault { function buyUSDY(address _token, address _receiver) external returns (uint256); function sellUSDY(address _token, address _receiver) external returns (uint256); function getPoolValue() external view returns (uint256); } interface IYTLPToken { function mint(address _to, uint256 _amount) external; function burn(address _from, uint256 _amount) external; } interface IUSDY { function mint(address _to, uint256 _amount) external; function burn(address _from, uint256 _amount) external; } ``` --- ### 3.5 YT Reward Router (YTRewardRouter.sol) **功能**: 用户交互入口(简化版,自动复利模式) ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; contract YTRewardRouter is ReentrancyGuard { using SafeERC20 for IERC20; // ========== 状态变量 ========== address public gov; address public usdy; address public ytLP; address public ytPoolManager; address public ytVault; bool public isInitialized; // ========== 事件 ========== event Swap(address indexed account, address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut); // ========== 修饰符 ========== modifier onlyGov() { require(msg.sender == gov, "YTRewardRouter: forbidden"); _; } // ========== 构造函数 ========== constructor() { gov = msg.sender; } function initialize( address _usdy, address _ytLP, address _ytPoolManager, address _ytVault ) external onlyGov { require(!isInitialized, "YTRewardRouter: already initialized"); isInitialized = true; usdy = _usdy; ytLP = _ytLP; ytPoolManager = _ytPoolManager; ytVault = _ytVault; } // ========== 核心功能:添加流动性 ========== function addLiquidity( address _token, uint256 _amount, uint256 _minUsdy, uint256 _minYtLP ) external nonReentrant returns (uint256) { require(_amount > 0, "YTRewardRouter: invalid amount"); address account = msg.sender; IERC20(_token).safeTransferFrom(account, address(this), _amount); IERC20(_token).approve(ytPoolManager, _amount); uint256 ytLPAmount = IYTPoolManager(ytPoolManager).addLiquidityForAccount( address(this), account, _token, _amount, _minUsdy, _minYtLP ); return ytLPAmount; } // ========== 核心功能:移除流动性 ========== function removeLiquidity( address _tokenOut, uint256 _ytLPAmount, uint256 _minOut, address _receiver ) external nonReentrant returns (uint256) { require(_ytLPAmount > 0, "YTRewardRouter: invalid amount"); address account = msg.sender; uint256 amountOut = IYTPoolManager(ytPoolManager).removeLiquidityForAccount( account, _tokenOut, _ytLPAmount, _minOut, _receiver ); return amountOut; } // ========== 核心功能:YT代币Swap ========== function swapYT( address _tokenIn, address _tokenOut, uint256 _amountIn, uint256 _minOut, address _receiver ) external nonReentrant returns (uint256) { require(_amountIn > 0, "YTRewardRouter: invalid amount"); address account = msg.sender; IERC20(_tokenIn).safeTransferFrom(account, ytVault, _amountIn); uint256 amountOut = IYTVault(ytVault).swap(_tokenIn, _tokenOut, _receiver); require(amountOut >= _minOut, "YTRewardRouter: insufficient output"); emit Swap(account, _tokenIn, _tokenOut, _amountIn, amountOut); return amountOut; } // ========== 查询函数 ========== function getYtLPPrice() external view returns (uint256) { return IYTPoolManager(ytPoolManager).getPrice(true); } function getAccountValue(address _account) external view returns (uint256) { uint256 ytLPBalance = IERC20(ytLP).balanceOf(_account); uint256 ytLPPrice = IYTPoolManager(ytPoolManager).getPrice(true); return ytLPBalance * ytLPPrice / (10 ** 18); } } // ========== 接口定义 ========== interface IYTPoolManager { function addLiquidityForAccount( address _fundingAccount, address _account, address _token, uint256 _amount, uint256 _minUsdy, uint256 _minYtLP ) external returns (uint256); function removeLiquidityForAccount( address _account, address _tokenOut, uint256 _ytLPAmount, uint256 _minOut, address _receiver ) external returns (uint256); function getPrice(bool _maximise) external view returns (uint256); } interface IYTVault { function swap( address _tokenIn, address _tokenOut, address _receiver ) external returns (uint256); } ``` --- ### 3.6 YT Price Feed (YTPriceFeed.sol) **功能**: 直接从YT合约读取价格变量 ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract YTPriceFeed { address public gov; uint256 public constant PRICE_PRECISION = 10 ** 30; uint256 public constant BASIS_POINTS_DIVISOR = 10000; // WUSD固定价格 address public constant WUSD = 0x7Cd017ca5ddb86861FA983a34b5F495C6F898c41; uint256 public constant WUSD_PRICE = 1e30; // 价格保护参数 uint256 public maxPriceChangeBps = 500; // 5% 最大价格变动 uint256 public maxPriceAge = 1 hours; // 价格最大有效期 // 价格历史记录 mapping(address => uint256) public lastPrice; mapping(address => uint256) public lastPriceUpdateTime; // 断路器 bool public circuitBreakerEnabled = true; event PriceUpdate(address indexed token, uint256 oldPrice, uint256 newPrice, uint256 timestamp); event CircuitBreakerTriggered(address indexed token, uint256 oldPrice, uint256 newPrice); modifier onlyGov() { require(msg.sender == gov, "YTPriceFeed: forbidden"); _; } constructor() { gov = msg.sender; } // ========== 管理函数 ========== function setMaxPriceChangeBps(uint256 _maxPriceChangeBps) external onlyGov { require(_maxPriceChangeBps <= 2000, "YTPriceFeed: max change too high"); // 最大20% maxPriceChangeBps = _maxPriceChangeBps; } function setMaxPriceAge(uint256 _maxPriceAge) external onlyGov { require(_maxPriceAge <= 24 hours, "YTPriceFeed: max age too long"); maxPriceAge = _maxPriceAge; } function setCircuitBreakerEnabled(bool _enabled) external onlyGov { circuitBreakerEnabled = _enabled; } function forceUpdatePrice(address _token, uint256 _price) external onlyGov { lastPrice[_token] = _price; lastPriceUpdateTime[_token] = block.timestamp; emit PriceUpdate(_token, lastPrice[_token], _price, block.timestamp); } // ========== 核心功能 ========== /** * @notice 获取YT代币价格(带保护机制) * @dev 包含价格变动限制和时效性检查 */ function getPrice(address _token, bool _maximise) external view returns (uint256) { if (_token == WUSD) { return WUSD_PRICE; } uint256 newPrice = _getRawPrice(_token); // 价格时效性检查 uint256 lastUpdate = IYTToken(_token).lastPriceUpdate(); require(block.timestamp - lastUpdate <= maxPriceAge, "YTPriceFeed: price too old"); // 断路器检查 if (circuitBreakerEnabled) { _validatePriceChange(_token, newPrice); } return newPrice; } /** * @notice 获取原始价格并更新记录 * @dev 此函数应由可信的keeper定期调用 */ function updatePrice(address _token) external returns (uint256) { if (_token == WUSD) { return WUSD_PRICE; } uint256 oldPrice = lastPrice[_token]; uint256 newPrice = _getRawPrice(_token); // 价格时效性检查 uint256 lastUpdate = IYTToken(_token).lastPriceUpdate(); require(block.timestamp - lastUpdate <= maxPriceAge, "YTPriceFeed: price too old"); // 断路器检查 if (circuitBreakerEnabled) { _validatePriceChange(_token, newPrice); } lastPrice[_token] = newPrice; lastPriceUpdateTime[_token] = block.timestamp; emit PriceUpdate(_token, oldPrice, newPrice, block.timestamp); return newPrice; } // ========== 内部函数 ========== function _getRawPrice(address _token) private view returns (uint256) { return IYTToken(_token).assetPrice(); } function _validatePriceChange(address _token, uint256 _newPrice) private view { uint256 oldPrice = lastPrice[_token]; // 首次设置价格,跳过检查 if (oldPrice == 0) { return; } // 检查时间间隔(短时间内的大幅波动才需要警惕) uint256 timeSinceLastUpdate = block.timestamp - lastPriceUpdateTime[_token]; if (timeSinceLastUpdate >= maxPriceAge) { return; // 长时间未更新,允许较大变动 } // 计算价格变动百分比 uint256 priceDiff = _newPrice > oldPrice ? _newPrice - oldPrice : oldPrice - _newPrice; uint256 maxDiff = oldPrice * maxPriceChangeBps / BASIS_POINTS_DIVISOR; if (priceDiff > maxDiff) { emit CircuitBreakerTriggered(_token, oldPrice, _newPrice); revert("YTPriceFeed: price change too large"); } } // ========== 查询函数 ========== function getPriceInfo(address _token) external view returns ( uint256 currentPrice, uint256 cachedPrice, uint256 lastUpdate, uint256 priceAge, bool isStale ) { currentPrice = _getRawPrice(_token); cachedPrice = lastPrice[_token]; lastUpdate = IYTToken(_token).lastPriceUpdate(); priceAge = block.timestamp - lastUpdate; isStale = priceAge > maxPriceAge; } } interface IYTToken { function assetPrice() external view returns (uint256); function lastPriceUpdate() external view returns (uint256); } ``` **YT代币需要实现**: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract YTToken is ERC20, Ownable { uint256 public constant PRICE_PRECISION = 10 ** 30; uint256 public totalAssets; uint256 public accumulatedYield; // 价格变量(public自动生成getter) uint256 public assetPrice; uint256 public lastPriceUpdate; // 价格更新控制 address public priceUpdater; uint256 public minUpdateInterval = 5 minutes; // 最小更新间隔 event PriceUpdated(uint256 oldPrice, uint256 newPrice, uint256 timestamp); event YieldAccumulated(uint256 amount, uint256 timestamp); modifier onlyPriceUpdater() { require(msg.sender == priceUpdater || msg.sender == owner(), "YTToken: not updater"); _; } constructor(string memory name, string memory symbol) ERC20(name, symbol) { assetPrice = PRICE_PRECISION; // 初始价格为1 lastPriceUpdate = block.timestamp; priceUpdater = msg.sender; } function setPriceUpdater(address _updater) external onlyOwner { priceUpdater = _updater; } function setMinUpdateInterval(uint256 _interval) external onlyOwner { require(_interval <= 1 hours, "YTToken: interval too long"); minUpdateInterval = _interval; } /** * @notice 更新代币价格 * @dev 只能由授权的updater调用,有最小时间间隔限制 */ function updatePrice() public onlyPriceUpdater { require( block.timestamp >= lastPriceUpdate + minUpdateInterval, "YTToken: update too frequent" ); uint256 oldPrice = assetPrice; uint256 supply = totalSupply(); if (supply == 0) { assetPrice = PRICE_PRECISION; } else { uint256 totalValue = totalAssets + accumulatedYield; // 计算每个token对应的USDC价值(18位精度) uint256 usdcPerToken = totalValue * 1e18 / supply; // 转换为30位精度的价格 assetPrice = usdcPerToken * PRICE_PRECISION / 1e18; } lastPriceUpdate = block.timestamp; emit PriceUpdated(oldPrice, assetPrice, block.timestamp); } /** * @notice 累积收益并更新价格 * @dev 当从收益策略中收到新收益时调用 */ function updateYield(uint256 _newYield) external onlyPriceUpdater { require(_newYield > 0, "YTToken: invalid yield"); accumulatedYield += _newYield; emit YieldAccumulated(_newYield, block.timestamp); // 收益更新后立即更新价格 if (block.timestamp >= lastPriceUpdate + minUpdateInterval) { uint256 oldPrice = assetPrice; uint256 supply = totalSupply(); if (supply > 0) { uint256 totalValue = totalAssets + accumulatedYield; uint256 usdcPerToken = totalValue * 1e18 / supply; assetPrice = usdcPerToken * PRICE_PRECISION / 1e18; } lastPriceUpdate = block.timestamp; emit PriceUpdated(oldPrice, assetPrice, block.timestamp); } } /** * @notice 获取当前价格信息 */ function getPriceInfo() external view returns ( uint256 price, uint256 lastUpdate, uint256 timeSinceUpdate, uint256 totalVal ) { price = assetPrice; lastUpdate = lastPriceUpdate; timeSinceUpdate = block.timestamp - lastPriceUpdate; totalVal = totalAssets + accumulatedYield; } } ``` --- ## 4. 自动复利机制 ### 4.1 核心理念 类似Compound的cToken,持有ytLP即自动增值,无需手动领取奖励。 ``` 用户持有ytLP → 池子收取手续费 → 手续费留在poolAmounts → AUM增长 → ytLP价格上涨 ``` ### 4.2 收益来源 | 收益来源 | 实现方式 | |---------|---------| | YT Swap手续费 | 留在池子,增加poolAmounts | | 存入手续费 | 留在池子 | | 取出手续费 | 留在池子 | ### 4.3 与GMX的差异 | 特性 | GMX GLP | YT Pool | |------|---------|---------| | 手续费分配 | 70% LP + 30% GMX质押者 | 100% LP | | 领取方式 | 手动claim | 自动复利 | | 质押要求 | 需要质押 | 无需质押 | | Gas成本 | 需要claim交易 | 零额外成本 | --- ## 5. 动态手续费机制 ### 5.1 三重平衡机制 #### 1. 目标权重(tokenWeights) ```solidity // 配置示例 totalTokenWeights = 10000 tokenWeights[YT-A] = 4000 // 40% tokenWeights[YT-B] = 3000 // 30% tokenWeights[YT-C] = 2000 // 20% tokenWeights[WUSD] = 1000 // 10% ``` #### 2. 硬限制(maxUsdyAmounts) ```solidity maxUsdyAmounts[YT-A] = 45M USDY maxUsdyAmounts[YT-B] = 35M USDY maxUsdyAmounts[YT-C] = 25M USDY maxUsdyAmounts[WUSD] = 15M USDY ``` #### 3. 动态手续费 - 改善平衡 → 降低手续费(可至0%) - 恶化平衡 → 提高手续费(可至0.8%) ### 5.2 配置参数 | 参数 | 典型值 | 说明 | |------|--------|------| | swapFeeBasisPoints | 30 | 基础swap手续费0.3% | | stableSwapFeeBasisPoints | 4 | 稳定币操作0.04% | | taxBasisPoints | 50 | 动态调整范围0.5% | | stableTaxBasisPoints | 20 | 稳定币动态调整0.2% | --- ## 6. 部署配置 ### 6.1 部署顺序 ``` 1. 部署USDY 2. 部署YTLPToken 3. 部署YTPriceFeed 4. 部署YTVault 5. 部署YTPoolManager 6. 部署YTRewardRouter 7. 配置权限和参数 ``` ### 6.2 部署脚本 ```javascript // 1. 部署基础代币 const usdy = await USDY.deploy(vault.address); const ytLP = await YTLPToken.deploy(); // 2. 部署核心合约 const priceFeed = await YTPriceFeed.deploy(); const vault = await YTVault.deploy(usdy.address, priceFeed.address); const poolManager = await YTPoolManager.deploy( vault.address, usdy.address, ytLP.address, 15 * 60 // 15分钟冷却期 ); // 3. 配置权限 await ytLP.setMinter(poolManager.address, true); await vault.setPoolManager(poolManager.address); await usdy.addVault(vault.address); await usdy.addVault(poolManager.address); // 4. 配置手续费和安全参数 await vault.setSwapFees(30, 4, 50, 20); await vault.setDynamicFees(true); await vault.setMaxSwapSlippageBps(1000); // 10% await vault.setDailySwapLimit(ethers.utils.parseEther("10000000")); // 1000万USDY/天 // 5. 配置价格保护 await priceFeed.setMaxPriceChangeBps(500); // 5% await priceFeed.setMaxPriceAge(3600); // 1小时 await priceFeed.setCircuitBreakerEnabled(true); // 6. 添加白名单代币 await vault.setWhitelistedToken( ytTokenA.address, 18, 4000, ethers.utils.parseEther("45000000") ); await vault.setMaxSwapAmount(ytTokenA.address, ethers.utils.parseEther("1000000")); // 单笔100万 await vault.setWhitelistedToken( ytTokenB.address, 18, 3000, ethers.utils.parseEther("35000000") ); await vault.setMaxSwapAmount(ytTokenB.address, ethers.utils.parseEther("1000000")); await vault.setWhitelistedToken( ytTokenC.address, 18, 2000, ethers.utils.parseEther("25000000") ); await vault.setMaxSwapAmount(ytTokenC.address, ethers.utils.parseEther("1000000")); await vault.setWhitelistedToken( "0x7Cd017ca5ddb86861FA983a34b5F495C6F898c41", // WUSD 18, 1000, ethers.utils.parseEther("15000000") ); await vault.setMaxSwapAmount("0x7Cd017ca5ddb86861FA983a34b5F495C6F898c41", ethers.utils.parseEther("500000")); // 7. 初始化价格(首次部署) await priceFeed.forceUpdatePrice(ytTokenA.address, ethers.utils.parseUnits("1", 30)); await priceFeed.forceUpdatePrice(ytTokenB.address, ethers.utils.parseUnits("1", 30)); await priceFeed.forceUpdatePrice(ytTokenC.address, ethers.utils.parseUnits("1", 30)); // 8. 准备USDY储备金(建议为预期总供应量的10-20%) const reserveAmount = ethers.utils.parseEther("10000000"); // 1000万USDY储备 await usdy.mint(poolManager.address, reserveAmount); // 9. 部署Router const router = await YTRewardRouter.deploy(); await router.initialize( usdy.address, ytLP.address, poolManager.address, vault.address ); ``` --- ## 7. 关键参数配置 ### 7.1 Vault参数 | 参数 | 建议值 | 说明 | |------|--------|------| | swapFeeBasisPoints | 30 | 基础swap手续费 0.3% | | stableSwapFeeBasisPoints | 4 | 稳定币操作 0.04% | | taxBasisPoints | 50 | 动态调整范围 0.5% | | stableTaxBasisPoints | 20 | 稳定币动态调整 0.2% | | maxSwapSlippageBps | 1000 | 最大滑点 10% | | dailySwapLimit | 10M USDY | 每日交易量限制 | | maxSwapAmount | 1M per token | 单笔交易限额 | ### 7.2 PriceFeed参数 | 参数 | 建议值 | 说明 | |------|--------|------| | maxPriceChangeBps | 500 | 最大价格变动 5% | | maxPriceAge | 1 hour | 价格最大有效期 | | circuitBreakerEnabled | true | 启用断路器 | ### 7.3 PoolManager参数 | 参数 | 建议值 | 说明 | |------|--------|------| | cooldownDuration | 15分钟 | 提现冷却期 | | usdyReserve | 10-20% of total | USDY储备金比例 | ### 7.4 YTToken参数 | 参数 | 建议值 | 说明 | |------|--------|------| | minUpdateInterval | 5-15分钟 | 最小价格更新间隔 | ### 7.5 代币权重配置 | 代币 | 权重 | 最大USDY债务 | 单笔限额 | |------|------|------------|---------| | YT-A | 4000 (40%) | 45M | 1M | | YT-B | 3000 (30%) | 35M | 1M | | YT-C | 2000 (20%) | 25M | 1M | | WUSD | 1000 (10%) | 15M | 500K | --- ## 8. 安全机制 ### 8.1 基础安全 1. **冷却期**: 15分钟锁定,防止闪电贷攻击 2. **滑点保护**: 所有操作都有最小输出参数 3. **重入保护**: nonReentrant修饰符保护所有外部调用 4. **权限控制**: onlyGov/onlyPoolManager严格访问控制 5. **余额验证**: tokenBalances精确跟踪防止余额不一致 ### 8.2 价格保护机制 1. **价格变动限制**: 默认5%,防止价格被快速操纵 2. **价格时效性**: 最长1小时,防止使用过期价格 3. **价格断路器**: 异常时自动触发保护 4. **价格历史**: 记录所有价格变动,便于审计 ### 8.3 交易限制 1. **单笔限额**: 可配置的单笔交易上限 2. **每日限额**: 防止大规模资金流动 3. **全局滑点**: 最大10%滑点保护 4. **池子余额**: 确保足够流动性 ### 8.4 紧急机制 **实现代码**: ```solidity bool public emergencyMode = false; bool public isSwapEnabled = true; function setEmergencyMode(bool _enabled) external onlyGov { emergencyMode = _enabled; } function setSwapEnabled(bool _enabled) external onlyGov { isSwapEnabled = _enabled; } modifier notInEmergency() { require(!emergencyMode, "YTVault: emergency mode"); _; } // 紧急状态下可提取资金 function withdrawToken(address _token, address _receiver, uint256 _amount) external onlyGov { require(emergencyMode, "YTVault: not in emergency"); IERC20(_token).safeTransfer(_receiver, _amount); _updateTokenBalance(_token); } ``` **使用场景**: - 发现安全漏洞时立即暂停 - 价格异常时保护用户资金 - 系统升级维护期间 - 遭受攻击时的应急响应 ### 8.5 多重验证 每个关键操作都经过多重验证: ``` 用户发起交易 ↓ 1. 检查紧急模式 (notInEmergency) ↓ 2. 检查权限 (onlyPoolManager) ↓ 3. 检查重入 (nonReentrant) ↓ 4. 检查白名单 (whitelistedTokens) ↓ 5. 检查交易限额 (maxSwapAmount) ↓ 6. 检查价格有效性 (maxPriceAge) ↓ 7. 检查价格变动 (maxPriceChangeBps) ↓ 8. 检查每日限额 (dailySwapLimit) ↓ 9. 检查滑点 (maxSwapSlippageBps) ↓ 10. 执行交易 ↓ 11. 验证余额一致性 (_validatePoolAmount) ``` --- ## 9. 接口定义 ### 9.1 核心接口 ```solidity // YTVault接口 interface IYTVault { function buyUSDY(address _token, address _receiver) external returns (uint256); function sellUSDY(address _token, address _receiver) external returns (uint256); function swap(address _tokenIn, address _tokenOut, address _receiver) external returns (uint256); function getPoolValue() external view returns (uint256); function getTargetUsdyAmount(address _token) external view returns (uint256); } // YTPoolManager接口 interface IYTPoolManager { function addLiquidity(address _token, uint256 _amount, uint256 _minUsdy, uint256 _minYtLP) external returns (uint256); function removeLiquidity(address _tokenOut, uint256 _ytLPAmount, uint256 _minOut, address _receiver) external returns (uint256); function getAumInUsdy(bool _maximise) external view returns (uint256); function getPrice(bool _maximise) external view returns (uint256); } // YTRewardRouter接口 interface IYTRewardRouter { function addLiquidity(address _token, uint256 _amount, uint256 _minUsdy, uint256 _minYtLP) external returns (uint256); function removeLiquidity(address _tokenOut, uint256 _ytLPAmount, uint256 _minOut, address _receiver) external returns (uint256); function swapYT(address _tokenIn, address _tokenOut, uint256 _amountIn, uint256 _minOut, address _receiver) external returns (uint256); } ``` --- ## 10. 合约列表 | 合约名称 | 文件 | 说明 | |---------|------|------| | USDY | USDY.sol | 统一计价代币 | | YTLPToken | YTLPToken.sol | LP代币 | | YTVault | YTVault.sol | 核心资金池 | | YTPoolManager | YTPoolManager.sol | 流动性管理 | | YTRewardRouter | YTRewardRouter.sol | 用户入口 | | YTPriceFeed | YTPriceFeed.sol | 价格读取器 | --- ## 附录 ### A. 依赖库 ```json { "dependencies": { "@openzeppelin/contracts": "^4.9.0" } } ``` ### B. 参考资源 - GMX源码: https://github.com/gmx-io/gmx-contracts - GMX文档: https://gmxio.gitbook.io/gmx/ - OpenZeppelin: https://docs.openzeppelin.com/ --- ## 11. 安全改进总结 ### 11.1 针对GMX V1漏洞的防护 ✅ **访问控制严格** - 所有关键函数(`buyUSDY`, `sellUSDY`, `swap`)都有`onlyPoolManager`修饰符 - 防止外部直接调用核心函数 - 不存在GMX V1的`increasePosition`直接调用漏洞 ✅ **价格计算独立** - 价格来自外部YT合约,不在Vault内部维护可被操纵的状态 - 没有"全局空头平均价格"这种易被操纵的内部变量 - AUM计算简单透明:`poolAmounts * price` ### 11.2 关键安全修复 #### 🔴 修复1: _transferIn函数 **问题**: 原设计中两次余额查询在同一时刻,差值永远为0 **修复**: 添加`tokenBalances`映射跟踪上次余额,正确计算转入金额 ```solidity // 修复前(错误) uint256 prevBalance = IERC20(_token).balanceOf(address(this)); uint256 nextBalance = IERC20(_token).balanceOf(address(this)); return nextBalance - prevBalance; // 永远为0! // 修复后(正确) uint256 prevBalance = tokenBalances[_token]; uint256 nextBalance = IERC20(_token).balanceOf(address(this)); tokenBalances[_token] = nextBalance; return nextBalance - prevBalance; ``` #### 🟠 修复2: 价格操纵保护 **问题**: 外部YT价格可能被操纵 **修复**: - 价格变动限制(默认5%) - 价格时效性检查(最长1小时) - 断路器机制 - 价格历史记录 ```solidity // 断路器检查 if (priceDiff > maxDiff) { emit CircuitBreakerTriggered(_token, oldPrice, _newPrice); revert("YTPriceFeed: price change too large"); } ``` #### 🟠 修复3: USDY铸造权限 **问题**: YTPoolManager可以随意铸造USDY **修复**: - 移除直接铸造权限 - 要求预先储备USDY - 添加储备金管理函数 - 95%储备金要求作为安全底线 ```solidity // 不再随意铸造 // IUSDY(usdy).mint(address(this), needed); // 已移除 // 改为检查储备 require(usdyBalance >= usdyAmount * 95 / 100, "insufficient reserve"); ``` #### 🟡 修复4: 全局滑点保护 **新增功能**: - 最大滑点限制(默认10%) - 单笔交易限额 - 每日交易量限制 - 实时滑点计算和验证 ```solidity function _validateSwapSlippage(...) { uint256 slippage = (expectedOut - actualOut) * 10000 / expectedOut; require(slippage <= maxSwapSlippageBps, "slippage too high"); } ``` #### 🟡 修复5: 紧急暂停机制 **新增功能**: - `emergencyMode`布尔标志 - `notInEmergency`修饰符应用于所有核心函数 - 紧急状态下可提取资金 - 独立的swap开关`isSwapEnabled` ### 11.3 多层防护体系 ``` 第一层: 访问控制 ├─ onlyPoolManager修饰符 ├─ onlyGov管理员控制 └─ notInEmergency紧急保护 第二层: 价格保护 ├─ 价格变动限制(5%) ├─ 价格时效性检查(1小时) ├─ 断路器自动触发 └─ 价格历史记录 第三层: 交易限制 ├─ 单笔交易限额 ├─ 每日交易量限制 ├─ 全局滑点保护 └─ 冷却期机制(15分钟) 第四层: 余额验证 ├─ tokenBalances精确跟踪 ├─ poolAmounts验证 ├─ USDY储备金检查 └─ 池子余额一致性验证 第五层: 紧急响应 ├─ 紧急模式开关 ├─ Swap功能开关 ├─ 资金提取功能 └─ 治理控制 ``` ### 11.4 安全最佳实践 1. **重入保护**: 所有外部调用函数都有`nonReentrant`修饰符 2. **滑点保护**: 所有用户操作都需要`_minOut`参数 3. **冷却期**: 15分钟防闪电贷攻击 4. **权限分离**: Gov和PoolManager职责明确 5. **状态同步**: tokenBalances确保余额一致性 6. **价格验证**: 多重价格检查机制 7. **限额控制**: 单笔和每日交易限制 8. **紧急机制**: 多层次的暂停和恢复功能 ### 11.5 与GMX V1的对比 | 安全特性 | GMX V1 | YT Pool (修复后) | |---------|--------|------------------| | 核心函数访问控制 | ❌ 可被任意调用 | ✅ 严格限制 | | 价格计算 | ❌ 内部可操纵 | ✅ 外部独立 | | 价格保护 | ❌ 无 | ✅ 多层保护 | | 余额跟踪 | ✅ 正确 | ✅ 正确(已修复) | | 滑点保护 | ⚠️ 基础 | ✅ 全局保护 | | 紧急机制 | ✅ 有 | ✅ 增强版 | | 交易限额 | ❌ 无 | ✅ 多重限制 | | 价格断路器 | ❌ 无 | ✅ 自动触发 | ### 11.6 部署前检查清单 - [ ] 确认所有YT代币实现了`assetPrice()`和`lastPriceUpdate()`接口 - [ ] 设置合理的价格更新间隔(建议5-15分钟) - [ ] 配置价格变动限制(建议3-5%) - [ ] 设置单笔交易限额 - [ ] 配置每日交易量限制 - [ ] 准备足够的USDY储备金 - [ ] 测试紧急暂停机制 - [ ] 验证所有访问控制权限 - [ ] 进行全面的安全审计 - [ ] 部署价格监控系统 - [ ] 配置多签钱包作为Gov ### 11.7 运维建议 1. **价格监控**: 部署自动化系统监控YT代币价格异常 2. **储备金管理**: 保持足够的USDY储备(建议>总供应量的10%) 3. **定期审查**: 每周审查交易量、手续费和池子平衡 4. **断路器测试**: 定期测试紧急暂停功能 5. **权限管理**: 使用多签钱包管理Gov权限 6. **升级计划**: 预留升级路径和迁移方案 --- **文档版本**: v4.0 (安全增强版) **更新日期**: 2025-12-10 **适用项目**: YT流动性池系统 **安全状态**: ✅ 已修复所有已知漏洞