Files
assetxContracts/test/YTLp.t.sol

1604 lines
56 KiB
Solidity
Raw Normal View History

2025-12-18 13:07:35 +08:00
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.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/vault/YTAssetVault.sol";
import "../contracts/vault/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 = new WUSD();
wusd.initialize("Wrapped USD", "WUSD");
// 部署代币(可升级合约)
usdy = new USDY();
usdy.initialize();
ytlp = new YTLPToken();
ytlp.initialize();
// 部署核心合约(可升级合约)
priceFeed = new YTPriceFeed();
priceFeed.initialize(address(wusd));
vault = new YTVault();
vault.initialize(address(usdy), address(priceFeed));
poolManager = new YTPoolManager();
poolManager.initialize(
address(vault),
address(usdy),
address(ytlp),
15 * 60
);
router = new YTRewardRouter();
router.initialize(
address(usdy),
address(ytlp),
address(poolManager),
address(vault)
);
// 部署YTAssetVault实现合约
YTAssetVault vaultImpl = new YTAssetVault();
// 部署YTAssetFactory
factory = new YTAssetFactory();
factory.initialize(
address(vaultImpl),
1000000 ether // defaultHardCap
);
// 通过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 {
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 {
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 cachedPrice,
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 {}
}