update lending contract and lending test case
This commit is contained in:
@@ -321,15 +321,19 @@ contract YtLendingTest is Test {
|
||||
lending.accrueInterest();
|
||||
|
||||
// 利用率 = 8000 / 10000 = 80%(在 kink 点)
|
||||
// Supply APY = 3%(在 kink 点)
|
||||
// 预期余额 ≈ 10,000 * 1.03 = 10,300 USDC
|
||||
// Supply APY 计算:
|
||||
// rate = base + (utilization × slope)
|
||||
// = 0% + (80% × 3%) = 2.4%
|
||||
// 预期余额 = 10,000 × 1.024 = 10,240 USDC
|
||||
uint256 aliceBalance = lending.balanceOf(alice);
|
||||
assertApproxEqRel(aliceBalance, 10300e18, 0.01e18, "Alice should earn ~3% interest");
|
||||
assertApproxEqRel(aliceBalance, 10240e18, 0.001e18, "Alice should earn 2.4% interest (0.1% tolerance)");
|
||||
|
||||
// Borrow APY = 1.5% + 5% = 6.5%(在 kink 点)
|
||||
// 预期债务 ≈ 8,000 * 1.065 = 8,520 USDC
|
||||
// Borrow APY 计算:
|
||||
// rate = base + (utilization × slope)
|
||||
// = 1.5% + (80% × 5%) = 5.5%
|
||||
// 预期债务 = 8,000 × 1.055 = 8,440 USDC
|
||||
uint256 bobDebt = lending.borrowBalanceOf(bob);
|
||||
assertApproxEqRel(bobDebt, 8520e18, 0.01e18, "Bob should owe ~6.5% interest");
|
||||
assertApproxEqRel(bobDebt, 8440e18, 0.001e18, "Bob should owe 5.5% interest (0.1% tolerance)");
|
||||
}
|
||||
|
||||
function test_11_InterestAccrual_Compound() public {
|
||||
@@ -399,7 +403,52 @@ contract YtLendingTest is Test {
|
||||
assertTrue(lending.isLiquidatable(bob), "Bob should be liquidatable");
|
||||
}
|
||||
|
||||
function test_14_Absorb_Single() public {
|
||||
function test_14_Liquidation_AtExactThreshold() public {
|
||||
// 这个测试验证:在刚好达到清算线时就可以被清算
|
||||
|
||||
// 0. Alice 先存入流动性
|
||||
vm.prank(alice);
|
||||
lending.supply(50000e18);
|
||||
|
||||
// 1. Bob 建立借款头寸
|
||||
vm.startPrank(bob);
|
||||
lending.supplyCollateral(address(weth), 10e18); // 10 ETH @ $2000 = $20,000
|
||||
lending.borrow(16000e18); // $16,000(80% LTV)
|
||||
vm.stopPrank();
|
||||
|
||||
// 2. 计算精确的清算价格
|
||||
// 清算条件(在 Solidity 整数运算中):
|
||||
// debtValue > collateralValue × liquidateCollateralFactor
|
||||
// 16000e8 > (10e18 × price_e8 / 1e18) × 0.85e18 / 1e18
|
||||
// 16000e8 > price_e8 × 8.5
|
||||
// price_e8 < 16000e8 / 8.5 = 188235294117.647...
|
||||
// 安全价格:price_e8 >= 188235294118($1,882.35294118)
|
||||
|
||||
// 在清算阈值之上(安全)
|
||||
wethPriceFeed.setPrice(1883e8); // $1,883
|
||||
assertFalse(lending.isLiquidatable(bob), "Bob should be safe at $1,883");
|
||||
|
||||
// 接近但仍在清算阈值之上
|
||||
wethPriceFeed.setPrice(188235294118); // $1,882.35294118(安全临界点)
|
||||
assertFalse(lending.isLiquidatable(bob), "Bob should be at the safe edge");
|
||||
|
||||
// 刚好跌破清算阈值
|
||||
wethPriceFeed.setPrice(188235294117); // $1,882.35294117(危险)
|
||||
// collateralValue = 188235294117 × 8.5 = 1,599,999,999,994.5
|
||||
// debtValue = 16000e8 = 1,600,000,000,000
|
||||
// 1,600,000,000,000 > 1,599,999,999,994 ✅ 可清算
|
||||
assertTrue(lending.isLiquidatable(bob), "Bob should be liquidatable just below threshold");
|
||||
|
||||
// 3. 执行清算
|
||||
vm.prank(liquidator);
|
||||
lending.absorb(bob);
|
||||
|
||||
// 4. 验证清算成功
|
||||
assertEq(lending.getCollateral(bob, address(weth)), 0, "Bob's collateral should be seized");
|
||||
assertEq(lending.getCollateralReserves(address(weth)), 10e18, "Collateral should be in reserves");
|
||||
}
|
||||
|
||||
function test_15_Absorb_Single() public {
|
||||
// 0. Alice 先存入流动性
|
||||
vm.prank(alice);
|
||||
lending.supply(50000e18);
|
||||
@@ -436,7 +485,7 @@ contract YtLendingTest is Test {
|
||||
assertTrue(lending.balanceOf(bob) > 0, "Bob should have positive balance from excess collateral");
|
||||
}
|
||||
|
||||
function test_15_AbsorbMultiple_Batch() public {
|
||||
function test_16_AbsorbMultiple_Batch() public {
|
||||
// 0. Alice 先存入流动性
|
||||
vm.prank(alice);
|
||||
lending.supply(50000e18);
|
||||
@@ -473,7 +522,7 @@ contract YtLendingTest is Test {
|
||||
BUY COLLATERAL 测试
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
function test_16_BuyCollateral_Basic() public {
|
||||
function test_17_BuyCollateral_Basic() public {
|
||||
// 0. Alice 先存入流动性
|
||||
vm.prank(alice);
|
||||
lending.supply(50000e18);
|
||||
@@ -511,7 +560,7 @@ contract YtLendingTest is Test {
|
||||
assertEq(lending.getCollateralReserves(address(weth)), 0, "Collateral reserve should be empty");
|
||||
}
|
||||
|
||||
function test_17_BuyCollateral_WithRecipient() public {
|
||||
function test_18_BuyCollateral_WithRecipient() public {
|
||||
// 先存入流动性
|
||||
vm.prank(owner);
|
||||
lending.supply(50000e18);
|
||||
@@ -535,7 +584,7 @@ contract YtLendingTest is Test {
|
||||
assertEq(weth.balanceOf(alice), 60e18, "Alice should receive the ETH (50 + 10)");
|
||||
}
|
||||
|
||||
function test_18_BuyCollateral_FailWhenReserveSufficient() public {
|
||||
function test_19_BuyCollateral_FailWhenReserveSufficient() public {
|
||||
// 这个测试验证:当 reserves >= targetReserves 时,不能购买抵押品
|
||||
// 为简化测试,我们直接验证 buyCollateral 的逻辑
|
||||
|
||||
@@ -586,12 +635,12 @@ contract YtLendingTest is Test {
|
||||
RESERVES 测试
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
function test_19_GetReserves_Initial() public view {
|
||||
function test_20_GetReserves_Initial() public view {
|
||||
// 初始储备金应该是 0
|
||||
assertEq(lending.getReserves(), 0, "Initial reserves should be 0");
|
||||
}
|
||||
|
||||
function test_20_GetReserves_AfterSupplyBorrow() public {
|
||||
function test_21_GetReserves_AfterSupplyBorrow() public {
|
||||
// Alice 存入 10,000 USDC
|
||||
vm.prank(alice);
|
||||
lending.supply(10000e18);
|
||||
@@ -610,7 +659,7 @@ contract YtLendingTest is Test {
|
||||
assertEq(lending.getReserves(), 0, "Reserves should still be 0");
|
||||
}
|
||||
|
||||
function test_21_GetReserves_WithInterest() public {
|
||||
function test_22_GetReserves_WithInterest() public {
|
||||
// 建立借贷
|
||||
vm.prank(alice);
|
||||
lending.supply(10000e18);
|
||||
@@ -625,15 +674,19 @@ contract YtLendingTest is Test {
|
||||
lending.accrueInterest();
|
||||
|
||||
// 借款利率 > 存款利率,reserves 应该增加
|
||||
// Borrow APY ≈ 6.5%, Supply APY ≈ 3%
|
||||
// 利差 ≈ 8000 * 0.065 - 10000 * 0.03 = 520 - 300 = 220
|
||||
// 由于利率是按秒计算,会有舍入误差
|
||||
// 利用率 = 80%(在 kink 点)
|
||||
// Supply APY = 0% + 80% × 3% = 2.4%
|
||||
// Borrow APY = 1.5% + 80% × 5% = 5.5%
|
||||
//
|
||||
// Alice 存款利息 = 10,000 × 2.4% = 240 USDC
|
||||
// Bob 借款利息 = 8,000 × 5.5% = 440 USDC
|
||||
// 储备金增加 = 440 - 240 = 200 USDC
|
||||
int256 reserves = lending.getReserves();
|
||||
assertTrue(reserves > 0, "Reserves should be positive from interest spread");
|
||||
assertApproxEqRel(uint256(reserves), 220e18, 0.15e18, "Reserves should be ~220 USDC");
|
||||
assertApproxEqRel(uint256(reserves), 200e18, 0.005e18, "Reserves should be 200 USDC (0.5% tolerance)");
|
||||
}
|
||||
|
||||
function test_22_WithdrawReserves_Success() public {
|
||||
function test_23_WithdrawReserves_Success() public {
|
||||
// 1. 累积储备金
|
||||
vm.prank(alice);
|
||||
lending.supply(10000e18);
|
||||
@@ -665,13 +718,13 @@ contract YtLendingTest is Test {
|
||||
);
|
||||
}
|
||||
|
||||
function test_23_WithdrawReserves_FailInsufficientReserves() public {
|
||||
function test_24_WithdrawReserves_FailInsufficientReserves() public {
|
||||
// 尝试提取不存在的储备金
|
||||
vm.expectRevert(ILending.InsufficientReserves.selector);
|
||||
lending.withdrawReserves(address(0x999), 1000e18);
|
||||
}
|
||||
|
||||
function test_24_WithdrawReserves_FailNotOwner() public {
|
||||
function test_25_WithdrawReserves_FailNotOwner() public {
|
||||
// 非 owner 尝试提取
|
||||
vm.prank(alice);
|
||||
vm.expectRevert();
|
||||
@@ -682,7 +735,7 @@ contract YtLendingTest is Test {
|
||||
VIEW FUNCTIONS 测试
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
function test_25_GetUtilization() public {
|
||||
function test_26_GetUtilization() public {
|
||||
// 初始利用率应该是 0
|
||||
assertEq(lending.getUtilization(), 0, "Initial utilization should be 0");
|
||||
|
||||
@@ -700,7 +753,7 @@ contract YtLendingTest is Test {
|
||||
assertEq(lending.getUtilization(), 0.8e18, "Utilization should be 80%");
|
||||
}
|
||||
|
||||
function test_26_GetSupplyRate_BelowKink() public {
|
||||
function test_27_GetSupplyRate_BelowKink() public {
|
||||
// 利用率 50%,低于 kink(80%)
|
||||
vm.prank(alice);
|
||||
lending.supply(10000e18);
|
||||
@@ -712,12 +765,13 @@ contract YtLendingTest is Test {
|
||||
|
||||
uint64 supplyRate = lending.getSupplyRate();
|
||||
|
||||
// 预期:base + 50% * slopeLow = 0 + 0.5 * 3% = 1.5% APY
|
||||
// 但是转换为每秒:1.5% / 31536000 ≈ 0.475e9
|
||||
assertApproxEqRel(supplyRate, 0.015e18, 0.01e18, "Supply rate should be ~1.5% APY");
|
||||
// 预期:base + utilization × slopeLow
|
||||
// = 0% + 50% × 3% = 1.5% APY
|
||||
// 这是简单计算,应该非常精确
|
||||
assertApproxEqRel(supplyRate, 0.015e18, 0.0001e18, "Supply rate should be 1.5% APY (0.01% tolerance)");
|
||||
}
|
||||
|
||||
function test_27_GetBorrowRate_AtKink() public {
|
||||
function test_28_GetBorrowRate_AtKink() public {
|
||||
// 利用率正好 80%
|
||||
vm.prank(alice);
|
||||
lending.supply(10000e18);
|
||||
@@ -729,12 +783,14 @@ contract YtLendingTest is Test {
|
||||
|
||||
uint64 borrowRate = lending.getBorrowRate();
|
||||
|
||||
// 预期:base + slopeLow = 1.5% + 5% = 6.5% APY
|
||||
// 实际:由于是线性插值 + 按秒计算再转年化,会有小的舍入误差
|
||||
assertApproxEqRel(borrowRate, 0.065e18, 0.2e18, "Borrow rate should be ~6.5% APY");
|
||||
// 预期:base + utilization × slopeLow
|
||||
// = 1.5% + 80% × 5%
|
||||
// = 1.5% + 4% = 5.5% APY
|
||||
// 注:getBorrowRate() 返回的是年化利率,精度很高
|
||||
assertApproxEqRel(borrowRate, 0.055e18, 0.0001e18, "Borrow rate should be 5.5% APY (0.01% tolerance)");
|
||||
}
|
||||
|
||||
function test_28_QuoteCollateral() public view {
|
||||
function test_29_QuoteCollateral() public view {
|
||||
// ETH 价格 $2000, liquidationFactor 0.95, storeFrontFactor 0.5
|
||||
// discount = 0.5 * (1 - 0.95) = 0.025 (2.5%)
|
||||
// 折扣价 = 2000 * (1 - 0.025) = $1,950
|
||||
@@ -750,7 +806,7 @@ contract YtLendingTest is Test {
|
||||
EDGE CASES 测试
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
function test_29_Borrow_MaxLTV() public {
|
||||
function test_30_Borrow_MaxLTV() public {
|
||||
// Bob 先存入流动性
|
||||
vm.prank(bob);
|
||||
lending.supply(50000e18);
|
||||
@@ -767,7 +823,7 @@ contract YtLendingTest is Test {
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function test_30_Borrow_FailOverLTV() public {
|
||||
function test_31_Borrow_FailOverLTV() public {
|
||||
// Bob 先存入流动性
|
||||
vm.prank(bob);
|
||||
lending.supply(50000e18);
|
||||
@@ -782,7 +838,7 @@ contract YtLendingTest is Test {
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function test_31_WithdrawCollateral_FailIfBorrowing() public {
|
||||
function test_32_WithdrawCollateral_FailIfBorrowing() public {
|
||||
// Bob 先存入流动性
|
||||
vm.prank(bob);
|
||||
lending.supply(50000e18);
|
||||
@@ -798,7 +854,7 @@ contract YtLendingTest is Test {
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function test_32_SupplyCollateral_FailExceedCap() public {
|
||||
function test_33_SupplyCollateral_FailExceedCap() public {
|
||||
// 尝试超过供应上限(100,000 ETH)
|
||||
weth.mint(alice, 200000e18);
|
||||
|
||||
@@ -808,7 +864,7 @@ contract YtLendingTest is Test {
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function test_33_ComplexScenario_MultipleUsers() public {
|
||||
function test_34_ComplexScenario_MultipleUsers() public {
|
||||
// 1. Alice 存款
|
||||
vm.prank(alice);
|
||||
lending.supply(50000e18);
|
||||
|
||||
Reference in New Issue
Block a user