Skip to content

Commit

Permalink
fix: minor improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
MathisGD committed Nov 2, 2023
1 parent 9de0936 commit 6e99116
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 33 deletions.
28 changes: 13 additions & 15 deletions src/SpeedJumpIrm.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import {MarketParamsLib} from "../lib/morpho-blue/src/libraries/MarketParamsLib.
import {Id, MarketParams, Market} from "../lib/morpho-blue/src/interfaces/IMorpho.sol";
import {WAD, MathLib as MorphoMathLib} from "../lib/morpho-blue/src/libraries/MathLib.sol";

/// @title AdaptativeCurveIRM
/// @title AdaptativeCurveIrm
/// @author Morpho Labs
/// @custom:contact security@morpho.xyz
contract AdaptativeCurveIRM is IIrm {
contract AdaptativeCurveIrm is IIrm {
using MathLib for int256;
using MathLib for uint256;
using UtilsLib for uint256;
Expand Down Expand Up @@ -105,7 +105,7 @@ contract AdaptativeCurveIRM is IIrm {
return avgBorrowRate;
}

/// @dev Returns err, newBorrowRate and avgBorrowRate.
/// @dev Returns err, endBorrowRate and avgBorrowRate.
/// @dev Assumes that the inputs `marketParams` and `id` match.
function _borrowRate(Id id, Market memory market) private view returns (uint256, uint256) {
uint256 utilization =
Expand All @@ -123,27 +123,25 @@ contract AdaptativeCurveIRM is IIrm {
// Safe "unchecked" cast because elapsed <= block.timestamp.
int256 linearVariation = speed * int256(elapsed);
uint256 variationMultiplier = MathLib.wExp(linearVariation);
// newRateAtTarget is bounded between MIN_RATE_AT_TARGET, MAX_RATE_AT_TARGET.
// newRateAtTarget is bounded between MIN_RATE_AT_TARGET and MAX_RATE_AT_TARGET.
uint256 newRateAtTarget = (prevRateAtTarget > 0)
? prevRateAtTarget.wMulDown(variationMultiplier).bound(MIN_RATE_AT_TARGET, MAX_RATE_AT_TARGET)
: INITIAL_RATE_AT_TARGET;
uint256 newBorrowRate = _curve(newRateAtTarget, err);
uint256 endBorrowRate = _curve(newRateAtTarget, err);

uint256 borrowRateStartOfThePeriod = _curve(prevRateAtTarget, err);
uint256 startBorrowRate = _curve(prevRateAtTarget, err);
// Then we compute the average rate over the period (this is what Morpho needs to accrue the interest).
// avgBorrowRate = 1 / elapsed * ∫ borrowRateStartOfThePeriod * exp(speed * t) dt between 0 and elapsed
// = borrowRateStartOfThePeriod * (exp(linearVariation) - 1) / linearVariation
// = (newBorrowRate - borrowRateStartOfThePeriod) / linearVariation
// And avgBorrowRate ~ borrowRateStartOfThePeriod ~ newBorrowRate for linearVariation around zero.
// avgBorrowRate = 1 / elapsed * ∫ startBorrowRate * exp(speed * t) dt between 0 and elapsed
// = startBorrowRate * (exp(linearVariation) - 1) / linearVariation
// = (endBorrowRate - startBorrowRate) / linearVariation
// And avgBorrowRate ~ startBorrowRate ~ endBorrowRate for linearVariation around zero.
// Also, when it is the first interaction (rateAtTarget == 0).
uint256 avgBorrowRate;
if (linearVariation == 0 || prevRateAtTarget == 0) {
avgBorrowRate = newBorrowRate;
avgBorrowRate = endBorrowRate;
} else {
// Safe "unchecked" cast to uint256 because linearVariation < 0 <=> newBorrowRate <=
// borrowRateStartOfThePeriod.
avgBorrowRate =
uint256((int256(newBorrowRate) - int256(borrowRateStartOfThePeriod)).wDivDown(linearVariation));
// Safe "unchecked" cast to uint256 because linearVariation < 0 <=> endBorrowRate <= startBorrowRate.
avgBorrowRate = uint256((int256(endBorrowRate) - int256(startBorrowRate)).wDivDown(linearVariation));
}

return (avgBorrowRate, newRateAtTarget);
Expand Down
73 changes: 55 additions & 18 deletions test/SpeedJumpIrmTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "../src/SpeedJumpIrm.sol";

import "../lib/forge-std/src/Test.sol";

contract AdaptativeCurveIRMTest is Test {
contract AdaptativeCurveIrmTest is Test {
using MathLib for int256;
using MathLib for int256;
using MathLib for uint256;
Expand All @@ -18,30 +18,61 @@ contract AdaptativeCurveIRMTest is Test {

uint256 internal constant CURVE_STEEPNESS = 4 ether;
uint256 internal constant ADJUSTMENT_SPEED = 50 ether / uint256(365 days);
uint256 internal constant TARGET_UTILIZATION = 0.8 ether;
uint256 internal constant INITIAL_BASE_RATE = 0.01 ether / uint256(365 days);
uint256 internal constant TARGET_UTILIZATION = 0.9 ether;
uint256 internal constant INITIAL_RATE_AT_TARGET = 0.01 ether / uint256(365 days);

AdaptativeCurveIRM internal irm;
AdaptativeCurveIrm internal irm;
MarketParams internal marketParams = MarketParams(address(0), address(0), address(0), address(0), 0);

function setUp() public {
irm =
new AdaptativeCurveIRM(address(this), CURVE_STEEPNESS, ADJUSTMENT_SPEED, TARGET_UTILIZATION, INITIAL_BASE_RATE);
new AdaptativeCurveIrm(address(this), CURVE_STEEPNESS, ADJUSTMENT_SPEED, TARGET_UTILIZATION, INITIAL_RATE_AT_TARGET);
vm.warp(90 days);
}

function testDeployment() public {
vm.expectRevert(bytes(ErrorsLib.ZERO_ADDRESS));
new AdaptativeCurveIRM(address(0), 0, 0, 0, 0);
new AdaptativeCurveIrm(address(0), 0, 0, 0, 0);
}

function testFirstBorrowRateEmptyMarket() public {
function testFirstBorrowRateUtilizationZero() public {
Market memory market;
uint256 avgBorrowRate = irm.borrowRate(marketParams, market);
uint256 rateAtTarget = irm.rateAtTarget(marketParams.id());

assertEq(avgBorrowRate, _curve(INITIAL_BASE_RATE, -1 ether), "avgBorrowRate");
assertEq(rateAtTarget, INITIAL_BASE_RATE, "rateAtTarget");
assertEq(irm.borrowRate(marketParams, market), INITIAL_RATE_AT_TARGET / 4, "avgBorrowRate");
assertEq(irm.rateAtTarget(marketParams.id()), INITIAL_RATE_AT_TARGET, "rateAtTarget");
}

function testFirstBorrowRateUtilizationOne() public {
Market memory market;
market.totalBorrowAssets = 1 ether;
market.totalSupplyAssets = 1 ether;

assertEq(irm.borrowRate(marketParams, market), INITIAL_RATE_AT_TARGET * 4, "avgBorrowRate");
assertEq(irm.rateAtTarget(marketParams.id()), INITIAL_RATE_AT_TARGET, "rateAtTarget");
}

function testFirstBorrowRateUtilizationTarget() public {
Market memory market;
market.totalBorrowAssets = 0.9 ether;
market.totalSupplyAssets = 1 ether;

assertEq(irm.borrowRate(marketParams, market), INITIAL_RATE_AT_TARGET, "avgBorrowRate");
assertEq(irm.rateAtTarget(marketParams.id()), INITIAL_RATE_AT_TARGET, "rateAtTarget");
}

function testRateAfterUtilizationOne() public {
Market memory market;
irm.borrowRate(marketParams, market);

vm.warp(365 days * 2);

market.totalBorrowAssets = 1 ether;
market.totalSupplyAssets = 1 ether;
market.lastUpdate = uint128(block.timestamp - 365 days);
irm.borrowRate(marketParams, market);

// TODO: fix
// assertGt(irm.borrowRate(marketParams, market), INITIAL_RATE_AT_TARGET * 50 * 4);
}

function testFirstBorrowRate(Market memory market) public {
Expand All @@ -51,8 +82,8 @@ contract AdaptativeCurveIRMTest is Test {
uint256 avgBorrowRate = irm.borrowRate(marketParams, market);
uint256 rateAtTarget = irm.rateAtTarget(marketParams.id());

assertEq(avgBorrowRate, _curve(INITIAL_BASE_RATE, _err(market)), "avgBorrowRate");
assertEq(rateAtTarget, INITIAL_BASE_RATE, "rateAtTarget");
assertEq(avgBorrowRate, _curve(INITIAL_RATE_AT_TARGET, _err(market)), "avgBorrowRate");
assertEq(rateAtTarget, INITIAL_RATE_AT_TARGET, "rateAtTarget");
}

function testBorrowRateEventEmission(Market memory market) public {
Expand All @@ -61,7 +92,9 @@ contract AdaptativeCurveIRMTest is Test {

vm.expectEmit(true, true, true, true, address(irm));
emit BorrowRateUpdate(
marketParams.id(), _curve(INITIAL_BASE_RATE, _err(market)), _expectedRateAtTarget(marketParams.id(), market)
marketParams.id(),
_curve(INITIAL_RATE_AT_TARGET, _err(market)),
_expectedRateAtTarget(marketParams.id(), market)
);
irm.borrowRate(marketParams, market);
}
Expand All @@ -73,7 +106,7 @@ contract AdaptativeCurveIRMTest is Test {
uint256 avgBorrowRate = irm.borrowRateView(marketParams, market);
uint256 rateAtTarget = irm.rateAtTarget(marketParams.id());

assertEq(avgBorrowRate, _curve(INITIAL_BASE_RATE, _err(market)), "avgBorrowRate");
assertEq(avgBorrowRate, _curve(INITIAL_RATE_AT_TARGET, _err(market)), "avgBorrowRate");
assertEq(rateAtTarget, 0, "prevBorrowRate");
}

Expand Down Expand Up @@ -139,12 +172,16 @@ contract AdaptativeCurveIRMTest is Test {

function invariantMinRateAtTarget() public {
Market memory market;
assertGt(irm.borrowRate(marketParams, market), irm.MIN_RATE_AT_TARGET());
market.totalBorrowAssets = 9 ether;
market.totalSupplyAssets = 10 ether;
assertGt(irm.borrowRate(marketParams, market), irm.MIN_RATE_AT_TARGET().wDivDown(CURVE_STEEPNESS));
}

function invariantMaxRateAtTarget() public {
Market memory market;
assertLt(irm.borrowRate(marketParams, market), irm.MAX_RATE_AT_TARGET());
market.totalBorrowAssets = 9 ether;
market.totalSupplyAssets = 10 ether;
assertLt(irm.borrowRate(marketParams, market), irm.MAX_RATE_AT_TARGET().wMulDown(CURVE_STEEPNESS));
}

function _expectedRateAtTarget(Id id, Market memory market) internal view returns (uint256) {
Expand All @@ -155,7 +192,7 @@ contract AdaptativeCurveIRMTest is Test {
uint256 variationMultiplier = MathLib.wExp(linearVariation);
return (rateAtTarget > 0)
? rateAtTarget.wMulDown(variationMultiplier).bound(irm.MIN_RATE_AT_TARGET(), irm.MAX_RATE_AT_TARGET())
: INITIAL_BASE_RATE;
: INITIAL_RATE_AT_TARGET;
}

function _expectedAvgRate(Id id, Market memory market) internal view returns (uint256) {
Expand Down

0 comments on commit 6e99116

Please sign in to comment.