# YT流动性池系统操作流程图 ## 目录 1. [添加流动性流程](#1-添加流动性流程) 2. [移除流动性流程](#2-移除流动性流程) 3. [代币互换流程](#3-代币互换流程) 4. [添加白名单代币流程](#4-添加白名单代币流程) 5. [移除白名单代币流程](#5-移除白名单代币流程) 6. [系统部署和初始化流程](#6-系统部署和初始化流程) 7. [路由器暂停功能流程](#7-路由器暂停功能流程) --- ## 1. 添加流动性流程 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 用户 (User) │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 1. 调用 addLiquidity(token, amount, minUsdy, minYtLP) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ YTRewardRouter.sol │ │ ───────────────────────────────────────────────────────────────── │ │ function addLiquidity( │ │ address _token, // YT-A 代币地址 │ │ uint256 _amount, // 1000 个代币 │ │ uint256 _minUsdy, // 最小997 USDY(滑点保护) │ │ uint256 _minYtLP // 最小995 ytLP(滑点保护) │ │ ) external nonReentrant whenNotPaused │ │ │ │ 修饰符检查: │ │ ✓ nonReentrant - 防重入保护 │ │ ✓ whenNotPaused - 暂停检查 │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 2. transferFrom(user → YTRewardRouter) │ 转移1000个YT-A到Router ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 代币转移检查 │ │ ✓ 检查用户授权额度 │ │ ✓ 转移代币到Router │ │ ✓ approve(YTPoolManager, amount) │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 3. 调用 addLiquidityForAccount() ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ YTPoolManager.sol │ │ ───────────────────────────────────────────────────────────────── │ │ function addLiquidityForAccount( │ │ address _fundingAccount, // YTRewardRouter │ │ address _account, // 用户地址 │ │ address _token, // YT-A │ │ uint256 _amount, // 1000 │ │ uint256 _minUsdy, // 997 │ │ uint256 _minYtLP // 995 │ │ ) │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 4. 计算当前AUM(使用MaxPrice) │ getAumInUsdy(true) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ AUM 计算(对用户保守) │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ YTVault.getPoolValue(true) │ │ │ │ • 获取池中所有YT代币数量 │ │ │ │ • 使用 MaxPrice(上浮价差)计算每个代币价值 │ │ │ │ • 汇总得到 AUM = $100,200 │ │ │ └───────────────────────────────────────────────────────┘ │ │ 当前ytLP供应量: 100,000 │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 5. 调用 buyUSDY() ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ YTVault.sol │ │ ───────────────────────────────────────────────────────────────── │ │ function buyUSDY(address _token, address _receiver) │ │ │ │ 步骤: │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ ① transferIn(_token) │ │ │ │ • 接收1000个YT-A │ │ │ │ │ │ │ │ ② 获取价格(使用MinPrice,低估用户资产) │ │ │ │ price = _getPrice(_token, false) │ │ │ │ • 基础价格: $1.00 │ │ │ │ • MinPrice: $0.998 (下压0.2%价差) │ │ │ │ │ │ │ │ ③ 计算理论USDY价值 │ │ │ │ usdyAmount = 1000 × $0.998 = $998 │ │ │ │ │ │ │ │ ④ 获取手续费率(动态) │ │ │ │ feeBps = getSwapFeeBasisPoints(_token, usdy, 998) │ │ │ │ • 检查是否稳定币互换 │ │ │ │ • YT代币: 30 bps (0.3%) │ │ │ │ • 稳定币: 4 bps (0.04%) │ │ │ │ • 根据池子平衡动态调整 │ │ │ │ → 本次: 30 bps │ │ │ │ │ │ │ │ ⑤ 计算扣费后的USDY │ │ │ │ feeAmount = 1000 × 0.3% = 3个代币 │ │ │ │ amountAfterFees = 997个代币 │ │ │ │ usdyAmountAfterFees = 997 × $0.998 = $995.006 │ │ │ │ │ │ │ │ ⑥ 池子记账 │ │ │ │ _increasePoolAmount(_token, 1000) // 全部代币入池 │ │ │ │ _increaseUsdyAmount(_token, 995) // 只记扣费后的债务 │ │ │ │ │ │ │ │ ⑦ 铸造USDY │ │ │ │ USDY.mint(_receiver, 995) │ │ │ │ • 手续费3个代币留在池中 │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ 返回: 995 USDY │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 6. 检查 USDY 数量 │ require(995 >= 997) ❌ 会失败 │ (实际应该传入更小的 minUsdy) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 回到 YTPoolManager - 铸造 ytLP │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 计算铸造数量: │ │ │ │ ytLPSupply = 100,000 │ │ │ │ aumInUsdy = $100,200 │ │ │ │ usdyAmount = $995 │ │ │ │ │ │ │ │ mintAmount = 995 × 100,000 / 100,200 = 993.03 ytLP │ │ │ │ │ │ │ │ 检查滑点保护: │ │ │ │ require(993.03 >= 995) ❌ 会失败 │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ 执行铸造: │ │ YTLPToken.mint(user, 993.03) │ │ lastAddedAt[user] = block.timestamp // 记录时间(冷却期) │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 7. 返回结果 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 用户收到 │ │ ✓ 993.03 ytLP │ │ ✓ 代表在池中的份额 │ │ │ │ 成本分析: │ │ 存入: 1000 YT-A │ │ 手续费: 3 YT-A (0.3%) │ │ 价差损失: ~4 YT-A (0.4%) │ │ 总成本: ~0.7% │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 涉及的合约函数: | 合约 | 函数 | 作用 | |------|------|------| | **YTRewardRouter** | `addLiquidity()` | 用户入口,处理代币转移 | | **YTPoolManager** | `addLiquidityForAccount()` | 流动性管理,计算ytLP | | **YTPoolManager** | `getAumInUsdy(true)` | 获取AUM(使用MaxPrice)| | **YTVault** | `buyUSDY()` | 接收代币,铸造USDY | | **YTVault** | `getPoolValue(true)` | 计算池子总价值 | | **YTVault** | `getSwapFeeBasisPoints()` | 获取动态手续费率 | | **YTPriceFeed** | `getPrice(_token, false)` | 获取MinPrice | | **USDY** | `mint()` | 铸造USDY代币 | | **YTLPToken** | `mint()` | 铸造ytLP代币 | --- ## 2. 移除流动性流程 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 用户 (User) │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 1. 调用 removeLiquidity(tokenOut, ytLPAmount, minOut, receiver) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ YTRewardRouter.sol │ │ ───────────────────────────────────────────────────────────────── │ │ function removeLiquidity( │ │ address _tokenOut, // YT-B 代币地址 │ │ uint256 _ytLPAmount, // 1000 ytLP │ │ uint256 _minOut, // 最小990 YT-B(滑点保护) │ │ address _receiver // 接收地址 │ │ ) external nonReentrant whenNotPaused │ │ │ │ 修饰符检查: │ │ ✓ nonReentrant - 防重入保护 │ │ ✓ whenNotPaused - 暂停检查 │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 2. 调用 removeLiquidityForAccount() ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ YTPoolManager.sol │ │ ───────────────────────────────────────────────────────────────── │ │ function removeLiquidityForAccount(...) │ │ │ │ 步骤: │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ ① 检查冷却期 │ │ │ │ require(lastAddedAt[user] + 15分钟 <= now) │ │ │ │ │ │ │ │ ② 计算AUM(使用MinPrice,对用户保守) │ │ │ │ aumInUsdy = getAumInUsdy(false) │ │ │ │ • YTVault.getPoolValue(false) │ │ │ │ • 使用MinPrice(下压价差) │ │ │ │ • AUM = $99,800 │ │ │ │ │ │ │ │ ③ 计算USDY价值 │ │ │ │ ytLPSupply = 100,000 │ │ │ │ usdyAmount = 1000 × 99,800 / 100,000 = $998 │ │ │ │ │ │ │ │ ④ 检查并铸造USDY(如果余额不足) │ │ │ │ balance = USDY.balanceOf(PoolManager) │ │ │ │ if (998 > balance) { │ │ │ │ USDY.mint(PoolManager, 998 - balance) │ │ │ │ } │ │ │ └─────────────────────────────────────────────────────────────┘ │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 3. 销毁ytLP │ YTLPToken.burn(user, 1000) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ ytLP 销毁完成 │ │ 用户的ytLP余额 -1000 │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 4. 调用 sellUSDY() ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ YTVault.sol │ │ ───────────────────────────────────────────────────────────────── │ │ function sellUSDY(address _token, address _receiver) │ │ │ │ 步骤: │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ ① transferIn(usdy) │ │ │ │ • 接收998 USDY │ │ │ │ │ │ │ │ ② 获取价格(使用MaxPrice,高估需支付的价值) │ │ │ │ price = _getPrice(_token, true) │ │ │ │ • 基础价格: $1.00 │ │ │ │ • MaxPrice: $1.002 (上浮0.2%价差) │ │ │ │ │ │ │ │ ③ 计算理论赎回数量 │ │ │ │ redemptionAmount = 998 / 1.002 = 996.01 YT-B │ │ │ │ │ │ │ │ ④ 获取赎回手续费率 │ │ │ │ feeBps = getRedemptionFeeBasisPoints(_token, 998) │ │ │ │ • USDY已被标记为稳定币 │ │ │ │ • 如果_token也是稳定币: 4 bps │ │ │ │ • 如果_token是YT代币: 30 bps │ │ │ │ → 本次(YT-B): 30 bps │ │ │ │ │ │ │ │ ⑤ 计算扣费后的输出 │ │ │ │ amountOut = 996.01 × (1 - 0.3%) │ │ │ │ = 993.02 YT-B │ │ │ │ │ │ │ │ ⑥ 池子记账 │ │ │ │ _decreasePoolAmount(_token, 993.02) // 减少池中代币 │ │ │ │ _decreaseUsdyAmount(_token, ...) // 减少债务 │ │ │ │ │ │ │ │ ⑦ 销毁USDY │ │ │ │ USDY.burn(address(this), 998) │ │ │ │ │ │ │ │ ⑧ 转出代币 │ │ │ │ transfer(_receiver, 993.02 YT-B) │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ 返回: 993.02 YT-B │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 5. 检查滑点保护 │ require(993.02 >= 990) ✓ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 用户收到 │ │ ✓ 993.02 YT-B │ │ │ │ 成本分析: │ │ 赎回: 1000 ytLP │ │ 手续费: ~3 YT-B (0.3%) │ │ 价差损失: ~4 YT-B (0.4%) │ │ 总成本: ~0.7% │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 涉及的合约函数: | 合约 | 函数 | 作用 | |------|------|------| | **YTRewardRouter** | `removeLiquidity()` | 用户入口 | | **YTPoolManager** | `removeLiquidityForAccount()` | 计算USDY价值 | | **YTPoolManager** | `getAumInUsdy(false)` | 获取AUM(使用MinPrice)| | **YTVault** | `sellUSDY()` | 用USDY换回代币 | | **YTVault** | `getRedemptionFeeBasisPoints()` | 获取赎回手续费率 | | **YTPriceFeed** | `getPrice(_token, true)` | 获取MaxPrice | | **USDY** | `mint()` | 补充USDY(如需要)| | **USDY** | `burn()` | 销毁USDY | | **YTLPToken** | `burn()` | 销毁ytLP | --- ## 3. 代币互换流程 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 用户 (User) │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 1. 调用 swapYT(tokenIn, tokenOut, amountIn, minOut, receiver) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ YTRewardRouter.sol │ │ ───────────────────────────────────────────────────────────────── │ │ function swapYT( │ │ address _tokenIn, // YT-A │ │ address _tokenOut, // YT-B │ │ uint256 _amountIn, // 1000 │ │ uint256 _minOut, // 990(滑点保护) │ │ address _receiver // 接收地址 │ │ ) external nonReentrant whenNotPaused │ │ │ │ 修饰符检查: │ │ ✓ nonReentrant - 防重入保护 │ │ ✓ whenNotPaused - 暂停检查 │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 2. transferFrom(user → YTVault) │ 转移1000个YT-A直接到Vault ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ YTVault.sol │ │ ───────────────────────────────────────────────────────────────── │ │ function swap( │ │ address _tokenIn, // YT-A │ │ address _tokenOut, // YT-B │ │ address _receiver // 用户 │ │ ) │ │ │ │ 步骤: │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ ① 安全检查 │ │ │ │ ✓ swap已启用 │ │ │ │ ✓ tokenIn已白名单 │ │ │ │ ✓ tokenOut已白名单 │ │ │ │ ✓ tokenIn != tokenOut │ │ │ │ │ │ │ │ ② transferIn(_tokenIn) │ │ │ │ • 检测转入的YT-A数量: 1000 │ │ │ │ │ │ │ │ ③ 检查单笔交易限额 │ │ │ │ if (maxSwapAmount[tokenIn] > 0) { │ │ │ │ require(1000 <= maxSwapAmount[tokenIn]) │ │ │ │ } │ │ │ │ │ │ │ │ ④ 获取价格(对协议有利的定价) │ │ │ │ priceIn = _getPrice(_tokenIn, false) // MinPrice │ │ │ │ = $0.998 (低估输入) │ │ │ │ priceOut = _getPrice(_tokenOut, true) // MaxPrice │ │ │ │ = $1.002 (高估输出) │ │ │ │ │ │ │ │ ⑤ 计算中间USDY价值 │ │ │ │ usdyAmount = 1000 × $0.998 / 1e30 = $998 │ │ │ │ │ │ │ │ ⑥ 检查每日交易量限制 │ │ │ │ _checkDailySwapLimit(998) │ │ │ │ │ │ │ │ ⑦ 计算理论输出(扣费前) │ │ │ │ amountOut = 998 × 1e30 / $1.002 │ │ │ │ = 996.01 YT-B │ │ │ │ │ │ │ │ ⑧ 获取swap手续费率(动态) │ │ │ │ feeBps = getSwapFeeBasisPoints(tokenIn, tokenOut, 998) │ │ │ │ │ │ │ │ 判断逻辑: │ │ │ │ • 是否稳定币互换? │ │ │ │ isStableSwap = stableTokens[YT-A] && stableTokens[YT-B] │ │ │ │ = false && false = false │ │ │ │ │ │ │ │ • 基础费率: 30 bps (非稳定币) │ │ │ │ • 税率: 50 bps │ │ │ │ │ │ │ │ • 动态调整(改善平衡 → 降低费率): │ │ │ │ ├─ YT-A: 当前50k, 目标40k → 过多 → 增加费率 │ │ │ │ └─ YT-B: 当前30k, 目标40k → 不足 → 减少费率 │ │ │ │ │ │ │ │ • 计算两个代币的费率,取较高者 │ │ │ │ fee_A = getFeeBasisPoints(YT-A, 998, 30, 50, true) │ │ │ │ = 40 bps (恶化平衡,提高) │ │ │ │ fee_B = getFeeBasisPoints(YT-B, 998, 30, 50, false) │ │ │ │ = 20 bps (改善平衡,降低) │ │ │ │ │ │ │ │ → 最终费率: max(40, 20) = 40 bps (0.4%) │ │ │ │ │ │ │ │ ⑨ 扣除手续费 │ │ │ │ amountOutAfterFees = 996.01 × (1 - 0.4%) │ │ │ │ = 992.02 YT-B │ │ │ │ │ │ │ │ ⑩ 全局滑点保护 │ │ │ │ _validateSwapSlippage(1000, 992.02, priceIn, priceOut) │ │ │ │ │ │ │ │ ⑪ 更新池子状态 │ │ │ │ _increasePoolAmount(YT-A, 1000) // YT-A入池 │ │ │ │ _decreasePoolAmount(YT-B, 992.02) // YT-B出池 │ │ │ │ _increaseUsdyAmount(YT-A, 998) // YT-A债务+998 │ │ │ │ _decreaseUsdyAmount(YT-B, 998) // YT-B债务-998 │ │ │ │ │ │ │ │ ⑫ 转出代币 │ │ │ │ transfer(_receiver, 992.02 YT-B) │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ 返回: 992.02 YT-B │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 3. 检查滑点保护 │ require(992.02 >= 990) ✓ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 用户收到 │ │ ✓ 992.02 YT-B │ │ │ │ 成本分析: │ │ 输入: 1000 YT-A │ │ 价差损失: ~4 YT (0.4%) │ │ 手续费: ~4 YT (0.4% 动态) │ │ 输出: 992.02 YT-B │ │ 总成本: ~0.8% │ │ │ │ 有效汇率: 1 YT-A = 0.9920 YT-B │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 涉及的合约函数: | 合约 | 函数 | 作用 | |------|------|------| | **YTRewardRouter** | `swapYT()` | 用户入口,转移代币 | | **YTVault** | `swap()` | 执行代币互换 | | **YTVault** | `getSwapFeeBasisPoints()` | 获取动态swap费率 | | **YTVault** | `getFeeBasisPoints()` | 计算单个代币的动态费率 | | **YTVault** | `_validateSwapSlippage()` | 全局滑点保护 | | **YTVault** | `_checkDailySwapLimit()` | 检查每日限额 | | **YTPriceFeed** | `getMinPrice()` | 输入代币价格 | | **YTPriceFeed** | `getMaxPrice()` | 输出代币价格 | --- ## 4. 添加白名单代币流程 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 管理员 (Gov) │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 1. 调用 setWhitelistedToken(...) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ YTVault.sol │ │ ───────────────────────────────────────────────────────────────── │ │ function setWhitelistedToken( │ │ address _token, // 新YT代币地址 │ │ uint256 _decimals, // 18 │ │ uint256 _weight, // 权重 10000 (10%) │ │ uint256 _maxUsdyAmount, // 最大USDY债务 1000000e18 │ │ bool _isStable // false (YT代币非稳定币) │ │ ) │ │ │ │ 步骤: │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ ① 安全检查 │ │ │ │ require(_token != address(0)) │ │ │ │ require(msg.sender == gov) // 只有管理员 │ │ │ │ │ │ │ │ ② 检查是否已在白名单 │ │ │ │ if (!whitelistedTokens[_token]) { │ │ │ │ allWhitelistedTokens.push(_token) // 添加到数组 │ │ │ │ whitelistedTokens[_token] = true // 标记为白名单 │ │ │ │ } │ │ │ │ │ │ │ │ ③ 更新总权重 │ │ │ │ oldWeight = tokenWeights[_token] // 0(新代币) │ │ │ │ totalTokenWeights = totalTokenWeights - oldWeight + 10000 │ │ │ │ │ │ │ │ 示例(自动比例调整): │ │ │ │ ┌──────────────┬────────┬──────────┬──────────┐ │ │ │ │ │ 代币 │ 权重 │ 旧占比 │ 新占比 │ │ │ │ │ ├──────────────┼────────┼──────────┼──────────┤ │ │ │ │ │ YT-A (原有) │ 30000 │ 30.0% │ 27.27% │ ← 自动变化│ │ │ │ │ YT-B (原有) │ 30000 │ 30.0% │ 27.27% │ │ │ │ │ │ YT-C (原有) │ 30000 │ 30.0% │ 27.27% │ │ │ │ │ │ USDC (原有) │ 10000 │ 10.0% │ 9.09% │ │ │ │ │ │ YT-D (新增) │ 10000 │ - │ 9.09% │ ← 新增 │ │ │ │ ├──────────────┼────────┼──────────┼──────────┤ │ │ │ │ │ 总计 │ 110000 │ 100% │ 100% │ │ │ │ │ └──────────────┴────────┴──────────┴──────────┘ │ │ │ │ │ │ │ │ ⚠️ 重要:使用相对权重,不需要保持总和为100000 │ │ │ │ totalTokenWeights可以是任意值,关键是相对比例 │ │ │ │ │ │ │ │ ④ 设置代币参数 │ │ │ │ tokenDecimals[_token] = 18 │ │ │ │ tokenWeights[_token] = 10000 │ │ │ │ maxUsdyAmounts[_token] = 1000000e18 // 最大100万USDY债务 │ │ │ │ stableTokens[_token] = false // 标记非稳定币 │ │ │ │ │ │ │ │ ⑤ 白名单添加完成 │ │ │ │ ✓ 代币可以被存入 │ │ │ │ ✓ 代币可以被swap │ │ │ │ ✓ 代币开始计入AUM │ │ │ └─────────────────────────────────────────────────────────────┘ │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 2. 需要配置价格预言机 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ YTPriceFeed.sol │ │ ───────────────────────────────────────────────────────────────── │ │ 需要执行的配置: │ │ │ │ ① 设置价差 (可选但推荐) │ │ setSpreadBasisPoints(YT-D, 20) // 0.2% 价差 │ │ │ │ ② 初始化价格 (如果需要) │ │ forceUpdatePrice(YT-D, 1e30) // 初始价格 $1.00 │ │ │ │ 注意: │ │ • YT代币需要实现 ytPrice() 接口 │ │ • 价格预言机会自动读取该接口获取YT价格 │ │ • USDC价格自动从Chainlink获取,无需手动配置 │ └─────────────────────────────────────────────────────────────────────┘ │ │ 3. 配置完成 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 白名单生效 │ │ │ │ 用户可以: │ │ ✓ 用YT-D添加流动性 │ │ ✓ 用ytLP换回YT-D │ │ ✓ YT-D与其他YT代币互换 │ │ │ │ 协议会: │ │ ✓ 将YT-D计入AUM(按10%权重) │ │ ✓ 对YT-D使用0.3%的swap费率(非稳定币) │ │ ✓ 动态调整费率以维持池子平衡 │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 涉及的合约函数: | 合约 | 函数 | 作用 | |------|------|------| | **YTVault** | `setWhitelistedToken()` | 添加代币到白名单 | | **YTPriceFeed** | `setSpreadBasisPoints()` | 设置代币价差 | | **YTPriceFeed** | `forceUpdatePrice()` | 初始化价格(可选)| ### 白名单代币要求: ``` ┌─────────────────────────────────────────────────────────────────────┐ │ YT代币必须实现的接口 │ │ │ │ interface IYTAssetVault { │ │ // 必需:返回当前YT资产价格(30位精度) │ │ function ytPrice() external view returns (uint256); │ │ │ │ // 可选:返回最后价格更新时间 │ │ function lastPriceUpdate() external view returns (uint256); │ │ │ │ // ERC20标准接口 │ │ function decimals() external view returns (uint8); │ │ function balanceOf(address) external view returns (uint256); │ │ function transfer(address, uint256) external returns (bool); │ │ function transferFrom(address, address, uint256) │ │ external returns (bool); │ │ } │ │ │ │ YT价格示例 (1e30精度): │ │ $1.00 = 1 × 10^30 = 1000000000000000000000000000000 │ │ $0.998 = 998000000000000000000000000000 │ │ │ │ USDC价格(从Chainlink获取,1e8精度): │ │ $1.00 = 100000000 (1e8) │ │ $0.998 = 99800000 │ │ 自动转换为1e30精度用于内部计算 │ └─────────────────────────────────────────────────────────────────────┘ ``` --- ## 5. 移除白名单代币流程 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 管理员 (Gov) │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 1. 先确保池中该代币已清空 │ (用户需先移除流动性) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 检查代币状态 │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 检查池子余额: │ │ │ │ poolAmounts[YT-D] = ? │ │ │ │ usdyAmounts[YT-D] = ? │ │ │ │ │ │ │ │ 安全建议: │ │ │ │ ✓ 池中余额应为0或接近0 │ │ │ │ ✓ USDY债务应为0 │ │ │ │ ✓ 没有待处理的用户流动性 │ │ │ └─────────────────────────────────────────────────────────────┘ │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 2. 调用 clearWhitelistedToken(token) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ YTVault.sol │ │ ───────────────────────────────────────────────────────────────── │ │ function clearWhitelistedToken(address _token) │ │ │ │ 步骤: │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ ① 安全检查 │ │ │ │ require(whitelistedTokens[_token]) // 必须已在白名单 │ │ │ │ require(msg.sender == gov) // 只有管理员 │ │ │ │ │ │ │ │ ② 更新总权重 │ │ │ │ oldWeight = tokenWeights[YT-D] = 10000 │ │ │ │ totalTokenWeights = totalTokenWeights - 10000 │ │ │ │ │ │ │ │ 结果: │ │ │ │ ┌──────────────┬────────┬────────┐ │ │ │ │ │ 代币 │ 权重 │ 占比 │ │ │ │ │ ├──────────────┼────────┼────────┤ │ │ │ │ │ YT-A │ 30000 │ 30% │ │ │ │ │ │ YT-B │ 30000 │ 30% │ │ │ │ │ │ YT-C │ 30000 │ 30% │ │ │ │ │ │ USDC │ 10000 │ 10% │ │ │ │ │ │ YT-D (删除) │ 0 │ - │ ← 已移除 │ │ │ │ ├──────────────┼────────┼────────┤ │ │ │ │ │ 总计 │ 100000 │ 100% │ │ │ │ │ └──────────────┴────────┴────────┘ │ │ │ │ │ │ │ │ ③ 清除所有配置 │ │ │ │ delete whitelistedTokens[_token] // 从白名单移除 │ │ │ │ delete stableTokens[_token] // 清除稳定币标记 │ │ │ │ delete tokenDecimals[_token] // 清除精度 │ │ │ │ delete tokenWeights[_token] // 清除权重 │ │ │ │ delete maxUsdyAmounts[_token] // 清除最大债务限制 │ │ │ │ │ │ │ │ ④ 白名单移除完成 │ │ │ │ ✓ 代币无法被存入 │ │ │ │ ✓ 代币无法被swap │ │ │ │ ✓ 代币不再计入AUM │ │ │ └─────────────────────────────────────────────────────────────┘ │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 3. 后续处理(可选) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 可选的清理操作 │ │ │ │ ① 从 allWhitelistedTokens 数组中移除 │ │ (注意: 合约当前没有自动移除,需要考虑gas成本) │ │ │ │ ② 如果池中还有少量余额,可以提取 │ │ (启用紧急模式后) │ │ emergencyMode = true │ │ withdrawToken(YT-D, gov, amount) │ │ │ │ ③ 从价格预言机移除配置(可选) │ │ 不强制,但可以节省存储 │ └─────────────────────────────────────────────────────────────────────┘ │ │ 4. 完成 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 移除完成 │ │ │ │ 用户无法: │ │ ✗ 用YT-D添加流动性 │ │ ✗ 用ytLP换回YT-D │ │ ✗ YT-D与其他YT代币互换 │ │ │ │ 协议: │ │ ✓ YT-D不再计入AUM计算 │ │ ✓ 权重已重新分配 │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 涉及的合约函数: | 合约 | 函数 | 作用 | |------|------|------| | **YTVault** | `clearWhitelistedToken()` | 从白名单移除代币 | | **YTVault** | `setEmergencyMode()` | 启用紧急模式(如需提取余额)| | **YTVault** | `withdrawToken()` | 提取剩余代币(紧急模式下)| ### 移除白名单的注意事项: ``` ⚠️ 重要提示: 1. 移除前确保: • 所有用户已移除该代币的流动性 • poolAmounts[token] = 0 或接近0 • usdyAmounts[token] = 0 • 没有pending的交易 2. 移除后影响: • 该代币的所有操作立即失效 • 如果用户还持有该代币的ytLP,无法再换回该代币 • 总权重自动减少,其他代币占比自动增加(不需要手动调整) 3. 不可逆操作: • 移除后该代币在 allWhitelistedTokens 数组中仍然存在(历史记录) • 但所有配置和权限已清除 • 如需重新添加,需要再次调用 setWhitelistedToken() 4. 最佳实践: • 提前公告,给用户足够时间移除流动性 • 移除前暂停该代币的新增流动性 • 记录剩余余额,在紧急模式下安全提取 • 移除后验证AUM计算正确 ``` --- ## 总结:合约调用关系 ``` ┌─────────────────┐ │ User │ └────────┬────────┘ │ ┌────────▼────────┐ │ YTRewardRouter │ ◄── 用户入口 │ • addLiquidity │ │ • removeLiq... │ │ • swapYT │ └────────┬────────┘ │ ┌────────▼────────┐ │ YTPoolManager │ ◄── 流动性管理 │ • addLiq... │ │ • removeLiq... │ │ • getAumInUsdy │ └────────┬────────┘ │ ┌───────────────────┼───────────────────┐ │ │ │ ┌────▼────┐ ┌──────▼──────┐ ┌────▼─────┐ │ YTVault │ │ YTLPToken │ │ USDY │ │ • buyUS │ │ • mint │ │ • mint │ │ • sellU │ │ • burn │ │ • burn │ │ • swap │ └─────────────┘ └──────────┘ └────┬────┘ │ ┌────▼────────┐ │ YTPriceFeed │ ◄── 价格预言机 │ • getPrice │ │ • 价差配置 │ └─────────────┘ ``` ### 核心合约职责: | 合约 | 职责 | 关键功能 | |------|------|----------| | **YTRewardRouter** | 用户入口 | 接收用户请求,处理代币转移 | | **YTPoolManager** | 流动性管理 | 计算ytLP,管理AUM | | **YTVault** | 资金池 | 存储资产,执行swap,管理手续费 | | **YTPriceFeed** | 价格预言机 | 提供价格,应用价差 | | **USDY** | 计价代币 | 内部记账单位 | | **YTLPToken** | LP代币 | 代表用户份额 | --- ## 6. 系统部署和初始化流程 ### 6.1 部署 YTPriceFeed(使用 Chainlink) ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 部署者 (Deployer) │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 1. 准备 USDC 和 Chainlink 地址 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 准备外部合约地址 │ │ ───────────────────────────────────────────────────────────────── │ │ • USDC 代币地址(BSC主网) │ │ usdc = 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d │ │ 注意:BSC主网USDC是18位精度 │ │ │ │ • Chainlink USDC/USD 价格预言机地址(BSC主网) │ │ usdcPriceFeed = 0x51597f405303C4377E36123cBc172b13269EA163 │ │ 精度:1e8(Chainlink标准) │ │ 价格示例:$1.00 = 100000000 (1e8) │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 2. 部署 YTPriceFeed(传入USDC和Chainlink地址) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 部署 YTPriceFeed(可升级) │ │ ───────────────────────────────────────────────────────────────── │ │ const YTPriceFeed = await ethers.getContractFactory("YTPriceFeed") │ │ const priceFeed = await upgrades.deployProxy( │ │ YTPriceFeed, │ │ [ │ │ usdcAddress, // ← USDC代币地址 │ │ usdcPriceFeedAddress // ← Chainlink预言机地址 │ │ ], │ │ { │ │ kind: "uups", │ │ initializer: "initialize" │ │ } │ │ ) │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 3. initialize(address _usdc, address _usdcPriceFeed) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ YTPriceFeed.initialize() │ │ ───────────────────────────────────────────────────────────────── │ │ function initialize( │ │ address _usdc, │ │ address _usdcPriceFeed │ │ ) external initializer │ │ │ │ 步骤: │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ ① __UUPSUpgradeable_init() │ │ │ │ • 初始化UUPS升级功能 │ │ │ │ │ │ │ │ ② 验证USDC地址 │ │ │ │ if (_usdc == address(0)) revert InvalidAddress() │ │ │ │ ✓ 0x8AC... 有效 │ │ │ │ │ │ │ │ ③ 验证Chainlink预言机地址 │ │ │ │ if (_usdcPriceFeed == address(0)) revert InvalidAddress()│ │ │ │ ✓ 0x515... 有效 │ │ │ │ │ │ │ │ ④ 保存地址 │ │ │ │ usdc = _usdc │ │ │ │ usdcPriceFeed = AggregatorV3Interface(_usdcPriceFeed) │ │ │ │ │ │ │ │ ⑤ 设置治理地址 │ │ │ │ gov = msg.sender (部署者) │ │ │ │ │ │ │ │ ⑥ 设置默认参数 │ │ │ │ maxPriceChangeBps = 500 // 5% 最大价格变动 │ │ │ └─────────────────────────────────────────────────────────────┘ │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 4. 初始化完成 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ YTPriceFeed 就绪 │ │ ───────────────────────────────────────────────────────────────── │ │ 状态: │ │ • usdc: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d ✓ │ │ • usdcPriceFeed: 0x51597f405303C4377E36123cBc172b13269EA163 ✓ │ │ • gov: 已设置 ✓ │ │ • maxPriceChangeBps: 500 (5%) │ │ │ │ 价格获取方式: │ │ • USDC价格:从Chainlink实时获取(自动更新) │ │ • YT代币价格:从YTAssetVault.ytPrice()读取(需keeper更新) │ │ │ │ 优势: │ │ ✓ USDC价格实时准确(Chainlink提供) │ │ ✓ 无需手动更新USDC价格 │ │ ✓ 价格数据去中心化,更可靠 │ │ ✓ 支持价格验证(负数/零值检查) │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 6.2 完整系统部署流程 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 系统部署顺序 │ │ │ │ 步骤 1: 准备 USDC 和 Chainlink 地址 │ │ ├─→ USDC: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d (BSC主网) │ │ └─→ Chainlink USDC/USD: 0x51597f405303C4377E36123cBc172b13269EA163 │ │ │ │ 步骤 2: 部署代币合约 │ │ ├─→ USDY.initialize() │ │ └─→ YTLPToken.initialize() │ │ │ │ 步骤 3: 部署 YTPriceFeed │ │ └─→ YTPriceFeed.initialize(usdcAddress, usdcPriceFeedAddress) │ │ │ │ 步骤 4: 部署 YTVault │ │ └─→ YTVault.initialize(usdyAddress, priceFeedAddress) │ │ │ │ 步骤 5: 部署 YTPoolManager │ │ └─→ YTPoolManager.initialize( │ │ vaultAddress, │ │ usdyAddress, │ │ ytLPAddress, │ │ cooldownDuration │ │ ) │ │ │ │ 步骤 6: 部署 YTRewardRouter │ │ └─→ YTRewardRouter.initialize( │ │ usdyAddress, │ │ ytLPAddress, │ │ poolManagerAddress, │ │ vaultAddress │ │ ) │ │ │ │ 步骤 7: 配置权限 │ │ ├─→ usdy.addVault(vaultAddress) │ │ ├─→ usdy.addVault(poolManagerAddress) │ │ ├─→ ytlp.setMinter(poolManagerAddress, true) │ │ ├─→ vault.setPoolManager(poolManagerAddress) │ │ ├─→ vault.setSwapper(routerAddress, true) │ │ └─→ poolManager.setHandler(routerAddress, true) │ │ │ │ 步骤 8: 配置 YTPriceFeed │ │ ├─→ priceFeed.setKeeper(keeperAddress, true) │ │ ├─→ priceFeed.setMaxPriceChangeBps(500) │ │ └─→ 注意:USDC价格自动从Chainlink获取,无需手动设置 │ │ │ │ 步骤 9: 配置 YTVault 参数 │ │ ├─→ vault.setSwapFees(30, 4, 50, 20) │ │ ├─→ vault.setDynamicFees(true) │ │ └─→ vault.setMaxSwapSlippageBps(1000) │ │ │ │ 步骤 10: 添加白名单代币 │ │ ├─→ vault.setWhitelistedToken(ytTokenA, 18, 4000, maxAmount, false) │ │ ├─→ vault.setWhitelistedToken(ytTokenB, 18, 3000, maxAmount, false) │ │ └─→ vault.setWhitelistedToken(ytTokenC, 18, 2000, maxAmount, false) │ │ │ │ 步骤 11: 初始化价格 │ │ ├─→ priceFeed.forceUpdatePrice(ytTokenA, 1e30) │ │ ├─→ priceFeed.forceUpdatePrice(ytTokenB, 1e30) │ │ └─→ priceFeed.forceUpdatePrice(ytTokenC, 1e30) │ │ │ │ ✓ 系统部署完成,可以开始使用 │ └─────────────────────────────────────────────────────────────────────┘ ``` --- ## 7. 路由器暂停功能流程 ### 7.1 暂停路由器 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ Gov (系统管理员) │ │ 检测到: 需要紧急暂停用户操作 │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 1. 调用 router.pause() ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ YTRewardRouter.pause() │ │ ───────────────────────────────────────────────────────────────── │ │ function pause() external onlyGov │ │ │ │ 权限检查: │ │ ✓ onlyGov - 只有治理地址可调用 │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 2. 执行暂停 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Pausable._pause() │ │ ───────────────────────────────────────────────────────────────── │ │ internal _pause() │ │ • 设置 paused = true │ │ • 触发 Paused(msg.sender) 事件 │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 3. 暂停生效 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Router已暂停 │ │ ───────────────────────────────────────────────────────────────── │ │ 被阻止的操作(revert EnforcedPause): │ │ ✗ addLiquidity() - 用户无法添加流动性 │ │ ✗ removeLiquidity() - 用户无法移除流动性 │ │ ✗ swapYT() - 用户无法进行代币互换 │ │ │ │ 仍可用的操作: │ │ ✓ getYtLPPrice() - 查询ytLP价格 │ │ ✓ getAccountValue() - 查询账户价值 │ │ │ │ 系统状态: │ │ • 所有用户资金操作暂停 │ │ • 底层 YTVault 和 YTPoolManager 仍然运行 │ │ • 直接调用 PoolManager 也会被阻止(权限检查) │ │ • 查询功能不受影响 │ │ │ │ 用户影响: │ │ • 无法通过Router进行任何交易 │ │ • 资产安全锁定 │ │ • 可以查看余额和价值 │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 7.2 恢复路由器 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ Gov (系统管理员) │ │ 问题已解决,恢复正常运行 │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 1. 调用 router.unpause() ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ YTRewardRouter.unpause() │ │ ───────────────────────────────────────────────────────────────── │ │ function unpause() external onlyGov │ │ │ │ 权限检查: │ │ ✓ onlyGov - 只有治理地址可调用 │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 2. 执行恢复 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Pausable._unpause() │ │ ───────────────────────────────────────────────────────────────── │ │ internal _unpause() │ │ • 设置 paused = false │ │ • 触发 Unpaused(msg.sender) 事件 │ └────────────────────────────┬────────────────────────────────────────┘ │ │ 3. 恢复完成 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Router恢复正常 │ │ ───────────────────────────────────────────────────────────────── │ │ 所有功能恢复: │ │ ✓ addLiquidity() - 用户可以添加流动性 │ │ ✓ removeLiquidity() - 用户可以移除流动性 │ │ ✓ swapYT() - 用户可以进行代币互换 │ │ │ │ 系统状态: │ │ • 所有操作恢复正常 │ │ • 用户可以继续交易 │ │ • 暂停期间的数据和状态完全保留 │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 7.3 多层暂停策略 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 紧急情况处理策略 │ │ │ │ 场景 1: 轻度风险 - 仅暂停Router │ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ │ 适用场景: │ │ • Router合约发现问题 │ │ • 前端交互异常 │ │ • 用户操作需要临时限制 │ │ │ │ 操作: │ │ router.pause() │ │ │ │ 影响: │ │ • 用户无法通过Router操作 │ │ • YTVault、YTPoolManager 继续运行 │ │ • 其他集成方可能仍可直接调用 PoolManager(如有权限) │ │ │ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ │ │ │ 场景 2: 中度风险 - 暂停Router + 启用Vault紧急模式 │ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ │ 适用场景: │ │ • Vault合约发现潜在问题 │ │ • 需要全面阻止交易 │ │ • 准备系统升级 │ │ │ │ 操作: │ │ router.pause() │ │ vault.setEmergencyMode(true) │ │ │ │ 影响: │ │ • Router完全暂停 │ │ • Vault的swap、buyUSDY、sellUSDY等操作全部阻止 │ │ • 系统几乎完全冻结 │ │ │ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ │ │ │ 场景 3: 高度风险 - 全面暂停(Router + 所有YTAssetVaults) │ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ │ 适用场景: │ │ • 发现重大安全漏洞 │ │ • YTAssetVault代币价格异常 │ │ • 系统性风险 │ │ │ │ 操作: │ │ router.pause() │ │ vault.setEmergencyMode(true) │ │ factory.pauseVaultBatch([ytTokenA, ytTokenB, ytTokenC]) │ │ │ │ 影响: │ │ • 整个系统完全冻结 │ │ • 用户无法进行任何资金操作 │ │ • YTAssetVault的存款/提款也被暂停 │ │ • 最大程度保护用户资产 │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 7.4 暂停功能的最佳实践 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 使用暂停功能的建议 │ │ │ │ 何时使用暂停: │ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ │ │ │ 1️⃣ 安全事件响应 │ │ • 发现安全漏洞或异常行为 │ │ • 立即暂停相关操作 │ │ • 评估影响范围 │ │ • 修复后恢复 │ │ │ │ 2️⃣ 系统维护升级 │ │ • 计划性系统升级 │ │ • 提前公告用户 │ │ • 暂停操作执行升级 │ │ • 测试验证后恢复 │ │ │ │ 3️⃣ 市场异常波动 │ │ • 价格剧烈波动 │ │ • 流动性枯竭 │ │ • 暂停防止损失扩大 │ │ • 市场稳定后恢复 │ │ │ │ 4️⃣ 合规要求 │ │ • 监管调查配合 │ │ • 临时限制操作 │ │ • 保持透明沟通 │ │ │ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ │ │ │ 操作流程: │ │ ✓ 发现问题 → 立即评估 │ │ ✓ 确定暂停范围(Router / Vault / 全系统) │ │ ✓ 执行暂停命令 │ │ ✓ 公告用户(如可能) │ │ ✓ 调查和修复 │ │ ✓ 测试验证 │ │ ✓ 恢复系统 │ │ ✓ 监控运行 │ │ │ │ 注意事项: │ │ ⚠️ 暂停不影响资产价值和余额 │ │ ⚠️ 查询功能始终可用 │ │ ⚠️ 暂停期间可以更新价格(仅gov) │ │ ⚠️ 恢复前应充分测试 │ │ ⚠️ 保持与用户的沟通透明 │ └─────────────────────────────────────────────────────────────────────┘ ```