Skip to content

Commit

Permalink
style: base rate naming
Browse files Browse the repository at this point in the history
  • Loading branch information
MathisGD committed Oct 31, 2023
1 parent 89a1fdc commit 8eb5285
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 50 deletions.
46 changes: 24 additions & 22 deletions src/SpeedJumpIrm.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ contract AdaptativeCurveIRM is IIrm {
/* EVENTS */

/// @notice Emitted when a borrow rate is updated.
event BorrowRateUpdate(Id indexed id, uint256 avgBorrowRate, uint256 baseRate);
event BorrowRateUpdate(Id indexed id, uint256 avgBorrowRate, uint256 rateAtTarget);

/* CONSTANTS */

Expand All @@ -49,8 +49,9 @@ contract AdaptativeCurveIRM is IIrm {

/* STORAGE */

/// @notice Base rate of markets.
mapping(Id => uint256) public baseRate;
/// @notice Rate at target utilization.
/// @dev Tells the height of the curve.
mapping(Id => uint256) public rateAtTarget;

/* CONSTRUCTOR */

Expand All @@ -59,13 +60,13 @@ contract AdaptativeCurveIRM is IIrm {
/// @param curveSteepness The curve steepness (scaled by WAD).
/// @param adjustmentSpeed The adjustment speed (scaled by WAD).
/// @param targetUtilization The target utilization (scaled by WAD).
/// @param initialBaseRate The initial base rate (scaled by WAD).
/// @param initialRateAtTarget The initial rate at target (scaled by WAD).
constructor(
address morpho,
uint256 curveSteepness,
uint256 adjustmentSpeed,
uint256 targetUtilization,
uint256 initialBaseRate
uint256 initialRateAtTarget
) {
require(morpho != address(0), ErrorsLib.ZERO_ADDRESS);
require(curveSteepness <= uint256(type(int256).max), ErrorsLib.INPUT_TOO_LARGE);
Expand All @@ -78,7 +79,7 @@ contract AdaptativeCurveIRM is IIrm {
CURVE_STEEPNESS = curveSteepness;
ADJUSTMENT_SPEED = adjustmentSpeed;
TARGET_UTILIZATION = targetUtilization;
INITIAL_BASE_RATE = initialBaseRate;
INITIAL_BASE_RATE = initialRateAtTarget;
}

/* BORROW RATES */
Expand All @@ -95,11 +96,11 @@ contract AdaptativeCurveIRM is IIrm {

Id id = marketParams.id();

(uint256 avgBorrowRate, uint256 newBaseRate) = _borrowRate(id, market);
(uint256 avgBorrowRate, uint256 newRateAtTarget) = _borrowRate(id, market);

baseRate[id] = newBaseRate;
rateAtTarget[id] = newRateAtTarget;

emit BorrowRateUpdate(id, avgBorrowRate, newBaseRate);
emit BorrowRateUpdate(id, avgBorrowRate, newRateAtTarget);

return avgBorrowRate;
}
Expand All @@ -116,43 +117,44 @@ contract AdaptativeCurveIRM is IIrm {
// Safe "unchecked" cast because ADJUSTMENT_SPEED <= type(int256).max.
int256 speed = int256(ADJUSTMENT_SPEED).wMulDown(err);

uint256 prevBaseRate = baseRate[id];
uint256 elapsed = (prevBaseRate > 0) ? block.timestamp - market.lastUpdate : 0;
uint256 prevRateAtTarget = rateAtTarget[id];
uint256 elapsed = (prevRateAtTarget > 0) ? block.timestamp - market.lastUpdate : 0;
// Safe "unchecked" cast because elapsed <= block.timestamp.
int256 linearVariation = speed * int256(elapsed);
uint256 variationMultiplier = MathLib.wExp(linearVariation);
// newBaseRate is bounded between MIN_BASE_RATE, MAX_BASE_RATE.
uint256 newBaseRate = (prevBaseRate > 0)
? prevBaseRate.wMulDown(variationMultiplier).bound(MIN_BASE_RATE, MAX_BASE_RATE)
// newRateAtTarget is bounded between MIN_BASE_RATE, MAX_BASE_RATE.
uint256 newRateAtTarget = (prevRateAtTarget > 0)
? prevRateAtTarget.wMulDown(variationMultiplier).bound(MIN_BASE_RATE, MAX_BASE_RATE)
: INITIAL_BASE_RATE;
uint256 newBorrowRate = _curve(newBaseRate, err);
uint256 newBorrowRate = _curve(newRateAtTarget, err);

uint256 borrowRateStartOfThePeriod = _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.
// Also, when it is the first interaction (baseRate == 0).
// Also, when it is the first interaction (rateAtTarget == 0).
uint256 avgBorrowRate;
if (linearVariation == 0 || prevBaseRate == 0) {
if (linearVariation == 0 || prevRateAtTarget == 0) {
avgBorrowRate = newBorrowRate;
} else {
// Safe "unchecked" cast to uint256 because linearVariation < 0 <=> newBorrowRate <=
// borrowRateStartOfThePeriod.
avgBorrowRate =
uint256((int256(newBorrowRate) - int256(_curve(prevBaseRate, err))).wDivDown(linearVariation));
uint256((int256(newBorrowRate) - int256(borrowRateStartOfThePeriod)).wDivDown(linearVariation));
}

return (avgBorrowRate, newBaseRate);
return (avgBorrowRate, newRateAtTarget);
}

function _curve(uint256 _baseRate, int256 err) internal view returns (uint256) {
function _curve(uint256 _rateAtTarget, int256 err) internal view returns (uint256) {
// Safe "unchecked" cast because err >= -1 (in WAD).
if (err < 0) {
return uint256((WAD_INT - WAD_INT.wDivDown(int256(CURVE_STEEPNESS))).wMulDown(err) + WAD_INT).wMulDown(
_baseRate
_rateAtTarget
);
}
return uint256((int256(CURVE_STEEPNESS) - WAD_INT).wMulDown(err) + WAD_INT).wMulDown(_baseRate);
return uint256((int256(CURVE_STEEPNESS) - WAD_INT).wMulDown(err) + WAD_INT).wMulDown(_rateAtTarget);
}
}
56 changes: 28 additions & 28 deletions test/SpeedJumpIrmTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ contract AdaptativeCurveIRMTest is Test {
using MorphoMathLib for uint256;
using MarketParamsLib for MarketParams;

event BorrowRateUpdate(Id indexed id, uint256 avgBorrowRate, uint256 baseRate);
event BorrowRateUpdate(Id indexed id, uint256 avgBorrowRate, uint256 rateAtTarget);

uint256 internal constant CURVE_STEEPNESS = 4 ether;
uint256 internal constant ADJUSTMENT_SPEED = 50 ether / uint256(365 days);
Expand All @@ -38,21 +38,21 @@ contract AdaptativeCurveIRMTest is Test {
function testFirstBorrowRateEmptyMarket() public {
Market memory market;
uint256 avgBorrowRate = irm.borrowRate(marketParams, market);
uint256 baseRate = irm.baseRate(marketParams.id());
uint256 rateAtTarget = irm.rateAtTarget(marketParams.id());

assertEq(avgBorrowRate, _curve(INITIAL_BASE_RATE, -1 ether), "avgBorrowRate");
assertEq(baseRate, INITIAL_BASE_RATE, "baseRate");
assertEq(rateAtTarget, INITIAL_BASE_RATE, "rateAtTarget");
}

function testFirstBorrowRate(Market memory market) public {
vm.assume(market.totalBorrowAssets > 0);
vm.assume(market.totalSupplyAssets >= market.totalBorrowAssets);

uint256 avgBorrowRate = irm.borrowRate(marketParams, market);
uint256 baseRate = irm.baseRate(marketParams.id());
uint256 rateAtTarget = irm.rateAtTarget(marketParams.id());

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

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

vm.expectEmit(true, true, true, true, address(irm));
emit BorrowRateUpdate(
marketParams.id(), _curve(INITIAL_BASE_RATE, _err(market)), _expectedBaseRate(marketParams.id(), market)
marketParams.id(), _curve(INITIAL_BASE_RATE, _err(market)), _expectedRateAtTarget(marketParams.id(), market)
);
irm.borrowRate(marketParams, market);
}
Expand All @@ -71,10 +71,10 @@ contract AdaptativeCurveIRMTest is Test {
vm.assume(market.totalSupplyAssets >= market.totalBorrowAssets);

uint256 avgBorrowRate = irm.borrowRateView(marketParams, market);
uint256 baseRate = irm.baseRate(marketParams.id());
uint256 rateAtTarget = irm.rateAtTarget(marketParams.id());

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

function testBorrowRate(Market memory market0, Market memory market1) public {
Expand All @@ -86,15 +86,15 @@ contract AdaptativeCurveIRMTest is Test {
vm.assume(market1.totalSupplyAssets >= market1.totalBorrowAssets);
market1.lastUpdate = uint128(bound(market1.lastUpdate, 0, block.timestamp - 1));

uint256 expectedBaseRate = _expectedBaseRate(marketParams.id(), market1);
uint256 expectedRateAtTarget = _expectedRateAtTarget(marketParams.id(), market1);
uint256 expectedAvgRate = _expectedAvgRate(marketParams.id(), market1);

uint256 borrowRateView = irm.borrowRateView(marketParams, market1);
uint256 borrowRate = irm.borrowRate(marketParams, market1);

assertEq(borrowRateView, borrowRate, "borrowRateView");
assertApproxEqRel(borrowRate, expectedAvgRate, 0.01 ether, "avgBorrowRate");
assertApproxEqRel(irm.baseRate(marketParams.id()), expectedBaseRate, 0.001 ether, "baseRate");
assertApproxEqRel(irm.rateAtTarget(marketParams.id()), expectedRateAtTarget, 0.001 ether, "rateAtTarget");
}

function testBorrowRateNoTimeElapsed(Market memory market0, Market memory market1) public {
Expand All @@ -106,15 +106,15 @@ contract AdaptativeCurveIRMTest is Test {
vm.assume(market1.totalSupplyAssets >= market1.totalBorrowAssets);
market1.lastUpdate = uint128(block.timestamp);

uint256 expectedBaseRate = _expectedBaseRate(marketParams.id(), market1);
uint256 expectedRateAtTarget = _expectedRateAtTarget(marketParams.id(), market1);
uint256 expectedAvgRate = _expectedAvgRate(marketParams.id(), market1);

uint256 borrowRateView = irm.borrowRateView(marketParams, market1);
uint256 borrowRate = irm.borrowRate(marketParams, market1);

assertEq(borrowRateView, borrowRate, "borrowRateView");
assertApproxEqRel(borrowRate, expectedAvgRate, 0.01 ether, "avgBorrowRate");
assertApproxEqRel(irm.baseRate(marketParams.id()), expectedBaseRate, 0.001 ether, "baseRate");
assertApproxEqRel(irm.rateAtTarget(marketParams.id()), expectedRateAtTarget, 0.001 ether, "rateAtTarget");
}

function testBorrowRateNoUtilizationChange(Market memory market0, Market memory market1) public {
Expand All @@ -126,55 +126,55 @@ contract AdaptativeCurveIRMTest is Test {
market1.totalSupplyAssets = market0.totalSupplyAssets;
market1.lastUpdate = uint128(bound(market1.lastUpdate, 0, block.timestamp - 1));

uint256 expectedBaseRate = _expectedBaseRate(marketParams.id(), market1);
uint256 expectedRateAtTarget = _expectedRateAtTarget(marketParams.id(), market1);
uint256 expectedAvgRate = _expectedAvgRate(marketParams.id(), market1);

uint256 borrowRateView = irm.borrowRateView(marketParams, market1);
uint256 borrowRate = irm.borrowRate(marketParams, market1);

assertEq(borrowRateView, borrowRate, "borrowRateView");
assertApproxEqRel(borrowRate, expectedAvgRate, 0.01 ether, "avgBorrowRate");
assertApproxEqRel(irm.baseRate(marketParams.id()), expectedBaseRate, 0.001 ether, "baseRate");
assertApproxEqRel(irm.rateAtTarget(marketParams.id()), expectedRateAtTarget, 0.001 ether, "rateAtTarget");
}

function _expectedBaseRate(Id id, Market memory market) internal view returns (uint256) {
uint256 baseRate = irm.baseRate(id);
function _expectedRateAtTarget(Id id, Market memory market) internal view returns (uint256) {
uint256 rateAtTarget = irm.rateAtTarget(id);
int256 speed = int256(ADJUSTMENT_SPEED).wMulDown(_err(market));
uint256 elapsed = (baseRate > 0) ? block.timestamp - market.lastUpdate : 0;
uint256 elapsed = (rateAtTarget > 0) ? block.timestamp - market.lastUpdate : 0;
int256 linearVariation = speed * int256(elapsed);
uint256 variationMultiplier = MathLib.wExp(linearVariation);
return (baseRate > 0)
? baseRate.wMulDown(variationMultiplier).bound(irm.MIN_BASE_RATE(), irm.MAX_BASE_RATE())
return (rateAtTarget > 0)
? rateAtTarget.wMulDown(variationMultiplier).bound(irm.MIN_BASE_RATE(), irm.MAX_BASE_RATE())
: INITIAL_BASE_RATE;
}

function _expectedAvgRate(Id id, Market memory market) internal view returns (uint256) {
uint256 baseRate = irm.baseRate(id);
uint256 rateAtTarget = irm.rateAtTarget(id);
int256 err = _err(market);
int256 speed = int256(ADJUSTMENT_SPEED).wMulDown(err);
uint256 elapsed = (baseRate > 0) ? block.timestamp - market.lastUpdate : 0;
uint256 elapsed = (rateAtTarget > 0) ? block.timestamp - market.lastUpdate : 0;
int256 linearVariation = speed * int256(elapsed);
uint256 newBaseRate = _expectedBaseRate(id, market);
uint256 newBorrowRate = _curve(newBaseRate, err);
uint256 newRateAtTarget = _expectedRateAtTarget(id, market);
uint256 newBorrowRate = _curve(newRateAtTarget, err);

uint256 avgBorrowRate;
if (linearVariation == 0 || baseRate == 0) {
if (linearVariation == 0 || rateAtTarget == 0) {
avgBorrowRate = newBorrowRate;
} else {
// Safe "unchecked" cast to uint256 because linearVariation < 0 <=> newBorrowRate <= borrowRateAfterJump.
avgBorrowRate = uint256((int256(newBorrowRate) - int256(_curve(baseRate, err))).wDivDown(linearVariation));
avgBorrowRate = uint256((int256(newBorrowRate) - int256(_curve(rateAtTarget, err))).wDivDown(linearVariation));
}
return avgBorrowRate;
}

function _curve(uint256 baseRate, int256 err) internal pure returns (uint256) {
function _curve(uint256 rateAtTarget, int256 err) internal pure returns (uint256) {
// Safe "unchecked" cast because err >= -1 (in WAD).
if (err < 0) {
return uint256((WAD_INT - WAD_INT.wDivDown(int256(CURVE_STEEPNESS))).wMulDown(err) + WAD_INT).wMulDown(
baseRate
rateAtTarget
);
}
return uint256((int256(CURVE_STEEPNESS) - WAD_INT).wMulDown(err) + WAD_INT).wMulDown(baseRate);
return uint256((int256(CURVE_STEEPNESS) - WAD_INT).wMulDown(err) + WAD_INT).wMulDown(rateAtTarget);
}

function _err(Market memory market) internal pure returns (int256) {
Expand Down

0 comments on commit 8eb5285

Please sign in to comment.