1602 lines
53 KiB
Solidity
1602 lines
53 KiB
Solidity
// SPDX-License-Identifier: MIT
|
||
pragma solidity ^0.8.0;
|
||
|
||
import "forge-std/Test.sol";
|
||
import "../contracts/vault/YTAssetVault.sol";
|
||
import "../contracts/vault/YTAssetFactory.sol";
|
||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
||
|
||
// Mock WUSD token for testing
|
||
contract MockWUSD is ERC20 {
|
||
constructor() ERC20("Wrapped USD", "WUSD") {
|
||
_mint(msg.sender, 10000000 * 1e18); // 铸造1000万WUSD用于测试
|
||
}
|
||
|
||
function mint(address to, uint256 amount) external {
|
||
_mint(to, amount);
|
||
}
|
||
}
|
||
|
||
contract VaultTest is Test {
|
||
YTAssetFactory public factory;
|
||
YTAssetVault public vaultImplementation;
|
||
YTAssetVault public vault;
|
||
MockWUSD public wusd;
|
||
|
||
address public owner;
|
||
address public manager;
|
||
address public user1;
|
||
address public user2;
|
||
|
||
// 常量
|
||
uint256 constant PRICE_PRECISION = 1e30;
|
||
uint256 constant INITIAL_WUSD_PRICE = 1e30; // 1.0
|
||
uint256 constant INITIAL_YT_PRICE = 1e30; // 1.0
|
||
uint256 constant HARD_CAP = 1000000 * 1e18; // 100万YT
|
||
|
||
event VaultCreated(
|
||
address indexed vault,
|
||
address indexed manager,
|
||
string name,
|
||
string symbol,
|
||
uint256 hardCap,
|
||
uint256 index
|
||
);
|
||
event Buy(address indexed user, uint256 wusdAmount, uint256 ytAmount);
|
||
event Sell(address indexed user, uint256 ytAmount, uint256 wusdAmount);
|
||
event PriceUpdated(uint256 wusdPrice, uint256 ytPrice, uint256 timestamp);
|
||
event AssetsWithdrawn(address indexed to, uint256 amount);
|
||
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 {
|
||
// 设置测试账户
|
||
owner = address(this);
|
||
manager = makeAddr("manager");
|
||
user1 = makeAddr("user1");
|
||
user2 = makeAddr("user2");
|
||
|
||
// 部署Mock WUSD
|
||
wusd = new MockWUSD();
|
||
|
||
// 部署实现合约
|
||
vaultImplementation = new YTAssetVault();
|
||
|
||
// 部署并初始化Factory
|
||
YTAssetFactory factoryImpl = new YTAssetFactory();
|
||
bytes memory factoryInitData = abi.encodeWithSelector(
|
||
YTAssetFactory.initialize.selector,
|
||
address(vaultImplementation),
|
||
HARD_CAP // 默认硬顶
|
||
);
|
||
ERC1967Proxy factoryProxy = new ERC1967Proxy(address(factoryImpl), factoryInitData);
|
||
factory = YTAssetFactory(address(factoryProxy));
|
||
|
||
// 给测试用户分配WUSD
|
||
wusd.transfer(user1, 100000 * 1e18); // 10万WUSD
|
||
wusd.transfer(user2, 100000 * 1e18); // 10万WUSD
|
||
wusd.transfer(manager, 100000 * 1e18); // 10万WUSD给manager
|
||
}
|
||
|
||
function _createVault() internal returns (YTAssetVault) {
|
||
uint256 redemptionTime = block.timestamp + 30 days;
|
||
|
||
address vaultAddr = factory.createVault(
|
||
"YT-A Token",
|
||
"YT-A",
|
||
manager,
|
||
HARD_CAP,
|
||
address(wusd),
|
||
redemptionTime,
|
||
INITIAL_WUSD_PRICE,
|
||
INITIAL_YT_PRICE
|
||
);
|
||
|
||
return YTAssetVault(vaultAddr);
|
||
}
|
||
|
||
function test_01_FactoryInitialization() public {
|
||
assertEq(factory.vaultImplementation(), address(vaultImplementation));
|
||
assertEq(factory.defaultHardCap(), HARD_CAP);
|
||
assertEq(factory.owner(), owner);
|
||
}
|
||
|
||
function test_02_CreateVault() public {
|
||
uint256 redemptionTime = block.timestamp + 30 days;
|
||
|
||
vm.expectEmit(false, true, false, true);
|
||
emit VaultCreated(
|
||
address(0), // vault地址未知,所以用0
|
||
manager,
|
||
"YT-A Token",
|
||
"YT-A",
|
||
HARD_CAP,
|
||
0 // 第一个vault,索引为0
|
||
);
|
||
|
||
address vaultAddr = factory.createVault(
|
||
"YT-A Token",
|
||
"YT-A",
|
||
manager,
|
||
HARD_CAP,
|
||
address(wusd),
|
||
redemptionTime,
|
||
INITIAL_WUSD_PRICE,
|
||
INITIAL_YT_PRICE
|
||
);
|
||
|
||
vault = YTAssetVault(vaultAddr);
|
||
|
||
// 验证vault基本信息
|
||
assertEq(vault.name(), "YT-A Token");
|
||
assertEq(vault.symbol(), "YT-A");
|
||
assertEq(vault.manager(), manager);
|
||
assertEq(vault.hardCap(), HARD_CAP);
|
||
assertEq(vault.wusdAddress(), address(wusd));
|
||
assertEq(vault.wusdPrice(), INITIAL_WUSD_PRICE);
|
||
assertEq(vault.ytPrice(), INITIAL_YT_PRICE);
|
||
assertEq(vault.nextRedemptionTime(), redemptionTime);
|
||
assertEq(vault.factory(), address(factory));
|
||
|
||
// 验证factory记录
|
||
assertEq(factory.getVaultCount(), 1);
|
||
assertTrue(factory.isVault(vaultAddr));
|
||
}
|
||
|
||
function test_03_CreateVaultWithCustomPrices() public {
|
||
uint256 customWusdPrice = 1050000000000000000000000000000; // 1.05
|
||
uint256 customYtPrice = 1020000000000000000000000000000; // 1.02
|
||
uint256 redemptionTime = block.timestamp + 60 days;
|
||
|
||
address vaultAddr = factory.createVault(
|
||
"YT-B Token",
|
||
"YT-B",
|
||
manager,
|
||
HARD_CAP,
|
||
address(wusd),
|
||
redemptionTime,
|
||
customWusdPrice,
|
||
customYtPrice
|
||
);
|
||
|
||
YTAssetVault customVault = YTAssetVault(vaultAddr);
|
||
|
||
assertEq(customVault.wusdPrice(), customWusdPrice);
|
||
assertEq(customVault.ytPrice(), customYtPrice);
|
||
}
|
||
|
||
function test_04_CreateVaultWithZeroPrices() public {
|
||
// 传入0价格应该使用默认值
|
||
uint256 redemptionTime = block.timestamp + 30 days;
|
||
|
||
address vaultAddr = factory.createVault(
|
||
"YT-C Token",
|
||
"YT-C",
|
||
manager,
|
||
HARD_CAP,
|
||
address(wusd),
|
||
redemptionTime,
|
||
0, // 使用默认价格
|
||
0 // 使用默认价格
|
||
);
|
||
|
||
YTAssetVault defaultVault = YTAssetVault(vaultAddr);
|
||
|
||
assertEq(defaultVault.wusdPrice(), PRICE_PRECISION); // 1.0
|
||
assertEq(defaultVault.ytPrice(), PRICE_PRECISION); // 1.0
|
||
}
|
||
|
||
function test_05_CannotCreateVaultWithZeroManager() public {
|
||
vm.expectRevert(YTAssetFactory.InvalidAddress.selector);
|
||
factory.createVault(
|
||
"YT-D Token",
|
||
"YT-D",
|
||
address(0), // 无效的manager地址
|
||
HARD_CAP,
|
||
address(wusd),
|
||
block.timestamp + 30 days,
|
||
INITIAL_WUSD_PRICE,
|
||
INITIAL_YT_PRICE
|
||
);
|
||
}
|
||
|
||
function test_06_CreateVaultOnlyOwner() public {
|
||
vm.prank(user1);
|
||
vm.expectRevert(abi.encodeWithSignature("OwnableUnauthorizedAccount(address)", user1));
|
||
factory.createVault(
|
||
"YT-E Token",
|
||
"YT-E",
|
||
manager,
|
||
HARD_CAP,
|
||
address(wusd),
|
||
block.timestamp + 30 days,
|
||
INITIAL_WUSD_PRICE,
|
||
INITIAL_YT_PRICE
|
||
);
|
||
}
|
||
|
||
function test_07_DepositYT() public {
|
||
vault = _createVault();
|
||
|
||
uint256 depositAmount = 1000 * 1e18; // 1000 WUSD
|
||
uint256 expectedYtAmount = 1000 * 1e18; // 价格1:1,获得1000 YT
|
||
|
||
// 授权
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), depositAmount);
|
||
|
||
// 预览购买
|
||
uint256 previewAmount = vault.previewBuy(depositAmount);
|
||
assertEq(previewAmount, expectedYtAmount);
|
||
|
||
// 存款
|
||
vm.expectEmit(true, false, false, true);
|
||
emit Buy(user1, depositAmount, expectedYtAmount);
|
||
|
||
uint256 ytReceived = vault.depositYT(depositAmount);
|
||
vm.stopPrank();
|
||
|
||
// 验证结果
|
||
assertEq(ytReceived, expectedYtAmount);
|
||
assertEq(vault.balanceOf(user1), expectedYtAmount);
|
||
assertEq(vault.totalSupply(), expectedYtAmount);
|
||
assertEq(wusd.balanceOf(address(vault)), depositAmount);
|
||
assertEq(vault.totalAssets(), depositAmount);
|
||
assertEq(vault.idleAssets(), depositAmount);
|
||
}
|
||
|
||
function test_08_DepositYTWithDifferentPrices() public {
|
||
vault = _createVault();
|
||
|
||
// 更新价格: WUSD = 1.05, YT = 1.02
|
||
factory.updateVaultPrices(
|
||
address(vault),
|
||
1050000000000000000000000000000, // 1.05
|
||
1020000000000000000000000000000 // 1.02
|
||
);
|
||
|
||
uint256 depositAmount = 1000 * 1e18; // 1000 WUSD
|
||
// ytAmount = 1000 * 1.05 / 1.02 = 1029.411764705882352941 YT
|
||
uint256 expectedYtAmount = (depositAmount * 1050000000000000000000000000000) / 1020000000000000000000000000000;
|
||
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), depositAmount);
|
||
uint256 ytReceived = vault.depositYT(depositAmount);
|
||
vm.stopPrank();
|
||
|
||
// 精确验证计算结果
|
||
assertEq(ytReceived, expectedYtAmount);
|
||
assertEq(ytReceived, 1029411764705882352941); // 约1029.41 YT
|
||
}
|
||
|
||
function test_09_DepositYTMultipleUsers() public {
|
||
vault = _createVault();
|
||
|
||
uint256 amount1 = 1000 * 1e18;
|
||
uint256 amount2 = 2000 * 1e18;
|
||
|
||
// User1存款
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), amount1);
|
||
vault.depositYT(amount1);
|
||
vm.stopPrank();
|
||
|
||
// User2存款
|
||
vm.startPrank(user2);
|
||
wusd.approve(address(vault), amount2);
|
||
vault.depositYT(amount2);
|
||
vm.stopPrank();
|
||
|
||
// 验证余额
|
||
assertEq(vault.balanceOf(user1), amount1);
|
||
assertEq(vault.balanceOf(user2), amount2);
|
||
assertEq(vault.totalSupply(), amount1 + amount2);
|
||
assertEq(vault.totalAssets(), amount1 + amount2);
|
||
}
|
||
|
||
function test_10_CannotDepositZeroAmount() public {
|
||
vault = _createVault();
|
||
|
||
vm.startPrank(user1);
|
||
vm.expectRevert(YTAssetVault.InvalidAmount.selector);
|
||
vault.depositYT(0);
|
||
vm.stopPrank();
|
||
}
|
||
|
||
function test_11_DepositYTHardCapEnforcement() public {
|
||
vault = _createVault();
|
||
|
||
// 尝试存款超过硬顶
|
||
uint256 overCapAmount = HARD_CAP + 1000 * 1e18;
|
||
|
||
vm.startPrank(user1);
|
||
wusd.mint(user1, overCapAmount); // 铸造足够的WUSD
|
||
wusd.approve(address(vault), overCapAmount);
|
||
|
||
vm.expectRevert(YTAssetVault.HardCapExceeded.selector);
|
||
vault.depositYT(overCapAmount);
|
||
vm.stopPrank();
|
||
}
|
||
|
||
function test_12_DepositYTExactlyAtHardCap() public {
|
||
vault = _createVault();
|
||
|
||
vm.startPrank(user1);
|
||
wusd.mint(user1, HARD_CAP);
|
||
wusd.approve(address(vault), HARD_CAP);
|
||
vault.depositYT(HARD_CAP);
|
||
vm.stopPrank();
|
||
|
||
assertEq(vault.totalSupply(), HARD_CAP);
|
||
assertEq(vault.balanceOf(user1), HARD_CAP);
|
||
}
|
||
|
||
function test_13_WithdrawYT() public {
|
||
vault = _createVault();
|
||
|
||
// 先存款
|
||
uint256 depositAmount = 1000 * 1e18;
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), depositAmount);
|
||
vault.depositYT(depositAmount);
|
||
vm.stopPrank();
|
||
|
||
// 快进到赎回时间之后
|
||
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, true, false, true);
|
||
emit WithdrawRequestCreated(0, user1, withdrawAmount, expectedWusd, 0);
|
||
|
||
uint256 requestId = vault.withdrawYT(withdrawAmount);
|
||
vm.stopPrank();
|
||
|
||
// 验证请求创建
|
||
assertEq(requestId, 0);
|
||
assertEq(vault.balanceOf(user1), depositAmount - withdrawAmount); // YT已销毁
|
||
assertEq(vault.totalSupply(), depositAmount - withdrawAmount);
|
||
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 {
|
||
vault = _createVault();
|
||
|
||
// 存款
|
||
uint256 depositAmount = 1000 * 1e18;
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), depositAmount);
|
||
vault.depositYT(depositAmount);
|
||
vm.stopPrank();
|
||
|
||
// 更新价格: WUSD = 0.98, YT = 1.05 (YT升值)
|
||
factory.updateVaultPrices(
|
||
address(vault),
|
||
980000000000000000000000000000, // 0.98
|
||
1050000000000000000000000000000 // 1.05
|
||
);
|
||
|
||
// 快进到赎回时间
|
||
vm.warp(vault.nextRedemptionTime() + 1);
|
||
|
||
// 提交提现请求
|
||
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 requestId = vault.withdrawYT(withdrawAmount);
|
||
vm.stopPrank();
|
||
|
||
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 {
|
||
vault = _createVault();
|
||
|
||
// 存款
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), 1000 * 1e18);
|
||
vault.depositYT(1000 * 1e18);
|
||
|
||
// 尝试在赎回时间前提款
|
||
vm.expectRevert(YTAssetVault.StillInLockPeriod.selector);
|
||
vault.withdrawYT(500 * 1e18);
|
||
vm.stopPrank();
|
||
}
|
||
|
||
function test_16_CannotWithdrawZeroAmount() public {
|
||
vault = _createVault();
|
||
|
||
vm.warp(vault.nextRedemptionTime() + 1);
|
||
|
||
vm.startPrank(user1);
|
||
vm.expectRevert(YTAssetVault.InvalidAmount.selector);
|
||
vault.withdrawYT(0);
|
||
vm.stopPrank();
|
||
}
|
||
|
||
function test_17_CannotWithdrawMoreThanBalance() public {
|
||
vault = _createVault();
|
||
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), 1000 * 1e18);
|
||
vault.depositYT(1000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
vm.warp(vault.nextRedemptionTime() + 1);
|
||
|
||
vm.startPrank(user1);
|
||
vm.expectRevert(YTAssetVault.InsufficientYTA.selector);
|
||
vault.withdrawYT(2000 * 1e18);
|
||
vm.stopPrank();
|
||
}
|
||
|
||
function test_18_CannotProcessWhenInsufficientWUSD() public {
|
||
vault = _createVault();
|
||
|
||
// User1存款
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), 1000 * 1e18);
|
||
vault.depositYT(1000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
// Manager提取所有WUSD
|
||
vm.prank(manager);
|
||
vault.withdrawForManagement(manager, 1000 * 1e18);
|
||
|
||
// 快进到赎回时间
|
||
vm.warp(vault.nextRedemptionTime() + 1);
|
||
|
||
// User1可以提交提现请求(即使vault中没有WUSD)
|
||
vm.startPrank(user1);
|
||
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 {
|
||
vault = _createVault();
|
||
|
||
uint256 newWusdPrice = 1050000000000000000000000000000; // 1.05
|
||
uint256 newYtPrice = 1020000000000000000000000000000; // 1.02
|
||
|
||
vm.expectEmit(false, false, false, true);
|
||
emit PriceUpdated(newWusdPrice, newYtPrice, block.timestamp);
|
||
|
||
factory.updateVaultPrices(address(vault), newWusdPrice, newYtPrice);
|
||
|
||
assertEq(vault.wusdPrice(), newWusdPrice);
|
||
assertEq(vault.ytPrice(), newYtPrice);
|
||
}
|
||
|
||
function test_20_UpdatePricesMultipleTimes() public {
|
||
vault = _createVault();
|
||
|
||
// 第一次更新
|
||
factory.updateVaultPrices(
|
||
address(vault),
|
||
1050000000000000000000000000000,
|
||
1020000000000000000000000000000
|
||
);
|
||
|
||
assertEq(vault.wusdPrice(), 1050000000000000000000000000000);
|
||
assertEq(vault.ytPrice(), 1020000000000000000000000000000);
|
||
|
||
// 第二次更新
|
||
factory.updateVaultPrices(
|
||
address(vault),
|
||
1030000000000000000000000000000,
|
||
1010000000000000000000000000000
|
||
);
|
||
|
||
assertEq(vault.wusdPrice(), 1030000000000000000000000000000);
|
||
assertEq(vault.ytPrice(), 1010000000000000000000000000000);
|
||
}
|
||
|
||
function test_21_UpdatePricesOnlyFactory() public {
|
||
vault = _createVault();
|
||
|
||
// 测试非factory调用者(包括manager)无法直接调用
|
||
vm.prank(user1);
|
||
vm.expectRevert(YTAssetVault.Forbidden.selector);
|
||
vault.updatePrices(1050000000000000000000000000000, 1020000000000000000000000000000);
|
||
|
||
// manager也不能直接调用
|
||
vm.prank(manager);
|
||
vm.expectRevert(YTAssetVault.Forbidden.selector);
|
||
vault.updatePrices(1050000000000000000000000000000, 1020000000000000000000000000000);
|
||
}
|
||
|
||
function test_22_UpdatePricesFactoryCanCall() public {
|
||
vault = _createVault();
|
||
|
||
// Factory也可以调用updatePrices(通过factory)
|
||
vm.prank(owner);
|
||
factory.updateVaultPrices(
|
||
address(vault),
|
||
1050000000000000000000000000000,
|
||
1020000000000000000000000000000
|
||
);
|
||
|
||
assertEq(vault.wusdPrice(), 1050000000000000000000000000000);
|
||
assertEq(vault.ytPrice(), 1020000000000000000000000000000);
|
||
}
|
||
|
||
function test_23_CannotUpdatePricesWithZero() public {
|
||
vault = _createVault();
|
||
|
||
vm.expectRevert(YTAssetVault.InvalidPrice.selector);
|
||
factory.updateVaultPrices(address(vault), 0, 1020000000000000000000000000000);
|
||
|
||
vm.expectRevert(YTAssetVault.InvalidPrice.selector);
|
||
factory.updateVaultPrices(address(vault), 1050000000000000000000000000000, 0);
|
||
}
|
||
|
||
function test_24_WithdrawForManagement() public {
|
||
vault = _createVault();
|
||
|
||
// 先存款
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), 10000 * 1e18);
|
||
vault.depositYT(10000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
// Manager提取用于投资
|
||
uint256 withdrawAmount = 5000 * 1e18;
|
||
uint256 managerBalanceBefore = wusd.balanceOf(manager);
|
||
|
||
vm.expectEmit(true, false, false, true);
|
||
emit AssetsWithdrawn(manager, withdrawAmount);
|
||
|
||
vm.prank(manager);
|
||
vault.withdrawForManagement(manager, withdrawAmount);
|
||
|
||
// 验证
|
||
assertEq(vault.managedAssets(), withdrawAmount);
|
||
assertEq(vault.idleAssets(), 5000 * 1e18);
|
||
assertEq(vault.totalAssets(), 10000 * 1e18); // totalAssets = idle + managed
|
||
assertEq(wusd.balanceOf(manager), managerBalanceBefore + withdrawAmount);
|
||
}
|
||
|
||
function test_25_DepositManagedAssetsFullReturn() public {
|
||
vault = _createVault();
|
||
|
||
// 存款
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), 10000 * 1e18);
|
||
vault.depositYT(10000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
// Manager提取
|
||
vm.prank(manager);
|
||
vault.withdrawForManagement(manager, 5000 * 1e18);
|
||
|
||
// Manager归还全部(无盈亏)
|
||
vm.startPrank(manager);
|
||
wusd.approve(address(vault), 5000 * 1e18);
|
||
|
||
vm.expectEmit(false, false, false, true);
|
||
emit AssetsDeposited(5000 * 1e18);
|
||
|
||
vault.depositManagedAssets(5000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
// 验证
|
||
assertEq(vault.managedAssets(), 0);
|
||
assertEq(vault.idleAssets(), 10000 * 1e18);
|
||
assertEq(vault.totalAssets(), 10000 * 1e18);
|
||
}
|
||
|
||
function test_26_DepositManagedAssetsWithProfit() public {
|
||
vault = _createVault();
|
||
|
||
// 存款
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), 10000 * 1e18);
|
||
vault.depositYT(10000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
// Manager提取
|
||
vm.prank(manager);
|
||
vault.withdrawForManagement(manager, 5000 * 1e18);
|
||
|
||
// Manager归还本金+利润
|
||
uint256 returnAmount = 6000 * 1e18; // 赚了1000 WUSD
|
||
vm.startPrank(manager);
|
||
wusd.approve(address(vault), returnAmount);
|
||
vault.depositManagedAssets(returnAmount);
|
||
vm.stopPrank();
|
||
|
||
// 验证
|
||
assertEq(vault.managedAssets(), 0);
|
||
assertEq(vault.idleAssets(), 11000 * 1e18); // 5000 + 6000
|
||
assertEq(vault.totalAssets(), 11000 * 1e18); // 增加了1000的利润
|
||
}
|
||
|
||
function test_27_DepositManagedAssetsPartialReturn() public {
|
||
vault = _createVault();
|
||
|
||
// 存款
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), 10000 * 1e18);
|
||
vault.depositYT(10000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
// Manager提取
|
||
vm.prank(manager);
|
||
vault.withdrawForManagement(manager, 5000 * 1e18);
|
||
|
||
// Manager部分归还
|
||
uint256 returnAmount = 3000 * 1e18;
|
||
vm.startPrank(manager);
|
||
wusd.approve(address(vault), returnAmount);
|
||
vault.depositManagedAssets(returnAmount);
|
||
vm.stopPrank();
|
||
|
||
// 验证
|
||
assertEq(vault.managedAssets(), 2000 * 1e18); // 还有2000在外面
|
||
assertEq(vault.idleAssets(), 8000 * 1e18); // 5000 + 3000
|
||
assertEq(vault.totalAssets(), 10000 * 1e18); // 总资产不变
|
||
}
|
||
|
||
function test_28_WithdrawForManagementOnlyManager() public {
|
||
vault = _createVault();
|
||
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), 1000 * 1e18);
|
||
vault.depositYT(1000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
vm.prank(user1);
|
||
vm.expectRevert(YTAssetVault.Forbidden.selector);
|
||
vault.withdrawForManagement(user1, 500 * 1e18);
|
||
}
|
||
|
||
function test_29_CannotWithdrawMoreThanAvailable() public {
|
||
vault = _createVault();
|
||
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), 1000 * 1e18);
|
||
vault.depositYT(1000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
vm.prank(manager);
|
||
vm.expectRevert(YTAssetVault.InvalidAmount.selector);
|
||
vault.withdrawForManagement(manager, 2000 * 1e18);
|
||
}
|
||
|
||
function test_30_SetHardCap() public {
|
||
vault = _createVault();
|
||
|
||
uint256 newHardCap = 2000000 * 1e18;
|
||
|
||
vm.expectEmit(false, false, false, true);
|
||
emit HardCapSet(newHardCap);
|
||
|
||
factory.setHardCap(address(vault), newHardCap);
|
||
|
||
assertEq(vault.hardCap(), newHardCap);
|
||
}
|
||
|
||
function test_31_CannotSetHardCapBelowTotalSupply() public {
|
||
vault = _createVault();
|
||
|
||
// 先存款
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), 100000 * 1e18);
|
||
vault.depositYT(100000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
// 尝试设置低于当前总供应量的硬顶
|
||
vm.expectRevert(YTAssetVault.InvalidHardCap.selector);
|
||
factory.setHardCap(address(vault), 50000 * 1e18);
|
||
}
|
||
|
||
function test_32_SetNextRedemptionTime() public {
|
||
vault = _createVault();
|
||
|
||
uint256 newRedemptionTime = block.timestamp + 90 days;
|
||
|
||
vm.expectEmit(false, false, false, true);
|
||
emit NextRedemptionTimeSet(newRedemptionTime);
|
||
|
||
factory.setVaultNextRedemptionTime(address(vault), newRedemptionTime);
|
||
|
||
assertEq(vault.nextRedemptionTime(), newRedemptionTime);
|
||
}
|
||
|
||
function test_33_BatchUpdatePrices() public {
|
||
// 创建多个vault
|
||
address vault1 = factory.createVault(
|
||
"YT-A", "YT-A", manager, HARD_CAP, address(wusd),
|
||
block.timestamp + 30 days, INITIAL_WUSD_PRICE, INITIAL_YT_PRICE
|
||
);
|
||
address vault2 = factory.createVault(
|
||
"YT-B", "YT-B", manager, HARD_CAP, address(wusd),
|
||
block.timestamp + 30 days, INITIAL_WUSD_PRICE, INITIAL_YT_PRICE
|
||
);
|
||
|
||
address[] memory vaults = new address[](2);
|
||
vaults[0] = vault1;
|
||
vaults[1] = vault2;
|
||
|
||
uint256[] memory wusdPrices = new uint256[](2);
|
||
wusdPrices[0] = 1050000000000000000000000000000;
|
||
wusdPrices[1] = 1030000000000000000000000000000;
|
||
|
||
uint256[] memory ytPrices = new uint256[](2);
|
||
ytPrices[0] = 1020000000000000000000000000000;
|
||
ytPrices[1] = 1010000000000000000000000000000;
|
||
|
||
factory.updateVaultPricesBatch(vaults, wusdPrices, ytPrices);
|
||
|
||
assertEq(YTAssetVault(vault1).wusdPrice(), wusdPrices[0]);
|
||
assertEq(YTAssetVault(vault1).ytPrice(), ytPrices[0]);
|
||
assertEq(YTAssetVault(vault2).wusdPrice(), wusdPrices[1]);
|
||
assertEq(YTAssetVault(vault2).ytPrice(), ytPrices[1]);
|
||
}
|
||
|
||
function test_34_GetVaultInfo() public {
|
||
vault = _createVault();
|
||
|
||
// 存款
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), 10000 * 1e18);
|
||
vault.depositYT(10000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
// Manager提取部分资金
|
||
vm.prank(manager);
|
||
vault.withdrawForManagement(manager, 3000 * 1e18);
|
||
|
||
(
|
||
uint256 totalAssets,
|
||
uint256 idleAssets,
|
||
uint256 managedAssets,
|
||
uint256 totalSupply,
|
||
uint256 hardCap,
|
||
uint256 wusdPrice,
|
||
uint256 ytPrice,
|
||
uint256 nextRedemptionTime
|
||
) = vault.getVaultInfo();
|
||
|
||
assertEq(totalAssets, 10000 * 1e18);
|
||
assertEq(idleAssets, 7000 * 1e18);
|
||
assertEq(managedAssets, 3000 * 1e18);
|
||
assertEq(totalSupply, 10000 * 1e18);
|
||
assertEq(hardCap, HARD_CAP);
|
||
assertEq(wusdPrice, INITIAL_WUSD_PRICE);
|
||
assertEq(ytPrice, INITIAL_YT_PRICE);
|
||
assertEq(nextRedemptionTime, vault.nextRedemptionTime());
|
||
}
|
||
|
||
function test_35_GetFactoryVaultInfo() public {
|
||
vault = _createVault();
|
||
|
||
(
|
||
bool exists,
|
||
uint256 totalAssets,
|
||
,, // idleAssets, managedAssets
|
||
, // totalSupply
|
||
uint256 hardCap,
|
||
,, // wusdPrice, ytPrice
|
||
// nextRedemptionTime
|
||
) = factory.getVaultInfo(address(vault));
|
||
|
||
assertTrue(exists);
|
||
assertEq(totalAssets, 0);
|
||
assertEq(hardCap, HARD_CAP);
|
||
}
|
||
|
||
function test_36_PreviewFunctions() public {
|
||
vault = _createVault();
|
||
|
||
// 更新价格
|
||
factory.updateVaultPrices(
|
||
address(vault),
|
||
1050000000000000000000000000000, // WUSD = 1.05
|
||
1020000000000000000000000000000 // YT = 1.02
|
||
);
|
||
|
||
// 预览买入
|
||
uint256 wusdAmount = 1000 * 1e18;
|
||
uint256 expectedYt = (wusdAmount * 1050000000000000000000000000000) / 1020000000000000000000000000000;
|
||
uint256 previewBuyAmount = vault.previewBuy(wusdAmount);
|
||
assertEq(previewBuyAmount, expectedYt);
|
||
assertEq(previewBuyAmount, 1029411764705882352941);
|
||
|
||
// 预览卖出
|
||
uint256 ytAmount = 1000 * 1e18;
|
||
uint256 expectedWusd = (ytAmount * 1020000000000000000000000000000) / 1050000000000000000000000000000;
|
||
uint256 previewSellAmount = vault.previewSell(ytAmount);
|
||
assertEq(previewSellAmount, expectedWusd);
|
||
assertEq(previewSellAmount, 971428571428571428571);
|
||
}
|
||
|
||
function test_37_CanRedeemNow() public {
|
||
vault = _createVault();
|
||
|
||
// 赎回时间前
|
||
assertFalse(vault.canRedeemNow());
|
||
|
||
// 赎回时间后
|
||
vm.warp(vault.nextRedemptionTime() + 1);
|
||
assertTrue(vault.canRedeemNow());
|
||
}
|
||
|
||
function test_38_GetTimeUntilNextRedemption() public {
|
||
vault = _createVault();
|
||
|
||
uint256 redemptionTime = vault.nextRedemptionTime();
|
||
uint256 currentTime = block.timestamp;
|
||
|
||
assertEq(vault.getTimeUntilNextRedemption(), redemptionTime - currentTime);
|
||
|
||
// 快进到赎回时间后
|
||
vm.warp(redemptionTime + 1);
|
||
assertEq(vault.getTimeUntilNextRedemption(), 0);
|
||
}
|
||
|
||
function test_39_CompleteLifecycle() public {
|
||
vault = _createVault();
|
||
|
||
// 1. 初始状态验证
|
||
assertEq(vault.totalSupply(), 0);
|
||
assertEq(vault.totalAssets(), 0);
|
||
|
||
// 2. User1和User2存款
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), 10000 * 1e18);
|
||
vault.depositYT(10000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
vm.startPrank(user2);
|
||
wusd.approve(address(vault), 5000 * 1e18);
|
||
vault.depositYT(5000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
assertEq(vault.totalSupply(), 15000 * 1e18);
|
||
assertEq(vault.totalAssets(), 15000 * 1e18);
|
||
|
||
// 3. Manager提取资金进行投资
|
||
vm.prank(manager);
|
||
vault.withdrawForManagement(manager, 8000 * 1e18);
|
||
|
||
assertEq(vault.managedAssets(), 8000 * 1e18);
|
||
assertEq(vault.idleAssets(), 7000 * 1e18);
|
||
assertEq(vault.totalAssets(), 15000 * 1e18);
|
||
|
||
// 4. 价格更新(模拟市场变化)
|
||
factory.updateVaultPrices(
|
||
address(vault),
|
||
1050000000000000000000000000000, // WUSD涨到1.05
|
||
1100000000000000000000000000000 // YT涨到1.10
|
||
);
|
||
|
||
// 5. Manager归还资金+利润
|
||
vm.startPrank(manager);
|
||
wusd.approve(address(vault), 10000 * 1e18);
|
||
vault.depositManagedAssets(10000 * 1e18); // 归还本金+2000利润
|
||
vm.stopPrank();
|
||
|
||
assertEq(vault.managedAssets(), 0);
|
||
assertEq(vault.idleAssets(), 17000 * 1e18); // 增加了2000利润
|
||
assertEq(vault.totalAssets(), 17000 * 1e18);
|
||
|
||
// 6. 快进到赎回时间
|
||
vm.warp(vault.nextRedemptionTime() + 1);
|
||
|
||
// 7. User1提交提现请求
|
||
uint256 user1YtBalance = vault.balanceOf(user1);
|
||
uint256 withdrawYtAmount = 5000 * 1e18;
|
||
uint256 user1WusdBefore = wusd.balanceOf(user1);
|
||
|
||
vm.startPrank(user1);
|
||
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(wusd.balanceOf(user1), user1WusdBefore + expectedWusd);
|
||
assertEq(expectedWusd, 5238095238095238095238);
|
||
|
||
// 验证最终状态
|
||
assertEq(vault.balanceOf(user1), user1YtBalance - withdrawYtAmount);
|
||
assertEq(vault.totalSupply(), 10000 * 1e18);
|
||
}
|
||
|
||
function test_40_PriceFluctuationScenario() public {
|
||
vault = _createVault();
|
||
|
||
// 初始存款
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), 10000 * 1e18);
|
||
uint256 ytReceived1 = vault.depositYT(10000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
assertEq(ytReceived1, 10000 * 1e18); // 1:1
|
||
|
||
// 价格上涨
|
||
factory.updateVaultPrices(
|
||
address(vault),
|
||
1100000000000000000000000000000, // 1.10
|
||
1200000000000000000000000000000 // 1.20
|
||
);
|
||
|
||
// User2此时存款,会获得更少的YT
|
||
vm.startPrank(user2);
|
||
wusd.approve(address(vault), 10000 * 1e18);
|
||
uint256 ytReceived2 = vault.depositYT(10000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
// ytAmount = 10000 * 1.10 / 1.20 = 9166.666666666666666666
|
||
assertEq(ytReceived2, 9166666666666666666666);
|
||
|
||
// 价格下跌
|
||
factory.updateVaultPrices(
|
||
address(vault),
|
||
950000000000000000000000000000, // 0.95
|
||
900000000000000000000000000000 // 0.90
|
||
);
|
||
|
||
// 快进到赎回时间
|
||
vm.warp(vault.nextRedemptionTime() + 1);
|
||
|
||
uint256 user1WusdBefore = wusd.balanceOf(user1);
|
||
uint256 user2WusdBefore = wusd.balanceOf(user2);
|
||
|
||
// User1提交提现请求
|
||
vm.startPrank(user1);
|
||
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(wusd.balanceOf(user1), user1WusdBefore + 9473684210526315789473);
|
||
|
||
// wusdAmount = 9166.666... * 0.90 / 0.95 = 8684.210526315789473684
|
||
// 允许1 wei的舍入误差
|
||
assertApproxEqAbs(wusd.balanceOf(user2), user2WusdBefore + 8684210526315789473684, 1);
|
||
}
|
||
|
||
// ==================== 暂停功能测试 ====================
|
||
|
||
function test_41_PauseByFactory() public {
|
||
vault = _createVault();
|
||
|
||
// Factory可以暂停
|
||
factory.pauseVault(address(vault));
|
||
assertTrue(vault.paused(), "vault should be paused");
|
||
|
||
// Factory可以恢复
|
||
factory.unpauseVault(address(vault));
|
||
assertFalse(vault.paused(), "vault should be unpaused");
|
||
}
|
||
|
||
function test_42_OnlyFactoryCanPause() public {
|
||
vault = _createVault();
|
||
|
||
// User不能暂停
|
||
vm.startPrank(user1);
|
||
vm.expectRevert(YTAssetVault.Forbidden.selector);
|
||
vault.pause();
|
||
vm.stopPrank();
|
||
|
||
// Manager也不能暂停
|
||
vm.startPrank(manager);
|
||
vm.expectRevert(YTAssetVault.Forbidden.selector);
|
||
vault.pause();
|
||
vm.stopPrank();
|
||
}
|
||
|
||
function test_43_CannotDepositWhenPaused() public {
|
||
vault = _createVault();
|
||
|
||
// 暂停vault
|
||
factory.pauseVault(address(vault));
|
||
|
||
// 尝试存款应该失败
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), 1000 * 1e18);
|
||
|
||
vm.expectRevert(abi.encodeWithSignature("EnforcedPause()"));
|
||
vault.depositYT(1000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
// 恢复后应该可以存款
|
||
factory.unpauseVault(address(vault));
|
||
|
||
vm.startPrank(user1);
|
||
uint256 ytReceived = vault.depositYT(1000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
assertEq(ytReceived, 1000 * 1e18, "deposit should work after unpause");
|
||
}
|
||
|
||
function test_44_CannotWithdrawWhenPaused() public {
|
||
vault = _createVault();
|
||
|
||
// 先存款
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), 1000 * 1e18);
|
||
vault.depositYT(1000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
// 快进到赎回时间
|
||
vm.warp(vault.nextRedemptionTime() + 1);
|
||
|
||
// 暂停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 requestId = vault.withdrawYT(500 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
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 {
|
||
vault = _createVault();
|
||
|
||
// 存款
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), 10000 * 1e18);
|
||
vault.depositYT(10000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
// 暂停vault
|
||
factory.pauseVault(address(vault));
|
||
|
||
// Manager尝试提取应该失败
|
||
vm.startPrank(manager);
|
||
vm.expectRevert(abi.encodeWithSignature("EnforcedPause()"));
|
||
vault.withdrawForManagement(manager, 5000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
// 恢复后应该可以提取
|
||
factory.unpauseVault(address(vault));
|
||
|
||
vm.startPrank(manager);
|
||
vault.withdrawForManagement(manager, 5000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
assertEq(vault.managedAssets(), 5000 * 1e18, "withdraw for management should work after unpause");
|
||
}
|
||
|
||
function test_46_CannotDepositManagedAssetsWhenPaused() public {
|
||
vault = _createVault();
|
||
|
||
// 存款并提取
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), 10000 * 1e18);
|
||
vault.depositYT(10000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
vm.startPrank(manager);
|
||
vault.withdrawForManagement(manager, 5000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
// 暂停vault
|
||
factory.pauseVault(address(vault));
|
||
|
||
// Manager尝试归还应该失败
|
||
vm.startPrank(manager);
|
||
wusd.approve(address(vault), 5000 * 1e18);
|
||
|
||
vm.expectRevert(abi.encodeWithSignature("EnforcedPause()"));
|
||
vault.depositManagedAssets(5000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
// 恢复后应该可以归还
|
||
factory.unpauseVault(address(vault));
|
||
|
||
vm.startPrank(manager);
|
||
vault.depositManagedAssets(5000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
assertEq(vault.managedAssets(), 0, "deposit managed assets should work after unpause");
|
||
}
|
||
|
||
function test_47_QueryFunctionsWorkWhenPaused() public {
|
||
vault = _createVault();
|
||
|
||
// 存款
|
||
vm.startPrank(user1);
|
||
wusd.approve(address(vault), 1000 * 1e18);
|
||
vault.depositYT(1000 * 1e18);
|
||
vm.stopPrank();
|
||
|
||
// 暂停vault
|
||
factory.pauseVault(address(vault));
|
||
|
||
// 查询函数应该仍然可用
|
||
assertEq(vault.totalSupply(), 1000 * 1e18, "totalSupply should work");
|
||
assertEq(vault.balanceOf(user1), 1000 * 1e18, "balanceOf should work");
|
||
assertEq(vault.totalAssets(), 1000 * 1e18, "totalAssets should work");
|
||
assertEq(vault.idleAssets(), 1000 * 1e18, "idleAssets should work");
|
||
|
||
// 预览函数应该可用
|
||
uint256 previewBuy = vault.previewBuy(100 * 1e18);
|
||
assertEq(previewBuy, 100 * 1e18, "previewBuy should work");
|
||
|
||
uint256 previewSell = vault.previewSell(100 * 1e18);
|
||
assertEq(previewSell, 100 * 1e18, "previewSell should work");
|
||
|
||
// getVaultInfo应该可用
|
||
(
|
||
uint256 totalAssets,
|
||
uint256 idleAssets,
|
||
,,,,,
|
||
) = vault.getVaultInfo();
|
||
|
||
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); // 现在已处理
|
||
}
|
||
}
|