Files
assetxContracts/test/YTLp.t.sol
2025-12-23 14:05:41 +08:00

1639 lines
58 KiB
Solidity
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "../contracts/ytLp/tokens/USDY.sol";
import "../contracts/ytLp/tokens/YTLPToken.sol";
import "../contracts/ytLp/core/YTPriceFeed.sol";
import "../contracts/ytLp/core/YTVault.sol";
import "../contracts/ytLp/core/YTPoolManager.sol";
import "../contracts/ytLp/core/YTRewardRouter.sol";
import "../contracts/ytVault/YTAssetVault.sol";
import "../contracts/ytVault/YTAssetFactory.sol";
import "../contracts/ytLp/tokens/WUSD.sol";
contract YTLpTest is Test {
address deployer;
address user1;
address user2;
address user3;
USDY usdy;
YTLPToken ytlp;
YTPriceFeed priceFeed;
YTVault vault;
YTPoolManager poolManager;
YTRewardRouter router;
WUSD wusd;
YTAssetFactory factory;
YTAssetVault ytTokenA;
YTAssetVault ytTokenB;
YTAssetVault ytTokenC;
uint256 constant PRICE_PRECISION = 1e30;
uint256 constant BASIS_POINTS = 10000;
function setUp() public {
deployer = address(this);
user1 = address(0x1);
user2 = address(0x2);
user3 = address(0x3);
vm.deal(user1, 100 ether);
vm.deal(user2, 100 ether);
vm.deal(user3, 100 ether);
// ✅ 使用代理模式部署 WUSD
WUSD wusdImpl = new WUSD();
bytes memory wusdInitData = abi.encodeWithSelector(
WUSD.initialize.selector,
"Wrapped USD",
"WUSD"
);
ERC1967Proxy wusdProxy = new ERC1967Proxy(address(wusdImpl), wusdInitData);
wusd = WUSD(address(wusdProxy));
// ✅ 使用代理模式部署 USDY
USDY usdyImpl = new USDY();
bytes memory usdyInitData = abi.encodeWithSelector(USDY.initialize.selector);
ERC1967Proxy usdyProxy = new ERC1967Proxy(address(usdyImpl), usdyInitData);
usdy = USDY(address(usdyProxy));
// ✅ 使用代理模式部署 YTLPToken
YTLPToken ytlpImpl = new YTLPToken();
bytes memory ytlpInitData = abi.encodeWithSelector(YTLPToken.initialize.selector);
ERC1967Proxy ytlpProxy = new ERC1967Proxy(address(ytlpImpl), ytlpInitData);
ytlp = YTLPToken(address(ytlpProxy));
// ✅ 使用代理模式部署 YTPriceFeed
YTPriceFeed priceFeedImpl = new YTPriceFeed();
bytes memory priceFeedInitData = abi.encodeWithSelector(
YTPriceFeed.initialize.selector,
address(wusd)
);
ERC1967Proxy priceFeedProxy = new ERC1967Proxy(address(priceFeedImpl), priceFeedInitData);
priceFeed = YTPriceFeed(address(priceFeedProxy));
// ✅ 使用代理模式部署 YTVault
YTVault vaultImpl = new YTVault();
bytes memory vaultInitData = abi.encodeWithSelector(
YTVault.initialize.selector,
address(usdy),
address(priceFeed)
);
ERC1967Proxy vaultProxy = new ERC1967Proxy(address(vaultImpl), vaultInitData);
vault = YTVault(address(vaultProxy));
// ✅ 使用代理模式部署 YTPoolManager
YTPoolManager poolManagerImpl = new YTPoolManager();
bytes memory poolManagerInitData = abi.encodeWithSelector(
YTPoolManager.initialize.selector,
address(vault),
address(usdy),
address(ytlp),
15 * 60
);
ERC1967Proxy poolManagerProxy = new ERC1967Proxy(address(poolManagerImpl), poolManagerInitData);
poolManager = YTPoolManager(address(poolManagerProxy));
// ✅ 使用代理模式部署 YTRewardRouter
YTRewardRouter routerImpl = new YTRewardRouter();
bytes memory routerInitData = abi.encodeWithSelector(
YTRewardRouter.initialize.selector,
address(usdy),
address(ytlp),
address(poolManager),
address(vault)
);
ERC1967Proxy routerProxy = new ERC1967Proxy(address(routerImpl), routerInitData);
router = YTRewardRouter(address(routerProxy));
// ✅ 部署YTAssetVault实现合约不初始化
YTAssetVault ytVaultImpl = new YTAssetVault();
// ✅ 使用代理模式部署 YTAssetFactory
YTAssetFactory factoryImpl = new YTAssetFactory();
bytes memory factoryInitData = abi.encodeWithSelector(
YTAssetFactory.initialize.selector,
address(ytVaultImpl),
1000000 ether // defaultHardCap
);
ERC1967Proxy factoryProxy = new ERC1967Proxy(address(factoryImpl), factoryInitData);
factory = YTAssetFactory(address(factoryProxy));
// 通过factory创建YTAssetVault代币
address ytTokenAAddr = factory.createVault(
"YT Token A",
"YT-A",
deployer, // manager
1000000 ether, // hardCap
address(wusd),
block.timestamp + 365 days, // redemptionTime
PRICE_PRECISION, // wusdPrice = 1.0
PRICE_PRECISION // ytPrice = 1.0
);
ytTokenA = YTAssetVault(ytTokenAAddr);
address ytTokenBAddr = factory.createVault(
"YT Token B",
"YT-B",
deployer,
1000000 ether,
address(wusd),
block.timestamp + 365 days,
PRICE_PRECISION,
PRICE_PRECISION
);
ytTokenB = YTAssetVault(ytTokenBAddr);
address ytTokenCAddr = factory.createVault(
"YT Token C",
"YT-C",
deployer,
1000000 ether,
address(wusd),
block.timestamp + 365 days,
PRICE_PRECISION,
PRICE_PRECISION
);
ytTokenC = YTAssetVault(ytTokenCAddr);
// 配置权限
usdy.addVault(address(vault));
usdy.addVault(address(poolManager));
ytlp.setMinter(address(poolManager), true);
vault.setPoolManager(address(poolManager));
vault.setSwapper(address(router), true);
poolManager.setHandler(address(router), true);
// 配置参数
vault.setSwapFees(30, 4, 50, 20); // 0.3%, 0.04%, 动态税率
vault.setDynamicFees(false); // 先关闭动态费率,便于精确测试
vault.setMaxSwapSlippageBps(1000);
priceFeed.setMaxPriceChangeBps(500);
// 设置WUSD价格来源使用ytTokenA
priceFeed.setWusdPriceSource(address(ytTokenA));
// 配置白名单
vault.setWhitelistedToken(address(ytTokenA), 18, 4000, 45000000 ether, false);
vault.setWhitelistedToken(address(ytTokenB), 18, 3000, 35000000 ether, false);
vault.setWhitelistedToken(address(ytTokenC), 18, 2000, 25000000 ether, false);
// 初始化价格 $1.00
priceFeed.forceUpdatePrice(address(ytTokenA), 1e30);
priceFeed.forceUpdatePrice(address(ytTokenB), 1e30);
priceFeed.forceUpdatePrice(address(ytTokenC), 1e30);
// 不设置价差(便于精确计算)
// 为测试用户铸造YT代币需要先给合约WUSD再depositYT
uint256 initialAmount = 10000 ether;
// 给deployer一些WUSD用于购买YT
wusd.mint(deployer, 30000 ether);
// Deployer购买YT代币
wusd.approve(address(ytTokenA), initialAmount);
ytTokenA.depositYT(initialAmount);
wusd.approve(address(ytTokenB), initialAmount);
ytTokenB.depositYT(initialAmount);
wusd.approve(address(ytTokenC), initialAmount);
ytTokenC.depositYT(initialAmount);
// 转账给用户
ytTokenA.transfer(user1, 5000 ether);
ytTokenB.transfer(user1, 5000 ether);
ytTokenC.transfer(user1, 5000 ether);
ytTokenA.transfer(user2, 3000 ether);
ytTokenB.transfer(user2, 3000 ether);
// 给用户一些WUSD用于后续可能的操作
wusd.mint(user1, 10000 ether);
wusd.mint(user2, 10000 ether);
wusd.mint(user3, 10000 ether);
}
// ==================== 1. 部署和初始化测试 ====================
function test_01_DeployContracts() public view {
assertEq(usdy.name(), "YT USD");
assertEq(usdy.symbol(), "USDY");
assertEq(usdy.decimals(), 18);
assertEq(ytlp.name(), "YT Liquidity Provider");
assertEq(ytlp.symbol(), "ytLP");
assertEq(ytlp.decimals(), 18);
assertEq(vault.ytPoolManager(), address(poolManager));
assertEq(poolManager.ytVault(), address(vault));
}
function test_02_ConfigurePermissions() public view {
assertTrue(usdy.vaults(address(vault)));
assertTrue(usdy.vaults(address(poolManager)));
assertTrue(ytlp.isMinter(address(poolManager)));
assertTrue(poolManager.isHandler(address(router)));
assertTrue(vault.isSwapper(address(router)));
}
function test_03_ConfigureWhitelist() public view {
assertTrue(vault.whitelistedTokens(address(ytTokenA)));
assertTrue(vault.whitelistedTokens(address(ytTokenB)));
assertTrue(vault.whitelistedTokens(address(ytTokenC)));
assertEq(vault.tokenWeights(address(ytTokenA)), 4000);
assertEq(vault.tokenWeights(address(ytTokenB)), 3000);
assertEq(vault.tokenWeights(address(ytTokenC)), 2000);
assertEq(vault.totalTokenWeights(), 9000);
assertFalse(vault.stableTokens(address(ytTokenA)));
assertTrue(vault.stableTokens(address(usdy))); // USDY被标记为稳定币
}
function test_04_ConfigureFees() public view {
assertEq(vault.swapFeeBasisPoints(), 30); // 0.3%
assertEq(vault.stableSwapFeeBasisPoints(), 4); // 0.04%
assertEq(vault.taxBasisPoints(), 50);
assertEq(vault.stableTaxBasisPoints(), 20);
assertFalse(vault.hasDynamicFees()); // 已关闭便于测试
}
function test_05_YTAssetVaultBasics() public view {
assertEq(ytTokenA.name(), "YT Token A");
assertEq(ytTokenA.symbol(), "YT-A");
assertEq(ytTokenA.ytPrice(), PRICE_PRECISION);
assertEq(ytTokenA.wusdPrice(), PRICE_PRECISION);
}
// ==================== 2. 添加流动性测试(精确计算)====================
function test_06_FirstAddLiquidity() public {
uint256 depositAmount = 1000 ether;
vm.startPrank(user1);
ytTokenA.approve(address(router), depositAmount);
uint256 ytLPBefore = ytlp.balanceOf(user1);
assertEq(ytLPBefore, 0);
// 添加流动性
uint256 ytLPReceived = router.addLiquidity(
address(ytTokenA),
depositAmount,
0,
0
);
vm.stopPrank();
// 精确计算预期值无价差0.3%手续费)
// 存入: 1000 YT-A @ $1.00
// 手续费: 1000 × 0.3% = 3 个代币
// 扣费后: 997 个代币
// USDY价值: 997 × $1.00 = 997 USDY
// 首次铸造: ytLP = USDY = 997 ether
uint256 expectedYtLP = 997 ether;
assertEq(ytLPReceived, expectedYtLP, "ytLP amount incorrect");
assertEq(ytlp.balanceOf(user1), expectedYtLP, "user1 balance incorrect");
assertEq(ytlp.totalSupply(), expectedYtLP, "total supply incorrect");
// 验证池子状态
assertEq(vault.poolAmounts(address(ytTokenA)), depositAmount, "pool amount incorrect");
assertEq(vault.usdyAmounts(address(ytTokenA)), expectedYtLP, "usdy amount incorrect");
// 验证ytLP价格
uint256 ytLPPrice = poolManager.getPrice(true);
// AUM = 1000 (池子有1000个代币) × $1.00 = $1000
// Supply = 997 ytLP
// Price = AUM * 1e18 / Supply (带18位精度)
assertTrue(ytLPPrice > 1 ether, "ytLP price should be > $1");
}
function test_07_SecondAddLiquidity() public {
// 用户1先添加
uint256 firstAmount = 1000 ether;
vm.startPrank(user1);
ytTokenA.approve(address(router), firstAmount);
router.addLiquidity(address(ytTokenA), firstAmount, 0, 0);
vm.stopPrank();
uint256 user1YtLP = ytlp.balanceOf(user1); // 997 ether
// 用户2添加
uint256 secondAmount = 2000 ether;
vm.startPrank(user2);
ytTokenB.approve(address(router), secondAmount);
uint256 ytLPReceived = router.addLiquidity(
address(ytTokenB),
secondAmount,
0,
0
);
vm.stopPrank();
// 精确计算
uint256 expectedYtLP = 1988.018 ether;
assertEq(ytLPReceived, expectedYtLP, "second add ytLP amount incorrect");
assertEq(ytlp.balanceOf(user2), expectedYtLP, "user2 balance incorrect");
assertEq(ytlp.totalSupply(), user1YtLP + expectedYtLP, "total supply incorrect");
}
function test_08_AddLiquiditySlippageProtection() public {
uint256 amount = 1000 ether;
uint256 tooHighMinYtLP = 1500 ether; // 设置过高的最小值
vm.startPrank(user1);
ytTokenA.approve(address(router), amount);
vm.expectRevert(abi.encodeWithSignature("InsufficientOutput()"));
router.addLiquidity(
address(ytTokenA),
amount,
0,
tooHighMinYtLP
);
vm.stopPrank();
}
// ==================== 3. 移除流动性测试 ====================
function test_09_RemoveLiquidity() public {
// 先添加流动性
uint256 addAmount = 1000 ether;
vm.startPrank(user1);
ytTokenA.approve(address(router), addAmount);
router.addLiquidity(address(ytTokenA), addAmount, 0, 0);
uint256 ytLPBalance = ytlp.balanceOf(user1); // 997 ether
// 等待冷却期
vm.warp(block.timestamp + 15 * 60 + 1);
uint256 tokenBalanceBefore = ytTokenA.balanceOf(user1);
// 移除流动性
uint256 amountOut = router.removeLiquidity(
address(ytTokenA),
ytLPBalance,
0,
user1
);
vm.stopPrank();
uint256 expectedOut = 997 ether;
assertEq(amountOut, expectedOut, "remove liquidity amount incorrect");
assertEq(ytTokenA.balanceOf(user1), tokenBalanceBefore + expectedOut, "user1 final balance incorrect");
assertEq(ytlp.balanceOf(user1), 0, "ytLP should be burned");
assertEq(ytlp.totalSupply(), 0, "ytLP supply should be 0");
}
function test_10_RemoveLiquidityCooldownProtection() public {
uint256 amount = 1000 ether;
vm.startPrank(user1);
ytTokenA.approve(address(router), amount);
router.addLiquidity(address(ytTokenA), amount, 0, 0);
uint256 ytLPBalance = ytlp.balanceOf(user1);
// 不等待冷却期,直接移除
vm.expectRevert(abi.encodeWithSignature("CooldownNotPassed()"));
router.removeLiquidity(address(ytTokenA), ytLPBalance, 0, user1);
vm.stopPrank();
}
// ==================== 4. Swap测试 ====================
function test_11_SwapYTTokens() public {
// 先为池子添加流动性
uint256 liquidityAmount = 2000 ether;
vm.startPrank(user1);
ytTokenA.approve(address(router), liquidityAmount);
router.addLiquidity(address(ytTokenA), liquidityAmount, 0, 0);
ytTokenB.approve(address(router), liquidityAmount);
router.addLiquidity(address(ytTokenB), liquidityAmount, 0, 0);
vm.stopPrank();
// Swap测试
uint256 swapAmount = 100 ether;
vm.startPrank(user2);
ytTokenA.approve(address(router), swapAmount);
uint256 balanceBBefore = ytTokenB.balanceOf(user2);
uint256 amountOut = router.swapYT(
address(ytTokenA),
address(ytTokenB),
swapAmount,
0,
user2
);
vm.stopPrank();
uint256 expectedOut = 99.7 ether;
assertEq(amountOut, expectedOut, "swap amount incorrect");
assertEq(ytTokenB.balanceOf(user2), balanceBBefore + expectedOut, "user2 balance incorrect");
}
function test_12_SwapSameTokenReverts() public {
uint256 amount = 1000 ether;
vm.startPrank(user1);
ytTokenA.approve(address(router), amount);
router.addLiquidity(address(ytTokenA), amount, 0, 0);
ytTokenA.approve(address(router), 100 ether);
vm.expectRevert(abi.encodeWithSignature("SameToken()"));
router.swapYT(address(ytTokenA), address(ytTokenA), 100 ether, 0, user1);
vm.stopPrank();
}
// ==================== 5. 价格测试 ====================
function test_13_PriceWithoutSpread() public view {
// 未设置价差时
uint256 price = priceFeed.getPrice(address(ytTokenA), true);
assertEq(price, 1e30, "price should be $1.00");
uint256 maxPrice = priceFeed.getMaxPrice(address(ytTokenA));
uint256 minPrice = priceFeed.getMinPrice(address(ytTokenA));
assertEq(maxPrice, 1e30, "maxPrice should equal base price");
assertEq(minPrice, 1e30, "minPrice should equal base price");
}
function test_14_PriceWithSpread() public {
// 设置0.2%价差 (20 bps)
priceFeed.setSpreadBasisPoints(address(ytTokenA), 20);
uint256 basePrice = 1e30; // $1.00
uint256 maxPrice = priceFeed.getMaxPrice(address(ytTokenA));
uint256 minPrice = priceFeed.getMinPrice(address(ytTokenA));
// MaxPrice = $1.00 × 1.002 = $1.002
uint256 expectedMax = basePrice * 10020 / 10000;
assertEq(maxPrice, expectedMax, "maxPrice with spread incorrect");
// MinPrice = $1.00 × 0.998 = $0.998
uint256 expectedMin = basePrice * 9980 / 10000;
assertEq(minPrice, expectedMin, "minPrice with spread incorrect");
// 清除价差设置
priceFeed.setSpreadBasisPoints(address(ytTokenA), 0);
}
function test_15_WUSDPriceFromVault() public view {
// WUSD价格应该从ytTokenA读取
uint256 wusdPrice = priceFeed.getPrice(address(wusd), true);
// 应该等于ytTokenA的wusdPrice
assertEq(wusdPrice, PRICE_PRECISION, "WUSD price should be 1.0");
}
// ==================== 6. YTAssetVault特定功能测试 ====================
function test_16_UpdateYTPrices() public {
uint256 newWusdPrice = 1.01e30; // $1.01
uint256 newYtPrice = 1.05e30; // $1.05
// 通过factory更新价格
factory.updateVaultPrices(address(ytTokenA), newWusdPrice, newYtPrice);
assertEq(ytTokenA.wusdPrice(), newWusdPrice, "wusdPrice update failed");
assertEq(ytTokenA.ytPrice(), newYtPrice, "ytPrice update failed");
// 重置价格
factory.updateVaultPrices(address(ytTokenA), PRICE_PRECISION, PRICE_PRECISION);
}
function test_17_BuyYTWithWUSD() public {
uint256 wusdAmount = 1000 ether;
vm.startPrank(user1);
wusd.approve(address(ytTokenA), wusdAmount);
uint256 ytBefore = ytTokenA.balanceOf(user1);
uint256 ytReceived = ytTokenA.depositYT(wusdAmount);
uint256 ytAfter = ytTokenA.balanceOf(user1);
vm.stopPrank();
// 价格都是1.0应该1:1兑换
assertEq(ytReceived, wusdAmount, "YT amount should equal WUSD amount");
assertEq(ytAfter - ytBefore, wusdAmount, "YT balance incorrect");
}
function test_18_HardCapProtection() public {
// 获取当前totalSupply
uint256 currentSupply = ytTokenA.totalSupply(); // 10000 ether
// 通过factory设置hardCap为当前供应量 + 500允许少量增加
uint256 newHardCap = currentSupply + 500 ether;
factory.setHardCap(address(ytTokenA), newHardCap);
vm.startPrank(user1);
wusd.approve(address(ytTokenA), 1000 ether);
// 尝试存入1000 ether会超过hardCap应该revert
vm.expectRevert(abi.encodeWithSignature("HardCapExceeded()"));
ytTokenA.depositYT(1000 ether);
vm.stopPrank();
// 恢复hardCap
factory.setHardCap(address(ytTokenA), 1000000 ether);
}
// ==================== 7. 权限测试 ====================
function test_19_OnlyFactoryCanUpdatePrices() public {
vm.startPrank(user1);
vm.expectRevert(abi.encodeWithSignature("Forbidden()"));
ytTokenA.updatePrices(1e30, 1e30);
vm.stopPrank();
}
function test_20_OnlyGovCanSetWhitelist() public {
vm.startPrank(user1);
vm.expectRevert(abi.encodeWithSignature("Forbidden()"));
vault.setWhitelistedToken(address(0x123), 18, 1000, 1000000 ether, false);
vm.stopPrank();
}
// ==================== 8. 完整流程测试 ====================
function test_21_CompleteFlow() public {
console.log("=== Complete Flow Test ===");
// 步骤1: User1添加YT-A流动性
vm.startPrank(user1);
ytTokenA.approve(address(router), 1000 ether);
uint256 ytLP1 = router.addLiquidity(address(ytTokenA), 1000 ether, 0, 0);
console.log("User1 added 1000 YT-A, received ytLP:", ytLP1);
assertEq(ytLP1, 997 ether);
vm.stopPrank();
// 步骤2: User1添加YT-B流动性
vm.startPrank(user1);
ytTokenB.approve(address(router), 1000 ether);
uint256 ytLP1b = router.addLiquidity(address(ytTokenB), 1000 ether, 0, 0);
console.log("User1 added 1000 YT-B, received ytLP:", ytLP1b);
assertEq(ytLP1b, 994.009 ether);
vm.stopPrank();
uint256 totalYtLP = ytlp.balanceOf(user1);
console.log("User1 total ytLP:", totalYtLP);
// 步骤3: User2执行Swap
vm.startPrank(user2);
ytTokenA.approve(address(router), 100 ether);
uint256 swapOut = router.swapYT(address(ytTokenA), address(ytTokenB), 100 ether, 0, user2);
console.log("User2 swapped 100 YT-A, received YT-B:", swapOut);
assertEq(swapOut, 99.7 ether);
vm.stopPrank();
// 步骤4: 等待冷却期后User1移除流动性
vm.warp(block.timestamp + 16 * 60);
vm.startPrank(user1);
uint256 removeAmount = totalYtLP / 2; // 移除一半
uint256 tokenOut = router.removeLiquidity(address(ytTokenA), removeAmount, 0, user1);
console.log("User1 removed half ytLP, received YT-A:", tokenOut);
vm.stopPrank();
assertTrue(tokenOut > 990 ether && tokenOut < 1000 ether, "token out should be around 997");
}
// ==================== 9. 手续费测试 ====================
function test_22_SwapFeesAccumulation() public {
// 添加初始流动性
uint256 liquidityAmount = 2000 ether;
vm.startPrank(user1);
ytTokenA.approve(address(router), liquidityAmount);
router.addLiquidity(address(ytTokenA), liquidityAmount, 0, 0);
ytTokenB.approve(address(router), liquidityAmount);
router.addLiquidity(address(ytTokenB), liquidityAmount, 0, 0);
uint256 ytLPBefore = ytlp.balanceOf(user1);
uint256 priceBefore = poolManager.getPrice(true);
vm.stopPrank();
// 执行swap累积手续费使用user2
uint256 swapAmount = 500 ether;
vm.startPrank(user2);
// Swap 1: YT-A → YT-B
ytTokenA.approve(address(router), swapAmount);
router.swapYT(address(ytTokenA), address(ytTokenB), swapAmount, 0, user2);
// Swap 2: YT-B → YT-A
ytTokenB.approve(address(router), swapAmount);
router.swapYT(address(ytTokenB), address(ytTokenA), swapAmount, 0, user2);
vm.stopPrank();
uint256 priceAfter = poolManager.getPrice(true);
// ytLP价格应该增长手续费留在池中
assertTrue(priceAfter > priceBefore, "ytLP price should increase");
// user1的ytLP数量不变
assertEq(ytlp.balanceOf(user1), ytLPBefore, "ytLP balance should not change");
}
function test_23_GetSwapFeeBasisPoints() public view {
uint256 usdyAmount = 1000 ether;
// YT代币之间互换
uint256 feeBps = vault.getSwapFeeBasisPoints(
address(ytTokenA),
address(ytTokenB),
usdyAmount
);
assertEq(feeBps, 30, "YT swap fee should be 30 bps");
// 赎回费率
uint256 redemptionFeeBps = vault.getRedemptionFeeBasisPoints(
address(ytTokenA),
usdyAmount
);
assertEq(redemptionFeeBps, 30, "redemption fee should be 30 bps");
}
// ==================== 10. 白名单管理测试 ====================
function test_24_AddWhitelistToken() public {
// 通过factory创建新的YTAssetVault
address ytTokenDAddr = factory.createVault(
"YT Token D",
"YT-D",
deployer,
1000000 ether,
address(wusd),
block.timestamp + 365 days,
PRICE_PRECISION,
PRICE_PRECISION
);
YTAssetVault ytTokenD = YTAssetVault(ytTokenDAddr);
// 铸造一些YT-D
wusd.mint(deployer, 1000 ether);
wusd.approve(address(ytTokenD), 1000 ether);
ytTokenD.depositYT(1000 ether);
// 添加到白名单
vault.setWhitelistedToken(address(ytTokenD), 18, 1000, 10000000 ether, false);
// 验证
assertTrue(vault.whitelistedTokens(address(ytTokenD)), "should be whitelisted");
assertEq(vault.tokenWeights(address(ytTokenD)), 1000, "weight incorrect");
assertEq(vault.totalTokenWeights(), 10000, "total weight incorrect");
// 初始化价格
priceFeed.forceUpdatePrice(address(ytTokenD), 1e30);
// 验证可以添加流动性
vm.startPrank(deployer);
ytTokenD.approve(address(router), 100 ether);
uint256 ytLPReceived = router.addLiquidity(address(ytTokenD), 100 ether, 0, 0);
vm.stopPrank();
assertEq(ytLPReceived, 99.7 ether, "first liquidity for new token incorrect");
}
function test_25_RemoveWhitelistToken() public {
// 确保池子是空的
assertEq(vault.poolAmounts(address(ytTokenC)), 0, "pool should be empty");
uint256 weightBefore = vault.totalTokenWeights();
// 移除白名单
vault.clearWhitelistedToken(address(ytTokenC));
// 验证
assertFalse(vault.whitelistedTokens(address(ytTokenC)), "should not be whitelisted");
assertEq(vault.tokenWeights(address(ytTokenC)), 0, "weight should be 0");
assertEq(vault.totalTokenWeights(), weightBefore - 2000, "total weight incorrect");
// 验证无法添加流动性
vm.startPrank(user1);
ytTokenC.approve(address(router), 100 ether);
vm.expectRevert(abi.encodeWithSignature("TokenNotWhitelisted()"));
router.addLiquidity(address(ytTokenC), 100 ether, 0, 0);
vm.stopPrank();
}
function test_26_UpdateTokenWeight() public {
uint256 oldWeight = vault.tokenWeights(address(ytTokenA));
assertEq(oldWeight, 4000);
// 更新权重
vault.setWhitelistedToken(address(ytTokenA), 18, 5000, 45000000 ether, false);
// 验证
assertEq(vault.tokenWeights(address(ytTokenA)), 5000, "updated weight incorrect");
assertEq(vault.totalTokenWeights(), 10000, "total weight after update incorrect");
}
// ==================== 11. 查询函数测试 ====================
function test_27_GetPoolValue() public {
vm.startPrank(user1);
ytTokenA.approve(address(router), 1000 ether);
router.addLiquidity(address(ytTokenA), 1000 ether, 0, 0);
ytTokenB.approve(address(router), 2000 ether);
router.addLiquidity(address(ytTokenB), 2000 ether, 0, 0);
vm.stopPrank();
// 获取池子总价值
uint256 poolValue = vault.getPoolValue(true);
// 池中有: 1000 YT-A + 2000 YT-B = $3000
uint256 expectedValue = 3000 ether;
assertEq(poolValue, expectedValue, "pool value incorrect");
}
function test_28_GetTargetUsdyAmount() public {
vm.startPrank(user1);
ytTokenA.approve(address(router), 1000 ether);
router.addLiquidity(address(ytTokenA), 1000 ether, 0, 0);
vm.stopPrank();
uint256 totalUsdy = usdy.totalSupply();
uint256 targetUsdy = vault.getTargetUsdyAmount(address(ytTokenA));
// YT-A权重 4000, 总权重 9000
uint256 expectedTarget = totalUsdy * 4000 / 9000;
assertEq(targetUsdy, expectedTarget, "target usdy amount incorrect");
}
function test_29_GetAccountValue() public {
uint256 amount = 1000 ether;
vm.startPrank(user1);
ytTokenA.approve(address(router), amount);
router.addLiquidity(address(ytTokenA), amount, 0, 0);
vm.stopPrank();
uint256 accountValue = router.getAccountValue(user1);
// 账户价值应该接近1000 USDY
assertTrue(accountValue >= 995 ether && accountValue <= 1005 ether, "account value should be around 1000");
}
// ==================== 12. 动态手续费测试 ====================
function test_30_DynamicFeesDisabled() public view {
assertFalse(vault.hasDynamicFees());
uint256 feeBps = vault.getFeeBasisPoints(
address(ytTokenA),
1000 ether,
30,
50,
true
);
assertEq(feeBps, 30, "should return base fee when dynamic disabled");
}
function test_31_DynamicFeesEnabled() public {
vault.setDynamicFees(true);
vm.startPrank(user1);
// 大量添加YT-A
ytTokenA.approve(address(router), 3000 ether);
router.addLiquidity(address(ytTokenA), 3000 ether, 0, 0);
// 少量添加YT-B
ytTokenB.approve(address(router), 500 ether);
router.addLiquidity(address(ytTokenB), 500 ether, 0, 0);
vm.stopPrank();
uint256 usdyAmount = 100 ether;
// YT-A → YT-B (恶化平衡,费率更高)
uint256 feeHigher = vault.getSwapFeeBasisPoints(
address(ytTokenA),
address(ytTokenB),
usdyAmount
);
// YT-B → YT-A (改善平衡,费率更低)
uint256 feeLower = vault.getSwapFeeBasisPoints(
address(ytTokenB),
address(ytTokenA),
usdyAmount
);
assertTrue(feeHigher > 30, "fee should be higher when worsening balance");
assertTrue(feeLower < 30, "fee should be lower when improving balance");
vault.setDynamicFees(false);
}
// ==================== 13. 价格预言机测试 ====================
function test_32_SetSpreadBasisPoints() public {
uint256 spreadBps = 20;
priceFeed.setSpreadBasisPoints(address(ytTokenA), spreadBps);
assertEq(priceFeed.spreadBasisPoints(address(ytTokenA)), spreadBps);
}
function test_33_SpreadBasisPointsTooHigh() public {
uint256 tooHighSpread = 300; // 3% > 最大2%
vm.expectRevert(abi.encodeWithSignature("SpreadTooHigh()"));
priceFeed.setSpreadBasisPoints(address(ytTokenA), tooHighSpread);
}
function test_34_BatchSetSpread() public {
address[] memory tokens = new address[](3);
tokens[0] = address(ytTokenA);
tokens[1] = address(ytTokenB);
tokens[2] = address(ytTokenC);
uint256[] memory spreads = new uint256[](3);
spreads[0] = 10;
spreads[1] = 20;
spreads[2] = 30;
priceFeed.setSpreadBasisPointsForMultiple(tokens, spreads);
assertEq(priceFeed.spreadBasisPoints(address(ytTokenA)), 10);
assertEq(priceFeed.spreadBasisPoints(address(ytTokenB)), 20);
assertEq(priceFeed.spreadBasisPoints(address(ytTokenC)), 30);
// 清除
spreads[0] = 0;
spreads[1] = 0;
spreads[2] = 0;
priceFeed.setSpreadBasisPointsForMultiple(tokens, spreads);
}
function test_35_PriceProtectionMaxChange() public {
priceFeed.forceUpdatePrice(address(ytTokenA), 1e30);
uint256 tooHighPrice = 1.06e30; // +6%
priceFeed.forceUpdatePrice(address(ytTokenA), tooHighPrice);
assertEq(priceFeed.maxPriceChangeBps(), 500, "max change should be 5%");
}
// ==================== 14. AUM计算测试 ====================
function test_36_GetAumWithMaximise() public {
vm.startPrank(user1);
ytTokenA.approve(address(router), 1000 ether);
router.addLiquidity(address(ytTokenA), 1000 ether, 0, 0);
vm.stopPrank();
uint256 aumMax = poolManager.getAumInUsdy(true);
uint256 aumMin = poolManager.getAumInUsdy(false);
// 无价差时,两者应该相等
assertEq(aumMax, aumMin, "aum should be equal without spread");
assertEq(aumMax, 1000 ether, "aum should be $1000");
}
function test_37_GetAumWithSpread() public {
priceFeed.setSpreadBasisPoints(address(ytTokenA), 20); // 0.2%
vm.startPrank(user1);
ytTokenA.approve(address(router), 1000 ether);
router.addLiquidity(address(ytTokenA), 1000 ether, 0, 0);
vm.stopPrank();
uint256 aumMax = poolManager.getAumInUsdy(true);
uint256 aumMin = poolManager.getAumInUsdy(false);
assertEq(aumMax, 1002 ether, "aum max with spread incorrect");
assertEq(aumMin, 998 ether, "aum min with spread incorrect");
priceFeed.setSpreadBasisPoints(address(ytTokenA), 0);
}
// ==================== 15. 多用户场景测试 ====================
function test_38_MultipleUsersAddLiquidity() public {
// User1 添加
vm.startPrank(user1);
ytTokenA.approve(address(router), 1000 ether);
uint256 ytLP1 = router.addLiquidity(address(ytTokenA), 1000 ether, 0, 0);
vm.stopPrank();
// User2 添加
vm.startPrank(user2);
ytTokenA.approve(address(router), 2000 ether);
uint256 ytLP2 = router.addLiquidity(address(ytTokenA), 2000 ether, 0, 0);
vm.stopPrank();
assertEq(ytLP1, 997 ether, "user1 ytLP incorrect");
assertEq(ytLP2, 1988.018 ether, "user2 ytLP incorrect");
// 份额比例
uint256 total = ytlp.totalSupply();
uint256 user1Share = ytLP1 * 10000 / total;
uint256 user2Share = ytLP2 * 10000 / total;
assertApproxEqAbs(user1Share, 3340, 1, "user1 share incorrect");
assertApproxEqAbs(user2Share, 6660, 1, "user2 share incorrect");
}
function test_39_RemoveLiquidityPartial() public {
uint256 addAmount = 1000 ether;
vm.startPrank(user1);
ytTokenA.approve(address(router), addAmount);
router.addLiquidity(address(ytTokenA), addAmount, 0, 0);
uint256 ytLPBalance = ytlp.balanceOf(user1);
uint256 removeAmount = ytLPBalance / 2;
vm.warp(block.timestamp + 15 * 60 + 1);
uint256 amountOut = router.removeLiquidity(
address(ytTokenA),
removeAmount,
0,
user1
);
vm.stopPrank();
uint256 expectedOut = 498.5 ether;
assertEq(amountOut, expectedOut, "partial remove amount incorrect");
assertEq(ytlp.balanceOf(user1), removeAmount, "remaining ytLP incorrect");
}
// ==================== 16. 安全功能测试 ====================
function test_40_EmergencyMode() public {
vm.startPrank(user1);
ytTokenA.approve(address(router), 1000 ether);
router.addLiquidity(address(ytTokenA), 1000 ether, 0, 0);
vm.stopPrank();
vault.setEmergencyMode(true);
vm.startPrank(user2);
ytTokenA.approve(address(router), 100 ether);
vm.expectRevert(abi.encodeWithSignature("EmergencyMode()"));
router.addLiquidity(address(ytTokenA), 100 ether, 0, 0);
vm.expectRevert(abi.encodeWithSignature("EmergencyMode()"));
router.swapYT(address(ytTokenA), address(ytTokenB), 100 ether, 0, user2);
vm.stopPrank();
vault.setEmergencyMode(false);
}
function test_41_SwapDisabled() public {
vault.setSwapEnabled(false);
vm.startPrank(user1);
ytTokenA.approve(address(router), 1000 ether);
vm.expectRevert(abi.encodeWithSignature("SwapDisabled()"));
router.addLiquidity(address(ytTokenA), 1000 ether, 0, 0);
vm.stopPrank();
vault.setSwapEnabled(true);
}
function test_42_MaxSwapAmount() public {
vm.startPrank(user1);
ytTokenA.approve(address(router), 2000 ether);
router.addLiquidity(address(ytTokenA), 2000 ether, 0, 0);
ytTokenB.approve(address(router), 2000 ether);
router.addLiquidity(address(ytTokenB), 2000 ether, 0, 0);
vm.stopPrank();
vault.setMaxSwapAmount(address(ytTokenA), 50 ether);
vm.startPrank(user2);
ytTokenA.approve(address(router), 100 ether);
vm.expectRevert(abi.encodeWithSignature("AmountExceedsLimit()"));
router.swapYT(address(ytTokenA), address(ytTokenB), 100 ether, 0, user2);
vm.stopPrank();
vault.setMaxSwapAmount(address(ytTokenA), 0);
}
// ==================== 17. 边界条件测试 ====================
function test_43_AddZeroAmountReverts() public {
vm.startPrank(user1);
ytTokenA.approve(address(router), 0);
vm.expectRevert(abi.encodeWithSignature("InvalidAmount()"));
router.addLiquidity(address(ytTokenA), 0, 0, 0);
vm.stopPrank();
}
function test_44_RemoveZeroAmountReverts() public {
vm.startPrank(user1);
vm.expectRevert(abi.encodeWithSignature("InvalidAmount()"));
router.removeLiquidity(address(ytTokenA), 0, 0, user1);
vm.stopPrank();
}
function test_45_SwapZeroAmountReverts() public {
vm.startPrank(user1);
vm.expectRevert(abi.encodeWithSignature("InvalidAmount()"));
router.swapYT(address(ytTokenA), address(ytTokenB), 0, 0, user1);
vm.stopPrank();
}
function test_46_SwapUnwhitelistedTokenReverts() public {
// 通过factory创建新的YTAssetVault
address ytTokenDAddr = factory.createVault(
"YT Token D",
"YT-D",
deployer,
1000000 ether,
address(wusd),
block.timestamp + 365 days,
PRICE_PRECISION,
PRICE_PRECISION
);
YTAssetVault ytTokenD = YTAssetVault(ytTokenDAddr);
wusd.mint(user1, 500 ether);
vm.startPrank(user1);
wusd.approve(address(ytTokenD), 500 ether);
ytTokenD.depositYT(500 ether);
// 添加YT-A流动性
ytTokenA.approve(address(router), 1000 ether);
router.addLiquidity(address(ytTokenA), 1000 ether, 0, 0);
// 尝试swap未白名单的代币
ytTokenD.approve(address(router), 100 ether);
vm.expectRevert(abi.encodeWithSignature("TokenNotWhitelisted()"));
router.swapYT(address(ytTokenD), address(ytTokenA), 100 ether, 0, user1);
vm.stopPrank();
}
// ==================== 18. 费用精确计算测试 ====================
function test_47_ExactFeeCalculation() public {
uint256 amount = 1000 ether;
vm.startPrank(user1);
ytTokenA.approve(address(router), amount);
uint256 poolAmountBefore = vault.poolAmounts(address(ytTokenA));
uint256 usdyBefore = vault.usdyAmounts(address(ytTokenA));
router.addLiquidity(address(ytTokenA), amount, 0, 0);
vm.stopPrank();
uint256 poolAmountAfter = vault.poolAmounts(address(ytTokenA));
uint256 usdyAfter = vault.usdyAmounts(address(ytTokenA));
// 池子应该收到全部代币
assertEq(poolAmountAfter - poolAmountBefore, amount, "pool should receive full amount");
// USDY债务只记录扣费后的
assertEq(usdyAfter - usdyBefore, 997 ether, "usdy debt incorrect");
}
function test_48_RedemptionFeeCalculation() public {
vm.startPrank(user1);
ytTokenA.approve(address(router), 1000 ether);
router.addLiquidity(address(ytTokenA), 1000 ether, 0, 0);
uint256 ytLPBalance = ytlp.balanceOf(user1);
vm.warp(block.timestamp + 16 * 60);
uint256 poolAmountBefore = vault.poolAmounts(address(ytTokenA));
router.removeLiquidity(address(ytTokenA), ytLPBalance, 0, user1);
vm.stopPrank();
uint256 poolAmountAfter = vault.poolAmounts(address(ytTokenA));
uint256 feeInPool = poolAmountBefore - poolAmountAfter;
assertEq(feeInPool, 997 ether, "fee should be collected");
assertEq(poolAmountAfter, 3 ether, "remaining pool incorrect");
}
// ==================== 19. ytLP价格增长测试 ====================
function test_49_YtLPPriceGrowthFromFees() public {
vm.startPrank(user1);
ytTokenA.approve(address(router), 2000 ether);
router.addLiquidity(address(ytTokenA), 2000 ether, 0, 0);
ytTokenB.approve(address(router), 2000 ether);
router.addLiquidity(address(ytTokenB), 2000 ether, 0, 0);
vm.stopPrank();
uint256 priceBefore = poolManager.getPrice(true);
uint256 supplyBefore = ytlp.totalSupply();
console.log("Price before swaps:", priceBefore);
console.log("Supply:", supplyBefore);
// 执行多次swap累积手续费
vm.startPrank(user1);
for (uint i = 0; i < 10; i++) {
ytTokenA.approve(address(router), 100 ether);
router.swapYT(address(ytTokenA), address(ytTokenB), 100 ether, 0, user2);
ytTokenB.approve(address(router), 100 ether);
router.swapYT(address(ytTokenB), address(ytTokenA), 100 ether, 0, user2);
}
vm.stopPrank();
uint256 priceAfter = poolManager.getPrice(true);
uint256 supplyAfter = ytlp.totalSupply();
console.log("Price after swaps:", priceAfter);
assertEq(supplyAfter, supplyBefore, "supply should not change");
assertTrue(priceAfter > priceBefore, "price should increase");
uint256 priceIncrease = (priceAfter - priceBefore) * 10000 / priceBefore;
console.log("Price increase (bps):", priceIncrease);
assertTrue(priceIncrease >= 10 && priceIncrease <= 30, "price increase should be 10-30 bps");
}
// ==================== 20. 价格查询测试 ====================
function test_50_GetPriceFromVault() public view {
uint256 price = vault.getPrice(address(ytTokenA), true);
assertEq(price, 1e30, "vault price incorrect");
uint256 maxPrice = vault.getMaxPrice(address(ytTokenA));
uint256 minPrice = vault.getMinPrice(address(ytTokenA));
assertEq(maxPrice, 1e30);
assertEq(minPrice, 1e30);
}
function test_51_GetPriceInfo() public view {
(
uint256 currentPrice,
,
uint256 maxPrice,
uint256 minPrice,
uint256 spread
) = priceFeed.getPriceInfo(address(ytTokenA));
assertEq(currentPrice, 1e30, "current price incorrect");
assertEq(maxPrice, 1e30, "max price incorrect");
assertEq(minPrice, 1e30, "min price incorrect");
assertEq(spread, 0, "spread should be 0");
}
function test_52_YtLPPriceCalculation() public {
uint256 amount = 1000 ether;
vm.startPrank(user1);
ytTokenA.approve(address(router), amount);
router.addLiquidity(address(ytTokenA), amount, 0, 0);
vm.stopPrank();
uint256 ytLPPrice = poolManager.getPrice(true);
assertTrue(ytLPPrice > 1 ether, "ytLP price should be > $1");
assertTrue(ytLPPrice < 1.01 ether, "ytLP price should be < $1.01");
}
function test_53_AddLiquidityWithSpread() public {
priceFeed.setSpreadBasisPoints(address(ytTokenA), 20); // 0.2%
uint256 amount = 1000 ether;
vm.startPrank(user1);
ytTokenA.approve(address(router), amount);
uint256 ytLPReceived = router.addLiquidity(address(ytTokenA), amount, 0, 0);
vm.stopPrank();
uint256 expectedYtLP = 995.006 ether;
assertEq(ytLPReceived, expectedYtLP, "ytLP with spread incorrect");
priceFeed.setSpreadBasisPoints(address(ytTokenA), 0);
}
function test_54_RemoveLiquiditySlippageProtection() public {
uint256 amount = 1000 ether;
vm.startPrank(user1);
ytTokenA.approve(address(router), amount);
router.addLiquidity(address(ytTokenA), amount, 0, 0);
uint256 ytLPBalance = ytlp.balanceOf(user1);
vm.warp(block.timestamp + 15 * 60 + 1);
uint256 tooHighMinOut = 2000 ether;
vm.expectRevert(abi.encodeWithSignature("InsufficientOutput()"));
router.removeLiquidity(address(ytTokenA), ytLPBalance, tooHighMinOut, user1);
vm.stopPrank();
}
function test_55_SwapSlippageProtection() public {
uint256 liquidityAmount = 2000 ether;
vm.startPrank(user1);
ytTokenA.approve(address(router), liquidityAmount);
router.addLiquidity(address(ytTokenA), liquidityAmount, 0, 0);
ytTokenB.approve(address(router), liquidityAmount);
router.addLiquidity(address(ytTokenB), liquidityAmount, 0, 0);
vm.stopPrank();
uint256 swapAmount = 100 ether;
uint256 tooHighMinOut = 150 ether;
vm.startPrank(user2);
ytTokenA.approve(address(router), swapAmount);
vm.expectRevert(abi.encodeWithSignature("InsufficientOutput()"));
router.swapYT(address(ytTokenA), address(ytTokenB), swapAmount, tooHighMinOut, user2);
vm.stopPrank();
}
function test_56_OnlyHandlerCanAddLiquidity() public {
vm.startPrank(user1);
ytTokenA.approve(address(poolManager), 1000 ether);
vm.expectRevert(abi.encodeWithSignature("Forbidden()"));
poolManager.addLiquidityForAccount(
user1,
user1,
address(ytTokenA),
1000 ether,
0,
0
);
vm.stopPrank();
}
function test_57_OnlyPoolManagerCanBuyUSDY() public {
vm.startPrank(user1);
ytTokenA.approve(address(vault), 1000 ether);
vm.expectRevert(abi.encodeWithSignature("OnlyPoolManager()"));
vault.buyUSDY(address(ytTokenA), user1);
vm.stopPrank();
}
function test_58_OnlyGovCanSetFees() public {
vm.startPrank(user1);
vm.expectRevert(abi.encodeWithSignature("Forbidden()"));
vault.setSwapFees(40, 5, 60, 25);
vm.stopPrank();
}
function test_59_OnlyKeeperCanUpdatePrice() public {
// 非keeper和非gov不能调用updatePrice
vm.startPrank(user1);
vm.expectRevert(abi.encodeWithSignature("Forbidden()"));
priceFeed.updatePrice(address(ytTokenA));
vm.stopPrank();
}
function test_60_SetKeeperPermission() public {
// 设置user1为keeper
priceFeed.setKeeper(user1, true);
assertTrue(priceFeed.isKeeper(user1), "user1 should be keeper");
// keeper可以调用updatePrice
vm.startPrank(user1);
uint256 price = priceFeed.updatePrice(address(ytTokenA));
vm.stopPrank();
assertEq(price, PRICE_PRECISION, "price should be updated");
// 移除keeper权限
priceFeed.setKeeper(user1, false);
assertFalse(priceFeed.isKeeper(user1), "user1 should not be keeper");
// 移除后不能调用
vm.startPrank(user1);
vm.expectRevert(abi.encodeWithSignature("Forbidden()"));
priceFeed.updatePrice(address(ytTokenA));
vm.stopPrank();
}
function test_61_GovCanAlwaysUpdatePrice() public {
// gov可以直接调用updatePrice
uint256 price = priceFeed.updatePrice(address(ytTokenA));
assertEq(price, PRICE_PRECISION, "gov can update price");
}
// ==================== 21. YTRewardRouter 暂停功能测试 ====================
function test_62_RouterPauseByGov() public {
// Gov可以暂停
router.pause();
assertTrue(router.paused(), "router should be paused");
// Gov可以恢复
router.unpause();
assertFalse(router.paused(), "router should be unpaused");
}
function test_63_OnlyGovCanPauseRouter() public {
// User不能暂停
vm.startPrank(user1);
vm.expectRevert(abi.encodeWithSignature("Forbidden()"));
router.pause();
vm.stopPrank();
// User不能恢复
router.pause(); // 由deployer暂停
vm.startPrank(user1);
vm.expectRevert(abi.encodeWithSignature("Forbidden()"));
router.unpause();
vm.stopPrank();
router.unpause(); // 恢复
}
function test_64_CannotAddLiquidityWhenRouterPaused() public {
// 暂停router
router.pause();
// 尝试添加流动性应该失败
vm.startPrank(user1);
ytTokenA.approve(address(router), 1000 ether);
vm.expectRevert(abi.encodeWithSignature("EnforcedPause()"));
router.addLiquidity(address(ytTokenA), 1000 ether, 0, 0);
vm.stopPrank();
// 恢复后应该可以添加
router.unpause();
vm.startPrank(user1);
uint256 ytLPReceived = router.addLiquidity(address(ytTokenA), 1000 ether, 0, 0);
vm.stopPrank();
assertEq(ytLPReceived, 997 ether, "add liquidity should work after unpause");
}
function test_65_CannotRemoveLiquidityWhenRouterPaused() public {
// 先添加流动性
vm.startPrank(user1);
ytTokenA.approve(address(router), 1000 ether);
router.addLiquidity(address(ytTokenA), 1000 ether, 0, 0);
uint256 ytLPBalance = ytlp.balanceOf(user1);
// 等待冷却期
vm.warp(block.timestamp + 15 * 60 + 1);
vm.stopPrank();
// 暂停router
router.pause();
// 尝试移除流动性应该失败
vm.startPrank(user1);
vm.expectRevert(abi.encodeWithSignature("EnforcedPause()"));
router.removeLiquidity(address(ytTokenA), ytLPBalance, 0, user1);
vm.stopPrank();
// 恢复后应该可以移除
router.unpause();
vm.startPrank(user1);
uint256 amountOut = router.removeLiquidity(address(ytTokenA), ytLPBalance, 0, user1);
vm.stopPrank();
assertEq(amountOut, 997 ether, "remove liquidity should work after unpause");
}
function test_66_CannotSwapWhenRouterPaused() public {
// 先添加流动性
vm.startPrank(user1);
ytTokenA.approve(address(router), 2000 ether);
router.addLiquidity(address(ytTokenA), 2000 ether, 0, 0);
ytTokenB.approve(address(router), 2000 ether);
router.addLiquidity(address(ytTokenB), 2000 ether, 0, 0);
vm.stopPrank();
// 暂停router
router.pause();
// 尝试swap应该失败
vm.startPrank(user2);
ytTokenA.approve(address(router), 100 ether);
vm.expectRevert(abi.encodeWithSignature("EnforcedPause()"));
router.swapYT(address(ytTokenA), address(ytTokenB), 100 ether, 0, user2);
vm.stopPrank();
// 恢复后应该可以swap
router.unpause();
vm.startPrank(user2);
uint256 amountOut = router.swapYT(address(ytTokenA), address(ytTokenB), 100 ether, 0, user2);
vm.stopPrank();
assertEq(amountOut, 99.7 ether, "swap should work after unpause");
}
function test_67_QueryFunctionsWorkWhenRouterPaused() public {
// 先添加流动性
vm.startPrank(user1);
ytTokenA.approve(address(router), 1000 ether);
router.addLiquidity(address(ytTokenA), 1000 ether, 0, 0);
vm.stopPrank();
// 暂停router
router.pause();
// 查询函数应该仍然可用
uint256 ytLPPrice = router.getYtLPPrice();
assertTrue(ytLPPrice > 0, "getYtLPPrice should work when paused");
uint256 accountValue = router.getAccountValue(user1);
assertTrue(accountValue > 0, "getAccountValue should work when paused");
// 验证返回的值是合理的
assertTrue(ytLPPrice > 1 ether, "ytLP price should be > $1");
assertTrue(accountValue >= 995 ether && accountValue <= 1005 ether, "account value should be around 1000");
}
function test_68_PauseRouterDoesNotAffectVaultDirectly() public {
// 暂停router不影响直接通过vault操作
router.pause();
vm.startPrank(user1);
ytTokenA.approve(address(vault), 1000 ether);
// 直接通过poolManager添加流动性仍然失败因为user1不是handler
vm.expectRevert(abi.encodeWithSignature("Forbidden()"));
poolManager.addLiquidityForAccount(user1, user1, address(ytTokenA), 1000 ether, 0, 0);
vm.stopPrank();
router.unpause();
}
function test_69_CompleteFlowWithPauseResume() public {
console.log("=== Complete Flow With Pause/Resume Test ===");
// 步骤1: 添加流动性
vm.startPrank(user1);
ytTokenA.approve(address(router), 1000 ether);
uint256 ytLP1 = router.addLiquidity(address(ytTokenA), 1000 ether, 0, 0);
console.log("Added liquidity, received ytLP:", ytLP1);
vm.stopPrank();
// 步骤2: 暂停router
router.pause();
console.log("Router paused");
// 步骤3: 验证所有操作都被阻止
vm.startPrank(user1);
ytTokenB.approve(address(router), 1000 ether);
vm.expectRevert(abi.encodeWithSignature("EnforcedPause()"));
router.addLiquidity(address(ytTokenB), 1000 ether, 0, 0);
console.log("Add liquidity blocked during pause");
vm.stopPrank();
// 步骤4: 恢复router
router.unpause();
console.log("Router unpaused");
// 步骤5: 继续正常操作
vm.startPrank(user1);
uint256 ytLP2 = router.addLiquidity(address(ytTokenB), 1000 ether, 0, 0);
console.log("Added liquidity after unpause, received ytLP:", ytLP2);
vm.stopPrank();
// 验证总余额
uint256 totalYtLP = ytlp.balanceOf(user1);
console.log("Total ytLP:", totalYtLP);
assertEq(totalYtLP, ytLP1 + ytLP2, "total ytLP should be sum of both additions");
}
function test_70_EmergencyScenarioPauseEverything() public {
console.log("=== Emergency Scenario: Pause Everything ===");
// 先建立一些状态
vm.startPrank(user1);
ytTokenA.approve(address(router), 2000 ether);
router.addLiquidity(address(ytTokenA), 2000 ether, 0, 0);
ytTokenB.approve(address(router), 2000 ether);
router.addLiquidity(address(ytTokenB), 2000 ether, 0, 0);
vm.stopPrank();
console.log("Initial liquidity added");
// 模拟紧急情况暂停router
router.pause();
console.log("Router paused for emergency");
// 同时暂停vault (通过设置紧急模式)
vault.setEmergencyMode(true);
console.log("Vault emergency mode activated");
// 验证所有操作都被阻止
vm.startPrank(user2);
ytTokenA.approve(address(router), 100 ether);
// Router暂停阻止操作
vm.expectRevert(abi.encodeWithSignature("EnforcedPause()"));
router.addLiquidity(address(ytTokenA), 100 ether, 0, 0);
vm.expectRevert(abi.encodeWithSignature("EnforcedPause()"));
router.swapYT(address(ytTokenA), address(ytTokenB), 100 ether, 0, user2);
vm.stopPrank();
console.log("All operations blocked during emergency");
// 恢复系统
router.unpause();
vault.setEmergencyMode(false);
console.log("System recovered from emergency");
// 验证系统恢复正常
vm.startPrank(user2);
uint256 swapOut = router.swapYT(address(ytTokenA), address(ytTokenB), 100 ether, 0, user2);
vm.stopPrank();
assertEq(swapOut, 99.7 ether, "swap should work after recovery");
console.log("System operational after recovery");
}
receive() external payable {}
}