Files
assetxContracts/doc/YT-LiquidityPool设计方案.md
2025-12-18 13:07:35 +08:00

61 KiB
Raw Blame History

YT流动性池合约设计方案

基于GMX GLP核心概念的YT代币流动性池系统


1. 项目概述

1.1 设计目标

基于GMX GLP原理设计一个支持多种YTYield 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-AERC4626代币本金是USDC
├─ YT-BERC4626代币本金是USDC
└─ YT-CERC4626代币本金是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

// 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)

// 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代币的存储、交换和手续费

// 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

// 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)

功能: 用户交互入口(简化版,自动复利模式)

// 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合约读取价格变量

// 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代币需要实现:

// 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

// 配置示例
totalTokenWeights = 10000

tokenWeights[YT-A] = 4000  // 40%
tokenWeights[YT-B] = 3000  // 30%
tokenWeights[YT-C] = 2000  // 20%
tokenWeights[WUSD] = 1000  // 10%

2. 硬限制maxUsdyAmounts

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 部署脚本

// 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 紧急机制

实现代码:

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 核心接口

// 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. 依赖库

{
  "dependencies": {
    "@openzeppelin/contracts": "^4.9.0"
  }
}

B. 参考资源


11. 安全改进总结

11.1 针对GMX V1漏洞的防护

访问控制严格

  • 所有关键函数(buyUSDY, sellUSDY, swap)都有onlyPoolManager修饰符
  • 防止外部直接调用核心函数
  • 不存在GMX V1的increasePosition直接调用漏洞

价格计算独立

  • 价格来自外部YT合约不在Vault内部维护可被操纵的状态
  • 没有"全局空头平均价格"这种易被操纵的内部变量
  • AUM计算简单透明poolAmounts * price

11.2 关键安全修复

🔴 修复1: _transferIn函数

问题: 原设计中两次余额查询在同一时刻差值永远为0 修复: 添加tokenBalances映射跟踪上次余额,正确计算转入金额

// 修复前(错误)
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小时
  • 断路器机制
  • 价格历史记录
// 断路器检查
if (priceDiff > maxDiff) {
    emit CircuitBreakerTriggered(_token, oldPrice, _newPrice);
    revert("YTPriceFeed: price change too large");
}

🟠 修复3: USDY铸造权限

问题: YTPoolManager可以随意铸造USDY 修复:

  • 移除直接铸造权限
  • 要求预先储备USDY
  • 添加储备金管理函数
  • 95%储备金要求作为安全底线
// 不再随意铸造
// IUSDY(usdy).mint(address(this), needed); // 已移除

// 改为检查储备
require(usdyBalance >= usdyAmount * 95 / 100, "insufficient reserve");

🟡 修复4: 全局滑点保护

新增功能:

  • 最大滑点限制默认10%
  • 单笔交易限额
  • 每日交易量限制
  • 实时滑点计算和验证
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流动性池系统
安全状态: 已修复所有已知漏洞