user logout in the vault changed to queuing mechanism, and add function of batch sending WUSD

This commit is contained in:
2025-12-19 13:26:49 +08:00
parent 21674f86a9
commit 399354646a
27 changed files with 2057 additions and 2877 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,16 @@
"name": "InsufficientReserves",
"type": "error"
},
{
"inputs": [],
"name": "InvalidBorrowCollateralFactor",
"type": "error"
},
{
"inputs": [],
"name": "InvalidLiquidateCollateralFactor",
"type": "error"
},
{
"inputs": [],
"name": "InvalidLiquidationFactor",

View File

@@ -66,11 +66,21 @@
"name": "InsufficientReserves",
"type": "error"
},
{
"inputs": [],
"name": "InvalidBorrowCollateralFactor",
"type": "error"
},
{
"inputs": [],
"name": "InvalidInitialization",
"type": "error"
},
{
"inputs": [],
"name": "InvalidLiquidateCollateralFactor",
"type": "error"
},
{
"inputs": [],
"name": "InvalidLiquidationFactor",

View File

@@ -152,6 +152,11 @@
"name": "InvalidAmount",
"type": "error"
},
{
"inputs": [],
"name": "InvalidBatchSize",
"type": "error"
},
{
"inputs": [],
"name": "InvalidHardCap",
@@ -177,6 +182,16 @@
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [],
"name": "RequestAlreadyProcessed",
"type": "error"
},
{
"inputs": [],
"name": "RequestNotFound",
"type": "error"
},
{
"inputs": [
{
@@ -266,6 +281,37 @@
"name": "AssetsWithdrawn",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "startIndex",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "endIndex",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "processedCount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "totalWusdDistributed",
"type": "uint256"
}
],
"name": "BatchProcessed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
@@ -457,6 +503,68 @@
"name": "Upgraded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "requestId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ytAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "wusdAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "queueIndex",
"type": "uint256"
}
],
"name": "WithdrawRequestCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "requestId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "wusdAmount",
"type": "uint256"
}
],
"name": "WithdrawRequestProcessed",
"type": "event"
},
{
"inputs": [],
"name": "PRICE_PRECISION",
@@ -621,6 +729,93 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getPendingRequestsCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getQueueProgress",
"outputs": [
{
"internalType": "uint256",
"name": "currentIndex",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "totalRequests",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "pendingRequests",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_requestId",
"type": "uint256"
}
],
"name": "getRequestDetails",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "uint256",
"name": "ytAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "wusdAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "requestTime",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "queueIndex",
"type": "uint256"
},
{
"internalType": "bool",
"name": "processed",
"type": "bool"
}
],
"internalType": "struct YTAssetVault.WithdrawRequest",
"name": "request",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getTimeUntilNextRedemption",
@@ -634,6 +829,76 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_user",
"type": "address"
}
],
"name": "getUserPendingRequests",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "uint256",
"name": "ytAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "wusdAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "requestTime",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "queueIndex",
"type": "uint256"
},
{
"internalType": "bool",
"name": "processed",
"type": "bool"
}
],
"internalType": "struct YTAssetVault.WithdrawRequest[]",
"name": "pendingRequests",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_user",
"type": "address"
}
],
"name": "getUserRequestIds",
"outputs": [
{
"internalType": "uint256[]",
"name": "",
"type": "uint256[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getVaultInfo",
@@ -828,6 +1093,19 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pendingRequestsCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
@@ -866,6 +1144,43 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_batchSize",
"type": "uint256"
}
],
"name": "processBatchWithdrawals",
"outputs": [
{
"internalType": "uint256",
"name": "processedCount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "totalDistributed",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "processedUpToIndex",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "proxiableUUID",
@@ -879,6 +1194,19 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "requestIdCounter",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
@@ -1071,6 +1399,50 @@
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "withdrawRequests",
"outputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "uint256",
"name": "ytAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "wusdAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "requestTime",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "queueIndex",
"type": "uint256"
},
{
"internalType": "bool",
"name": "processed",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
@@ -1083,7 +1455,7 @@
"outputs": [
{
"internalType": "uint256",
"name": "wusdAmount",
"name": "requestId",
"type": "uint256"
}
],

File diff suppressed because one or more lines are too long

View File

@@ -31,6 +31,9 @@ contract YTAssetVault is
error InsufficientWUSD();
error InsufficientYTA();
error StillInLockPeriod();
error RequestNotFound();
error RequestAlreadyProcessed();
error InvalidBatchSize();
/// @notice 工厂合约地址
address public factory;
@@ -59,6 +62,31 @@ contract YTAssetVault is
/// @notice 下一个赎回开放时间(所有用户统一)
uint256 public nextRedemptionTime;
/// @notice 提现请求结构体
struct WithdrawRequest {
address user; // 用户地址
uint256 ytAmount; // YT数量
uint256 wusdAmount; // 应得WUSD数量
uint256 requestTime; // 请求时间
uint256 queueIndex; // 队列位置
bool processed; // 是否已处理
}
/// @notice 请求ID => 请求详情
mapping(uint256 => WithdrawRequest) public withdrawRequests;
/// @notice 用户地址 => 用户的所有请求ID列表
mapping(address => uint256[]) private userRequestIds;
/// @notice 请求ID计数器
uint256 public requestIdCounter;
/// @notice 已处理到的队列位置
uint256 public processedUpToIndex;
/// @notice 当前待处理的请求数量(实时维护,避免循环计算)
uint256 public pendingRequestsCount;
event HardCapSet(uint256 newHardCap);
event ManagerSet(address indexed newManager);
event AssetsWithdrawn(address indexed to, uint256 amount);
@@ -67,6 +95,9 @@ contract YTAssetVault is
event Buy(address indexed user, uint256 wusdAmount, uint256 ytAmount);
event Sell(address indexed user, uint256 ytAmount, uint256 wusdAmount);
event NextRedemptionTimeSet(uint256 newRedemptionTime);
event WithdrawRequestCreated(uint256 indexed requestId, address indexed user, uint256 ytAmount, uint256 wusdAmount, uint256 queueIndex);
event WithdrawRequestProcessed(uint256 indexed requestId, address indexed user, uint256 wusdAmount);
event BatchProcessed(uint256 startIndex, uint256 endIndex, uint256 processedCount, uint256 totalWusdDistributed);
modifier onlyFactory() {
if (msg.sender != factory) revert Forbidden();
@@ -218,15 +249,16 @@ contract YTAssetVault is
}
/**
* @notice 卖出YT换取WUSD(需要等到统一赎回时间)
* @notice 提交YT提现请求(需要等到统一赎回时间)
* @param _ytAmount 卖出的YT数量
* @return wusdAmount 实际获得的WUSD数量
* @return requestId 提现请求ID
* @dev 用户提交请求后YT会立即销毁但WUSD需要等待批量处理后才能领取
*/
function withdrawYT(uint256 _ytAmount)
external
nonReentrant
whenNotPaused
returns (uint256 wusdAmount)
returns (uint256 requestId)
{
if (_ytAmount == 0) revert InvalidAmount();
if (balanceOf(msg.sender) < _ytAmount) revert InsufficientYTA();
@@ -237,19 +269,177 @@ contract YTAssetVault is
}
// 计算可以换取的WUSD数量
wusdAmount = (_ytAmount * ytPrice) / wusdPrice;
uint256 wusdAmount = (_ytAmount * ytPrice) / wusdPrice;
// 检查合约是否有足够的WUSD
uint256 availableWUSD = IERC20(wusdAddress).balanceOf(address(this));
if (wusdAmount > availableWUSD) revert InsufficientWUSD();
// 销毁YT
// 销毁YT代币
_burn(msg.sender, _ytAmount);
// 转出WUSD
IERC20(wusdAddress).safeTransfer(msg.sender, wusdAmount);
// 创建提现请求
requestId = requestIdCounter;
withdrawRequests[requestId] = WithdrawRequest({
user: msg.sender,
ytAmount: _ytAmount,
wusdAmount: wusdAmount,
requestTime: block.timestamp,
queueIndex: requestId,
processed: false
});
emit Sell(msg.sender, _ytAmount, wusdAmount);
// 记录用户的请求ID
userRequestIds[msg.sender].push(requestId);
// 递增计数器
requestIdCounter++;
// 增加待处理请求计数
pendingRequestsCount++;
emit WithdrawRequestCreated(requestId, msg.sender, _ytAmount, wusdAmount, requestId);
}
/**
* @notice 批量处理提现请求仅manager或factory可调用
* @param _batchSize 本批次最多处理的请求数量
* @return processedCount 实际处理的请求数量
* @return totalDistributed 实际分发的WUSD总量
* @dev 按照请求ID顺序即时间先后依次处理遇到资金不足时停止
*/
function processBatchWithdrawals(uint256 _batchSize)
external
nonReentrant
whenNotPaused
returns (uint256 processedCount, uint256 totalDistributed)
{
// 权限检查只有manager或factory可以调用
if (msg.sender != manager && msg.sender != factory) {
revert Forbidden();
}
if (_batchSize == 0) revert InvalidBatchSize();
uint256 availableWUSD = IERC20(wusdAddress).balanceOf(address(this));
uint256 startIndex = processedUpToIndex;
for (uint256 i = processedUpToIndex; i < requestIdCounter && processedCount < _batchSize; i++) {
WithdrawRequest storage request = withdrawRequests[i];
// 跳过已处理的请求
if (request.processed) {
continue;
}
// 检查是否有足够的WUSD
if (availableWUSD >= request.wusdAmount) {
// 转账WUSD给用户
IERC20(wusdAddress).safeTransfer(request.user, request.wusdAmount);
// 标记为已处理
request.processed = true;
// 更新统计
availableWUSD -= request.wusdAmount;
totalDistributed += request.wusdAmount;
processedCount++;
// 减少待处理请求计数
pendingRequestsCount--;
emit WithdrawRequestProcessed(i, request.user, request.wusdAmount);
} else {
// WUSD不足停止处理
break;
}
}
// 更新处理进度(跳到下一个未处理的位置)
if (processedCount > 0) {
// 找到下一个未处理的位置
for (uint256 i = processedUpToIndex; i < requestIdCounter; i++) {
if (!withdrawRequests[i].processed) {
processedUpToIndex = i;
break;
}
// 如果所有请求都已处理完
if (i == requestIdCounter - 1) {
processedUpToIndex = requestIdCounter;
}
}
}
emit BatchProcessed(startIndex, processedUpToIndex, processedCount, totalDistributed);
}
/**
* @notice 查询用户的所有提现请求ID
* @param _user 用户地址
* @return 用户的所有请求ID数组
*/
function getUserRequestIds(address _user) external view returns (uint256[] memory) {
return userRequestIds[_user];
}
/**
* @notice 查询指定请求的详情
* @param _requestId 请求ID
* @return request 请求详情
*/
function getRequestDetails(uint256 _requestId) external view returns (WithdrawRequest memory request) {
if (_requestId >= requestIdCounter) revert RequestNotFound();
return withdrawRequests[_requestId];
}
/**
* @notice 获取待处理的请求数量
* @return 待处理的请求总数
* @dev 使用实时维护的计数器O(1)复杂度避免gas爆炸
*/
function getPendingRequestsCount() external view returns (uint256) {
return pendingRequestsCount;
}
/**
* @notice 获取用户待处理的请求
* @param _user 用户地址
* @return pendingRequests 用户待处理的请求详情数组
*/
function getUserPendingRequests(address _user) external view returns (WithdrawRequest[] memory pendingRequests) {
uint256[] memory requestIds = userRequestIds[_user];
// 先计算有多少待处理的请求
uint256 pendingCount = 0;
for (uint256 i = 0; i < requestIds.length; i++) {
if (!withdrawRequests[requestIds[i]].processed) {
pendingCount++;
}
}
// 构造返回数组
pendingRequests = new WithdrawRequest[](pendingCount);
uint256 index = 0;
for (uint256 i = 0; i < requestIds.length; i++) {
uint256 requestId = requestIds[i];
if (!withdrawRequests[requestId].processed) {
pendingRequests[index] = withdrawRequests[requestId];
index++;
}
}
}
/**
* @notice 获取队列处理进度
* @return currentIndex 当前处理到的位置
* @return totalRequests 总请求数
* @return pendingRequests 待处理请求数
* @dev 使用实时维护的计数器,避免循环计算
*/
function getQueueProgress() external view returns (
uint256 currentIndex,
uint256 totalRequests,
uint256 pendingRequests
) {
currentIndex = processedUpToIndex;
totalRequests = requestIdCounter;
pendingRequests = pendingRequestsCount;
}
/**

View File

@@ -7,7 +7,7 @@
"defaultHardCap": "10000000000000000000000000",
"contracts": {
"YTAssetVault": {
"implementation": "0x5f0BB22F72BFc2F0903038c46E03d49E254EBCD4"
"implementation": "0xcBaD1799D5E33A3bd9c1A8eD48501195c28c4f14"
},
"YTAssetFactory": {
"proxy": "0x982716f32F10BCB5B5944c1473a8992354bF632b",
@@ -24,7 +24,9 @@
"hardCap": "10000000000000000000000000",
"redemptionTime": 1797565718,
"wusdPrice": "1000000000000000000000000000000",
"ytPrice": "1000000000000000000000000000000"
"ytPrice": "1000000000000000000000000000000",
"implementationAddress": "0xcBaD1799D5E33A3bd9c1A8eD48501195c28c4f14",
"lastUpgraded": "2025-12-19T05:15:25.594Z"
},
{
"name": "YT Token B",
@@ -35,7 +37,9 @@
"hardCap": "10000000000000000000000000",
"redemptionTime": 1797565718,
"wusdPrice": "1000000000000000000000000000000",
"ytPrice": "1000000000000000000000000000000"
"ytPrice": "1000000000000000000000000000000",
"implementationAddress": "0xcBaD1799D5E33A3bd9c1A8eD48501195c28c4f14",
"lastUpgraded": "2025-12-19T05:15:29.413Z"
},
{
"name": "YT Token C",
@@ -46,8 +50,18 @@
"hardCap": "10000000000000000000000000",
"redemptionTime": 1797565718,
"wusdPrice": "1000000000000000000000000000000",
"ytPrice": "1000000000000000000000000000000"
"ytPrice": "1000000000000000000000000000000",
"implementationAddress": "0xcBaD1799D5E33A3bd9c1A8eD48501195c28c4f14",
"lastUpgraded": "2025-12-19T05:15:34.429Z"
}
],
"lastUpdate": "2025-12-18T03:48:52.103Z"
"lastUpdate": "2025-12-19T05:15:34.430Z",
"upgradeHistory": [
{
"timestamp": "2025-12-19T05:15:34.430Z",
"oldImplementation": "0x5f0BB22F72BFc2F0903038c46E03d49E254EBCD4",
"newImplementation": "0xcBaD1799D5E33A3bd9c1A8eD48501195c28c4f14",
"upgrader": "0xa013422A5918CD099C63c8CC35283EACa99a705d"
}
]
}

View File

@@ -3,7 +3,10 @@
## 目录
1. [创建Vault流程](#1-创建vault流程)
2. [用户存款流程depositYT](#2-用户存款流程deposityt)
3. [用户提款流程withdrawYT](#3-用户提款流程withdrawyt)
3. [用户提款流程withdrawYT - 两阶段提现](#3-用户提款流程withdrawyt---两阶段提现)
- 3.1 [第一阶段:提交提现请求](#31-第一阶段提交提现请求)
- 3.2 [第二阶段:批量处理提现请求](#32-第二阶段批量处理提现请求)
- 3.3 [资金不足时的处理](#33-资金不足时的处理)
4. [价格更新流程](#4-价格更新流程)
5. [资产管理流程 - 提取投资](#5-资产管理流程---提取投资)
6. [资产管理流程 - 归还资产](#6-资产管理流程---归还资产)
@@ -285,7 +288,9 @@
---
## 3. 用户提款流程withdrawYT
## 3. 用户提款流程withdrawYT - 两阶段提现
### 3.1 第一阶段:提交提现请求
```
┌─────────────────────────────────────────────────────────────────────┐
@@ -329,12 +334,15 @@
│ 2. 调用 withdrawYT(5000e18)
┌─────────────────────────────────────────────────────────────────────┐
YTAssetVault.withdrawYT()
│ YTAssetVault.withdrawYT() - 提交请求
│ ───────────────────────────────────────────────────────────────── │
│ function withdrawYT(uint256 _ytAmount)
│ function withdrawYT(uint256 _ytAmount) returns (uint256 requestId)
│ • 非重入保护: nonReentrant │
│ • 暂停检查: whenNotPaused │
│ • 参数: 5,000 YT │
│ │
│ 🔴 重要变化此函数不再立即发放WUSD
│ 只创建提现请求,进入排队等待 │
└────────────────────────────┬────────────────────────────────────────┘
│ 3. 多重验证
@@ -352,9 +360,11 @@
│ ③ block.timestamp >= nextRedemptionTime │
│ ✓ 2025-02-16 >= 2025-02-15 通过 │
│ (已到赎回时间) │
│ │
│ ⚠️ 不再检查流动性即使vault中WUSD不足也可以提交请求 │
└────────────────────────────┬────────────────────────────────────────┘
│ 4. 计算可获得的WUSD数量
│ 4. 计算得的WUSD数量
┌─────────────────────────────────────────────────────────────────────┐
│ 计算WUSD数量 │
@@ -367,32 +377,13 @@
│ │
│ 计算过程: │
│ wusdAmount = (5,000e18 × 1.10e30) ÷ 1.00e30 │
│ = (5,000e18 × 1.10) ÷ 1.00 │
│ = 5,500e18 │
│ = 5,500 WUSD │
│ │
用户获得收益: 5,500 - 5,000 = 500 WUSD (10%增值)
💡 这个金额会锁定在请求中,不受后续价格变化影响
└────────────────────────────┬────────────────────────────────────────┘
│ 5. 检查Vault流动性
┌─────────────────────────────────────────────────────────────────────┐
│ 流动性检查 │
│ ───────────────────────────────────────────────────────────────── │
│ uint256 availableWUSD = IERC20(wusdAddress).balanceOf(vault) │
│ if (wusdAmount > availableWUSD) revert InsufficientWUSD() │
│ │
│ 当前状态: │
│ • Vault中WUSD余额: 10,000 WUSD │
│ • 需要支付: 5,500 WUSD │
│ • 5,500 ≤ 10,000 ✓ 流动性充足 │
│ │
│ 注意: │
│ 如果manager已提取部分资金进行投资availableWUSD可能不足 │
│ 此时用户需要等待manager归还资金 │
└────────────────────────────┬────────────────────────────────────────┘
│ 6. 销毁用户的YTCEI - Effects
│ 5. 立即销毁YT代币CEI - Effects
┌─────────────────────────────────────────────────────────────────────┐
│ 销毁YT代币 │
@@ -406,51 +397,295 @@
│ 结果: │
│ • 用户YT余额: 10,000 → 5,000 YT │
│ • 总供应量: 10,000 → 5,000 YT │
│ │
│ ⚠️ 注意YT已销毁但WUSD还未发放
└────────────────────────────┬────────────────────────────────────────┘
7. 转出WUSD给用户
6. 创建提现请求记录
┌─────────────────────────────────────────────────────────────────────┐
代币转移Interactions
创建WithdrawRequest
│ ───────────────────────────────────────────────────────────────── │
IERC20(wusdAddress).safeTransfer(
│ msg.sender, // 用户地址 │
│ 5500e18 // 转出5,500 WUSD │
│ ) │
requestId = requestIdCounter // 当前假设为 requestId = 42
│ │
结果:
• Vault WUSD余额: 10,000 → 4,500
• 用户WUSD余额: 0 → 5,500
withdrawRequests[42] = WithdrawRequest({
user: msg.sender, // 用户地址
ytAmount: 5000e18, // 5,000 YT
│ wusdAmount: 5500e18, // 应得5,500 WUSD │
│ requestTime: block.timestamp, // 当前时间戳 │
│ queueIndex: 42, // 队列位置 │
│ processed: false // 未处理 │
│ }) │
│ │
│ userRequestIds[msg.sender].push(42) // 记录到用户请求列表 │
│ requestIdCounter++ // 43 │
│ pendingRequestsCount++ // 待处理计数+1 │
└────────────────────────────┬────────────────────────────────────────┘
8. 触发事件
7. 触发事件
┌─────────────────────────────────────────────────────────────────────┐
│ 事件记录 │
│ ───────────────────────────────────────────────────────────────── │
│ emit Sell(
│ emit WithdrawRequestCreated(
│ 42, // requestId │
│ msg.sender, // 用户地址 │
│ 5000e18, // YT数量 │
│ 5500e18 // WUSD数量
│ 5500e18, // 应得WUSD数量 │
│ 42 // queueIndex │
│ ) │
└────────────────────────────┬────────────────────────────────────────┘
9. 返回WUSD数量
8. 返回请求ID
┌─────────────────────────────────────────────────────────────────────┐
提款完成 │
请求提交完成 │
│ ───────────────────────────────────────────────────────────────── │
用户最终状态:
│ • YT余额: 5,000 YT (剩余) │
│ • WUSD余额: 5,500 WUSD (获得) │
│ • 收益: 500 WUSD (10%增值) │
返回值: requestId = 42
│ │
Vault最终状态: │
│ • totalSupply: 5,000 YT
│ • totalAssets: 4,500 WUSD (假设无managedAssets)
│ • idleAssets: 4,500 WUSD
用户当前状态:
│ • YT余额: 5,000 YT (已减少)
│ • WUSD余额: 0 WUSD (尚未到账) ⏳
│ • 提现请求: requestId = 42 (排队中)
│ │
返回值: 5,500 WUSD
Vault状态
│ • totalSupply: 5,000 YT (已减少) │
│ • pendingRequestsCount: +1 │
│ • requestIdCounter: 43 │
│ │
│ 📍 下一步: │
│ 用户需要等待Manager或Factory调用processBatchWithdrawals() │
│ 批量处理提现请求后WUSD才会到账 │
│ │
│ 查询请求状态: │
│ • getRequestDetails(42) - 查看请求详情 │
│ • getUserPendingRequests(user) - 查看所有待处理请求 │
│ • getQueueProgress() - 查看队列处理进度 │
└─────────────────────────────────────────────────────────────────────┘
```
### 3.2 第二阶段:批量处理提现请求
```
┌─────────────────────────────────────────────────────────────────────┐
│ Manager/Factory (资产管理方) │
│ 当前状态: │
│ • Vault中有足够的WUSD (基金赎回资金已到账) │
│ • 待处理请求: 150个 │
│ • 准备批量发放WUSD给用户 │
└────────────────────────────┬────────────────────────────────────────┘
│ 1. 查询队列状态(可选)
│ getQueueProgress()
┌─────────────────────────────────────────────────────────────────────┐
│ 查询队列进度 │
│ ───────────────────────────────────────────────────────────────── │
│ function getQueueProgress() returns ( │
│ uint256 currentIndex, // 当前处理到的位置: 100 │
│ uint256 totalRequests, // 总请求数: 250 │
│ uint256 pendingRequests // 待处理数: 150 │
│ ) │
│ │
│ 状态信息: │
│ • 已处理: 100个请求 │
│ • 待处理: 150个请求 │
│ • 处理进度: 100/250 = 40% │
└─────────────────────────────────────────────────────────────────────┘
│ 2. 调用 processBatchWithdrawals(50)
│ 每批处理50个请求
┌─────────────────────────────────────────────────────────────────────┐
│ YTAssetVault.processBatchWithdrawals() │
│ ───────────────────────────────────────────────────────────────── │
│ function processBatchWithdrawals(uint256 _batchSize) │
│ returns (uint256 processedCount, uint256 totalDistributed) │
│ │
│ 参数: │
│ • _batchSize: 50 (本批次最多处理50个) │
│ │
│ 权限检查: │
│ • msg.sender == manager ✓ 或 │
│ • msg.sender == factory ✓ │
│ │
│ 保护机制: │
│ • nonReentrant - 重入保护 │
│ • whenNotPaused - 暂停检查 │
└────────────────────────────┬────────────────────────────────────────┘
│ 3. 验证批次大小
┌─────────────────────────────────────────────────────────────────────┐
│ 参数检查 │
│ ───────────────────────────────────────────────────────────────── │
│ if (_batchSize == 0) revert InvalidBatchSize() │
│ ✓ 50 > 0 通过 │
└────────────────────────────┬────────────────────────────────────────┘
│ 4. 获取可用WUSD并开始循环处理
┌─────────────────────────────────────────────────────────────────────┐
│ 循环处理请求 │
│ ───────────────────────────────────────────────────────────────── │
│ uint256 availableWUSD = vault.balance(WUSD) // 假设: 100,000 WUSD │
│ uint256 processedCount = 0 │
│ uint256 totalDistributed = 0 │
│ │
│ for (i = processedUpToIndex; i < requestIdCounter; i++) { │
│ if (processedCount >= _batchSize) break // 达到批次限制 │
│ │
│ WithdrawRequest storage request = withdrawRequests[i] │
│ │
│ if (request.processed) continue // 跳过已处理的 │
│ │
│ if (availableWUSD >= request.wusdAmount) { │
│ // ✅ 可以处理此请求 │
│ 处理逻辑 ▼ │
│ } else { │
│ // ❌ WUSD不足停止处理 │
│ break │
│ } │
│ } │
└────────────────────────────┬────────────────────────────────────────┘
│ 5. 处理单个请求(循环内)
┌─────────────────────────────────────────────────────────────────────┐
│ 处理请求详细步骤 │
│ ───────────────────────────────────────────────────────────────── │
│ 以 requestId = 42 为例: │
│ │
│ ① 转账WUSD给用户 │
│ IERC20(wusd).safeTransfer(request.user, 5500e18) │
│ • 用户WUSD余额: 0 → 5,500 WUSD ✅ │
│ │
│ ② 标记为已处理 │
│ request.processed = true │
│ │
│ ③ 更新统计数据 │
│ availableWUSD -= 5500e18 // 剩余可用WUSD减少 │
│ totalDistributed += 5500e18 // 累计发放增加 │
│ processedCount++ // 处理计数+1 │
│ pendingRequestsCount-- // 待处理计数-1 │
│ │
│ ④ 触发事件 │
│ emit WithdrawRequestProcessed(42, user, 5500e18) │
│ │
│ 然后继续处理下一个请求... │
└────────────────────────────┬────────────────────────────────────────┘
│ 6. 更新处理进度指针
┌─────────────────────────────────────────────────────────────────────┐
│ 更新processedUpToIndex │
│ ───────────────────────────────────────────────────────────────── │
│ if (processedCount > 0) { │
│ // 找到下一个未处理的位置 │
│ for (i = processedUpToIndex; i < requestIdCounter; i++) { │
│ if (!withdrawRequests[i].processed) { │
│ processedUpToIndex = i │
│ break │
│ } │
│ } │
│ } │
│ │
│ 更新结果: │
│ • processedUpToIndex: 100 → 150 │
└────────────────────────────┬────────────────────────────────────────┘
│ 7. 触发批次处理事件
┌─────────────────────────────────────────────────────────────────────┐
│ 批次处理完成事件 │
│ ───────────────────────────────────────────────────────────────── │
│ emit BatchProcessed( │
│ 100, // startIndex (开始位置) │
│ 150, // endIndex (结束位置) │
│ 50, // processedCount (实际处理数量) │
│ 275000e18 // totalDistributed (总发放WUSD) │
│ ) │
└────────────────────────────┬────────────────────────────────────────┘
│ 8. 返回处理结果
┌─────────────────────────────────────────────────────────────────────┐
│ 批量处理完成 │
│ ───────────────────────────────────────────────────────────────── │
│ 返回值: │
│ • processedCount: 50 (处理了50个请求) │
│ • totalDistributed: 275,000 WUSD (总共发放) │
│ │
│ 更新后的状态: │
│ • pendingRequestsCount: 150 → 100 (还剩100个待处理) │
│ • processedUpToIndex: 100 → 150 │
│ • Vault WUSD余额: 100,000 → (100,000 - 275,000) = -175,000 ❌ │
│ (这里假设vault有足够资金实际会提前检查) │
│ │
│ 用户影响requestId = 42的用户
│ ✅ YT余额: 5,000 YT (已销毁) │
│ ✅ WUSD余额: 5,500 WUSD (已到账!) │
│ ✅ 提现完成可以自由使用WUSD │
│ │
│ 队列状态: │
│ • 已处理: 150/250 = 60% │
│ • 待处理: 100个 │
│ • 可继续调用processBatchWithdrawals()处理剩余请求 │
└─────────────────────────────────────────────────────────────────────┘
```
### 3.3 资金不足时的处理
```
┌─────────────────────────────────────────────────────────────────────┐
│ 场景WUSD资金不足 │
│ ───────────────────────────────────────────────────────────────── │
│ 当前状态: │
│ • Vault中WUSD: 50,000 │
│ • 待处理请求: 100个 │
│ • 前10个请求需要: 60,000 WUSD │
└────────────────────────────┬────────────────────────────────────────┘
│ 调用 processBatchWithdrawals(100)
┌─────────────────────────────────────────────────────────────────────┐
│ 处理过程 │
│ ───────────────────────────────────────────────────────────────── │
│ 循环处理: │
│ • Request 1: 需要5,000 WUSD ✅ 处理成功 (剩余45,000) │
│ • Request 2: 需要5,000 WUSD ✅ 处理成功 (剩余40,000) │
│ • Request 3: 需要5,000 WUSD ✅ 处理成功 (剩余35,000) │
│ • Request 4: 需要5,000 WUSD ✅ 处理成功 (剩余30,000) │
│ • Request 5: 需要5,000 WUSD ✅ 处理成功 (剩余25,000) │
│ • Request 6: 需要5,000 WUSD ✅ 处理成功 (剩余20,000) │
│ • Request 7: 需要5,000 WUSD ✅ 处理成功 (剩余15,000) │
│ • Request 8: 需要5,000 WUSD ✅ 处理成功 (剩余10,000) │
│ • Request 9: 需要5,000 WUSD ✅ 处理成功 (剩余5,000) │
│ • Request 10: 需要5,000 WUSD ✅ 处理成功 (剩余0) │
│ • Request 11: 需要5,000 WUSD ❌ 资金不足,停止处理 │
│ │
│ 处理结果: │
│ • processedCount: 10 (只处理了10个而不是100个) │
│ • totalDistributed: 50,000 WUSD │
│ • 剩余90个请求继续排队 │
│ │
│ ⚠️ 不会revert优雅地停止
└────────────────────────────┬────────────────────────────────────────┘
│ 后续处理
┌─────────────────────────────────────────────────────────────────────┐
│ 等待资金到账 │
│ ───────────────────────────────────────────────────────────────── │
│ Manager操作
│ ① 等待基金赎回下一批资金到账 │
│ ② 调用 depositManagedAssets(100000e18) 充值 │
│ ③ 再次调用 processBatchWithdrawals(100) 继续处理 │
│ │
│ 用户体验: │
│ • Request 1-10的用户: ✅ WUSD已到账 │
│ • Request 11+的用户: ⏳ 继续排队等待 │
│ • 可通过getUserPendingRequests()查询状态 │
└─────────────────────────────────────────────────────────────────────┘
```
@@ -988,6 +1223,11 @@
## 8. 查询信息流程
- 8.1 [查询单个Vault信息](#81-查询单个vault信息)
- 8.2 [通过Factory查询Vault信息](#82-通过factory查询vault信息)
- 8.3 [查询所有Vault列表](#83-查询所有vault列表)
- 8.4 [查询提现请求信息(新增)](#84-查询提现请求信息新增)
### 8.1 查询单个Vault信息
```
@@ -1147,6 +1387,165 @@
└─────────────────────────────────────────────────────────────────────┘
```
### 8.4 查询提现请求信息(新增)
```
┌─────────────────────────────────────────────────────────────────────┐
│ 用户 / 前端应用 │
│ 需求:查询提现请求的状态 │
└────────────────────────────┬────────────────────────────────────────┘
├──────────────┬──────────────┬──────────────┐
│ │ │ │
方式1: 查询单个请求 方式2: 查询用户请求 方式3: 查询队列进度
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ getRequestDetails │ │getUserRequestIds │ │ getQueueProgress │
│ (requestId) │ │(userAddress) │ │() │
└──────────┬──────────┘ └──────────┬──────────┘ └──────────┬──────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────┐
│ 方式1: 查询单个请求详情 │
│ ───────────────────────────────────────────────────────────────── │
│ function getRequestDetails(uint256 _requestId) │
│ returns (WithdrawRequest memory) │
│ │
│ 输入: requestId = 42 │
│ │
│ 返回: │
│ { │
│ user: 0xUser123..., │
│ ytAmount: 5000e18, // 提现的YT数量 │
│ wusdAmount: 5500e18, // 应得WUSD数量 │
│ requestTime: 1739692800, // 请求时间戳 │
│ queueIndex: 42, // 队列位置 │
│ processed: false // 是否已处理 │
│ } │
│ │
│ 前端展示: │
│ ┌────────────────────────────────────────┐ │
│ │ 📋 提现请求 #42 │ │
│ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ │
│ │ 状态: ⏳ 排队中 │ │
│ │ YT数量: 5,000 YT │ │
│ │ 应得WUSD: 5,500 WUSD │ │
│ │ 提交时间: 2025-02-16 10:00:00 │ │
│ │ 队列位置: 第42位 │ │
│ └────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 方式2: 查询用户所有请求 │
│ ───────────────────────────────────────────────────────────────── │
│ 步骤1: 获取用户的请求ID列表 │
│ function getUserRequestIds(address _user) │
│ returns (uint256[] memory) │
│ │
│ 返回: [15, 42, 68] (用户有3个请求) │
│ │
│ 步骤2: 获取用户待处理的请求 │
│ function getUserPendingRequests(address _user) │
│ returns (WithdrawRequest[] memory) │
│ │
│ 返回: 只返回 processed = false 的请求 │
│ [ │
│ { │
│ user: 0xUser123..., │
│ ytAmount: 5000e18, │
│ wusdAmount: 5500e18, │
│ requestTime: 1739692800, │
│ queueIndex: 42, │
│ processed: false │
│ } │
│ ] │
│ │
│ 前端展示: │
│ ┌────────────────────────────────────────┐ │
│ │ 👤 我的提现请求 │ │
│ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ │
│ │ ✅ 请求#15 1,000 YT → 1,100 WUSD │ │
│ │ 状态: 已完成 2025-02-10 │ │
│ │ │ │
│ │ ⏳ 请求#42 5,000 YT → 5,500 WUSD │ │
│ │ 状态: 排队中 队列第42位 │ │
│ │ 提交于: 2025-02-16 10:00 │ │
│ │ │ │
│ │ ⏳ 请求#68 3,000 YT → 3,300 WUSD │ │
│ │ 状态: 排队中 队列第68位 │ │
│ │ 提交于: 2025-02-16 15:30 │ │
│ └────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 方式3: 查询队列处理进度 │
│ ───────────────────────────────────────────────────────────────── │
│ function getQueueProgress() returns ( │
│ uint256 currentIndex, │
│ uint256 totalRequests, │
│ uint256 pendingRequests │
│ ) │
│ │
│ 返回示例: │
│ { │
│ currentIndex: 38, // 当前处理到第38个 │
│ totalRequests: 150, // 总共150个请求 │
│ pendingRequests: 112 // 还有112个待处理 │
│ } │
│ │
│ 衍生计算: │
│ • 已处理: 38个 │
│ • 处理进度: 38/150 = 25.3% │
│ • 待处理: 112个 │
│ │
│ 前端展示: │
│ ┌────────────────────────────────────────┐ │
│ │ 🔄 提现处理进度 │ │
│ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ │
│ │ [████████░░░░░░░░░░░░░░░] 25.3% │ │
│ │ │ │
│ │ 已处理: 38 / 150 │ │
│ │ 待处理: 112个请求 │ │
│ │ 当前位置: 第38位 │ │
│ │ │ │
│ │ 💡 提示: │ │
│ │ 您的请求#42在第42位前面还有4个请求 │ │
│ │ 预计等待时间: 约5分钟 │ │
│ └────────────────────────────────────────┘ │
│ │
│ 优势: │
│ ✓ 实时查看全局处理进度 │
│ ✓ 估算自己的等待时间 │
│ ✓ O(1)查询gas友好 │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 方式4: 查询待处理总数 │
│ ───────────────────────────────────────────────────────────────── │
│ function getPendingRequestsCount() returns (uint256) │
│ │
│ 返回: 112 (还有112个待处理) │
│ │
│ 特点: │
│ • O(1)复杂度,实时维护的计数器 │
│ • 不需要循环避免gas爆炸 │
│ • 可用于前端显示统计信息 │
│ │
│ 前端展示: │
│ ┌────────────────────────────────────────┐ │
│ │ 📊 系统统计 │ │
│ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ │
│ │ 待处理提现: 112个 │ │
│ │ Vault余额: 50,000 WUSD │ │
│ │ 预计可处理: 约45个请求 │ │
│ └────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 附录:重要概念说明
@@ -1308,6 +1707,104 @@ UUPS (Universal Upgradeable Proxy Standard):
• 保留__gap数组用于未来扩展
```
### H. 两阶段提现机制Withdraw Queue
```
两阶段提现机制:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
解决资金分批到账的提现排队问题
设计目的:
• 基金赎回需要时间,资金不会一次性到账
• 按用户提现请求时间先后排队FIFO
• 资金到账后由后端统一批量发放
阶段一:用户提交请求
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
用户调用 withdrawYT(_ytAmount)
执行内容:
1. 验证参数和赎回时间
2. 立即销毁用户的YT代币
3. 计算应得WUSD数量锁定当前价格
4. 创建WithdrawRequest记录
5. 返回requestId供用户查询
关键特点:
✓ YT立即销毁防止重复提现
✓ WUSD暂不发放等待批量处理
✓ 金额已锁定(不受后续价格变化影响)
✓ 即使vault资金不足也可提交请求
阶段二:批量处理发放
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Manager/Factory调用 processBatchWithdrawals(_batchSize)
执行内容:
1. 获取vault当前可用WUSD
2. 按requestId顺序FIFO处理请求
3. 依次给用户转账WUSD
4. 标记请求为已处理
5. 资金不足时自动停止
关键特点:
✓ FIFO严格保证先提交先处理
✓ 支持分批处理避免gas超限
✓ 断点续传(记录处理进度)
✓ 资金不足不会revert优雅停止
数据结构:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
struct WithdrawRequest {
address user; // 用户地址
uint256 ytAmount; // YT数量
uint256 wusdAmount; // 应得WUSD数量锁定
uint256 requestTime; // 请求时间
uint256 queueIndex; // 队列位置
bool processed; // 是否已处理
}
状态变量:
• withdrawRequests[requestId] - 所有请求记录
• userRequestIds[user] - 用户的请求ID列表
• requestIdCounter - 请求ID计数器递增
• processedUpToIndex - 已处理到的位置
• pendingRequestsCount - 待处理请求数O(1)查询)
查询功能:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• getUserRequestIds(user) - 用户的所有请求ID
• getRequestDetails(requestId) - 请求详情
• getUserPendingRequests(user) - 用户待处理请求
• getPendingRequestsCount() - 待处理总数
• getQueueProgress() - 队列处理进度
优势:
✓ 解决资金分批到账问题
✓ 公平的FIFO排队机制
✓ Gas优化计数器避免循环
✓ 用户体验好(可查询状态)
✓ 管理灵活(支持分批处理)
使用场景:
1. 基金赎回需要T+1或T+N到账
2. 资金分多批次回流
3. 避免流动性挤兑
4. 统一管理提现流程
风险控制:
⚠️ YT已销毁但WUSD未到账的风险
→ 解决Manager有责任及时处理请求
→ 解决Factory可以代为处理
⚠️ 用户长时间等待的风险
→ 解决:可查询队列进度
→ 解决:前端显示预计等待时间
⚠️ 价格锁定可能错失市场波动
→ 解决:这是设计特性,确保公平性
```
---
## 9. 暂停功能流程

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"id":"01e6a324b39f7016","source_id_to_path":{"0":"contracts/interfaces/IUSDY.sol","1":"contracts/interfaces/IYTLPToken.sol","2":"contracts/interfaces/IYTPoolManager.sol","3":"contracts/interfaces/IYTPriceFeed.sol","4":"contracts/interfaces/IYTToken.sol","5":"contracts/interfaces/IYTVault.sol","6":"contracts/vault/YTAssetFactory.sol","7":"contracts/vault/YTAssetVault.sol","8":"contracts/ytLending/Configurator.sol","9":"contracts/ytLending/ConfiguratorStorage.sol","10":"contracts/ytLending/Lending.sol","11":"contracts/ytLending/LendingConfiguration.sol","12":"contracts/ytLending/LendingFactory.sol","13":"contracts/ytLending/LendingMath.sol","14":"contracts/ytLending/LendingStorage.sol","15":"contracts/ytLending/interfaces/ILending.sol","16":"contracts/ytLending/interfaces/IPriceFeed.sol","17":"contracts/ytLp/core/YTPoolManager.sol","18":"contracts/ytLp/core/YTPriceFeed.sol","19":"contracts/ytLp/core/YTRewardRouter.sol","20":"contracts/ytLp/core/YTVault.sol","21":"contracts/ytLp/tokens/USDY.sol","22":"contracts/ytLp/tokens/WUSD.sol","23":"contracts/ytLp/tokens/YTLPToken.sol","24":"lib/forge-std/src/Base.sol","25":"lib/forge-std/src/StdAssertions.sol","26":"lib/forge-std/src/StdChains.sol","27":"lib/forge-std/src/StdCheats.sol","28":"lib/forge-std/src/StdConstants.sol","29":"lib/forge-std/src/StdError.sol","30":"lib/forge-std/src/StdInvariant.sol","31":"lib/forge-std/src/StdJson.sol","32":"lib/forge-std/src/StdMath.sol","33":"lib/forge-std/src/StdStorage.sol","34":"lib/forge-std/src/StdStyle.sol","35":"lib/forge-std/src/StdToml.sol","36":"lib/forge-std/src/StdUtils.sol","37":"lib/forge-std/src/Test.sol","38":"lib/forge-std/src/Vm.sol","39":"lib/forge-std/src/console.sol","40":"lib/forge-std/src/console2.sol","41":"lib/forge-std/src/interfaces/IMulticall3.sol","42":"lib/forge-std/src/safeconsole.sol","43":"node_modules/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol","44":"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol","45":"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol","46":"node_modules/@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol","47":"node_modules/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol","48":"node_modules/@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol","49":"node_modules/@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol","50":"node_modules/@openzeppelin/contracts/access/Ownable.sol","51":"node_modules/@openzeppelin/contracts/interfaces/IERC1363.sol","52":"node_modules/@openzeppelin/contracts/interfaces/IERC165.sol","53":"node_modules/@openzeppelin/contracts/interfaces/IERC1967.sol","54":"node_modules/@openzeppelin/contracts/interfaces/IERC20.sol","55":"node_modules/@openzeppelin/contracts/interfaces/draft-IERC1822.sol","56":"node_modules/@openzeppelin/contracts/interfaces/draft-IERC6093.sol","57":"node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol","58":"node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol","59":"node_modules/@openzeppelin/contracts/proxy/Proxy.sol","60":"node_modules/@openzeppelin/contracts/proxy/beacon/IBeacon.sol","61":"node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol","62":"node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol","63":"node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol","64":"node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol","65":"node_modules/@openzeppelin/contracts/utils/Address.sol","66":"node_modules/@openzeppelin/contracts/utils/Context.sol","67":"node_modules/@openzeppelin/contracts/utils/Errors.sol","68":"node_modules/@openzeppelin/contracts/utils/StorageSlot.sol","69":"node_modules/@openzeppelin/contracts/utils/introspection/IERC165.sol","70":"test/Vault.t.sol","71":"test/YTLp.t.sol","72":"test/YtLending.t.sol"},"language":"Solidity"}

View File

@@ -1 +0,0 @@
{"id":"2ad4f0a8394548ce","source_id_to_path":{"0":"contracts/interfaces/IUSDY.sol","1":"contracts/interfaces/IYTLPToken.sol","2":"contracts/interfaces/IYTPoolManager.sol","3":"contracts/interfaces/IYTPriceFeed.sol","4":"contracts/interfaces/IYTToken.sol","5":"contracts/interfaces/IYTVault.sol","6":"contracts/vault/YTAssetFactory.sol","7":"contracts/vault/YTAssetVault.sol","8":"contracts/ytLp/core/YTPoolManager.sol","9":"contracts/ytLp/core/YTPriceFeed.sol","10":"contracts/ytLp/core/YTRewardRouter.sol","11":"contracts/ytLp/core/YTVault.sol","12":"contracts/ytLp/tokens/USDY.sol","13":"contracts/ytLp/tokens/WUSD.sol","14":"contracts/ytLp/tokens/YTLPToken.sol","15":"lib/forge-std/src/Base.sol","16":"lib/forge-std/src/StdAssertions.sol","17":"lib/forge-std/src/StdChains.sol","18":"lib/forge-std/src/StdCheats.sol","19":"lib/forge-std/src/StdConstants.sol","20":"lib/forge-std/src/StdError.sol","21":"lib/forge-std/src/StdInvariant.sol","22":"lib/forge-std/src/StdJson.sol","23":"lib/forge-std/src/StdMath.sol","24":"lib/forge-std/src/StdStorage.sol","25":"lib/forge-std/src/StdStyle.sol","26":"lib/forge-std/src/StdToml.sol","27":"lib/forge-std/src/StdUtils.sol","28":"lib/forge-std/src/Test.sol","29":"lib/forge-std/src/Vm.sol","30":"lib/forge-std/src/console.sol","31":"lib/forge-std/src/console2.sol","32":"lib/forge-std/src/interfaces/IMulticall3.sol","33":"lib/forge-std/src/safeconsole.sol","34":"node_modules/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol","35":"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol","36":"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol","37":"node_modules/@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol","38":"node_modules/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol","39":"node_modules/@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol","40":"node_modules/@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol","41":"node_modules/@openzeppelin/contracts/interfaces/IERC1363.sol","42":"node_modules/@openzeppelin/contracts/interfaces/IERC165.sol","43":"node_modules/@openzeppelin/contracts/interfaces/IERC1967.sol","44":"node_modules/@openzeppelin/contracts/interfaces/IERC20.sol","45":"node_modules/@openzeppelin/contracts/interfaces/draft-IERC1822.sol","46":"node_modules/@openzeppelin/contracts/interfaces/draft-IERC6093.sol","47":"node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol","48":"node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol","49":"node_modules/@openzeppelin/contracts/proxy/Proxy.sol","50":"node_modules/@openzeppelin/contracts/proxy/beacon/IBeacon.sol","51":"node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol","52":"node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol","53":"node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol","54":"node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol","55":"node_modules/@openzeppelin/contracts/utils/Address.sol","56":"node_modules/@openzeppelin/contracts/utils/Context.sol","57":"node_modules/@openzeppelin/contracts/utils/Errors.sol","58":"node_modules/@openzeppelin/contracts/utils/StorageSlot.sol","59":"node_modules/@openzeppelin/contracts/utils/introspection/IERC165.sol","60":"test/Vault.t.sol","61":"test/YTLp.t.sol"},"language":"Solidity"}

View File

@@ -0,0 +1 @@
{"id":"945e0d5b9cb1cbc7","source_id_to_path":{"0":"contracts/vault/YTAssetFactory.sol","1":"contracts/vault/YTAssetVault.sol","2":"contracts/ytLending/Configurator.sol","3":"contracts/ytLending/ConfiguratorStorage.sol","4":"contracts/ytLending/Lending.sol","5":"contracts/ytLending/LendingConfiguration.sol","6":"contracts/ytLending/LendingFactory.sol","7":"contracts/ytLending/LendingMath.sol","8":"contracts/ytLending/LendingStorage.sol","9":"contracts/ytLending/interfaces/ILending.sol","10":"contracts/ytLending/interfaces/IPriceFeed.sol","11":"lib/forge-std/src/Base.sol","12":"lib/forge-std/src/StdAssertions.sol","13":"lib/forge-std/src/StdChains.sol","14":"lib/forge-std/src/StdCheats.sol","15":"lib/forge-std/src/StdConstants.sol","16":"lib/forge-std/src/StdError.sol","17":"lib/forge-std/src/StdInvariant.sol","18":"lib/forge-std/src/StdJson.sol","19":"lib/forge-std/src/StdMath.sol","20":"lib/forge-std/src/StdStorage.sol","21":"lib/forge-std/src/StdStyle.sol","22":"lib/forge-std/src/StdToml.sol","23":"lib/forge-std/src/StdUtils.sol","24":"lib/forge-std/src/Test.sol","25":"lib/forge-std/src/Vm.sol","26":"lib/forge-std/src/console.sol","27":"lib/forge-std/src/console2.sol","28":"lib/forge-std/src/interfaces/IMulticall3.sol","29":"lib/forge-std/src/safeconsole.sol","30":"node_modules/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol","31":"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol","32":"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol","33":"node_modules/@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol","34":"node_modules/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol","35":"node_modules/@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol","36":"node_modules/@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol","37":"node_modules/@openzeppelin/contracts/access/Ownable.sol","38":"node_modules/@openzeppelin/contracts/interfaces/IERC1363.sol","39":"node_modules/@openzeppelin/contracts/interfaces/IERC165.sol","40":"node_modules/@openzeppelin/contracts/interfaces/IERC1967.sol","41":"node_modules/@openzeppelin/contracts/interfaces/IERC20.sol","42":"node_modules/@openzeppelin/contracts/interfaces/draft-IERC1822.sol","43":"node_modules/@openzeppelin/contracts/interfaces/draft-IERC6093.sol","44":"node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol","45":"node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol","46":"node_modules/@openzeppelin/contracts/proxy/Proxy.sol","47":"node_modules/@openzeppelin/contracts/proxy/beacon/IBeacon.sol","48":"node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol","49":"node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol","50":"node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol","51":"node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol","52":"node_modules/@openzeppelin/contracts/utils/Address.sol","53":"node_modules/@openzeppelin/contracts/utils/Context.sol","54":"node_modules/@openzeppelin/contracts/utils/Errors.sol","55":"node_modules/@openzeppelin/contracts/utils/StorageSlot.sol","56":"node_modules/@openzeppelin/contracts/utils/introspection/IERC165.sol","57":"test/Vault.t.sol"},"language":"Solidity"}

View File

@@ -1 +0,0 @@
{"id":"d40601d3822c5f89","source_id_to_path":{"0":"contracts/ytLending/Configurator.sol","1":"contracts/ytLending/ConfiguratorStorage.sol","2":"contracts/ytLending/Lending.sol","3":"contracts/ytLending/LendingConfiguration.sol","4":"contracts/ytLending/LendingFactory.sol","5":"contracts/ytLending/LendingMath.sol","6":"contracts/ytLending/LendingStorage.sol","7":"contracts/ytLending/interfaces/ILending.sol","8":"contracts/ytLending/interfaces/IPriceFeed.sol","9":"lib/forge-std/src/Base.sol","10":"lib/forge-std/src/StdAssertions.sol","11":"lib/forge-std/src/StdChains.sol","12":"lib/forge-std/src/StdCheats.sol","13":"lib/forge-std/src/StdConstants.sol","14":"lib/forge-std/src/StdError.sol","15":"lib/forge-std/src/StdInvariant.sol","16":"lib/forge-std/src/StdJson.sol","17":"lib/forge-std/src/StdMath.sol","18":"lib/forge-std/src/StdStorage.sol","19":"lib/forge-std/src/StdStyle.sol","20":"lib/forge-std/src/StdToml.sol","21":"lib/forge-std/src/StdUtils.sol","22":"lib/forge-std/src/Test.sol","23":"lib/forge-std/src/Vm.sol","24":"lib/forge-std/src/console.sol","25":"lib/forge-std/src/console2.sol","26":"lib/forge-std/src/interfaces/IMulticall3.sol","27":"lib/forge-std/src/safeconsole.sol","28":"node_modules/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol","29":"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol","30":"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol","31":"node_modules/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol","32":"node_modules/@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol","33":"node_modules/@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol","34":"node_modules/@openzeppelin/contracts/access/Ownable.sol","35":"node_modules/@openzeppelin/contracts/interfaces/IERC1363.sol","36":"node_modules/@openzeppelin/contracts/interfaces/IERC165.sol","37":"node_modules/@openzeppelin/contracts/interfaces/IERC1967.sol","38":"node_modules/@openzeppelin/contracts/interfaces/IERC20.sol","39":"node_modules/@openzeppelin/contracts/interfaces/draft-IERC1822.sol","40":"node_modules/@openzeppelin/contracts/interfaces/draft-IERC6093.sol","41":"node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol","42":"node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol","43":"node_modules/@openzeppelin/contracts/proxy/Proxy.sol","44":"node_modules/@openzeppelin/contracts/proxy/beacon/IBeacon.sol","45":"node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol","46":"node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol","47":"node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol","48":"node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol","49":"node_modules/@openzeppelin/contracts/utils/Address.sol","50":"node_modules/@openzeppelin/contracts/utils/Context.sol","51":"node_modules/@openzeppelin/contracts/utils/Errors.sol","52":"node_modules/@openzeppelin/contracts/utils/StorageSlot.sol","53":"node_modules/@openzeppelin/contracts/utils/introspection/IERC165.sol","54":"test/YtLending.t.sol"},"language":"Solidity"}

View File

@@ -0,0 +1 @@
{"id":"e5c4e55070c0fa6b","source_id_to_path":{"0":"contracts/vault/YTAssetFactory.sol","1":"contracts/vault/YTAssetVault.sol","2":"lib/forge-std/src/Base.sol","3":"lib/forge-std/src/StdAssertions.sol","4":"lib/forge-std/src/StdChains.sol","5":"lib/forge-std/src/StdCheats.sol","6":"lib/forge-std/src/StdConstants.sol","7":"lib/forge-std/src/StdError.sol","8":"lib/forge-std/src/StdInvariant.sol","9":"lib/forge-std/src/StdJson.sol","10":"lib/forge-std/src/StdMath.sol","11":"lib/forge-std/src/StdStorage.sol","12":"lib/forge-std/src/StdStyle.sol","13":"lib/forge-std/src/StdToml.sol","14":"lib/forge-std/src/StdUtils.sol","15":"lib/forge-std/src/Test.sol","16":"lib/forge-std/src/Vm.sol","17":"lib/forge-std/src/console.sol","18":"lib/forge-std/src/console2.sol","19":"lib/forge-std/src/interfaces/IMulticall3.sol","20":"lib/forge-std/src/safeconsole.sol","21":"node_modules/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol","22":"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol","23":"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol","24":"node_modules/@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol","25":"node_modules/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol","26":"node_modules/@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol","27":"node_modules/@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol","28":"node_modules/@openzeppelin/contracts/interfaces/IERC1363.sol","29":"node_modules/@openzeppelin/contracts/interfaces/IERC165.sol","30":"node_modules/@openzeppelin/contracts/interfaces/IERC1967.sol","31":"node_modules/@openzeppelin/contracts/interfaces/IERC20.sol","32":"node_modules/@openzeppelin/contracts/interfaces/draft-IERC1822.sol","33":"node_modules/@openzeppelin/contracts/interfaces/draft-IERC6093.sol","34":"node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol","35":"node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol","36":"node_modules/@openzeppelin/contracts/proxy/Proxy.sol","37":"node_modules/@openzeppelin/contracts/proxy/beacon/IBeacon.sol","38":"node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol","39":"node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol","40":"node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol","41":"node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol","42":"node_modules/@openzeppelin/contracts/utils/Address.sol","43":"node_modules/@openzeppelin/contracts/utils/Context.sol","44":"node_modules/@openzeppelin/contracts/utils/Errors.sol","45":"node_modules/@openzeppelin/contracts/utils/StorageSlot.sol","46":"node_modules/@openzeppelin/contracts/utils/introspection/IERC165.sol","47":"test/Vault.t.sol"},"language":"Solidity"}

View File

@@ -0,0 +1,219 @@
import { ethers, upgrades } from "hardhat";
import * as fs from "fs";
import * as path from "path";
/**
* 升级 YTAssetVault 合约
*
* 升级步骤:
* 1. 部署新的 YTAssetVault 实现合约
* 2. 通过 Factory 更新 vaultImplementation 地址
* 3. 可选:批量升级现有的 Vault 代理合约
*
* 注意:
* - 升级后,新创建的 vault 将使用新实现
* - 已存在的 vault 需要手动升级才能使用新功能
*/
async function main() {
const [deployer] = await ethers.getSigners();
console.log("\n==========================================");
console.log("🔄 升级 YTAssetVault 系统");
console.log("==========================================");
console.log("升级账户:", deployer.address);
console.log("账户余额:", ethers.formatEther(await ethers.provider.getBalance(deployer.address)), "ETH\n");
// ========== 读取部署信息 ==========
const deploymentsPath = path.join(__dirname, "../../deployments-vault-system.json");
if (!fs.existsSync(deploymentsPath)) {
throw new Error("未找到部署信息文件 deployments-vault-system.json请先运行部署脚本");
}
const deployments = JSON.parse(fs.readFileSync(deploymentsPath, "utf-8"));
if (!deployments.contracts?.YTAssetVault?.implementation) {
throw new Error("未找到 YTAssetVault 部署信息");
}
console.log("📋 当前部署的合约:");
console.log(" YTAssetVault Implementation (旧):", deployments.contracts.YTAssetVault.implementation);
console.log(" YTAssetFactory Proxy: ", deployments.contracts.YTAssetFactory.proxy);
console.log(" YTAssetFactory Implementation: ", deployments.contracts.YTAssetFactory.implementation);
console.log(" 已创建的 Vaults 数量: ", deployments.vaults?.length || 0);
console.log("");
// ========== Phase 1: 部署新的 YTAssetVault 实现 ==========
console.log("🔄 Phase 1: 部署新的 YTAssetVault 实现合约");
console.log(" 编译新的 YTAssetVault 合约...");
const YTAssetVaultV2 = await ethers.getContractFactory("YTAssetVault");
console.log(" 部署新实现合约...");
const newVaultImpl = await YTAssetVaultV2.deploy();
await newVaultImpl.waitForDeployment();
const newVaultImplAddress = await newVaultImpl.getAddress();
console.log(" ✅ 新 YTAssetVault Implementation:", newVaultImplAddress);
console.log("");
// ========== Phase 2: 通过 Factory 更新实现地址 ==========
console.log("🔄 Phase 2: 通过 Factory 更新 vaultImplementation");
const factory = await ethers.getContractAt(
"YTAssetFactory",
deployments.contracts.YTAssetFactory.proxy
);
console.log(" 当前 Factory 的 vaultImplementation:", await factory.vaultImplementation());
console.log(" 准备更新为新实现地址...");
const updateTx = await factory.setVaultImplementation(newVaultImplAddress);
await updateTx.wait();
console.log(" ✅ Factory vaultImplementation 已更新!");
console.log(" 新地址:", await factory.vaultImplementation());
console.log("");
// ========== Phase 3: 升级现有的 Vault 代理(可选) ==========
console.log("🔄 Phase 3: 升级现有的 Vault 代理合约");
const existingVaults = deployments.vaults || [];
if (existingVaults.length === 0) {
console.log(" ⚠️ 没有已部署的 Vault跳过此步骤");
} else {
console.log(` 发现 ${existingVaults.length} 个已部署的 Vault\n`);
// 询问是否升级现有 Vault在实际使用中可以配置
const UPGRADE_EXISTING_VAULTS = true; // 设置为 true 自动升级所有 vault
const VAULTS_TO_UPGRADE: number[] = [0, 1, 2]; // 可以指定要升级的 vault 索引,如 [0, 1]
if (UPGRADE_EXISTING_VAULTS) {
console.log(" 📝 准备升级现有的 Vault 代理合约...\n");
const vaultsToProcess = VAULTS_TO_UPGRADE.length > 0
? VAULTS_TO_UPGRADE
: existingVaults.map((_: any, idx: number) => idx);
for (const idx of vaultsToProcess) {
const vaultInfo = existingVaults[idx];
if (!vaultInfo) {
console.log(` ⚠️ 索引 ${idx} 无效,跳过`);
continue;
}
console.log(` [${idx + 1}/${vaultsToProcess.length}] 升级 ${vaultInfo.symbol} (${vaultInfo.address})`);
try {
// 通过 Factory 调用 upgradeVault
const upgradeTx = await factory.upgradeVault(
vaultInfo.address,
newVaultImplAddress
);
await upgradeTx.wait();
// 验证升级
const vault = await ethers.getContractAt("YTAssetVault", vaultInfo.address);
const currentImpl = await upgrades.erc1967.getImplementationAddress(vaultInfo.address);
if (currentImpl.toLowerCase() === newVaultImplAddress.toLowerCase()) {
console.log(` ✅ 升级成功!新实现: ${currentImpl}`);
// 验证新功能(检查是否有排队提现机制)
try {
const pendingCount = await vault.pendingRequestsCount();
console.log(` ✅ 新功能验证通过pendingRequestsCount: ${pendingCount}`);
} catch (e) {
console.log(` ⚠️ 新功能验证失败,可能升级未完全生效`);
}
// 更新部署信息中的实现地址
vaultInfo.implementationAddress = currentImpl;
vaultInfo.lastUpgraded = new Date().toISOString();
} else {
console.log(` ⚠️ 升级可能未成功,当前实现: ${currentImpl}`);
}
} catch (error: any) {
console.log(` ❌ 升级失败: ${error.message}`);
}
console.log("");
}
} else {
console.log(" 配置为不自动升级现有 Vault");
console.log(" 💡 提示:可以稍后通过 Factory.upgradeVault() 手动升级\n");
}
}
// ========== 保存更新的部署信息 ==========
// 保存旧的实现地址作为历史记录
if (!deployments.upgradeHistory) {
deployments.upgradeHistory = [];
}
deployments.upgradeHistory.push({
timestamp: new Date().toISOString(),
oldImplementation: deployments.contracts.YTAssetVault.implementation,
newImplementation: newVaultImplAddress,
upgrader: deployer.address
});
// 更新当前实现地址
deployments.contracts.YTAssetVault.implementation = newVaultImplAddress;
deployments.lastUpdate = new Date().toISOString();
fs.writeFileSync(deploymentsPath, JSON.stringify(deployments, null, 2));
console.log("💾 升级信息已保存到:", deploymentsPath);
// ========== 升级总结 ==========
console.log("\n🎉 升级总结:");
console.log("=====================================");
console.log("旧 YTAssetVault Implementation:");
console.log(" ", deployments.upgradeHistory[deployments.upgradeHistory.length - 1].oldImplementation);
console.log("");
console.log("新 YTAssetVault Implementation:");
console.log(" ", newVaultImplAddress);
console.log("");
console.log("Factory Proxy (不变):");
console.log(" ", deployments.contracts.YTAssetFactory.proxy);
console.log("");
console.log("已升级的 Vaults:");
if (existingVaults.length > 0) {
existingVaults.forEach((v: any, idx: number) => {
if (v.lastUpgraded) {
console.log(` ✅ [${idx}] ${v.symbol}: ${v.address}`);
} else {
console.log(` ⏸️ [${idx}] ${v.symbol}: ${v.address} (未升级)`);
}
});
} else {
console.log(" (无)");
}
console.log("=====================================\n");
console.log("✅ 升级完成!");
console.log("");
console.log("📌 重要提示:");
console.log(" 1. Factory 已更新为新实现,新创建的 vault 将使用新版本");
console.log(" 2. 已升级的 vault 代理地址不变,状态数据已保留");
console.log(" 3. 新增功能:");
console.log(" • 两阶段提现机制(排队领取)");
console.log(" • WithdrawRequest 请求记录");
console.log(" • processBatchWithdrawals 批量处理");
console.log(" • 多个查询函数(请求详情、队列进度等)");
console.log(" 4. 如有未升级的 vault可通过以下方式升级");
console.log(" factory.upgradeVault(vaultAddress, newImplementation)");
console.log("");
console.log("📝 下一步:");
console.log(" 1. 在测试环境验证新功能");
console.log(" 2. 测试 withdrawYT 的排队机制");
console.log(" 3. 测试 processBatchWithdrawals 的批量处理");
console.log(" 4. 确认所有查询函数工作正常");
console.log(" 5. 主网升级前务必充分测试\n");
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});

View File

@@ -0,0 +1,182 @@
import { ethers, upgrades } from "hardhat";
import * as fs from "fs";
import * as path from "path";
/**
* 验证 YTAssetVault 升级结果
*
* 功能:
* 1. 检查 Factory 的实现地址是否已更新
* 2. 验证现有 Vault 的实现地址
* 3. 测试新功能是否可用
*/
async function main() {
console.log("\n==========================================");
console.log("🔍 验证 YTAssetVault 升级结果");
console.log("==========================================\n");
// ========== 读取部署信息 ==========
const deploymentsPath = path.join(__dirname, "../../deployments-vault-system.json");
if (!fs.existsSync(deploymentsPath)) {
throw new Error("未找到部署信息文件");
}
const deployments = JSON.parse(fs.readFileSync(deploymentsPath, "utf-8"));
const factory = await ethers.getContractAt(
"YTAssetFactory",
deployments.contracts.YTAssetFactory.proxy
);
// ========== 验证 Factory ==========
console.log("📋 验证 Factory 配置");
console.log("=====================================");
const currentImplInFactory = await factory.vaultImplementation();
const expectedImpl = deployments.contracts.YTAssetVault.implementation;
console.log("Factory Proxy: ", deployments.contracts.YTAssetFactory.proxy);
console.log("当前 vaultImplementation:", currentImplInFactory);
console.log("配置文件中的实现: ", expectedImpl);
if (currentImplInFactory.toLowerCase() === expectedImpl.toLowerCase()) {
console.log("✅ Factory 配置正确!\n");
} else {
console.log("❌ Factory 配置不匹配!\n");
}
// ========== 验证已部署的 Vaults ==========
const vaults = deployments.vaults || [];
if (vaults.length === 0) {
console.log(" 没有已部署的 Vault\n");
return;
}
console.log("📋 验证已部署的 Vaults");
console.log("=====================================");
console.log(`发现 ${vaults.length} 个 Vault\n`);
const results: any[] = [];
for (let i = 0; i < vaults.length; i++) {
const vaultInfo = vaults[i];
console.log(`[${i + 1}/${vaults.length}] 检查 ${vaultInfo.symbol} (${vaultInfo.address})`);
try {
// 获取实现地址
const implAddress = await upgrades.erc1967.getImplementationAddress(vaultInfo.address);
const isUpgraded = implAddress.toLowerCase() === expectedImpl.toLowerCase();
console.log(` 实现地址: ${implAddress}`);
console.log(` 状态: ${isUpgraded ? '✅ 已升级' : '⏸️ 未升级'}`);
// 如果已升级,测试新功能
if (isUpgraded) {
const vault = await ethers.getContractAt("YTAssetVault", vaultInfo.address);
try {
// 测试新增的状态变量
const pendingCount = await vault.pendingRequestsCount();
const requestIdCounter = await vault.requestIdCounter();
const processedUpToIndex = await vault.processedUpToIndex();
console.log(` 新功能验证:`);
console.log(` - pendingRequestsCount: ${pendingCount}`);
console.log(` - requestIdCounter: ${requestIdCounter}`);
console.log(` - processedUpToIndex: ${processedUpToIndex}`);
// 测试新增的查询函数
const queueProgress = await vault.getQueueProgress();
console.log(` - 队列进度: ${queueProgress[0]}/${queueProgress[1]} (待处理: ${queueProgress[2]})`);
console.log(` ✅ 新功能工作正常`);
results.push({
index: i,
symbol: vaultInfo.symbol,
address: vaultInfo.address,
upgraded: true,
functional: true
});
} catch (error: any) {
console.log(` ⚠️ 新功能测试失败: ${error.message}`);
results.push({
index: i,
symbol: vaultInfo.symbol,
address: vaultInfo.address,
upgraded: true,
functional: false,
error: error.message
});
}
} else {
results.push({
index: i,
symbol: vaultInfo.symbol,
address: vaultInfo.address,
upgraded: false,
functional: false
});
}
} catch (error: any) {
console.log(` ❌ 检查失败: ${error.message}`);
results.push({
index: i,
symbol: vaultInfo.symbol,
address: vaultInfo.address,
upgraded: false,
functional: false,
error: error.message
});
}
console.log("");
}
// ========== 验证总结 ==========
console.log("📊 验证总结");
console.log("=====================================");
const upgraded = results.filter(r => r.upgraded);
const functional = results.filter(r => r.functional);
const needsUpgrade = results.filter(r => !r.upgraded);
console.log(`总 Vaults 数量: ${results.length}`);
console.log(`已升级: ${upgraded.length}`);
console.log(`功能正常: ${functional.length}`);
console.log(`待升级: ${needsUpgrade.length} ${needsUpgrade.length > 0 ? '⏸️' : ''}`);
console.log("");
if (needsUpgrade.length > 0) {
console.log("⏸️ 待升级的 Vaults:");
needsUpgrade.forEach(v => {
console.log(` [${v.index}] ${v.symbol}: ${v.address}`);
});
console.log("");
console.log("💡 升级命令:");
console.log(` factory.upgradeVault("vaultAddress", "${expectedImpl}")`);
console.log("");
}
// ========== 升级历史 ==========
if (deployments.upgradeHistory && deployments.upgradeHistory.length > 0) {
console.log("📜 升级历史");
console.log("=====================================");
deployments.upgradeHistory.forEach((h: any, idx: number) => {
console.log(`[${idx + 1}] ${h.timestamp}`);
console.log(` 升级者: ${h.upgrader}`);
console.log(` 旧实现: ${h.oldImplementation}`);
console.log(` 新实现: ${h.newImplementation}`);
});
console.log("");
}
console.log("✅ 验证完成!\n");
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});

View File

@@ -50,6 +50,9 @@ contract VaultTest is Test {
event AssetsDeposited(uint256 amount);
event HardCapSet(uint256 newHardCap);
event NextRedemptionTimeSet(uint256 newRedemptionTime);
event WithdrawRequestCreated(uint256 indexed requestId, address indexed user, uint256 ytAmount, uint256 wusdAmount, uint256 queueIndex);
event WithdrawRequestProcessed(uint256 indexed requestId, address indexed user, uint256 wusdAmount);
event BatchProcessed(uint256 startIndex, uint256 endIndex, uint256 processedCount, uint256 totalWusdDistributed);
function setUp() public {
// 设置测试账户
@@ -346,24 +349,35 @@ contract VaultTest is Test {
// 快进到赎回时间之后
vm.warp(vault.nextRedemptionTime() + 1);
// 提
// 提交提现请求
uint256 withdrawAmount = 500 * 1e18; // 提取500 YT
uint256 expectedWusd = 500 * 1e18; // 价格1:1获得500 WUSD
uint256 user1WusdBefore = wusd.balanceOf(user1);
vm.startPrank(user1);
vm.expectEmit(true, false, false, true);
emit Sell(user1, withdrawAmount, expectedWusd);
vm.expectEmit(true, true, false, true);
emit WithdrawRequestCreated(0, user1, withdrawAmount, expectedWusd, 0);
uint256 wusdReceived = vault.withdrawYT(withdrawAmount);
uint256 requestId = vault.withdrawYT(withdrawAmount);
vm.stopPrank();
// 验证结果
assertEq(wusdReceived, expectedWusd);
assertEq(vault.balanceOf(user1), depositAmount - withdrawAmount);
// 验证请求创建
assertEq(requestId, 0);
assertEq(vault.balanceOf(user1), depositAmount - withdrawAmount); // YT已销毁
assertEq(vault.totalSupply(), depositAmount - withdrawAmount);
assertEq(wusd.balanceOf(user1), user1WusdBefore + expectedWusd);
assertEq(wusd.balanceOf(user1), user1WusdBefore); // WUSD还未发放
assertEq(vault.pendingRequestsCount(), 1);
// 批量处理提现请求
vm.prank(manager);
(uint256 processedCount, uint256 totalDistributed) = vault.processBatchWithdrawals(10);
// 验证结果
assertEq(processedCount, 1);
assertEq(totalDistributed, expectedWusd);
assertEq(wusd.balanceOf(user1), user1WusdBefore + expectedWusd); // 现在收到了WUSD
assertEq(vault.pendingRequestsCount(), 0);
}
function test_14_WithdrawYTWithDifferentPrices() public {
@@ -386,17 +400,26 @@ contract VaultTest is Test {
// 快进到赎回时间
vm.warp(vault.nextRedemptionTime() + 1);
// 提款500 YT
// 提交提现请求
uint256 withdrawAmount = 500 * 1e18;
// wusdAmount = 500 * 1.05 / 0.98 = 535.714285714285714285 WUSD
uint256 expectedWusd = (withdrawAmount * 1050000000000000000000000000000) / 980000000000000000000000000000;
uint256 user1BalanceBefore = wusd.balanceOf(user1);
vm.startPrank(user1);
uint256 wusdReceived = vault.withdrawYT(withdrawAmount);
uint256 requestId = vault.withdrawYT(withdrawAmount);
vm.stopPrank();
assertEq(wusdReceived, expectedWusd);
assertEq(wusdReceived, 535714285714285714285); // 约535.71 WUSD
assertEq(requestId, 0);
// 批量处理
vm.prank(manager);
vault.processBatchWithdrawals(10);
// 验证用户收到的WUSD余额增加量
assertEq(wusd.balanceOf(user1), user1BalanceBefore + expectedWusd);
assertEq(expectedWusd, 535714285714285714285); // 约535.71 WUSD
}
function test_15_CannotWithdrawBeforeRedemptionTime() public {
@@ -440,7 +463,7 @@ contract VaultTest is Test {
vm.stopPrank();
}
function test_18_CannotWithdrawWhenInsufficientWUSD() public {
function test_18_CannotProcessWhenInsufficientWUSD() public {
vault = _createVault();
// User1存款
@@ -456,11 +479,33 @@ contract VaultTest is Test {
// 快进到赎回时间
vm.warp(vault.nextRedemptionTime() + 1);
// User1尝试提款,但vault中没有WUSD
// User1可以提交提现请求(即使vault中没有WUSD
vm.startPrank(user1);
vm.expectRevert(YTAssetVault.InsufficientWUSD.selector);
vault.withdrawYT(500 * 1e18);
uint256 requestId = vault.withdrawYT(500 * 1e18);
vm.stopPrank();
assertEq(requestId, 0);
assertEq(vault.pendingRequestsCount(), 1);
// 但是批量处理时会因为资金不足而处理0个请求
vm.prank(manager);
(uint256 processedCount, ) = vault.processBatchWithdrawals(10);
assertEq(processedCount, 0); // 没有处理任何请求
assertEq(vault.pendingRequestsCount(), 1); // 请求仍在队列中
// Manager归还资金后可以处理
vm.startPrank(manager);
wusd.approve(address(vault), 1000 * 1e18);
vault.depositManagedAssets(1000 * 1e18);
vm.stopPrank();
// 现在可以处理了
vm.prank(manager);
(uint256 processedCount2, ) = vault.processBatchWithdrawals(10);
assertEq(processedCount2, 1);
assertEq(vault.pendingRequestsCount(), 0);
}
function test_19_UpdatePrices() public {
@@ -895,18 +940,25 @@ contract VaultTest is Test {
// 6. 快进到赎回时间
vm.warp(vault.nextRedemptionTime() + 1);
// 7. User1提取部分YT
// 7. User1提交提现请求
uint256 user1YtBalance = vault.balanceOf(user1);
uint256 withdrawYtAmount = 5000 * 1e18;
uint256 user1WusdBefore = wusd.balanceOf(user1);
vm.startPrank(user1);
uint256 wusdReceived = vault.withdrawYT(withdrawYtAmount);
uint256 requestId = vault.withdrawYT(withdrawYtAmount);
vm.stopPrank();
assertEq(requestId, 0);
// 8. 批量处理提现
vm.prank(manager);
vault.processBatchWithdrawals(10);
// 按新价格计算: 5000 * 1.10 / 1.05 = 5238.095238095238095238 WUSD
uint256 expectedWusd = (withdrawYtAmount * 1100000000000000000000000000000) / 1050000000000000000000000000000;
assertEq(wusdReceived, expectedWusd);
assertEq(wusdReceived, 5238095238095238095238);
assertEq(wusd.balanceOf(user1), user1WusdBefore + expectedWusd);
assertEq(expectedWusd, 5238095238095238095238);
// 验证最终状态
assertEq(vault.balanceOf(user1), user1YtBalance - withdrawYtAmount);
@@ -950,22 +1002,32 @@ contract VaultTest is Test {
// 快进到赎回时间
vm.warp(vault.nextRedemptionTime() + 1);
// User1提取
uint256 user1WusdBefore = wusd.balanceOf(user1);
uint256 user2WusdBefore = wusd.balanceOf(user2);
// User1提交提现请求
vm.startPrank(user1);
uint256 wusdBack1 = vault.withdrawYT(ytReceived1);
uint256 requestId1 = vault.withdrawYT(ytReceived1);
vm.stopPrank();
// User2提交提现请求
vm.startPrank(user2);
uint256 requestId2 = vault.withdrawYT(ytReceived2);
vm.stopPrank();
assertEq(requestId1, 0);
assertEq(requestId2, 1);
// 批量处理所有请求
vm.prank(manager);
vault.processBatchWithdrawals(10);
// wusdAmount = 10000 * 0.90 / 0.95 = 9473.684210526315789473
assertEq(wusdBack1, 9473684210526315789473);
// User2提取
vm.startPrank(user2);
uint256 wusdBack2 = vault.withdrawYT(ytReceived2);
vm.stopPrank();
assertEq(wusd.balanceOf(user1), user1WusdBefore + 9473684210526315789473);
// wusdAmount = 9166.666... * 0.90 / 0.95 = 8684.210526315789473684
// 允许1 wei的舍入误差
assertApproxEqAbs(wusdBack2, 8684210526315789473684, 1);
assertApproxEqAbs(wusd.balanceOf(user2), user2WusdBefore + 8684210526315789473684, 1);
}
// ==================== 暂停功能测试 ====================
@@ -1037,20 +1099,28 @@ contract VaultTest is Test {
// 暂停vault
factory.pauseVault(address(vault));
// 尝试提应该失败
// 尝试提交提现请求应该失败
vm.startPrank(user1);
vm.expectRevert(abi.encodeWithSignature("EnforcedPause()"));
vault.withdrawYT(500 * 1e18);
vm.stopPrank();
// 恢复后应该可以提
// 恢复后应该可以提交请求和处理
factory.unpauseVault(address(vault));
uint256 user1WusdBefore = wusd.balanceOf(user1);
vm.startPrank(user1);
uint256 wusdReceived = vault.withdrawYT(500 * 1e18);
uint256 requestId = vault.withdrawYT(500 * 1e18);
vm.stopPrank();
assertEq(wusdReceived, 500 * 1e18, "withdraw should work after unpause");
assertEq(requestId, 0);
// 批量处理
vm.prank(manager);
vault.processBatchWithdrawals(10);
assertEq(wusd.balanceOf(user1), user1WusdBefore + 500 * 1e18, "withdraw should work after unpause");
}
function test_45_CannotWithdrawForManagementWhenPaused() public {
@@ -1150,4 +1220,382 @@ contract VaultTest is Test {
assertEq(totalAssets, 1000 * 1e18, "getVaultInfo should work");
assertEq(idleAssets, 1000 * 1e18, "getVaultInfo should work");
}
// ==================== 排队提现机制测试 ====================
function test_48_WithdrawQueueBasic() public {
vault = _createVault();
// User1和User2存款
vm.startPrank(user1);
wusd.approve(address(vault), 1000 * 1e18);
vault.depositYT(1000 * 1e18);
vm.stopPrank();
vm.startPrank(user2);
wusd.approve(address(vault), 2000 * 1e18);
vault.depositYT(2000 * 1e18);
vm.stopPrank();
// 快进到赎回时间
vm.warp(vault.nextRedemptionTime() + 1);
// User1先提交请求
vm.prank(user1);
uint256 requestId1 = vault.withdrawYT(500 * 1e18);
// User2后提交请求
vm.prank(user2);
uint256 requestId2 = vault.withdrawYT(1000 * 1e18);
assertEq(requestId1, 0);
assertEq(requestId2, 1);
assertEq(vault.pendingRequestsCount(), 2);
// 查询队列进度
(uint256 currentIndex, uint256 totalRequests, uint256 pendingRequests) = vault.getQueueProgress();
assertEq(currentIndex, 0);
assertEq(totalRequests, 2);
assertEq(pendingRequests, 2);
}
function test_49_ProcessBatchWithdrawals() public {
vault = _createVault();
// 3个用户存款
vm.startPrank(user1);
wusd.approve(address(vault), 1000 * 1e18);
vault.depositYT(1000 * 1e18);
vm.stopPrank();
vm.startPrank(user2);
wusd.approve(address(vault), 2000 * 1e18);
vault.depositYT(2000 * 1e18);
vm.stopPrank();
address user3 = makeAddr("user3");
wusd.transfer(user3, 3000 * 1e18);
vm.startPrank(user3);
wusd.approve(address(vault), 3000 * 1e18);
vault.depositYT(3000 * 1e18);
vm.stopPrank();
// 快进到赎回时间
vm.warp(vault.nextRedemptionTime() + 1);
uint256 user1WusdBefore = wusd.balanceOf(user1);
uint256 user2WusdBefore = wusd.balanceOf(user2);
uint256 user3WusdBefore = wusd.balanceOf(user3);
// 提交3个提现请求
vm.prank(user1);
vault.withdrawYT(500 * 1e18);
vm.prank(user2);
vault.withdrawYT(1000 * 1e18);
vm.prank(user3);
vault.withdrawYT(1500 * 1e18);
assertEq(vault.pendingRequestsCount(), 3);
// 批量处理所有请求
vm.prank(manager);
(uint256 processedCount, uint256 totalDistributed) = vault.processBatchWithdrawals(10);
assertEq(processedCount, 3);
assertEq(totalDistributed, 3000 * 1e18);
assertEq(vault.pendingRequestsCount(), 0);
// 验证用户收到WUSD
assertEq(wusd.balanceOf(user1), user1WusdBefore + 500 * 1e18);
assertEq(wusd.balanceOf(user2), user2WusdBefore + 1000 * 1e18);
assertEq(wusd.balanceOf(user3), user3WusdBefore + 1500 * 1e18);
}
function test_50_ProcessBatchWithLimit() public {
vault = _createVault();
// 准备5个用户和请求
address[] memory users = new address[](5);
for (uint i = 0; i < 5; i++) {
users[i] = makeAddr(string(abi.encodePacked("user", i)));
wusd.transfer(users[i], 1000 * 1e18);
vm.startPrank(users[i]);
wusd.approve(address(vault), 1000 * 1e18);
vault.depositYT(1000 * 1e18);
vm.stopPrank();
}
// 快进到赎回时间
vm.warp(vault.nextRedemptionTime() + 1);
// 提交5个提现请求
for (uint i = 0; i < 5; i++) {
vm.prank(users[i]);
vault.withdrawYT(500 * 1e18);
}
assertEq(vault.pendingRequestsCount(), 5);
// 第一次批量处理只处理2个
vm.prank(manager);
(uint256 processedCount1, ) = vault.processBatchWithdrawals(2);
assertEq(processedCount1, 2);
assertEq(vault.pendingRequestsCount(), 3);
// 第二次批量处理处理剩余3个
vm.prank(manager);
(uint256 processedCount2, ) = vault.processBatchWithdrawals(10);
assertEq(processedCount2, 3);
assertEq(vault.pendingRequestsCount(), 0);
}
function test_51_ProcessStopsWhenInsufficientFunds() public {
vault = _createVault();
// User1存款1000
vm.startPrank(user1);
wusd.approve(address(vault), 1000 * 1e18);
vault.depositYT(1000 * 1e18);
vm.stopPrank();
// User2存款2000
vm.startPrank(user2);
wusd.approve(address(vault), 2000 * 1e18);
vault.depositYT(2000 * 1e18);
vm.stopPrank();
// Manager提取大部分资金
vm.prank(manager);
vault.withdrawForManagement(manager, 2500 * 1e18);
// 现在vault只有500 WUSD
assertEq(vault.idleAssets(), 500 * 1e18);
// 快进到赎回时间
vm.warp(vault.nextRedemptionTime() + 1);
// 两个用户提交提现请求
vm.prank(user1);
vault.withdrawYT(1000 * 1e18); // 需要1000 WUSD
vm.prank(user2);
vault.withdrawYT(2000 * 1e18); // 需要2000 WUSD
// 批量处理只能处理第一个请求500 WUSD不够
vm.prank(manager);
(uint256 processedCount, ) = vault.processBatchWithdrawals(10);
assertEq(processedCount, 0); // 第一个请求需要1000但只有500
assertEq(vault.pendingRequestsCount(), 2);
// Manager归还资金
vm.startPrank(manager);
wusd.approve(address(vault), 2500 * 1e18);
vault.depositManagedAssets(2500 * 1e18);
vm.stopPrank();
// 现在可以处理所有请求
vm.prank(manager);
(uint256 processedCount2, ) = vault.processBatchWithdrawals(10);
assertEq(processedCount2, 2);
assertEq(vault.pendingRequestsCount(), 0);
}
function test_52_GetUserRequestIds() public {
vault = _createVault();
// User1存款并提交多个请求
vm.startPrank(user1);
wusd.approve(address(vault), 3000 * 1e18);
vault.depositYT(3000 * 1e18);
vm.stopPrank();
vm.warp(vault.nextRedemptionTime() + 1);
vm.startPrank(user1);
vault.withdrawYT(500 * 1e18);
vault.withdrawYT(1000 * 1e18);
vault.withdrawYT(500 * 1e18);
vm.stopPrank();
// 查询用户的所有请求ID
uint256[] memory userRequests = vault.getUserRequestIds(user1);
assertEq(userRequests.length, 3);
assertEq(userRequests[0], 0);
assertEq(userRequests[1], 1);
assertEq(userRequests[2], 2);
}
function test_53_GetRequestDetails() public {
vault = _createVault();
vm.startPrank(user1);
wusd.approve(address(vault), 1000 * 1e18);
vault.depositYT(1000 * 1e18);
vm.stopPrank();
vm.warp(vault.nextRedemptionTime() + 1);
vm.prank(user1);
uint256 requestId = vault.withdrawYT(500 * 1e18);
// 查询请求详情
YTAssetVault.WithdrawRequest memory request = vault.getRequestDetails(requestId);
assertEq(request.user, user1);
assertEq(request.ytAmount, 500 * 1e18);
assertEq(request.wusdAmount, 500 * 1e18);
assertEq(request.queueIndex, 0);
assertFalse(request.processed);
}
function test_54_GetUserPendingRequests() public {
vault = _createVault();
vm.startPrank(user1);
wusd.approve(address(vault), 3000 * 1e18);
vault.depositYT(3000 * 1e18);
vm.stopPrank();
vm.warp(vault.nextRedemptionTime() + 1);
// 提交3个请求
vm.startPrank(user1);
vault.withdrawYT(500 * 1e18);
vault.withdrawYT(1000 * 1e18);
vault.withdrawYT(500 * 1e18);
vm.stopPrank();
// 查询待处理的请求
YTAssetVault.WithdrawRequest[] memory pendingRequests = vault.getUserPendingRequests(user1);
assertEq(pendingRequests.length, 3);
// 处理第一个请求
vm.prank(manager);
vault.processBatchWithdrawals(1);
// 再次查询
YTAssetVault.WithdrawRequest[] memory pendingRequests2 = vault.getUserPendingRequests(user1);
assertEq(pendingRequests2.length, 2);
}
function test_55_FactoryCanProcessWithdrawals() public {
vault = _createVault();
vm.startPrank(user1);
wusd.approve(address(vault), 1000 * 1e18);
vault.depositYT(1000 * 1e18);
vm.stopPrank();
vm.warp(vault.nextRedemptionTime() + 1);
vm.prank(user1);
vault.withdrawYT(500 * 1e18);
// Factory也可以调用processBatchWithdrawals
vm.prank(address(factory));
(uint256 processedCount, ) = vault.processBatchWithdrawals(10);
assertEq(processedCount, 1);
}
function test_56_OnlyManagerOrFactoryCanProcess() public {
vault = _createVault();
vm.startPrank(user1);
wusd.approve(address(vault), 1000 * 1e18);
vault.depositYT(1000 * 1e18);
vm.stopPrank();
vm.warp(vault.nextRedemptionTime() + 1);
vm.prank(user1);
vault.withdrawYT(500 * 1e18);
// 普通用户不能调用processBatchWithdrawals
vm.prank(user2);
vm.expectRevert(YTAssetVault.Forbidden.selector);
vault.processBatchWithdrawals(10);
}
function test_57_CannotProcessWithZeroBatchSize() public {
vault = _createVault();
vm.prank(manager);
vm.expectRevert(YTAssetVault.InvalidBatchSize.selector);
vault.processBatchWithdrawals(0);
}
function test_58_FIFOOrderGuarantee() public {
vault = _createVault();
// 3个用户按顺序存款
address user3 = makeAddr("user3");
vm.startPrank(user1);
wusd.approve(address(vault), 1000 * 1e18);
vault.depositYT(1000 * 1e18);
vm.stopPrank();
vm.startPrank(user2);
wusd.approve(address(vault), 1000 * 1e18);
vault.depositYT(1000 * 1e18);
vm.stopPrank();
wusd.transfer(user3, 1000 * 1e18);
vm.startPrank(user3);
wusd.approve(address(vault), 1000 * 1e18);
vault.depositYT(1000 * 1e18);
vm.stopPrank();
// Manager提取资金只留下1500 WUSD
vm.prank(manager);
vault.withdrawForManagement(manager, 1500 * 1e18);
vm.warp(vault.nextRedemptionTime() + 1);
uint256 user1WusdBefore = wusd.balanceOf(user1);
uint256 user2WusdBefore = wusd.balanceOf(user2);
uint256 user3WusdBefore = wusd.balanceOf(user3);
// 按顺序提交请求
vm.prank(user1);
vault.withdrawYT(1000 * 1e18); // requestId = 0, 需要1000 WUSD
vm.prank(user2);
vault.withdrawYT(1000 * 1e18); // requestId = 1, 需要1000 WUSD
vm.prank(user3);
vault.withdrawYT(1000 * 1e18); // requestId = 2, 需要1000 WUSD
// 批量处理只有1500 WUSD应该按FIFO顺序处理
vm.prank(manager);
(uint256 processedCount, ) = vault.processBatchWithdrawals(10);
// 只能处理前1个user1第2个需要1000但只剩500
assertEq(processedCount, 1);
assertEq(wusd.balanceOf(user1), user1WusdBefore + 1000 * 1e18); // 已处理
assertEq(wusd.balanceOf(user2), user2WusdBefore); // 未处理
assertEq(wusd.balanceOf(user3), user3WusdBefore); // 未处理
// 归还资金后继续处理
vm.startPrank(manager);
wusd.approve(address(vault), 1500 * 1e18);
vault.depositManagedAssets(1500 * 1e18);
vm.stopPrank();
// 处理剩余请求
vm.prank(manager);
(uint256 processedCount2, ) = vault.processBatchWithdrawals(10);
assertEq(processedCount2, 2);
assertEq(wusd.balanceOf(user2), user2WusdBefore + 1000 * 1e18); // 现在已处理
assertEq(wusd.balanceOf(user3), user3WusdBefore + 1000 * 1e18); // 现在已处理
}
}