Files
assetxContracts/doc/YT-LiquidityPool设计方案.md

1955 lines
61 KiB
Markdown
Raw Normal View History

2025-12-18 13:07:35 +08:00
# 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
```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流动性池系统
**安全状态**: ✅ 已修复所有已知漏洞