From 087536cf9422698fe150025fc6641c016ae2f1a1 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Wed, 15 Nov 2023 15:40:44 +0100 Subject: [PATCH 1/5] test(irm): add ping tests --- test/SpeedJumpIrmTest.sol | 42 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/test/SpeedJumpIrmTest.sol b/test/SpeedJumpIrmTest.sol index 57d84e24..af9ad264 100644 --- a/test/SpeedJumpIrmTest.sol +++ b/test/SpeedJumpIrmTest.sol @@ -121,6 +121,48 @@ contract AdaptativeCurveIrmTest is Test { assertApproxEqRel(irm.borrowRateView(marketParams, market), uint256(0.00057 ether) / 365 days, 0.1 ether); } + function testRateAfter60DaysNoPing() public { + Market memory market; + market.totalSupplyAssets = 10 ether; + market.totalBorrowAssets = 9 ether; + assertEq(irm.borrowRate(marketParams, market), uint256(INITIAL_RATE_AT_TARGET)); + assertEq(irm.rateAtTarget(marketParams.id()), INITIAL_RATE_AT_TARGET); + + market.lastUpdate = uint128(block.timestamp); + vm.warp(block.timestamp + 60 days); + + market.totalBorrowAssets = 9.5 ether; + irm.borrowRate(marketParams, market); + + assertApproxEqRel(irm.rateAtTarget(marketParams.id()), int256(0.6092 ether) / 365 days, 0.0001 ether); + } + + function testRateAfter60DaysPingEveryDay() public { + Market memory market; + market.totalSupplyAssets = 10 ether; + market.totalBorrowAssets = 9 ether; + assertEq(irm.borrowRate(marketParams, market), uint256(INITIAL_RATE_AT_TARGET)); + assertEq(irm.rateAtTarget(marketParams.id()), INITIAL_RATE_AT_TARGET); + + market.totalBorrowAssets = 9.5 ether; + + for (uint256 i; i < 60; ++i) { + market.lastUpdate = uint128(block.timestamp); + vm.warp(block.timestamp + 1 days); + + uint256 avgBorrowRate = irm.borrowRate(marketParams, market); + uint256 interest = market.totalBorrowAssets.wMulDown(avgBorrowRate.wTaylorCompounded(1 days)); + market.totalSupplyAssets += uint128(interest); + market.totalBorrowAssets += uint128(interest); + + // Utilization starts to grow significantly (0.5%) due to each ping. + assertApproxEqRel(market.totalBorrowAssets.wDivDown(market.totalSupplyAssets), 0.95 ether, 0.005 ether); + } + + // End rate at target is significantly greater than expected (5%). + assertApproxEqRel(irm.rateAtTarget(marketParams.id()), int256(0.6092 ether) / 365 days, 0.05 ether); + } + function testFirstBorrowRate(Market memory market) public { vm.assume(market.totalBorrowAssets > 0); vm.assume(market.totalSupplyAssets >= market.totalBorrowAssets); From 339d06da47547debfc4cb2193c71ef235afd1ae8 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Wed, 15 Nov 2023 19:03:58 +0100 Subject: [PATCH 2/5] test(irm): add failing test case --- test/SpeedJumpIrmTest.sol | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/test/SpeedJumpIrmTest.sol b/test/SpeedJumpIrmTest.sol index 8594336d..334572fe 100644 --- a/test/SpeedJumpIrmTest.sol +++ b/test/SpeedJumpIrmTest.sol @@ -155,14 +155,39 @@ contract AdaptativeCurveIrmTest is Test { market.totalSupplyAssets += uint128(interest); market.totalBorrowAssets += uint128(interest); - // Utilization starts to grow significantly (0.5%) due to each ping. + // Utilization starts to grow significantly (<0.5%) due to each ping. assertApproxEqRel(market.totalBorrowAssets.wDivDown(market.totalSupplyAssets), 0.95 ether, 0.005 ether); } - // End rate at target is significantly greater than expected (6%). + // End rate at target is significantly greater than expected (<6%). assertApproxEqRel(irm.rateAtTarget(marketParams.id()), int256(0.6092 ether) / 365 days, 0.06 ether); } + function testRateAfter21DaysPingEveryMinute() public { + int256 initialRateAtTarget = int256(10 ether) / 365 days; // 1000% + + irm = + new AdaptativeCurveIrm(address(this), CURVE_STEEPNESS, ADJUSTMENT_SPEED, TARGET_UTILIZATION, initialRateAtTarget); + + Market memory market; + market.totalSupplyAssets = 10 ether; + market.totalBorrowAssets = 9 ether; + assertEq(irm.borrowRate(marketParams, market), uint256(initialRateAtTarget)); + assertEq(irm.rateAtTarget(marketParams.id()), initialRateAtTarget); + + for (uint256 i; i < 21 days / 1 minutes; ++i) { + market.lastUpdate = uint128(block.timestamp); + vm.warp(block.timestamp + 1 minutes); + + uint256 avgBorrowRate = irm.borrowRate(marketParams, market); + uint256 interest = market.totalBorrowAssets.wMulDown(avgBorrowRate.wTaylorCompounded(1 minutes)); + market.totalSupplyAssets += uint128(interest); + market.totalBorrowAssets += uint128(interest); + } + + assertApproxEqRel(irm.rateAtTarget(marketParams.id()), initialRateAtTarget, 0.1 ether); + } + function testFirstBorrowRate(Market memory market) public { vm.assume(market.totalBorrowAssets > 0); vm.assume(market.totalSupplyAssets >= market.totalBorrowAssets); From 3fbeaf108cd39453018a9e56c10c3c712d968085 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Thu, 16 Nov 2023 09:25:28 +0100 Subject: [PATCH 3/5] test(irm): improve tests --- test/SpeedJumpIrmTest.sol | 53 ++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/test/SpeedJumpIrmTest.sol b/test/SpeedJumpIrmTest.sol index 334572fe..aca6f533 100644 --- a/test/SpeedJumpIrmTest.sol +++ b/test/SpeedJumpIrmTest.sol @@ -62,7 +62,7 @@ contract AdaptativeCurveIrmTest is Test { function testFirstBorrowRateUtilizationTarget() public { Market memory market; - market.totalBorrowAssets = 0.9 ether; + market.totalBorrowAssets = uint128(uint256(TARGET_UTILIZATION)); market.totalSupplyAssets = 1 ether; assertEq(irm.borrowRate(marketParams, market), uint256(INITIAL_RATE_AT_TARGET), "avgBorrowRate"); @@ -121,30 +121,30 @@ contract AdaptativeCurveIrmTest is Test { assertApproxEqRel(irm.borrowRateView(marketParams, market), uint256(0.00057 ether) / 365 days, 0.1 ether); } - function testRateAfter60DaysNoPing() public { + function testRateAfter60DaysUtilizationAboveTargetNoPing() public { Market memory market; - market.totalSupplyAssets = 10 ether; - market.totalBorrowAssets = 9 ether; + market.totalSupplyAssets = 1 ether; + market.totalBorrowAssets = uint128(uint256(TARGET_UTILIZATION)); assertEq(irm.borrowRate(marketParams, market), uint256(INITIAL_RATE_AT_TARGET)); assertEq(irm.rateAtTarget(marketParams.id()), INITIAL_RATE_AT_TARGET); market.lastUpdate = uint128(block.timestamp); vm.warp(block.timestamp + 60 days); - market.totalBorrowAssets = 9.5 ether; + market.totalBorrowAssets = uint128(uint256(TARGET_UTILIZATION + 1 ether) / 2); irm.borrowRate(marketParams, market); assertApproxEqRel(irm.rateAtTarget(marketParams.id()), int256(0.6092 ether) / 365 days, 0.0001 ether); } - function testRateAfter60DaysPingEveryHour() public { + function testRateAfter60DaysUtilizationAboveTargetPingEveryHour() public { Market memory market; - market.totalSupplyAssets = 10 ether; - market.totalBorrowAssets = 9 ether; + market.totalSupplyAssets = 1 ether; + market.totalBorrowAssets = uint128(uint256(TARGET_UTILIZATION)); assertEq(irm.borrowRate(marketParams, market), uint256(INITIAL_RATE_AT_TARGET)); assertEq(irm.rateAtTarget(marketParams.id()), INITIAL_RATE_AT_TARGET); - market.totalBorrowAssets = 9.5 ether; + market.totalBorrowAssets = uint128(uint256(TARGET_UTILIZATION + 1 ether) / 2); // Error = 50% for (uint256 i; i < 60 days / 1 hours; ++i) { market.lastUpdate = uint128(block.timestamp); @@ -154,28 +154,42 @@ contract AdaptativeCurveIrmTest is Test { uint256 interest = market.totalBorrowAssets.wMulDown(avgBorrowRate.wTaylorCompounded(1 hours)); market.totalSupplyAssets += uint128(interest); market.totalBorrowAssets += uint128(interest); - - // Utilization starts to grow significantly (<0.5%) due to each ping. - assertApproxEqRel(market.totalBorrowAssets.wDivDown(market.totalSupplyAssets), 0.95 ether, 0.005 ether); } - // End rate at target is significantly greater than expected (<6%). + assertApproxEqRel(market.totalBorrowAssets.wDivDown(market.totalSupplyAssets), 0.95 ether, 0.005 ether); assertApproxEqRel(irm.rateAtTarget(marketParams.id()), int256(0.6092 ether) / 365 days, 0.06 ether); } - function testRateAfter21DaysPingEveryMinute() public { - int256 initialRateAtTarget = int256(10 ether) / 365 days; // 1000% + function testRateAfterUtilizationTargetNoPing(uint256 elapsed) public { + elapsed = bound(elapsed, 0, type(uint48).max); + + Market memory market; + market.totalSupplyAssets = 1 ether; + market.totalBorrowAssets = uint128(uint256(TARGET_UTILIZATION)); + assertEq(irm.borrowRate(marketParams, market), uint256(INITIAL_RATE_AT_TARGET)); + assertEq(irm.rateAtTarget(marketParams.id()), INITIAL_RATE_AT_TARGET); + + market.lastUpdate = uint128(block.timestamp); + vm.warp(block.timestamp + elapsed); + + irm.borrowRate(marketParams, market); + + assertEq(irm.rateAtTarget(marketParams.id()), INITIAL_RATE_AT_TARGET); + } + + function testRateAfter3WeeksUtilizationTargetPingEveryMinute() public { + int256 initialRateAtTarget = int256(1 ether) / 365 days; // 100% irm = new AdaptativeCurveIrm(address(this), CURVE_STEEPNESS, ADJUSTMENT_SPEED, TARGET_UTILIZATION, initialRateAtTarget); Market memory market; - market.totalSupplyAssets = 10 ether; - market.totalBorrowAssets = 9 ether; + market.totalSupplyAssets = 1 ether; + market.totalBorrowAssets = uint128(uint256(TARGET_UTILIZATION)); assertEq(irm.borrowRate(marketParams, market), uint256(initialRateAtTarget)); assertEq(irm.rateAtTarget(marketParams.id()), initialRateAtTarget); - for (uint256 i; i < 21 days / 1 minutes; ++i) { + for (uint256 i; i < 3 weeks / 1 minutes; ++i) { market.lastUpdate = uint128(block.timestamp); vm.warp(block.timestamp + 1 minutes); @@ -185,6 +199,9 @@ contract AdaptativeCurveIrmTest is Test { market.totalBorrowAssets += uint128(interest); } + assertApproxEqRel( + market.totalBorrowAssets.wDivDown(market.totalSupplyAssets), uint256(TARGET_UTILIZATION), 0.01 ether + ); assertApproxEqRel(irm.rateAtTarget(marketParams.id()), initialRateAtTarget, 0.1 ether); } From 0fedf48fe56835014b3087dd9eea1c05ae0a6c30 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Thu, 16 Nov 2023 16:49:11 +0100 Subject: [PATCH 4/5] fix: apply suggestions --- test/SpeedJumpIrmTest.sol | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/test/SpeedJumpIrmTest.sol b/test/SpeedJumpIrmTest.sol index aca6f533..78652e74 100644 --- a/test/SpeedJumpIrmTest.sol +++ b/test/SpeedJumpIrmTest.sol @@ -60,15 +60,6 @@ contract AdaptativeCurveIrmTest is Test { assertEq(irm.rateAtTarget(marketParams.id()), INITIAL_RATE_AT_TARGET, "rateAtTarget"); } - function testFirstBorrowRateUtilizationTarget() public { - Market memory market; - market.totalBorrowAssets = uint128(uint256(TARGET_UTILIZATION)); - market.totalSupplyAssets = 1 ether; - - assertEq(irm.borrowRate(marketParams, market), uint256(INITIAL_RATE_AT_TARGET), "avgBorrowRate"); - assertEq(irm.rateAtTarget(marketParams.id()), INITIAL_RATE_AT_TARGET, "rateAtTarget"); - } - function testRateAfterUtilizationOne() public { vm.warp(365 days * 2); Market memory market; @@ -157,7 +148,13 @@ contract AdaptativeCurveIrmTest is Test { } assertApproxEqRel(market.totalBorrowAssets.wDivDown(market.totalSupplyAssets), 0.95 ether, 0.005 ether); - assertApproxEqRel(irm.rateAtTarget(marketParams.id()), int256(0.6092 ether) / 365 days, 0.06 ether); + + int256 rateAtTarget = irm.rateAtTarget(marketParams.id()); + int256 expectedRateAtTarget = int256(0.6092 ether) / 365 days; + assertGe(rateAtTarget, expectedRateAtTarget); + // Expected rate: 0.1% * exp(50 * 60 / 365 * 50%) = 60.92%. + // The rate is tolerated to be +6% (relatively) because of the hourly pings. + assertApproxEqRel(rateAtTarget, expectedRateAtTarget, 0.06 ether); } function testRateAfterUtilizationTargetNoPing(uint256 elapsed) public { @@ -202,7 +199,11 @@ contract AdaptativeCurveIrmTest is Test { assertApproxEqRel( market.totalBorrowAssets.wDivDown(market.totalSupplyAssets), uint256(TARGET_UTILIZATION), 0.01 ether ); - assertApproxEqRel(irm.rateAtTarget(marketParams.id()), initialRateAtTarget, 0.1 ether); + + int256 rateAtTarget = irm.rateAtTarget(marketParams.id()); + assertGe(rateAtTarget, initialRateAtTarget); + // The rate is tolerated to be +10% (relatively) because of the pings every minute. + assertApproxEqRel(rateAtTarget, initialRateAtTarget, 0.1 ether); } function testFirstBorrowRate(Market memory market) public { From 08f10de3e48c3686a7c62e9bb8a42f862f765fd2 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Thu, 16 Nov 2023 19:34:14 +0100 Subject: [PATCH 5/5] test(irm): add tests --- test/SpeedJumpIrmTest.sol | 41 +++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/test/SpeedJumpIrmTest.sol b/test/SpeedJumpIrmTest.sol index 47e1821c..1637a196 100644 --- a/test/SpeedJumpIrmTest.sol +++ b/test/SpeedJumpIrmTest.sol @@ -112,7 +112,7 @@ contract AdaptiveCurveIrmTest is Test { assertApproxEqRel(irm.borrowRateView(marketParams, market), uint256(0.00181 ether) / 365 days, 0.1 ether); } - function testRateAfter60DaysUtilizationAboveTargetNoPing() public { + function testRateAfter45DaysUtilizationAboveTargetNoPing() public { Market memory market; market.totalSupplyAssets = 1 ether; market.totalBorrowAssets = uint128(uint256(TARGET_UTILIZATION)); @@ -120,41 +120,52 @@ contract AdaptiveCurveIrmTest is Test { assertEq(irm.rateAtTarget(marketParams.id()), INITIAL_RATE_AT_TARGET); market.lastUpdate = uint128(block.timestamp); - vm.warp(block.timestamp + 60 days); + vm.warp(block.timestamp + 45 days); - market.totalBorrowAssets = uint128(uint256(TARGET_UTILIZATION + 1 ether) / 2); + market.totalBorrowAssets = uint128(uint256(TARGET_UTILIZATION + 1 ether) / 2); // Error = 50% irm.borrowRate(marketParams, market); - assertApproxEqRel(irm.rateAtTarget(marketParams.id()), int256(0.6092 ether) / 365 days, 0.0001 ether); + // Expected rate: 1% * exp(50 * 45 / 365 * 50%) = 21.81%. + assertApproxEqRel(irm.rateAtTarget(marketParams.id()), int256(0.2181 ether) / 365 days, 0.005 ether); } - function testRateAfter60DaysUtilizationAboveTargetPingEveryHour() public { + function testRateAfter45DaysUtilizationAboveTargetPingEveryMinute() public { Market memory market; market.totalSupplyAssets = 1 ether; market.totalBorrowAssets = uint128(uint256(TARGET_UTILIZATION)); assertEq(irm.borrowRate(marketParams, market), uint256(INITIAL_RATE_AT_TARGET)); assertEq(irm.rateAtTarget(marketParams.id()), INITIAL_RATE_AT_TARGET); - market.totalBorrowAssets = uint128(uint256(TARGET_UTILIZATION + 1 ether) / 2); // Error = 50% + uint128 initialBorrowAssets = uint128(uint256(TARGET_UTILIZATION + 1 ether) / 2); // Error = 50% + + market.totalBorrowAssets = initialBorrowAssets; - for (uint256 i; i < 60 days / 1 hours; ++i) { + for (uint256 i; i < 45 days / 1 minutes; ++i) { market.lastUpdate = uint128(block.timestamp); - vm.warp(block.timestamp + 1 hours); + vm.warp(block.timestamp + 1 minutes); uint256 avgBorrowRate = irm.borrowRate(marketParams, market); - uint256 interest = market.totalBorrowAssets.wMulDown(avgBorrowRate.wTaylorCompounded(1 hours)); + uint256 interest = market.totalBorrowAssets.wMulDown(avgBorrowRate.wTaylorCompounded(1 minutes)); market.totalSupplyAssets += uint128(interest); market.totalBorrowAssets += uint128(interest); } - assertApproxEqRel(market.totalBorrowAssets.wDivDown(market.totalSupplyAssets), 0.95 ether, 0.005 ether); + assertApproxEqRel( + market.totalBorrowAssets.wDivDown(market.totalSupplyAssets), 0.95 ether, 0.002 ether, "utilization" + ); int256 rateAtTarget = irm.rateAtTarget(marketParams.id()); - int256 expectedRateAtTarget = int256(0.6092 ether) / 365 days; + // Expected rate: 1% * exp(50 * 45 / 365 * 50%) = 21.81%. + int256 expectedRateAtTarget = int256(0.2181 ether) / 365 days; assertGe(rateAtTarget, expectedRateAtTarget); - // Expected rate: 0.1% * exp(50 * 60 / 365 * 50%) = 60.92%. - // The rate is tolerated to be +6% (relatively) because of the hourly pings. - assertApproxEqRel(rateAtTarget, expectedRateAtTarget, 0.06 ether); + // The rate is tolerated to be +2% (relatively) because of the pings every minute. + assertApproxEqRel(rateAtTarget, expectedRateAtTarget, 0.02 ether, "expectedRateAtTarget"); + + // Expected growth: exp(21.81% * 3.5 * 45 / 365) = +9.87%. + // The growth is tolerated to be +8% (relatively) because of the pings every minute. + assertApproxEqRel( + market.totalBorrowAssets, initialBorrowAssets.wMulDown(1.0987 ether), 0.08 ether, "totalBorrowAssets" + ); } function testRateAfterUtilizationTargetNoPing(uint256 elapsed) public { @@ -178,7 +189,7 @@ contract AdaptiveCurveIrmTest is Test { int256 initialRateAtTarget = int256(1 ether) / 365 days; // 100% irm = - new AdaptativeCurveIrm(address(this), CURVE_STEEPNESS, ADJUSTMENT_SPEED, TARGET_UTILIZATION, initialRateAtTarget); + new AdaptiveCurveIrm(address(this), CURVE_STEEPNESS, ADJUSTMENT_SPEED, TARGET_UTILIZATION, initialRateAtTarget); Market memory market; market.totalSupplyAssets = 1 ether;