From 12ce108473b73c1d3f90ab501b6bfca07a66c783 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Mon, 27 Nov 2023 17:35:46 +0100 Subject: [PATCH 01/35] feat(ifc): add adaptive curve irm interface --- src/AdaptiveCurveIrm.sol | 3 ++- src/interfaces/IAdaptiveCurveIrm.sol | 16 ++++++++++++++++ test/AdaptiveCurveIrmTest.sol | 16 +++++++++------- 3 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 src/interfaces/IAdaptiveCurveIrm.sol diff --git a/src/AdaptiveCurveIrm.sol b/src/AdaptiveCurveIrm.sol index 9785dad4..fc12e795 100644 --- a/src/AdaptiveCurveIrm.sol +++ b/src/AdaptiveCurveIrm.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.19; import {IIrm} from "../lib/morpho-blue/src/interfaces/IIrm.sol"; +import {IAdaptiveCurveIrm} from "./interfaces/IAdaptiveCurveIrm.sol"; import {UtilsLib} from "./libraries/UtilsLib.sol"; import {ErrorsLib} from "./libraries/ErrorsLib.sol"; @@ -15,7 +16,7 @@ import {MathLib as MorphoMathLib} from "../lib/morpho-blue/src/libraries/MathLib /// @title AdaptiveCurveIrm /// @author Morpho Labs /// @custom:contact security@morpho.org -contract AdaptiveCurveIrm is IIrm { +contract AdaptiveCurveIrm is IAdaptiveCurveIrm { using MathLib for int256; using UtilsLib for int256; using MorphoMathLib for uint128; diff --git a/src/interfaces/IAdaptiveCurveIrm.sol b/src/interfaces/IAdaptiveCurveIrm.sol new file mode 100644 index 00000000..f24b5cf3 --- /dev/null +++ b/src/interfaces/IAdaptiveCurveIrm.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.5.0; + +import {IIrm} from "../../lib/morpho-blue/src/interfaces/IIrm.sol"; +import {Id} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; + +interface IAdaptiveCurveIrm is IIrm { + function MORPHO() external view returns (address); + + function CURVE_STEEPNESS() external view returns (int256); + function ADJUSTMENT_SPEED() external view returns (int256); + function TARGET_UTILIZATION() external view returns (int256); + function INITIAL_RATE_AT_TARGET() external view returns (int256); + + function rateAtTarget(Id id) external view returns (int256); +} diff --git a/test/AdaptiveCurveIrmTest.sol b/test/AdaptiveCurveIrmTest.sol index b08d4ec0..4e52a34b 100644 --- a/test/AdaptiveCurveIrmTest.sol +++ b/test/AdaptiveCurveIrmTest.sol @@ -20,12 +20,13 @@ contract AdaptiveCurveIrmTest is Test { int256 internal constant TARGET_UTILIZATION = 0.9 ether; int256 internal constant INITIAL_RATE_AT_TARGET = int256(0.01 ether) / 365 days; - AdaptiveCurveIrm internal irm; + IAdaptiveCurveIrm internal irm; MarketParams internal marketParams = MarketParams(address(0), address(0), address(0), address(0), 0); function setUp() public { - irm = - new AdaptiveCurveIrm(address(this), CURVE_STEEPNESS, ADJUSTMENT_SPEED, TARGET_UTILIZATION, INITIAL_RATE_AT_TARGET); + irm = new AdaptiveCurveIrm( + address(this), CURVE_STEEPNESS, ADJUSTMENT_SPEED, TARGET_UTILIZATION, INITIAL_RATE_AT_TARGET + ); vm.warp(90 days); bytes4[] memory selectors = new bytes4[](1); @@ -187,8 +188,9 @@ contract AdaptiveCurveIrmTest is Test { function testRateAfter3WeeksUtilizationTargetPingEveryMinute() public { int256 initialRateAtTarget = int256(1 ether) / 365 days; // 100% - irm = - new AdaptiveCurveIrm(address(this), CURVE_STEEPNESS, ADJUSTMENT_SPEED, TARGET_UTILIZATION, initialRateAtTarget); + irm = new AdaptiveCurveIrm( + address(this), CURVE_STEEPNESS, ADJUSTMENT_SPEED, TARGET_UTILIZATION, initialRateAtTarget + ); Market memory market; market.totalSupplyAssets = 1 ether; @@ -358,7 +360,7 @@ contract AdaptiveCurveIrmTest is Test { /* HELPERS */ function _expectedRateAtTarget(Id id, Market memory market) internal view returns (int256) { - int256 rateAtTarget = int256(irm.rateAtTarget(id)); + int256 rateAtTarget = irm.rateAtTarget(id); int256 speed = ADJUSTMENT_SPEED.wMulDown(_err(market)); uint256 elapsed = (rateAtTarget > 0) ? block.timestamp - market.lastUpdate : 0; int256 linearAdaptation = speed * int256(elapsed); @@ -371,7 +373,7 @@ contract AdaptiveCurveIrmTest is Test { } function _expectedAvgRate(Id id, Market memory market) internal view returns (uint256) { - int256 rateAtTarget = int256(irm.rateAtTarget(id)); + int256 rateAtTarget = irm.rateAtTarget(id); int256 err = _err(market); int256 speed = ADJUSTMENT_SPEED.wMulDown(err); uint256 elapsed = (rateAtTarget > 0) ? block.timestamp - market.lastUpdate : 0; From 01b77ffdbaaafef611828a44b5196843a151f737 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Tue, 28 Nov 2023 15:18:11 +0100 Subject: [PATCH 02/35] refactor(ifc): move natspecs to interfaces --- src/AdaptiveCurveIrm.sol | 19 ++++++------------- src/interfaces/IAdaptiveCurveIrm.sol | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/AdaptiveCurveIrm.sol b/src/AdaptiveCurveIrm.sol index fc12e795..4e769b4b 100644 --- a/src/AdaptiveCurveIrm.sol +++ b/src/AdaptiveCurveIrm.sol @@ -30,31 +30,24 @@ contract AdaptiveCurveIrm is IAdaptiveCurveIrm { /* IMMUTABLES */ - /// @notice Address of Morpho. + /// @inheritdoc IAdaptiveCurveIrm address public immutable MORPHO; - /// @notice Curve steepness (scaled by WAD). - /// @dev Verified to be inside the expected range at construction. + /// @inheritdoc IAdaptiveCurveIrm int256 public immutable CURVE_STEEPNESS; - /// @notice Adjustment speed (scaled by WAD). - /// @dev The speed is per second, so the rate moves at a speed of ADJUSTMENT_SPEED * err each second (while being - /// continuously compounded). A typical value for the ADJUSTMENT_SPEED would be 10 ether / 365 days. - /// @dev Verified to be inside the expected range at construction. + /// @inheritdoc IAdaptiveCurveIrm int256 public immutable ADJUSTMENT_SPEED; - /// @notice Target utilization (scaled by WAD). - /// @dev Verified to be strictly between 0 and 1 at construction. + /// @inheritdoc IAdaptiveCurveIrm int256 public immutable TARGET_UTILIZATION; - /// @notice Initial rate at target per second (scaled by WAD). - /// @dev Verified to be between MIN_RATE_AT_TARGET and MAX_RATE_AT_TARGET at contruction. + /// @inheritdoc IAdaptiveCurveIrm int256 public immutable INITIAL_RATE_AT_TARGET; /* STORAGE */ - /// @notice Rate at target utilization. - /// @dev Tells the height of the curve. + /// @inheritdoc IAdaptiveCurveIrm mapping(Id => int256) public rateAtTarget; /* CONSTRUCTOR */ diff --git a/src/interfaces/IAdaptiveCurveIrm.sol b/src/interfaces/IAdaptiveCurveIrm.sol index f24b5cf3..347ae50e 100644 --- a/src/interfaces/IAdaptiveCurveIrm.sol +++ b/src/interfaces/IAdaptiveCurveIrm.sol @@ -5,12 +5,28 @@ import {IIrm} from "../../lib/morpho-blue/src/interfaces/IIrm.sol"; import {Id} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; interface IAdaptiveCurveIrm is IIrm { + /// @notice Address of Morpho. function MORPHO() external view returns (address); + /// @notice Curve steepness (scaled by WAD). + /// @dev Verified to be inside the expected range at construction. function CURVE_STEEPNESS() external view returns (int256); + + /// @notice Adjustment speed (scaled by WAD). + /// @dev The speed is per second, so the rate moves at a speed of ADJUSTMENT_SPEED * err each second (while being + /// continuously compounded). A typical value for the ADJUSTMENT_SPEED would be 10 ether / 365 days. + /// @dev Verified to be inside the expected range at construction. function ADJUSTMENT_SPEED() external view returns (int256); + + /// @notice Target utilization (scaled by WAD). + /// @dev Verified to be strictly between 0 and 1 at construction. function TARGET_UTILIZATION() external view returns (int256); + + /// @notice Initial rate at target per second (scaled by WAD). + /// @dev Verified to be between MIN_RATE_AT_TARGET and MAX_RATE_AT_TARGET at contruction. function INITIAL_RATE_AT_TARGET() external view returns (int256); + /// @notice Rate at target utilization. + /// @dev Tells the height of the curve. function rateAtTarget(Id id) external view returns (int256); } From c6a55799821a5b3806716f94d12841db1fe7e565 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Sun, 3 Dec 2023 23:30:52 +0100 Subject: [PATCH 03/35] chore: make params constants --- src/AdaptiveCurveIrm.sol | 55 +++----- src/libraries/ErrorsLib.sol | 9 -- src/libraries/adaptive-curve/ConstantsLib.sol | 19 --- test/AdaptiveCurveIrmTest.sol | 122 +++++++++--------- test/ExpLibTest.sol | 3 +- 5 files changed, 75 insertions(+), 133 deletions(-) delete mode 100644 src/libraries/adaptive-curve/ConstantsLib.sol diff --git a/src/AdaptiveCurveIrm.sol b/src/AdaptiveCurveIrm.sol index 9785dad4..7caf7ad0 100644 --- a/src/AdaptiveCurveIrm.sol +++ b/src/AdaptiveCurveIrm.sol @@ -7,7 +7,6 @@ import {UtilsLib} from "./libraries/UtilsLib.sol"; import {ErrorsLib} from "./libraries/ErrorsLib.sol"; import {MathLib, WAD_INT as WAD} from "./libraries/MathLib.sol"; import {ExpLib} from "./libraries/adaptive-curve/ExpLib.sol"; -import {ConstantsLib} from "./libraries/adaptive-curve/ConstantsLib.sol"; import {MarketParamsLib} from "../lib/morpho-blue/src/libraries/MarketParamsLib.sol"; import {Id, MarketParams, Market} from "../lib/morpho-blue/src/interfaces/IMorpho.sol"; import {MathLib as MorphoMathLib} from "../lib/morpho-blue/src/libraries/MathLib.sol"; @@ -27,28 +26,28 @@ contract AdaptiveCurveIrm is IIrm { /// @notice Emitted when a borrow rate is updated. event BorrowRateUpdate(Id indexed id, uint256 avgBorrowRate, uint256 rateAtTarget); - /* IMMUTABLES */ + /* CONSTANT */ /// @notice Address of Morpho. address public immutable MORPHO; /// @notice Curve steepness (scaled by WAD). - /// @dev Verified to be inside the expected range at construction. - int256 public immutable CURVE_STEEPNESS; + int256 public constant CURVE_STEEPNESS = 4 ether; - /// @notice Adjustment speed (scaled by WAD). - /// @dev The speed is per second, so the rate moves at a speed of ADJUSTMENT_SPEED * err each second (while being - /// continuously compounded). A typical value for the ADJUSTMENT_SPEED would be 10 ether / 365 days. - /// @dev Verified to be inside the expected range at construction. - int256 public immutable ADJUSTMENT_SPEED; + /// @notice Adjustment speed per second (scaled by WAD). + int256 public constant ADJUSTMENT_SPEED = int256(50 ether) / 365 days; /// @notice Target utilization (scaled by WAD). - /// @dev Verified to be strictly between 0 and 1 at construction. - int256 public immutable TARGET_UTILIZATION; + int256 public constant TARGET_UTILIZATION = 0.9 ether; /// @notice Initial rate at target per second (scaled by WAD). - /// @dev Verified to be between MIN_RATE_AT_TARGET and MAX_RATE_AT_TARGET at contruction. - int256 public immutable INITIAL_RATE_AT_TARGET; + int256 public constant INITIAL_RATE_AT_TARGET = int256(0.01 ether) / 365 days; + + /// @notice Mininimum rate at target per second (scaled by WAD) (0.1% APR). + int256 public constant MIN_RATE_AT_TARGET = int256(0.001 ether) / 365 days; + + /// @notice Maximum rate at target per second (scaled by WAD) (1B% APR). + int256 public constant MAX_RATE_AT_TARGET = int256(0.01e9 ether) / 365 days; /* STORAGE */ @@ -60,32 +59,10 @@ contract AdaptiveCurveIrm is IIrm { /// @notice Constructor. /// @param morpho The address of Morpho. - /// @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 initialRateAtTarget The initial rate at target (scaled by WAD). - constructor( - address morpho, - int256 curveSteepness, - int256 adjustmentSpeed, - int256 targetUtilization, - int256 initialRateAtTarget - ) { + constructor(address morpho) { require(morpho != address(0), ErrorsLib.ZERO_ADDRESS); - require(curveSteepness >= WAD, ErrorsLib.INPUT_TOO_SMALL); - require(curveSteepness <= ConstantsLib.MAX_CURVE_STEEPNESS, ErrorsLib.INPUT_TOO_LARGE); - require(adjustmentSpeed >= 0, ErrorsLib.INPUT_TOO_SMALL); - require(adjustmentSpeed <= ConstantsLib.MAX_ADJUSTMENT_SPEED, ErrorsLib.INPUT_TOO_LARGE); - require(targetUtilization < WAD, ErrorsLib.INPUT_TOO_LARGE); - require(targetUtilization > 0, ErrorsLib.ZERO_INPUT); - require(initialRateAtTarget >= ConstantsLib.MIN_RATE_AT_TARGET, ErrorsLib.INPUT_TOO_SMALL); - require(initialRateAtTarget <= ConstantsLib.MAX_RATE_AT_TARGET, ErrorsLib.INPUT_TOO_LARGE); MORPHO = morpho; - CURVE_STEEPNESS = curveSteepness; - ADJUSTMENT_SPEED = adjustmentSpeed; - TARGET_UTILIZATION = targetUtilization; - INITIAL_RATE_AT_TARGET = initialRateAtTarget; } /* BORROW RATES */ @@ -172,7 +149,7 @@ contract AdaptiveCurveIrm is IIrm { /// The formula of the curve is the following: /// r = ((1-1/C)*err + 1) * rateAtTarget if err < 0 /// ((C-1)*err + 1) * rateAtTarget else. - function _curve(int256 _rateAtTarget, int256 err) private view returns (int256) { + function _curve(int256 _rateAtTarget, int256 err) private pure returns (int256) { // Non negative because 1 - 1/C >= 0, C - 1 >= 0. int256 coeff = err < 0 ? WAD - WAD.wDivDown(CURVE_STEEPNESS) : CURVE_STEEPNESS - WAD; // Non negative if _rateAtTarget >= 0 because if err < 0, coeff <= 1. @@ -183,8 +160,6 @@ contract AdaptiveCurveIrm is IIrm { /// The formula is: max(min(startRateAtTarget * exp(linearAdaptation), maxRateAtTarget), minRateAtTarget). function _newRateAtTarget(int256 startRateAtTarget, int256 linearAdaptation) private pure returns (int256) { // Non negative because MIN_RATE_AT_TARGET > 0. - return startRateAtTarget.wMulDown(ExpLib.wExp(linearAdaptation)).bound( - ConstantsLib.MIN_RATE_AT_TARGET, ConstantsLib.MAX_RATE_AT_TARGET - ); + return startRateAtTarget.wMulDown(ExpLib.wExp(linearAdaptation)).bound(MIN_RATE_AT_TARGET, MAX_RATE_AT_TARGET); } } diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 8a88f349..5cb2c632 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -6,18 +6,9 @@ pragma solidity ^0.8.0; /// @custom:contact security@morpho.org /// @notice Library exposing error messages. library ErrorsLib { - /// @dev Thrown when the input is too large to fit in the expected type. - string internal constant INPUT_TOO_LARGE = "input too large"; - - /// @dev Thrown when the input is too small. - string internal constant INPUT_TOO_SMALL = "input too small"; - /// @dev Thrown when passing the zero address. string internal constant ZERO_ADDRESS = "zero address"; - /// @dev Thrown when passing the zero input. - string internal constant ZERO_INPUT = "zero input"; - /// @dev Thrown when the caller is not Morpho. string internal constant NOT_MORPHO = "not Morpho"; } diff --git a/src/libraries/adaptive-curve/ConstantsLib.sol b/src/libraries/adaptive-curve/ConstantsLib.sol deleted file mode 100644 index 3166d1c4..00000000 --- a/src/libraries/adaptive-curve/ConstantsLib.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/// @title ConstantsLib -/// @author Morpho Labs -/// @custom:contact security@morpho.org -library ConstantsLib { - /// @notice Maximum rate at target per second (scaled by WAD) (1B% APR). - int256 internal constant MAX_RATE_AT_TARGET = int256(0.01e9 ether) / 365 days; - - /// @notice Mininimum rate at target per second (scaled by WAD) (0.1% APR). - int256 internal constant MIN_RATE_AT_TARGET = int256(0.001 ether) / 365 days; - - /// @notice Maximum curve steepness allowed (scaled by WAD). - int256 internal constant MAX_CURVE_STEEPNESS = 100 ether; - - /// @notice Maximum adjustment speed allowed (scaled by WAD). - int256 internal constant MAX_ADJUSTMENT_SPEED = int256(1_000 ether) / 365 days; -} diff --git a/test/AdaptiveCurveIrmTest.sol b/test/AdaptiveCurveIrmTest.sol index ea980cf9..4e6c72a3 100644 --- a/test/AdaptiveCurveIrmTest.sol +++ b/test/AdaptiveCurveIrmTest.sol @@ -15,18 +15,11 @@ contract AdaptiveCurveIrmTest is Test { event BorrowRateUpdate(Id indexed id, uint256 avgBorrowRate, uint256 rateAtTarget); - int256 internal constant CURVE_STEEPNESS = 4 ether; - int256 internal constant ADJUSTMENT_SPEED = int256(50 ether) / 365 days; - int256 internal constant TARGET_UTILIZATION = 0.9 ether; - int256 internal constant INITIAL_RATE_AT_TARGET = int256(0.01 ether) / 365 days; - AdaptiveCurveIrm internal irm; MarketParams internal marketParams = MarketParams(address(0), address(0), address(0), address(0), 0); function setUp() public { - irm = new AdaptiveCurveIrm( - address(this), CURVE_STEEPNESS, ADJUSTMENT_SPEED, TARGET_UTILIZATION, INITIAL_RATE_AT_TARGET - ); + irm = new AdaptiveCurveIrm(address(this)); vm.warp(90 days); bytes4[] memory selectors = new bytes4[](1); @@ -39,16 +32,19 @@ contract AdaptiveCurveIrmTest is Test { function testDeployment() public { vm.expectRevert(bytes(ErrorsLib.ZERO_ADDRESS)); - new AdaptiveCurveIrm(address(0), 0, 0, 0, 0); + new AdaptiveCurveIrm(address(0)); } function testFirstBorrowRateUtilizationZero() public { Market memory market; assertApproxEqRel( - irm.borrowRate(marketParams, market), uint256(INITIAL_RATE_AT_TARGET / 4), 0.0001 ether, "avgBorrowRate" + irm.borrowRate(marketParams, market), + uint256(irm.INITIAL_RATE_AT_TARGET() / 4), + 0.0001 ether, + "avgBorrowRate" ); - assertEq(irm.rateAtTarget(marketParams.id()), INITIAL_RATE_AT_TARGET, "rateAtTarget"); + assertEq(irm.rateAtTarget(marketParams.id()), irm.INITIAL_RATE_AT_TARGET(), "rateAtTarget"); } function testFirstBorrowRateUtilizationOne() public { @@ -56,14 +52,14 @@ contract AdaptiveCurveIrmTest is Test { market.totalBorrowAssets = 1 ether; market.totalSupplyAssets = 1 ether; - assertEq(irm.borrowRate(marketParams, market), uint256(INITIAL_RATE_AT_TARGET * 4), "avgBorrowRate"); - assertEq(irm.rateAtTarget(marketParams.id()), INITIAL_RATE_AT_TARGET, "rateAtTarget"); + assertEq(irm.borrowRate(marketParams, market), uint256(irm.INITIAL_RATE_AT_TARGET() * 4), "avgBorrowRate"); + assertEq(irm.rateAtTarget(marketParams.id()), irm.INITIAL_RATE_AT_TARGET(), "rateAtTarget"); } function testRateAfterUtilizationOne() public { vm.warp(365 days * 2); Market memory market; - assertApproxEqRel(irm.borrowRate(marketParams, market), uint256(INITIAL_RATE_AT_TARGET / 4), 0.001 ether); + assertApproxEqRel(irm.borrowRate(marketParams, market), uint256(irm.INITIAL_RATE_AT_TARGET() / 4), 0.001 ether); market.totalBorrowAssets = 1 ether; market.totalSupplyAssets = 1 ether; @@ -72,13 +68,17 @@ contract AdaptiveCurveIrmTest is Test { // (exp((50/365)*5) ~= 1.9836. assertApproxEqRel( irm.borrowRateView(marketParams, market), - uint256((INITIAL_RATE_AT_TARGET * 4).wMulDown((1.9836 ether - 1 ether) * WAD / (ADJUSTMENT_SPEED * 5 days))), + uint256( + (irm.INITIAL_RATE_AT_TARGET() * 4).wMulDown( + (1.9836 ether - 1 ether) * WAD / (irm.ADJUSTMENT_SPEED() * 5 days) + ) + ), 0.1 ether ); // The average value of exp((50/365)*x) between 0 and 5 is approx. 1.4361. assertApproxEqRel( irm.borrowRateView(marketParams, market), - uint256((INITIAL_RATE_AT_TARGET * 4).wMulDown(1.4361 ether)), + uint256((irm.INITIAL_RATE_AT_TARGET() * 4).wMulDown(1.4361 ether)), 0.1 ether ); // Expected rate: 5.744%. @@ -88,7 +88,7 @@ contract AdaptiveCurveIrmTest is Test { function testRateAfterUtilizationZero() public { vm.warp(365 days * 2); Market memory market; - assertApproxEqRel(irm.borrowRate(marketParams, market), uint256(INITIAL_RATE_AT_TARGET / 4), 0.001 ether); + assertApproxEqRel(irm.borrowRate(marketParams, market), uint256(irm.INITIAL_RATE_AT_TARGET() / 4), 0.001 ether); market.totalBorrowAssets = 0 ether; market.totalSupplyAssets = 1 ether; @@ -98,14 +98,16 @@ contract AdaptiveCurveIrmTest is Test { assertApproxEqRel( irm.borrowRateView(marketParams, market), uint256( - (INITIAL_RATE_AT_TARGET / 4).wMulDown((0.5041 ether - 1 ether) * WAD / (-ADJUSTMENT_SPEED * 5 days)) + (irm.INITIAL_RATE_AT_TARGET() / 4).wMulDown( + (0.5041 ether - 1 ether) * WAD / (-irm.ADJUSTMENT_SPEED() * 5 days) + ) ), 0.1 ether ); // The average value of exp((-50/365*x)) between 0 and 5 is approx. 0.7240. assertApproxEqRel( irm.borrowRateView(marketParams, market), - uint256((INITIAL_RATE_AT_TARGET / 4).wMulDown(0.724 ether)), + uint256((irm.INITIAL_RATE_AT_TARGET() / 4).wMulDown(0.724 ether)), 0.1 ether ); // Expected rate: 0.181%. @@ -115,14 +117,14 @@ contract AdaptiveCurveIrmTest is Test { function testRateAfter45DaysUtilizationAboveTargetNoPing() 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(irm.TARGET_UTILIZATION())); + assertEq(irm.borrowRate(marketParams, market), uint256(irm.INITIAL_RATE_AT_TARGET())); + assertEq(irm.rateAtTarget(marketParams.id()), irm.INITIAL_RATE_AT_TARGET()); market.lastUpdate = uint128(block.timestamp); vm.warp(block.timestamp + 45 days); - market.totalBorrowAssets = uint128(uint256(TARGET_UTILIZATION + 1 ether) / 2); // Error = 50% + market.totalBorrowAssets = uint128(uint256(irm.TARGET_UTILIZATION() + 1 ether) / 2); // Error = 50% irm.borrowRate(marketParams, market); // Expected rate: 1% * exp(50 * 45 / 365 * 50%) = 21.81%. @@ -132,11 +134,11 @@ contract AdaptiveCurveIrmTest is Test { 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(irm.TARGET_UTILIZATION())); + assertEq(irm.borrowRate(marketParams, market), uint256(irm.INITIAL_RATE_AT_TARGET())); + assertEq(irm.rateAtTarget(marketParams.id()), irm.INITIAL_RATE_AT_TARGET()); - uint128 initialBorrowAssets = uint128(uint256(TARGET_UTILIZATION + 1 ether) / 2); // Error = 50% + uint128 initialBorrowAssets = uint128(uint256(irm.TARGET_UTILIZATION() + 1 ether) / 2); // Error = 50% market.totalBorrowAssets = initialBorrowAssets; @@ -173,30 +175,26 @@ contract AdaptiveCurveIrmTest is Test { 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(irm.TARGET_UTILIZATION())); + assertEq(irm.borrowRate(marketParams, market), uint256(irm.INITIAL_RATE_AT_TARGET())); + assertEq(irm.rateAtTarget(marketParams.id()), irm.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); + assertEq(irm.rateAtTarget(marketParams.id()), irm.INITIAL_RATE_AT_TARGET()); } function testRateAfter3WeeksUtilizationTargetPingEveryMinute() public { - int256 initialRateAtTarget = int256(1 ether) / 365 days; // 100% - - irm = new AdaptiveCurveIrm( - address(this), CURVE_STEEPNESS, ADJUSTMENT_SPEED, TARGET_UTILIZATION, initialRateAtTarget - ); + irm = new AdaptiveCurveIrm(address(this)); Market memory market; market.totalSupplyAssets = 1 ether; - market.totalBorrowAssets = uint128(uint256(TARGET_UTILIZATION)); - assertEq(irm.borrowRate(marketParams, market), uint256(initialRateAtTarget)); - assertEq(irm.rateAtTarget(marketParams.id()), initialRateAtTarget); + market.totalBorrowAssets = uint128(uint256(irm.TARGET_UTILIZATION())); + assertEq(irm.borrowRate(marketParams, market), uint256(irm.INITIAL_RATE_AT_TARGET())); + assertEq(irm.rateAtTarget(marketParams.id()), irm.INITIAL_RATE_AT_TARGET()); for (uint256 i; i < 3 weeks / 1 minutes; ++i) { market.lastUpdate = uint128(block.timestamp); @@ -209,13 +207,13 @@ contract AdaptiveCurveIrmTest is Test { } assertApproxEqRel( - market.totalBorrowAssets.wDivDown(market.totalSupplyAssets), uint256(TARGET_UTILIZATION), 0.01 ether + market.totalBorrowAssets.wDivDown(market.totalSupplyAssets), uint256(irm.TARGET_UTILIZATION()), 0.01 ether ); int256 rateAtTarget = irm.rateAtTarget(marketParams.id()); - assertGe(rateAtTarget, initialRateAtTarget); + assertGe(rateAtTarget, irm.INITIAL_RATE_AT_TARGET()); // The rate is tolerated to be +10% (relatively) because of the pings every minute. - assertApproxEqRel(rateAtTarget, initialRateAtTarget, 0.1 ether); + assertApproxEqRel(rateAtTarget, irm.INITIAL_RATE_AT_TARGET(), 0.1 ether); } function testFirstBorrowRate(Market memory market) public { @@ -225,8 +223,8 @@ contract AdaptiveCurveIrmTest is Test { uint256 avgBorrowRate = irm.borrowRate(marketParams, market); int256 rateAtTarget = irm.rateAtTarget(marketParams.id()); - assertEq(avgBorrowRate, _curve(int256(INITIAL_RATE_AT_TARGET), _err(market)), "avgBorrowRate"); - assertEq(rateAtTarget, INITIAL_RATE_AT_TARGET, "rateAtTarget"); + assertEq(avgBorrowRate, _curve(int256(irm.INITIAL_RATE_AT_TARGET()), _err(market)), "avgBorrowRate"); + assertEq(rateAtTarget, irm.INITIAL_RATE_AT_TARGET(), "rateAtTarget"); } function testBorrowRateEventEmission(Market memory market) public { @@ -236,7 +234,7 @@ contract AdaptiveCurveIrmTest is Test { vm.expectEmit(true, true, true, true, address(irm)); emit BorrowRateUpdate( marketParams.id(), - _curve(int256(INITIAL_RATE_AT_TARGET), _err(market)), + _curve(int256(irm.INITIAL_RATE_AT_TARGET()), _err(market)), uint256(_expectedRateAtTarget(marketParams.id(), market)) ); irm.borrowRate(marketParams, market); @@ -249,7 +247,7 @@ contract AdaptiveCurveIrmTest is Test { uint256 avgBorrowRate = irm.borrowRateView(marketParams, market); int256 rateAtTarget = irm.rateAtTarget(marketParams.id()); - assertEq(avgBorrowRate, _curve(int256(INITIAL_RATE_AT_TARGET), _err(market)), "avgBorrowRate"); + assertEq(avgBorrowRate, _curve(int256(irm.INITIAL_RATE_AT_TARGET()), _err(market)), "avgBorrowRate"); assertEq(rateAtTarget, 0, "prevBorrowRate"); } @@ -337,10 +335,10 @@ contract AdaptiveCurveIrmTest is Test { market.totalSupplyAssets = 10 ether; assertGe( - irm.borrowRateView(marketParams, market), uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDivDown(CURVE_STEEPNESS)) + irm.borrowRateView(marketParams, market), uint256(irm.MIN_RATE_AT_TARGET().wDivDown(irm.CURVE_STEEPNESS())) ); assertGe( - irm.borrowRate(marketParams, market), uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDivDown(CURVE_STEEPNESS)) + irm.borrowRate(marketParams, market), uint256(irm.MIN_RATE_AT_TARGET().wDivDown(irm.CURVE_STEEPNESS())) ); } @@ -350,10 +348,10 @@ contract AdaptiveCurveIrmTest is Test { market.totalSupplyAssets = 10 ether; assertLe( - irm.borrowRateView(marketParams, market), uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMulDown(CURVE_STEEPNESS)) + irm.borrowRateView(marketParams, market), uint256(irm.MAX_RATE_AT_TARGET().wMulDown(irm.CURVE_STEEPNESS())) ); assertLe( - irm.borrowRate(marketParams, market), uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMulDown(CURVE_STEEPNESS)) + irm.borrowRate(marketParams, market), uint256(irm.MAX_RATE_AT_TARGET().wMulDown(irm.CURVE_STEEPNESS())) ); } @@ -361,21 +359,19 @@ contract AdaptiveCurveIrmTest is Test { function _expectedRateAtTarget(Id id, Market memory market) internal view returns (int256) { int256 rateAtTarget = int256(irm.rateAtTarget(id)); - int256 speed = ADJUSTMENT_SPEED.wMulDown(_err(market)); + int256 speed = irm.ADJUSTMENT_SPEED().wMulDown(_err(market)); uint256 elapsed = (rateAtTarget > 0) ? block.timestamp - market.lastUpdate : 0; int256 linearAdaptation = speed * int256(elapsed); int256 adaptationMultiplier = ExpLib.wExp(linearAdaptation); return (rateAtTarget > 0) - ? rateAtTarget.wMulDown(adaptationMultiplier).bound( - ConstantsLib.MIN_RATE_AT_TARGET, ConstantsLib.MAX_RATE_AT_TARGET - ) - : INITIAL_RATE_AT_TARGET; + ? rateAtTarget.wMulDown(adaptationMultiplier).bound(irm.MIN_RATE_AT_TARGET(), irm.MAX_RATE_AT_TARGET()) + : irm.INITIAL_RATE_AT_TARGET(); } function _expectedAvgRate(Id id, Market memory market) internal view returns (uint256) { int256 rateAtTarget = int256(irm.rateAtTarget(id)); int256 err = _err(market); - int256 speed = ADJUSTMENT_SPEED.wMulDown(err); + int256 speed = irm.ADJUSTMENT_SPEED().wMulDown(err); uint256 elapsed = (rateAtTarget > 0) ? block.timestamp - market.lastUpdate : 0; int256 linearAdaptation = speed * int256(elapsed); int256 endRateAtTarget = int256(_expectedRateAtTarget(id, market)); @@ -392,24 +388,24 @@ contract AdaptiveCurveIrmTest is Test { return avgBorrowRate; } - function _curve(int256 rateAtTarget, int256 err) internal pure returns (uint256) { + function _curve(int256 rateAtTarget, int256 err) internal view returns (uint256) { // Safe "unchecked" cast because err >= -1 (in WAD). if (err < 0) { - return uint256(((WAD - WAD.wDivDown(CURVE_STEEPNESS)).wMulDown(err) + WAD).wMulDown(rateAtTarget)); + return uint256(((WAD - WAD.wDivDown(irm.CURVE_STEEPNESS())).wMulDown(err) + WAD).wMulDown(rateAtTarget)); } else { - return uint256(((CURVE_STEEPNESS - WAD).wMulDown(err) + WAD).wMulDown(rateAtTarget)); + return uint256(((irm.CURVE_STEEPNESS() - WAD).wMulDown(err) + WAD).wMulDown(rateAtTarget)); } } - function _err(Market memory market) internal pure returns (int256 err) { + function _err(Market memory market) internal view returns (int256 err) { if (market.totalSupplyAssets == 0) return -1 ether; int256 utilization = int256(market.totalBorrowAssets.wDivDown(market.totalSupplyAssets)); - if (utilization > TARGET_UTILIZATION) { - err = (utilization - TARGET_UTILIZATION).wDivDown(WAD - TARGET_UTILIZATION); + if (utilization > irm.TARGET_UTILIZATION()) { + err = (utilization - irm.TARGET_UTILIZATION()).wDivDown(WAD - irm.TARGET_UTILIZATION()); } else { - err = (utilization - TARGET_UTILIZATION).wDivDown(TARGET_UTILIZATION); + err = (utilization - irm.TARGET_UTILIZATION()).wDivDown(irm.TARGET_UTILIZATION()); } } } diff --git a/test/ExpLibTest.sol b/test/ExpLibTest.sol index 81e4cd5d..f6cf71a0 100644 --- a/test/ExpLibTest.sol +++ b/test/ExpLibTest.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.0; import {MathLib, WAD_INT} from "../src/libraries/MathLib.sol"; -import {ConstantsLib} from "../src/libraries/adaptive-curve/ConstantsLib.sol"; import {ExpLib} from "../src/libraries/adaptive-curve/ExpLib.sol"; import {wadExp} from "../lib/solmate/src/utils/SignedWadMath.sol"; import {MathLib as MorphoMathLib} from "../lib/morpho-blue/src/libraries/MathLib.sol"; @@ -63,7 +62,7 @@ contract ExpLibTest is Test { } function testWExpWMulDownMaxRate() public pure { - ExpLib.wExp(ExpLib.WEXP_UPPER_BOUND).wMulDown(ConstantsLib.MAX_RATE_AT_TARGET); + ExpLib.wExp(ExpLib.WEXP_UPPER_BOUND).wMulDown(int256(0.01e9 ether) / 365 days); } function _wExpUnbounded(int256 x) internal pure returns (int256) { From 654819b6b0c65fcab66b54ebaa13bd9a2365a9d5 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Sun, 3 Dec 2023 23:42:28 +0100 Subject: [PATCH 04/35] feat: new inital rate at target --- src/AdaptiveCurveIrm.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AdaptiveCurveIrm.sol b/src/AdaptiveCurveIrm.sol index 7caf7ad0..b94c72b0 100644 --- a/src/AdaptiveCurveIrm.sol +++ b/src/AdaptiveCurveIrm.sol @@ -41,7 +41,7 @@ contract AdaptiveCurveIrm is IIrm { int256 public constant TARGET_UTILIZATION = 0.9 ether; /// @notice Initial rate at target per second (scaled by WAD). - int256 public constant INITIAL_RATE_AT_TARGET = int256(0.01 ether) / 365 days; + int256 public constant INITIAL_RATE_AT_TARGET = int256(0.04 ether) / 365 days; /// @notice Mininimum rate at target per second (scaled by WAD) (0.1% APR). int256 public constant MIN_RATE_AT_TARGET = int256(0.001 ether) / 365 days; From 7ba35f69b93f9c1a464082c8421c160845daf243 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Mon, 4 Dec 2023 10:12:50 +0100 Subject: [PATCH 05/35] feat: constants lib --- src/AdaptiveCurveIrm.sol | 41 +++---- src/libraries/adaptive-curve/ConstantsLib.sol | 25 +++++ test/AdaptiveCurveIrmTest.sol | 106 ++++++++++-------- test/ExpLibTest.sol | 3 +- 4 files changed, 98 insertions(+), 77 deletions(-) create mode 100644 src/libraries/adaptive-curve/ConstantsLib.sol diff --git a/src/AdaptiveCurveIrm.sol b/src/AdaptiveCurveIrm.sol index b94c72b0..13416f16 100644 --- a/src/AdaptiveCurveIrm.sol +++ b/src/AdaptiveCurveIrm.sol @@ -5,8 +5,9 @@ import {IIrm} from "../lib/morpho-blue/src/interfaces/IIrm.sol"; import {UtilsLib} from "./libraries/UtilsLib.sol"; import {ErrorsLib} from "./libraries/ErrorsLib.sol"; -import {MathLib, WAD_INT as WAD} from "./libraries/MathLib.sol"; import {ExpLib} from "./libraries/adaptive-curve/ExpLib.sol"; +import {MathLib, WAD_INT as WAD} from "./libraries/MathLib.sol"; +import {ConstantsLib} from "./libraries/adaptive-curve/ConstantsLib.sol"; import {MarketParamsLib} from "../lib/morpho-blue/src/libraries/MarketParamsLib.sol"; import {Id, MarketParams, Market} from "../lib/morpho-blue/src/interfaces/IMorpho.sol"; import {MathLib as MorphoMathLib} from "../lib/morpho-blue/src/libraries/MathLib.sol"; @@ -26,29 +27,11 @@ contract AdaptiveCurveIrm is IIrm { /// @notice Emitted when a borrow rate is updated. event BorrowRateUpdate(Id indexed id, uint256 avgBorrowRate, uint256 rateAtTarget); - /* CONSTANT */ + /* IMMUTABLES */ /// @notice Address of Morpho. address public immutable MORPHO; - /// @notice Curve steepness (scaled by WAD). - int256 public constant CURVE_STEEPNESS = 4 ether; - - /// @notice Adjustment speed per second (scaled by WAD). - int256 public constant ADJUSTMENT_SPEED = int256(50 ether) / 365 days; - - /// @notice Target utilization (scaled by WAD). - int256 public constant TARGET_UTILIZATION = 0.9 ether; - - /// @notice Initial rate at target per second (scaled by WAD). - int256 public constant INITIAL_RATE_AT_TARGET = int256(0.04 ether) / 365 days; - - /// @notice Mininimum rate at target per second (scaled by WAD) (0.1% APR). - int256 public constant MIN_RATE_AT_TARGET = int256(0.001 ether) / 365 days; - - /// @notice Maximum rate at target per second (scaled by WAD) (1B% APR). - int256 public constant MAX_RATE_AT_TARGET = int256(0.01e9 ether) / 365 days; - /* STORAGE */ /// @notice Rate at target utilization. @@ -96,8 +79,10 @@ contract AdaptiveCurveIrm is IIrm { int256 utilization = int256(market.totalSupplyAssets > 0 ? market.totalBorrowAssets.wDivDown(market.totalSupplyAssets) : 0); - int256 errNormFactor = utilization > TARGET_UTILIZATION ? WAD - TARGET_UTILIZATION : TARGET_UTILIZATION; - int256 err = (utilization - TARGET_UTILIZATION).wDivDown(errNormFactor); + int256 errNormFactor = utilization > ConstantsLib.TARGET_UTILIZATION + ? WAD - ConstantsLib.TARGET_UTILIZATION + : ConstantsLib.TARGET_UTILIZATION; + int256 err = (utilization - ConstantsLib.TARGET_UTILIZATION).wDivDown(errNormFactor); int256 startRateAtTarget = rateAtTarget[id]; @@ -106,12 +91,12 @@ contract AdaptiveCurveIrm is IIrm { if (startRateAtTarget == 0) { // First interaction. - avgRateAtTarget = INITIAL_RATE_AT_TARGET; - endRateAtTarget = INITIAL_RATE_AT_TARGET; + avgRateAtTarget = ConstantsLib.INITIAL_RATE_AT_TARGET; + endRateAtTarget = ConstantsLib.INITIAL_RATE_AT_TARGET; } else { // Note that the speed is assumed constant between two interactions, but in theory it increases because of // interests. So the rate will be slightly underestimated. - int256 speed = ADJUSTMENT_SPEED.wMulDown(err); + int256 speed = ConstantsLib.ADJUSTMENT_SPEED.wMulDown(err); // market.lastUpdate != 0 because it is not the first interaction with this market. // Safe "unchecked" cast because block.timestamp - market.lastUpdate <= block.timestamp <= type(int256).max. int256 elapsed = int256(block.timestamp - market.lastUpdate); @@ -151,7 +136,7 @@ contract AdaptiveCurveIrm is IIrm { /// ((C-1)*err + 1) * rateAtTarget else. function _curve(int256 _rateAtTarget, int256 err) private pure returns (int256) { // Non negative because 1 - 1/C >= 0, C - 1 >= 0. - int256 coeff = err < 0 ? WAD - WAD.wDivDown(CURVE_STEEPNESS) : CURVE_STEEPNESS - WAD; + int256 coeff = err < 0 ? WAD - WAD.wDivDown(ConstantsLib.CURVE_STEEPNESS) : ConstantsLib.CURVE_STEEPNESS - WAD; // Non negative if _rateAtTarget >= 0 because if err < 0, coeff <= 1. return (coeff.wMulDown(err) + WAD).wMulDown(int256(_rateAtTarget)); } @@ -160,6 +145,8 @@ contract AdaptiveCurveIrm is IIrm { /// The formula is: max(min(startRateAtTarget * exp(linearAdaptation), maxRateAtTarget), minRateAtTarget). function _newRateAtTarget(int256 startRateAtTarget, int256 linearAdaptation) private pure returns (int256) { // Non negative because MIN_RATE_AT_TARGET > 0. - return startRateAtTarget.wMulDown(ExpLib.wExp(linearAdaptation)).bound(MIN_RATE_AT_TARGET, MAX_RATE_AT_TARGET); + return startRateAtTarget.wMulDown(ExpLib.wExp(linearAdaptation)).bound( + ConstantsLib.MIN_RATE_AT_TARGET, ConstantsLib.MAX_RATE_AT_TARGET + ); } } diff --git a/src/libraries/adaptive-curve/ConstantsLib.sol b/src/libraries/adaptive-curve/ConstantsLib.sol new file mode 100644 index 00000000..3096901e --- /dev/null +++ b/src/libraries/adaptive-curve/ConstantsLib.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title ConstantsLib +/// @author Morpho Labs +/// @custom:contact security@morpho.org +library ConstantsLib { + /// @notice Curve steepness (scaled by WAD). + int256 public constant CURVE_STEEPNESS = 4 ether; + + /// @notice Adjustment speed per second (scaled by WAD). + int256 public constant ADJUSTMENT_SPEED = int256(50 ether) / 365 days; + + /// @notice Target utilization (scaled by WAD). + int256 public constant TARGET_UTILIZATION = 0.9 ether; + + /// @notice Initial rate at target per second (scaled by WAD). + int256 public constant INITIAL_RATE_AT_TARGET = int256(0.04 ether) / 365 days; + + /// @notice Mininimum rate at target per second (scaled by WAD) (0.1% APR). + int256 public constant MIN_RATE_AT_TARGET = int256(0.001 ether) / 365 days; + + /// @notice Maximum rate at target per second (scaled by WAD) (1B% APR). + int256 public constant MAX_RATE_AT_TARGET = int256(0.01e9 ether) / 365 days; +} diff --git a/test/AdaptiveCurveIrmTest.sol b/test/AdaptiveCurveIrmTest.sol index 4e6c72a3..b820bb0e 100644 --- a/test/AdaptiveCurveIrmTest.sol +++ b/test/AdaptiveCurveIrmTest.sol @@ -40,11 +40,11 @@ contract AdaptiveCurveIrmTest is Test { assertApproxEqRel( irm.borrowRate(marketParams, market), - uint256(irm.INITIAL_RATE_AT_TARGET() / 4), + uint256(ConstantsLib.INITIAL_RATE_AT_TARGET / 4), 0.0001 ether, "avgBorrowRate" ); - assertEq(irm.rateAtTarget(marketParams.id()), irm.INITIAL_RATE_AT_TARGET(), "rateAtTarget"); + assertEq(irm.rateAtTarget(marketParams.id()), ConstantsLib.INITIAL_RATE_AT_TARGET, "rateAtTarget"); } function testFirstBorrowRateUtilizationOne() public { @@ -52,14 +52,18 @@ contract AdaptiveCurveIrmTest is Test { market.totalBorrowAssets = 1 ether; market.totalSupplyAssets = 1 ether; - assertEq(irm.borrowRate(marketParams, market), uint256(irm.INITIAL_RATE_AT_TARGET() * 4), "avgBorrowRate"); - assertEq(irm.rateAtTarget(marketParams.id()), irm.INITIAL_RATE_AT_TARGET(), "rateAtTarget"); + assertEq( + irm.borrowRate(marketParams, market), uint256(ConstantsLib.INITIAL_RATE_AT_TARGET * 4), "avgBorrowRate" + ); + assertEq(irm.rateAtTarget(marketParams.id()), ConstantsLib.INITIAL_RATE_AT_TARGET, "rateAtTarget"); } function testRateAfterUtilizationOne() public { vm.warp(365 days * 2); Market memory market; - assertApproxEqRel(irm.borrowRate(marketParams, market), uint256(irm.INITIAL_RATE_AT_TARGET() / 4), 0.001 ether); + assertApproxEqRel( + irm.borrowRate(marketParams, market), uint256(ConstantsLib.INITIAL_RATE_AT_TARGET / 4), 0.001 ether + ); market.totalBorrowAssets = 1 ether; market.totalSupplyAssets = 1 ether; @@ -69,8 +73,8 @@ contract AdaptiveCurveIrmTest is Test { assertApproxEqRel( irm.borrowRateView(marketParams, market), uint256( - (irm.INITIAL_RATE_AT_TARGET() * 4).wMulDown( - (1.9836 ether - 1 ether) * WAD / (irm.ADJUSTMENT_SPEED() * 5 days) + (ConstantsLib.INITIAL_RATE_AT_TARGET * 4).wMulDown( + (1.9836 ether - 1 ether) * WAD / (ConstantsLib.ADJUSTMENT_SPEED * 5 days) ) ), 0.1 ether @@ -78,7 +82,7 @@ contract AdaptiveCurveIrmTest is Test { // The average value of exp((50/365)*x) between 0 and 5 is approx. 1.4361. assertApproxEqRel( irm.borrowRateView(marketParams, market), - uint256((irm.INITIAL_RATE_AT_TARGET() * 4).wMulDown(1.4361 ether)), + uint256((ConstantsLib.INITIAL_RATE_AT_TARGET * 4).wMulDown(1.4361 ether)), 0.1 ether ); // Expected rate: 5.744%. @@ -88,7 +92,9 @@ contract AdaptiveCurveIrmTest is Test { function testRateAfterUtilizationZero() public { vm.warp(365 days * 2); Market memory market; - assertApproxEqRel(irm.borrowRate(marketParams, market), uint256(irm.INITIAL_RATE_AT_TARGET() / 4), 0.001 ether); + assertApproxEqRel( + irm.borrowRate(marketParams, market), uint256(ConstantsLib.INITIAL_RATE_AT_TARGET / 4), 0.001 ether + ); market.totalBorrowAssets = 0 ether; market.totalSupplyAssets = 1 ether; @@ -98,8 +104,8 @@ contract AdaptiveCurveIrmTest is Test { assertApproxEqRel( irm.borrowRateView(marketParams, market), uint256( - (irm.INITIAL_RATE_AT_TARGET() / 4).wMulDown( - (0.5041 ether - 1 ether) * WAD / (-irm.ADJUSTMENT_SPEED() * 5 days) + (ConstantsLib.INITIAL_RATE_AT_TARGET / 4).wMulDown( + (0.5041 ether - 1 ether) * WAD / (-ConstantsLib.ADJUSTMENT_SPEED * 5 days) ) ), 0.1 ether @@ -107,7 +113,7 @@ contract AdaptiveCurveIrmTest is Test { // The average value of exp((-50/365*x)) between 0 and 5 is approx. 0.7240. assertApproxEqRel( irm.borrowRateView(marketParams, market), - uint256((irm.INITIAL_RATE_AT_TARGET() / 4).wMulDown(0.724 ether)), + uint256((ConstantsLib.INITIAL_RATE_AT_TARGET / 4).wMulDown(0.724 ether)), 0.1 ether ); // Expected rate: 0.181%. @@ -117,14 +123,14 @@ contract AdaptiveCurveIrmTest is Test { function testRateAfter45DaysUtilizationAboveTargetNoPing() public { Market memory market; market.totalSupplyAssets = 1 ether; - market.totalBorrowAssets = uint128(uint256(irm.TARGET_UTILIZATION())); - assertEq(irm.borrowRate(marketParams, market), uint256(irm.INITIAL_RATE_AT_TARGET())); - assertEq(irm.rateAtTarget(marketParams.id()), irm.INITIAL_RATE_AT_TARGET()); + market.totalBorrowAssets = uint128(uint256(ConstantsLib.TARGET_UTILIZATION)); + assertEq(irm.borrowRate(marketParams, market), uint256(ConstantsLib.INITIAL_RATE_AT_TARGET)); + assertEq(irm.rateAtTarget(marketParams.id()), ConstantsLib.INITIAL_RATE_AT_TARGET); market.lastUpdate = uint128(block.timestamp); vm.warp(block.timestamp + 45 days); - market.totalBorrowAssets = uint128(uint256(irm.TARGET_UTILIZATION() + 1 ether) / 2); // Error = 50% + market.totalBorrowAssets = uint128(uint256(ConstantsLib.TARGET_UTILIZATION + 1 ether) / 2); // Error = 50% irm.borrowRate(marketParams, market); // Expected rate: 1% * exp(50 * 45 / 365 * 50%) = 21.81%. @@ -134,11 +140,11 @@ contract AdaptiveCurveIrmTest is Test { function testRateAfter45DaysUtilizationAboveTargetPingEveryMinute() public { Market memory market; market.totalSupplyAssets = 1 ether; - market.totalBorrowAssets = uint128(uint256(irm.TARGET_UTILIZATION())); - assertEq(irm.borrowRate(marketParams, market), uint256(irm.INITIAL_RATE_AT_TARGET())); - assertEq(irm.rateAtTarget(marketParams.id()), irm.INITIAL_RATE_AT_TARGET()); + market.totalBorrowAssets = uint128(uint256(ConstantsLib.TARGET_UTILIZATION)); + assertEq(irm.borrowRate(marketParams, market), uint256(ConstantsLib.INITIAL_RATE_AT_TARGET)); + assertEq(irm.rateAtTarget(marketParams.id()), ConstantsLib.INITIAL_RATE_AT_TARGET); - uint128 initialBorrowAssets = uint128(uint256(irm.TARGET_UTILIZATION() + 1 ether) / 2); // Error = 50% + uint128 initialBorrowAssets = uint128(uint256(ConstantsLib.TARGET_UTILIZATION + 1 ether) / 2); // Error = 50% market.totalBorrowAssets = initialBorrowAssets; @@ -175,16 +181,16 @@ contract AdaptiveCurveIrmTest is Test { Market memory market; market.totalSupplyAssets = 1 ether; - market.totalBorrowAssets = uint128(uint256(irm.TARGET_UTILIZATION())); - assertEq(irm.borrowRate(marketParams, market), uint256(irm.INITIAL_RATE_AT_TARGET())); - assertEq(irm.rateAtTarget(marketParams.id()), irm.INITIAL_RATE_AT_TARGET()); + market.totalBorrowAssets = uint128(uint256(ConstantsLib.TARGET_UTILIZATION)); + assertEq(irm.borrowRate(marketParams, market), uint256(ConstantsLib.INITIAL_RATE_AT_TARGET)); + assertEq(irm.rateAtTarget(marketParams.id()), ConstantsLib.INITIAL_RATE_AT_TARGET); market.lastUpdate = uint128(block.timestamp); vm.warp(block.timestamp + elapsed); irm.borrowRate(marketParams, market); - assertEq(irm.rateAtTarget(marketParams.id()), irm.INITIAL_RATE_AT_TARGET()); + assertEq(irm.rateAtTarget(marketParams.id()), ConstantsLib.INITIAL_RATE_AT_TARGET); } function testRateAfter3WeeksUtilizationTargetPingEveryMinute() public { @@ -192,9 +198,9 @@ contract AdaptiveCurveIrmTest is Test { Market memory market; market.totalSupplyAssets = 1 ether; - market.totalBorrowAssets = uint128(uint256(irm.TARGET_UTILIZATION())); - assertEq(irm.borrowRate(marketParams, market), uint256(irm.INITIAL_RATE_AT_TARGET())); - assertEq(irm.rateAtTarget(marketParams.id()), irm.INITIAL_RATE_AT_TARGET()); + market.totalBorrowAssets = uint128(uint256(ConstantsLib.TARGET_UTILIZATION)); + assertEq(irm.borrowRate(marketParams, market), uint256(ConstantsLib.INITIAL_RATE_AT_TARGET)); + assertEq(irm.rateAtTarget(marketParams.id()), ConstantsLib.INITIAL_RATE_AT_TARGET); for (uint256 i; i < 3 weeks / 1 minutes; ++i) { market.lastUpdate = uint128(block.timestamp); @@ -207,13 +213,15 @@ contract AdaptiveCurveIrmTest is Test { } assertApproxEqRel( - market.totalBorrowAssets.wDivDown(market.totalSupplyAssets), uint256(irm.TARGET_UTILIZATION()), 0.01 ether + market.totalBorrowAssets.wDivDown(market.totalSupplyAssets), + uint256(ConstantsLib.TARGET_UTILIZATION), + 0.01 ether ); int256 rateAtTarget = irm.rateAtTarget(marketParams.id()); - assertGe(rateAtTarget, irm.INITIAL_RATE_AT_TARGET()); + assertGe(rateAtTarget, ConstantsLib.INITIAL_RATE_AT_TARGET); // The rate is tolerated to be +10% (relatively) because of the pings every minute. - assertApproxEqRel(rateAtTarget, irm.INITIAL_RATE_AT_TARGET(), 0.1 ether); + assertApproxEqRel(rateAtTarget, ConstantsLib.INITIAL_RATE_AT_TARGET, 0.1 ether); } function testFirstBorrowRate(Market memory market) public { @@ -223,8 +231,8 @@ contract AdaptiveCurveIrmTest is Test { uint256 avgBorrowRate = irm.borrowRate(marketParams, market); int256 rateAtTarget = irm.rateAtTarget(marketParams.id()); - assertEq(avgBorrowRate, _curve(int256(irm.INITIAL_RATE_AT_TARGET()), _err(market)), "avgBorrowRate"); - assertEq(rateAtTarget, irm.INITIAL_RATE_AT_TARGET(), "rateAtTarget"); + assertEq(avgBorrowRate, _curve(int256(ConstantsLib.INITIAL_RATE_AT_TARGET), _err(market)), "avgBorrowRate"); + assertEq(rateAtTarget, ConstantsLib.INITIAL_RATE_AT_TARGET, "rateAtTarget"); } function testBorrowRateEventEmission(Market memory market) public { @@ -234,7 +242,7 @@ contract AdaptiveCurveIrmTest is Test { vm.expectEmit(true, true, true, true, address(irm)); emit BorrowRateUpdate( marketParams.id(), - _curve(int256(irm.INITIAL_RATE_AT_TARGET()), _err(market)), + _curve(int256(ConstantsLib.INITIAL_RATE_AT_TARGET), _err(market)), uint256(_expectedRateAtTarget(marketParams.id(), market)) ); irm.borrowRate(marketParams, market); @@ -247,7 +255,7 @@ contract AdaptiveCurveIrmTest is Test { uint256 avgBorrowRate = irm.borrowRateView(marketParams, market); int256 rateAtTarget = irm.rateAtTarget(marketParams.id()); - assertEq(avgBorrowRate, _curve(int256(irm.INITIAL_RATE_AT_TARGET()), _err(market)), "avgBorrowRate"); + assertEq(avgBorrowRate, _curve(int256(ConstantsLib.INITIAL_RATE_AT_TARGET), _err(market)), "avgBorrowRate"); assertEq(rateAtTarget, 0, "prevBorrowRate"); } @@ -335,10 +343,10 @@ contract AdaptiveCurveIrmTest is Test { market.totalSupplyAssets = 10 ether; assertGe( - irm.borrowRateView(marketParams, market), uint256(irm.MIN_RATE_AT_TARGET().wDivDown(irm.CURVE_STEEPNESS())) + irm.borrowRateView(marketParams, market), uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDivDown(ConstantsLib.CURVE_STEEPNESS)) ); assertGe( - irm.borrowRate(marketParams, market), uint256(irm.MIN_RATE_AT_TARGET().wDivDown(irm.CURVE_STEEPNESS())) + irm.borrowRate(marketParams, market), uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDivDown(ConstantsLib.CURVE_STEEPNESS)) ); } @@ -348,10 +356,10 @@ contract AdaptiveCurveIrmTest is Test { market.totalSupplyAssets = 10 ether; assertLe( - irm.borrowRateView(marketParams, market), uint256(irm.MAX_RATE_AT_TARGET().wMulDown(irm.CURVE_STEEPNESS())) + irm.borrowRateView(marketParams, market), uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMulDown(ConstantsLib.CURVE_STEEPNESS)) ); assertLe( - irm.borrowRate(marketParams, market), uint256(irm.MAX_RATE_AT_TARGET().wMulDown(irm.CURVE_STEEPNESS())) + irm.borrowRate(marketParams, market), uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMulDown(ConstantsLib.CURVE_STEEPNESS)) ); } @@ -359,19 +367,19 @@ contract AdaptiveCurveIrmTest is Test { function _expectedRateAtTarget(Id id, Market memory market) internal view returns (int256) { int256 rateAtTarget = int256(irm.rateAtTarget(id)); - int256 speed = irm.ADJUSTMENT_SPEED().wMulDown(_err(market)); + int256 speed = ConstantsLib.ADJUSTMENT_SPEED.wMulDown(_err(market)); uint256 elapsed = (rateAtTarget > 0) ? block.timestamp - market.lastUpdate : 0; int256 linearAdaptation = speed * int256(elapsed); int256 adaptationMultiplier = ExpLib.wExp(linearAdaptation); return (rateAtTarget > 0) - ? rateAtTarget.wMulDown(adaptationMultiplier).bound(irm.MIN_RATE_AT_TARGET(), irm.MAX_RATE_AT_TARGET()) - : irm.INITIAL_RATE_AT_TARGET(); + ? rateAtTarget.wMulDown(adaptationMultiplier).bound(ConstantsLib.MIN_RATE_AT_TARGET, ConstantsLib.MAX_RATE_AT_TARGET) + : ConstantsLib.INITIAL_RATE_AT_TARGET; } function _expectedAvgRate(Id id, Market memory market) internal view returns (uint256) { int256 rateAtTarget = int256(irm.rateAtTarget(id)); int256 err = _err(market); - int256 speed = irm.ADJUSTMENT_SPEED().wMulDown(err); + int256 speed = ConstantsLib.ADJUSTMENT_SPEED.wMulDown(err); uint256 elapsed = (rateAtTarget > 0) ? block.timestamp - market.lastUpdate : 0; int256 linearAdaptation = speed * int256(elapsed); int256 endRateAtTarget = int256(_expectedRateAtTarget(id, market)); @@ -388,24 +396,24 @@ contract AdaptiveCurveIrmTest is Test { return avgBorrowRate; } - function _curve(int256 rateAtTarget, int256 err) internal view returns (uint256) { + function _curve(int256 rateAtTarget, int256 err) internal pure returns (uint256) { // Safe "unchecked" cast because err >= -1 (in WAD). if (err < 0) { - return uint256(((WAD - WAD.wDivDown(irm.CURVE_STEEPNESS())).wMulDown(err) + WAD).wMulDown(rateAtTarget)); + return uint256(((WAD - WAD.wDivDown(ConstantsLib.CURVE_STEEPNESS)).wMulDown(err) + WAD).wMulDown(rateAtTarget)); } else { - return uint256(((irm.CURVE_STEEPNESS() - WAD).wMulDown(err) + WAD).wMulDown(rateAtTarget)); + return uint256(((ConstantsLib.CURVE_STEEPNESS - WAD).wMulDown(err) + WAD).wMulDown(rateAtTarget)); } } - function _err(Market memory market) internal view returns (int256 err) { + function _err(Market memory market) internal pure returns (int256 err) { if (market.totalSupplyAssets == 0) return -1 ether; int256 utilization = int256(market.totalBorrowAssets.wDivDown(market.totalSupplyAssets)); - if (utilization > irm.TARGET_UTILIZATION()) { - err = (utilization - irm.TARGET_UTILIZATION()).wDivDown(WAD - irm.TARGET_UTILIZATION()); + if (utilization > ConstantsLib.TARGET_UTILIZATION) { + err = (utilization - ConstantsLib.TARGET_UTILIZATION).wDivDown(WAD - ConstantsLib.TARGET_UTILIZATION); } else { - err = (utilization - irm.TARGET_UTILIZATION()).wDivDown(irm.TARGET_UTILIZATION()); + err = (utilization - ConstantsLib.TARGET_UTILIZATION).wDivDown(ConstantsLib.TARGET_UTILIZATION); } } } diff --git a/test/ExpLibTest.sol b/test/ExpLibTest.sol index f6cf71a0..dac89ec9 100644 --- a/test/ExpLibTest.sol +++ b/test/ExpLibTest.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import {MathLib, WAD_INT} from "../src/libraries/MathLib.sol"; import {ExpLib} from "../src/libraries/adaptive-curve/ExpLib.sol"; import {wadExp} from "../lib/solmate/src/utils/SignedWadMath.sol"; +import {ConstantsLib} from "../src/libraries/adaptive-curve/ConstantsLib.sol"; import {MathLib as MorphoMathLib} from "../lib/morpho-blue/src/libraries/MathLib.sol"; import "../lib/forge-std/src/Test.sol"; @@ -62,7 +63,7 @@ contract ExpLibTest is Test { } function testWExpWMulDownMaxRate() public pure { - ExpLib.wExp(ExpLib.WEXP_UPPER_BOUND).wMulDown(int256(0.01e9 ether) / 365 days); + ExpLib.wExp(ExpLib.WEXP_UPPER_BOUND).wMulDown(ConstantsLib.MAX_RATE_AT_TARGET); } function _wExpUnbounded(int256 x) internal pure returns (int256) { From e1b8f5ef44aeba2a80c1757b19d994f302954bbc Mon Sep 17 00:00:00 2001 From: MathisGD Date: Mon, 4 Dec 2023 12:16:29 +0100 Subject: [PATCH 06/35] test: fix test with new initial rate --- test/AdaptiveCurveIrmTest.sol | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/AdaptiveCurveIrmTest.sol b/test/AdaptiveCurveIrmTest.sol index b820bb0e..2808e8a0 100644 --- a/test/AdaptiveCurveIrmTest.sol +++ b/test/AdaptiveCurveIrmTest.sol @@ -85,8 +85,8 @@ contract AdaptiveCurveIrmTest is Test { uint256((ConstantsLib.INITIAL_RATE_AT_TARGET * 4).wMulDown(1.4361 ether)), 0.1 ether ); - // Expected rate: 5.744%. - assertApproxEqRel(irm.borrowRateView(marketParams, market), uint256(0.05744 ether) / 365 days, 0.1 ether); + // Expected rate: 22.976%. + assertApproxEqRel(irm.borrowRateView(marketParams, market), uint256(0.22976 ether) / 365 days, 0.1 ether); } function testRateAfterUtilizationZero() public { @@ -116,8 +116,8 @@ contract AdaptiveCurveIrmTest is Test { uint256((ConstantsLib.INITIAL_RATE_AT_TARGET / 4).wMulDown(0.724 ether)), 0.1 ether ); - // Expected rate: 0.181%. - assertApproxEqRel(irm.borrowRateView(marketParams, market), uint256(0.00181 ether) / 365 days, 0.1 ether); + // Expected rate: 0.7240%. + assertApproxEqRel(irm.borrowRateView(marketParams, market), uint256(0.007240 ether) / 365 days, 0.1 ether); } function testRateAfter45DaysUtilizationAboveTargetNoPing() public { @@ -133,8 +133,8 @@ contract AdaptiveCurveIrmTest is Test { market.totalBorrowAssets = uint128(uint256(ConstantsLib.TARGET_UTILIZATION + 1 ether) / 2); // Error = 50% irm.borrowRate(marketParams, market); - // Expected rate: 1% * exp(50 * 45 / 365 * 50%) = 21.81%. - assertApproxEqRel(irm.rateAtTarget(marketParams.id()), int256(0.2181 ether) / 365 days, 0.005 ether); + // Expected rate: 4% * exp(50 * 45 / 365 * 50%) = 87.22%. + assertApproxEqRel(irm.rateAtTarget(marketParams.id()), int256(0.8722 ether) / 365 days, 0.005 ether); } function testRateAfter45DaysUtilizationAboveTargetPingEveryMinute() public { @@ -159,20 +159,20 @@ contract AdaptiveCurveIrmTest is Test { } assertApproxEqRel( - market.totalBorrowAssets.wDivDown(market.totalSupplyAssets), 0.95 ether, 0.002 ether, "utilization" + market.totalBorrowAssets.wDivDown(market.totalSupplyAssets), 0.95 ether, 0.01 ether, "utilization" ); int256 rateAtTarget = irm.rateAtTarget(marketParams.id()); - // Expected rate: 1% * exp(50 * 45 / 365 * 50%) = 21.81%. - int256 expectedRateAtTarget = int256(0.2181 ether) / 365 days; + // Expected rate: 4% * exp(50 * 45 / 365 * 50%) = 87.22%. + int256 expectedRateAtTarget = int256(0.8722 ether) / 365 days; assertGe(rateAtTarget, expectedRateAtTarget); - // The rate is tolerated to be +2% (relatively) because of the pings every minute. - assertApproxEqRel(rateAtTarget, expectedRateAtTarget, 0.02 ether, "expectedRateAtTarget"); + // The rate is tolerated to be +8% (relatively) because of the pings every minute. + assertApproxEqRel(rateAtTarget, expectedRateAtTarget, 0.08 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. + // Expected growth: exp(87.22% * 3.5 * 45 / 365) = +45.70%. + // The growth is tolerated to be +30% (relatively) because of the pings every minute. assertApproxEqRel( - market.totalBorrowAssets, initialBorrowAssets.wMulDown(1.0987 ether), 0.08 ether, "totalBorrowAssets" + market.totalBorrowAssets, initialBorrowAssets.wMulDown(1.4570 ether), 0.3 ether, "totalBorrowAssets" ); } From 20d4835860b29bf68ebf31092e7ad358f36a40bc Mon Sep 17 00:00:00 2001 From: MathisGD Date: Mon, 4 Dec 2023 13:03:08 +0100 Subject: [PATCH 07/35] chore: fmt --- test/AdaptiveCurveIrmTest.sol | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/test/AdaptiveCurveIrmTest.sol b/test/AdaptiveCurveIrmTest.sol index 2808e8a0..23f4c8fd 100644 --- a/test/AdaptiveCurveIrmTest.sol +++ b/test/AdaptiveCurveIrmTest.sol @@ -117,7 +117,7 @@ contract AdaptiveCurveIrmTest is Test { 0.1 ether ); // Expected rate: 0.7240%. - assertApproxEqRel(irm.borrowRateView(marketParams, market), uint256(0.007240 ether) / 365 days, 0.1 ether); + assertApproxEqRel(irm.borrowRateView(marketParams, market), uint256(0.00724 ether) / 365 days, 0.1 ether); } function testRateAfter45DaysUtilizationAboveTargetNoPing() public { @@ -172,7 +172,7 @@ contract AdaptiveCurveIrmTest is Test { // Expected growth: exp(87.22% * 3.5 * 45 / 365) = +45.70%. // The growth is tolerated to be +30% (relatively) because of the pings every minute. assertApproxEqRel( - market.totalBorrowAssets, initialBorrowAssets.wMulDown(1.4570 ether), 0.3 ether, "totalBorrowAssets" + market.totalBorrowAssets, initialBorrowAssets.wMulDown(1.457 ether), 0.3 ether, "totalBorrowAssets" ); } @@ -343,10 +343,12 @@ contract AdaptiveCurveIrmTest is Test { market.totalSupplyAssets = 10 ether; assertGe( - irm.borrowRateView(marketParams, market), uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDivDown(ConstantsLib.CURVE_STEEPNESS)) + irm.borrowRateView(marketParams, market), + uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDivDown(ConstantsLib.CURVE_STEEPNESS)) ); assertGe( - irm.borrowRate(marketParams, market), uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDivDown(ConstantsLib.CURVE_STEEPNESS)) + irm.borrowRate(marketParams, market), + uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDivDown(ConstantsLib.CURVE_STEEPNESS)) ); } @@ -356,10 +358,12 @@ contract AdaptiveCurveIrmTest is Test { market.totalSupplyAssets = 10 ether; assertLe( - irm.borrowRateView(marketParams, market), uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMulDown(ConstantsLib.CURVE_STEEPNESS)) + irm.borrowRateView(marketParams, market), + uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMulDown(ConstantsLib.CURVE_STEEPNESS)) ); assertLe( - irm.borrowRate(marketParams, market), uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMulDown(ConstantsLib.CURVE_STEEPNESS)) + irm.borrowRate(marketParams, market), + uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMulDown(ConstantsLib.CURVE_STEEPNESS)) ); } @@ -372,7 +376,9 @@ contract AdaptiveCurveIrmTest is Test { int256 linearAdaptation = speed * int256(elapsed); int256 adaptationMultiplier = ExpLib.wExp(linearAdaptation); return (rateAtTarget > 0) - ? rateAtTarget.wMulDown(adaptationMultiplier).bound(ConstantsLib.MIN_RATE_AT_TARGET, ConstantsLib.MAX_RATE_AT_TARGET) + ? rateAtTarget.wMulDown(adaptationMultiplier).bound( + ConstantsLib.MIN_RATE_AT_TARGET, ConstantsLib.MAX_RATE_AT_TARGET + ) : ConstantsLib.INITIAL_RATE_AT_TARGET; } @@ -399,7 +405,8 @@ contract AdaptiveCurveIrmTest is Test { function _curve(int256 rateAtTarget, int256 err) internal pure returns (uint256) { // Safe "unchecked" cast because err >= -1 (in WAD). if (err < 0) { - return uint256(((WAD - WAD.wDivDown(ConstantsLib.CURVE_STEEPNESS)).wMulDown(err) + WAD).wMulDown(rateAtTarget)); + return + uint256(((WAD - WAD.wDivDown(ConstantsLib.CURVE_STEEPNESS)).wMulDown(err) + WAD).wMulDown(rateAtTarget)); } else { return uint256(((ConstantsLib.CURVE_STEEPNESS - WAD).wMulDown(err) + WAD).wMulDown(rateAtTarget)); } From 14cc0035e8272f7c67e3c627093150ffdf0b5c52 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Thu, 7 Dec 2023 09:21:07 +0100 Subject: [PATCH 08/35] fix(constants): lower max rate at target --- src/libraries/adaptive-curve/ConstantsLib.sol | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libraries/adaptive-curve/ConstantsLib.sol b/src/libraries/adaptive-curve/ConstantsLib.sol index 3096901e..8056951c 100644 --- a/src/libraries/adaptive-curve/ConstantsLib.sol +++ b/src/libraries/adaptive-curve/ConstantsLib.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +import {WAD_INT} from "../MathLib.sol"; + /// @title ConstantsLib /// @author Morpho Labs /// @custom:contact security@morpho.org @@ -17,9 +19,9 @@ library ConstantsLib { /// @notice Initial rate at target per second (scaled by WAD). int256 public constant INITIAL_RATE_AT_TARGET = int256(0.04 ether) / 365 days; - /// @notice Mininimum rate at target per second (scaled by WAD) (0.1% APR). - int256 public constant MIN_RATE_AT_TARGET = int256(0.001 ether) / 365 days; + /// @notice Mininimum rate at target per second (scaled by WAD) (0.025% APR). + int256 public constant MIN_RATE_AT_TARGET = int256(0.00025 ether) * CURVE_STEEPNESS / WAD_INT / 365 days; - /// @notice Maximum rate at target per second (scaled by WAD) (1B% APR). - int256 public constant MAX_RATE_AT_TARGET = int256(0.01e9 ether) / 365 days; + /// @notice Maximum rate at target per second (scaled by WAD) (1000% APR). + int256 public constant MAX_RATE_AT_TARGET = int256(0.01e3 ether) * WAD_INT / CURVE_STEEPNESS / 365 days; } From e17490c72d553c91e37252700f26bd9f2cedcf44 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Thu, 7 Dec 2023 10:51:18 +0100 Subject: [PATCH 09/35] docs(constants): updated values --- src/libraries/adaptive-curve/ConstantsLib.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/adaptive-curve/ConstantsLib.sol b/src/libraries/adaptive-curve/ConstantsLib.sol index 8056951c..1e3d5cbf 100644 --- a/src/libraries/adaptive-curve/ConstantsLib.sol +++ b/src/libraries/adaptive-curve/ConstantsLib.sol @@ -16,12 +16,12 @@ library ConstantsLib { /// @notice Target utilization (scaled by WAD). int256 public constant TARGET_UTILIZATION = 0.9 ether; - /// @notice Initial rate at target per second (scaled by WAD). - int256 public constant INITIAL_RATE_AT_TARGET = int256(0.04 ether) / 365 days; + /// @notice Initial rate at target per second (scaled by WAD) (4% APR <=> 1% min APR). + int256 public constant INITIAL_RATE_AT_TARGET = int256(0.01 ether) * CURVE_STEEPNESS / WAD_INT / 365 days; - /// @notice Mininimum rate at target per second (scaled by WAD) (0.025% APR). + /// @notice Minimum rate at target per second (scaled by WAD) (0.1% APR <=> 0.025% min APR). int256 public constant MIN_RATE_AT_TARGET = int256(0.00025 ether) * CURVE_STEEPNESS / WAD_INT / 365 days; - /// @notice Maximum rate at target per second (scaled by WAD) (1000% APR). + /// @notice Maximum rate at target per second (scaled by WAD) (250% APR <=> 1000% max APR). int256 public constant MAX_RATE_AT_TARGET = int256(0.01e3 ether) * WAD_INT / CURVE_STEEPNESS / 365 days; } From ae47e0b94c7405ed4ad114a37903d39e92927ecf Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Fri, 8 Dec 2023 09:16:28 +0100 Subject: [PATCH 10/35] fix(constants): increase clarity --- src/libraries/adaptive-curve/ConstantsLib.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/adaptive-curve/ConstantsLib.sol b/src/libraries/adaptive-curve/ConstantsLib.sol index 1e3d5cbf..2ff96e34 100644 --- a/src/libraries/adaptive-curve/ConstantsLib.sol +++ b/src/libraries/adaptive-curve/ConstantsLib.sol @@ -20,8 +20,8 @@ library ConstantsLib { int256 public constant INITIAL_RATE_AT_TARGET = int256(0.01 ether) * CURVE_STEEPNESS / WAD_INT / 365 days; /// @notice Minimum rate at target per second (scaled by WAD) (0.1% APR <=> 0.025% min APR). - int256 public constant MIN_RATE_AT_TARGET = int256(0.00025 ether) * CURVE_STEEPNESS / WAD_INT / 365 days; + int256 public constant MIN_RATE_AT_TARGET = int256(0.025 * 0.01 ether) * CURVE_STEEPNESS / WAD_INT / 365 days; - /// @notice Maximum rate at target per second (scaled by WAD) (250% APR <=> 1000% max APR). - int256 public constant MAX_RATE_AT_TARGET = int256(0.01e3 ether) * WAD_INT / CURVE_STEEPNESS / 365 days; + /// @notice Maximum rate at target per second (scaled by WAD) (200% APR <=> 800% max APR). + int256 public constant MAX_RATE_AT_TARGET = int256(800 * 0.01 ether) * WAD_INT / CURVE_STEEPNESS / 365 days; } From 643304a69cdde0fa915ae71181cb6f16d0699bd4 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Fri, 8 Dec 2023 18:54:40 +0100 Subject: [PATCH 11/35] refactor: rename math operations --- src/AdaptiveCurveIrm.sol | 10 ++++----- src/libraries/MathLib.sol | 6 +++-- test/AdaptiveCurveIrmTest.sol | 41 ++++++++++++++--------------------- test/ExpLibTest.sol | 4 ++-- 4 files changed, 27 insertions(+), 34 deletions(-) diff --git a/src/AdaptiveCurveIrm.sol b/src/AdaptiveCurveIrm.sol index 4e769b4b..705089d4 100644 --- a/src/AdaptiveCurveIrm.sol +++ b/src/AdaptiveCurveIrm.sol @@ -114,7 +114,7 @@ contract AdaptiveCurveIrm is IAdaptiveCurveIrm { int256(market.totalSupplyAssets > 0 ? market.totalBorrowAssets.wDivDown(market.totalSupplyAssets) : 0); int256 errNormFactor = utilization > TARGET_UTILIZATION ? WAD - TARGET_UTILIZATION : TARGET_UTILIZATION; - int256 err = (utilization - TARGET_UTILIZATION).wDivDown(errNormFactor); + int256 err = (utilization - TARGET_UTILIZATION).wDiv(errNormFactor); int256 startRateAtTarget = rateAtTarget[id]; @@ -128,7 +128,7 @@ contract AdaptiveCurveIrm is IAdaptiveCurveIrm { } else { // Note that the speed is assumed constant between two interactions, but in theory it increases because of // interests. So the rate will be slightly underestimated. - int256 speed = ADJUSTMENT_SPEED.wMulDown(err); + int256 speed = ADJUSTMENT_SPEED.wMul(err); // market.lastUpdate != 0 because it is not the first interaction with this market. // Safe "unchecked" cast because block.timestamp - market.lastUpdate <= block.timestamp <= type(int256).max. int256 elapsed = int256(block.timestamp - market.lastUpdate); @@ -168,16 +168,16 @@ contract AdaptiveCurveIrm is IAdaptiveCurveIrm { /// ((C-1)*err + 1) * rateAtTarget else. function _curve(int256 _rateAtTarget, int256 err) private view returns (int256) { // Non negative because 1 - 1/C >= 0, C - 1 >= 0. - int256 coeff = err < 0 ? WAD - WAD.wDivDown(CURVE_STEEPNESS) : CURVE_STEEPNESS - WAD; + int256 coeff = err < 0 ? WAD - WAD.wDiv(CURVE_STEEPNESS) : CURVE_STEEPNESS - WAD; // Non negative if _rateAtTarget >= 0 because if err < 0, coeff <= 1. - return (coeff.wMulDown(err) + WAD).wMulDown(int256(_rateAtTarget)); + return (coeff.wMul(err) + WAD).wMul(int256(_rateAtTarget)); } /// @dev Returns the new rate at target, for a given `startRateAtTarget` and a given `linearAdaptation`. /// The formula is: max(min(startRateAtTarget * exp(linearAdaptation), maxRateAtTarget), minRateAtTarget). function _newRateAtTarget(int256 startRateAtTarget, int256 linearAdaptation) private pure returns (int256) { // Non negative because MIN_RATE_AT_TARGET > 0. - return startRateAtTarget.wMulDown(ExpLib.wExp(linearAdaptation)).bound( + return startRateAtTarget.wMul(ExpLib.wExp(linearAdaptation)).bound( ConstantsLib.MIN_RATE_AT_TARGET, ConstantsLib.MAX_RATE_AT_TARGET ); } diff --git a/src/libraries/MathLib.sol b/src/libraries/MathLib.sol index fb0c7000..15ffd78d 100644 --- a/src/libraries/MathLib.sol +++ b/src/libraries/MathLib.sol @@ -10,11 +10,13 @@ int256 constant WAD_INT = int256(WAD); /// @custom:contact security@morpho.org /// @notice Library to manage fixed-point arithmetic on signed integers. library MathLib { - function wMulDown(int256 a, int256 b) internal pure returns (int256) { + /// @dev Returns the multiplication of `a` by `b` (in WAD) rounded towards 0. + function wMul(int256 a, int256 b) internal pure returns (int256) { return a * b / WAD_INT; } - function wDivDown(int256 a, int256 b) internal pure returns (int256) { + /// @dev Returns the division of `a` by `b` (in WAD) rounded towards 0. + function wDiv(int256 a, int256 b) internal pure returns (int256) { return a * WAD_INT / b; } } diff --git a/test/AdaptiveCurveIrmTest.sol b/test/AdaptiveCurveIrmTest.sol index 4e52a34b..de5e6c63 100644 --- a/test/AdaptiveCurveIrmTest.sol +++ b/test/AdaptiveCurveIrmTest.sol @@ -72,13 +72,13 @@ contract AdaptiveCurveIrmTest is Test { // (exp((50/365)*5) ~= 1.9836. assertApproxEqRel( irm.borrowRateView(marketParams, market), - uint256((INITIAL_RATE_AT_TARGET * 4).wMulDown((1.9836 ether - 1 ether) * WAD / (ADJUSTMENT_SPEED * 5 days))), + uint256((INITIAL_RATE_AT_TARGET * 4).wMul((1.9836 ether - 1 ether) * WAD / (ADJUSTMENT_SPEED * 5 days))), 0.1 ether ); // The average value of exp((50/365)*x) between 0 and 5 is approx. 1.4361. assertApproxEqRel( irm.borrowRateView(marketParams, market), - uint256((INITIAL_RATE_AT_TARGET * 4).wMulDown(1.4361 ether)), + uint256((INITIAL_RATE_AT_TARGET * 4).wMul(1.4361 ether)), 0.1 ether ); // Expected rate: 5.744%. @@ -97,16 +97,12 @@ contract AdaptiveCurveIrmTest is Test { // (exp((-50/365)*5) ~= 0.5041. assertApproxEqRel( irm.borrowRateView(marketParams, market), - uint256( - (INITIAL_RATE_AT_TARGET / 4).wMulDown((0.5041 ether - 1 ether) * WAD / (-ADJUSTMENT_SPEED * 5 days)) - ), + uint256((INITIAL_RATE_AT_TARGET / 4).wMul((0.5041 ether - 1 ether) * WAD / (-ADJUSTMENT_SPEED * 5 days))), 0.1 ether ); // The average value of exp((-50/365*x)) between 0 and 5 is approx. 0.7240. assertApproxEqRel( - irm.borrowRateView(marketParams, market), - uint256((INITIAL_RATE_AT_TARGET / 4).wMulDown(0.724 ether)), - 0.1 ether + irm.borrowRateView(marketParams, market), uint256((INITIAL_RATE_AT_TARGET / 4).wMul(0.724 ether)), 0.1 ether ); // Expected rate: 0.181%. assertApproxEqRel(irm.borrowRateView(marketParams, market), uint256(0.00181 ether) / 365 days, 0.1 ether); @@ -337,11 +333,9 @@ contract AdaptiveCurveIrmTest is Test { market.totalSupplyAssets = 10 ether; assertGe( - irm.borrowRateView(marketParams, market), uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDivDown(CURVE_STEEPNESS)) - ); - assertGe( - irm.borrowRate(marketParams, market), uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDivDown(CURVE_STEEPNESS)) + irm.borrowRateView(marketParams, market), uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDiv(CURVE_STEEPNESS)) ); + assertGe(irm.borrowRate(marketParams, market), uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDiv(CURVE_STEEPNESS))); } function invariantLeMaxRateAtTarget() public { @@ -350,23 +344,21 @@ contract AdaptiveCurveIrmTest is Test { market.totalSupplyAssets = 10 ether; assertLe( - irm.borrowRateView(marketParams, market), uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMulDown(CURVE_STEEPNESS)) - ); - assertLe( - irm.borrowRate(marketParams, market), uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMulDown(CURVE_STEEPNESS)) + irm.borrowRateView(marketParams, market), uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMul(CURVE_STEEPNESS)) ); + assertLe(irm.borrowRate(marketParams, market), uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMul(CURVE_STEEPNESS))); } /* HELPERS */ function _expectedRateAtTarget(Id id, Market memory market) internal view returns (int256) { int256 rateAtTarget = irm.rateAtTarget(id); - int256 speed = ADJUSTMENT_SPEED.wMulDown(_err(market)); + int256 speed = ADJUSTMENT_SPEED.wMul(_err(market)); uint256 elapsed = (rateAtTarget > 0) ? block.timestamp - market.lastUpdate : 0; int256 linearAdaptation = speed * int256(elapsed); int256 adaptationMultiplier = ExpLib.wExp(linearAdaptation); return (rateAtTarget > 0) - ? rateAtTarget.wMulDown(adaptationMultiplier).bound( + ? rateAtTarget.wMul(adaptationMultiplier).bound( ConstantsLib.MIN_RATE_AT_TARGET, ConstantsLib.MAX_RATE_AT_TARGET ) : INITIAL_RATE_AT_TARGET; @@ -375,7 +367,7 @@ contract AdaptiveCurveIrmTest is Test { function _expectedAvgRate(Id id, Market memory market) internal view returns (uint256) { int256 rateAtTarget = irm.rateAtTarget(id); int256 err = _err(market); - int256 speed = ADJUSTMENT_SPEED.wMulDown(err); + int256 speed = ADJUSTMENT_SPEED.wMul(err); uint256 elapsed = (rateAtTarget > 0) ? block.timestamp - market.lastUpdate : 0; int256 linearAdaptation = speed * int256(elapsed); int256 endRateAtTarget = int256(_expectedRateAtTarget(id, market)); @@ -386,8 +378,7 @@ contract AdaptiveCurveIrmTest is Test { avgBorrowRate = newBorrowRate; } else { // Safe "unchecked" cast to uint256 because linearAdaptation < 0 <=> newBorrowRate <= borrowRateAfterJump. - avgBorrowRate = - uint256((int256(newBorrowRate) - int256(_curve(rateAtTarget, err))).wDivDown(linearAdaptation)); + avgBorrowRate = uint256((int256(newBorrowRate) - int256(_curve(rateAtTarget, err))).wDiv(linearAdaptation)); } return avgBorrowRate; } @@ -395,9 +386,9 @@ contract AdaptiveCurveIrmTest is Test { function _curve(int256 rateAtTarget, int256 err) internal pure returns (uint256) { // Safe "unchecked" cast because err >= -1 (in WAD). if (err < 0) { - return uint256(((WAD - WAD.wDivDown(CURVE_STEEPNESS)).wMulDown(err) + WAD).wMulDown(rateAtTarget)); + return uint256(((WAD - WAD.wDiv(CURVE_STEEPNESS)).wMul(err) + WAD).wMul(rateAtTarget)); } else { - return uint256(((CURVE_STEEPNESS - WAD).wMulDown(err) + WAD).wMulDown(rateAtTarget)); + return uint256(((CURVE_STEEPNESS - WAD).wMul(err) + WAD).wMul(rateAtTarget)); } } @@ -407,9 +398,9 @@ contract AdaptiveCurveIrmTest is Test { int256 utilization = int256(market.totalBorrowAssets.wDivDown(market.totalSupplyAssets)); if (utilization > TARGET_UTILIZATION) { - err = (utilization - TARGET_UTILIZATION).wDivDown(WAD - TARGET_UTILIZATION); + err = (utilization - TARGET_UTILIZATION).wDiv(WAD - TARGET_UTILIZATION); } else { - err = (utilization - TARGET_UTILIZATION).wDivDown(TARGET_UTILIZATION); + err = (utilization - TARGET_UTILIZATION).wDiv(TARGET_UTILIZATION); } } } diff --git a/test/ExpLibTest.sol b/test/ExpLibTest.sol index 81e4cd5d..fb28857a 100644 --- a/test/ExpLibTest.sol +++ b/test/ExpLibTest.sol @@ -62,8 +62,8 @@ contract ExpLibTest is Test { assertLe(ExpLib.wExp(x), 1e18); } - function testWExpWMulDownMaxRate() public pure { - ExpLib.wExp(ExpLib.WEXP_UPPER_BOUND).wMulDown(ConstantsLib.MAX_RATE_AT_TARGET); + function testWExpWMulMaxRate() public pure { + ExpLib.wExp(ExpLib.WEXP_UPPER_BOUND).wMul(ConstantsLib.MAX_RATE_AT_TARGET); } function _wExpUnbounded(int256 x) internal pure returns (int256) { From 143bc6d275dfebce25b517e935303f054af591b1 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Mon, 11 Dec 2023 11:10:46 +0100 Subject: [PATCH 12/35] fix(constants): change def --- src/libraries/adaptive-curve/ConstantsLib.sol | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/libraries/adaptive-curve/ConstantsLib.sol b/src/libraries/adaptive-curve/ConstantsLib.sol index 2ff96e34..6d9b0cf0 100644 --- a/src/libraries/adaptive-curve/ConstantsLib.sol +++ b/src/libraries/adaptive-curve/ConstantsLib.sol @@ -7,6 +7,8 @@ import {WAD_INT} from "../MathLib.sol"; /// @author Morpho Labs /// @custom:contact security@morpho.org library ConstantsLib { + int256 public constant ONE_BPS = 0.0001 ether; + /// @notice Curve steepness (scaled by WAD). int256 public constant CURVE_STEEPNESS = 4 ether; @@ -16,12 +18,12 @@ library ConstantsLib { /// @notice Target utilization (scaled by WAD). int256 public constant TARGET_UTILIZATION = 0.9 ether; - /// @notice Initial rate at target per second (scaled by WAD) (4% APR <=> 1% min APR). - int256 public constant INITIAL_RATE_AT_TARGET = int256(0.01 ether) * CURVE_STEEPNESS / WAD_INT / 365 days; + /// @notice Initial rate at target per second (scaled by WAD). + int256 public constant INITIAL_RATE_AT_TARGET = 4_00 * ONE_BPS / 365 days; - /// @notice Minimum rate at target per second (scaled by WAD) (0.1% APR <=> 0.025% min APR). - int256 public constant MIN_RATE_AT_TARGET = int256(0.025 * 0.01 ether) * CURVE_STEEPNESS / WAD_INT / 365 days; + /// @notice Minimum rate at target per second (scaled by WAD) (min APR is MAX_RATE_AT_TARGET / CURVE_STEEPNESS). + int256 public constant MIN_RATE_AT_TARGET = 10 * ONE_BPS / 365 days; - /// @notice Maximum rate at target per second (scaled by WAD) (200% APR <=> 800% max APR). - int256 public constant MAX_RATE_AT_TARGET = int256(800 * 0.01 ether) * WAD_INT / CURVE_STEEPNESS / 365 days; + /// @notice Maximum rate at target per second (scaled by WAD) (max APR is MAX_RATE_AT_TARGET * CURVE_STEEPNESS). + int256 public constant MAX_RATE_AT_TARGET = 200_00 * ONE_BPS / 365 days; } From 07deb50f8c63052ffe6264e87b373d5587e57799 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Tue, 12 Dec 2023 09:25:05 +0100 Subject: [PATCH 13/35] test: constants ranges --- test/AdaptiveCurveIrmTest.sol | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/AdaptiveCurveIrmTest.sol b/test/AdaptiveCurveIrmTest.sol index a1b27590..c01d344c 100644 --- a/test/AdaptiveCurveIrmTest.sol +++ b/test/AdaptiveCurveIrmTest.sol @@ -367,6 +367,17 @@ contract AdaptiveCurveIrmTest is Test { ); } + function testConstants() public { + assertGe(ConstantsLib.CURVE_STEEPNESS, 1 ether, "curveSteepness too small"); + assertLe(ConstantsLib.CURVE_STEEPNESS, 100 ether, "curveSteepness too big"); + assertGe(ConstantsLib.ADJUSTMENT_SPEED, 0, "adjustmentSpeed too small"); + assertLe(ConstantsLib.ADJUSTMENT_SPEED, int256(1_000 ether) / 365 days, "adjustmentSpeed too big"); + assertGt(ConstantsLib.TARGET_UTILIZATION, 0, "targetUtilization too small"); + assertLt(ConstantsLib.TARGET_UTILIZATION, 1 ether, "targetUtilization too big"); + assertGe(ConstantsLib.INITIAL_RATE_AT_TARGET, ConstantsLib.MIN_RATE_AT_TARGET, "initialRateAtTarget too small"); + assertLe(ConstantsLib.INITIAL_RATE_AT_TARGET, ConstantsLib.MAX_RATE_AT_TARGET, "initialRateAtTarget too large"); + } + /* HELPERS */ function _expectedRateAtTarget(Id id, Market memory market) internal view returns (int256) { From b449f69b678ee5cc2721936cca26610678d2cf33 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Tue, 12 Dec 2023 09:53:46 +0100 Subject: [PATCH 14/35] style: naming --- src/AdaptiveCurveIrm.sol | 10 +++++----- src/libraries/MathLib.sol | 4 ++-- test/AdaptiveCurveIrmTest.sol | 32 ++++++++++++++++---------------- test/ExpLibTest.sol | 2 +- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/AdaptiveCurveIrm.sol b/src/AdaptiveCurveIrm.sol index 705089d4..6d4167a9 100644 --- a/src/AdaptiveCurveIrm.sol +++ b/src/AdaptiveCurveIrm.sol @@ -114,7 +114,7 @@ contract AdaptiveCurveIrm is IAdaptiveCurveIrm { int256(market.totalSupplyAssets > 0 ? market.totalBorrowAssets.wDivDown(market.totalSupplyAssets) : 0); int256 errNormFactor = utilization > TARGET_UTILIZATION ? WAD - TARGET_UTILIZATION : TARGET_UTILIZATION; - int256 err = (utilization - TARGET_UTILIZATION).wDiv(errNormFactor); + int256 err = (utilization - TARGET_UTILIZATION).wDivTo0(errNormFactor); int256 startRateAtTarget = rateAtTarget[id]; @@ -128,7 +128,7 @@ contract AdaptiveCurveIrm is IAdaptiveCurveIrm { } else { // Note that the speed is assumed constant between two interactions, but in theory it increases because of // interests. So the rate will be slightly underestimated. - int256 speed = ADJUSTMENT_SPEED.wMul(err); + int256 speed = ADJUSTMENT_SPEED.wMulTo0(err); // market.lastUpdate != 0 because it is not the first interaction with this market. // Safe "unchecked" cast because block.timestamp - market.lastUpdate <= block.timestamp <= type(int256).max. int256 elapsed = int256(block.timestamp - market.lastUpdate); @@ -168,16 +168,16 @@ contract AdaptiveCurveIrm is IAdaptiveCurveIrm { /// ((C-1)*err + 1) * rateAtTarget else. function _curve(int256 _rateAtTarget, int256 err) private view returns (int256) { // Non negative because 1 - 1/C >= 0, C - 1 >= 0. - int256 coeff = err < 0 ? WAD - WAD.wDiv(CURVE_STEEPNESS) : CURVE_STEEPNESS - WAD; + int256 coeff = err < 0 ? WAD - WAD.wDivTo0(CURVE_STEEPNESS) : CURVE_STEEPNESS - WAD; // Non negative if _rateAtTarget >= 0 because if err < 0, coeff <= 1. - return (coeff.wMul(err) + WAD).wMul(int256(_rateAtTarget)); + return (coeff.wMulTo0(err) + WAD).wMulTo0(int256(_rateAtTarget)); } /// @dev Returns the new rate at target, for a given `startRateAtTarget` and a given `linearAdaptation`. /// The formula is: max(min(startRateAtTarget * exp(linearAdaptation), maxRateAtTarget), minRateAtTarget). function _newRateAtTarget(int256 startRateAtTarget, int256 linearAdaptation) private pure returns (int256) { // Non negative because MIN_RATE_AT_TARGET > 0. - return startRateAtTarget.wMul(ExpLib.wExp(linearAdaptation)).bound( + return startRateAtTarget.wMulTo0(ExpLib.wExp(linearAdaptation)).bound( ConstantsLib.MIN_RATE_AT_TARGET, ConstantsLib.MAX_RATE_AT_TARGET ); } diff --git a/src/libraries/MathLib.sol b/src/libraries/MathLib.sol index 15ffd78d..413d2f0b 100644 --- a/src/libraries/MathLib.sol +++ b/src/libraries/MathLib.sol @@ -11,12 +11,12 @@ int256 constant WAD_INT = int256(WAD); /// @notice Library to manage fixed-point arithmetic on signed integers. library MathLib { /// @dev Returns the multiplication of `a` by `b` (in WAD) rounded towards 0. - function wMul(int256 a, int256 b) internal pure returns (int256) { + function wMulTo0(int256 a, int256 b) internal pure returns (int256) { return a * b / WAD_INT; } /// @dev Returns the division of `a` by `b` (in WAD) rounded towards 0. - function wDiv(int256 a, int256 b) internal pure returns (int256) { + function wDivTo0(int256 a, int256 b) internal pure returns (int256) { return a * WAD_INT / b; } } diff --git a/test/AdaptiveCurveIrmTest.sol b/test/AdaptiveCurveIrmTest.sol index de5e6c63..d3dbfb6b 100644 --- a/test/AdaptiveCurveIrmTest.sol +++ b/test/AdaptiveCurveIrmTest.sol @@ -72,13 +72,13 @@ contract AdaptiveCurveIrmTest is Test { // (exp((50/365)*5) ~= 1.9836. assertApproxEqRel( irm.borrowRateView(marketParams, market), - uint256((INITIAL_RATE_AT_TARGET * 4).wMul((1.9836 ether - 1 ether) * WAD / (ADJUSTMENT_SPEED * 5 days))), + uint256((INITIAL_RATE_AT_TARGET * 4).wMulTo0((1.9836 ether - 1 ether) * WAD / (ADJUSTMENT_SPEED * 5 days))), 0.1 ether ); // The average value of exp((50/365)*x) between 0 and 5 is approx. 1.4361. assertApproxEqRel( irm.borrowRateView(marketParams, market), - uint256((INITIAL_RATE_AT_TARGET * 4).wMul(1.4361 ether)), + uint256((INITIAL_RATE_AT_TARGET * 4).wMulTo0(1.4361 ether)), 0.1 ether ); // Expected rate: 5.744%. @@ -97,12 +97,12 @@ contract AdaptiveCurveIrmTest is Test { // (exp((-50/365)*5) ~= 0.5041. assertApproxEqRel( irm.borrowRateView(marketParams, market), - uint256((INITIAL_RATE_AT_TARGET / 4).wMul((0.5041 ether - 1 ether) * WAD / (-ADJUSTMENT_SPEED * 5 days))), + uint256((INITIAL_RATE_AT_TARGET / 4).wMulTo0((0.5041 ether - 1 ether) * WAD / (-ADJUSTMENT_SPEED * 5 days))), 0.1 ether ); // The average value of exp((-50/365*x)) between 0 and 5 is approx. 0.7240. assertApproxEqRel( - irm.borrowRateView(marketParams, market), uint256((INITIAL_RATE_AT_TARGET / 4).wMul(0.724 ether)), 0.1 ether + irm.borrowRateView(marketParams, market), uint256((INITIAL_RATE_AT_TARGET / 4).wMulTo0(0.724 ether)), 0.1 ether ); // Expected rate: 0.181%. assertApproxEqRel(irm.borrowRateView(marketParams, market), uint256(0.00181 ether) / 365 days, 0.1 ether); @@ -333,9 +333,9 @@ contract AdaptiveCurveIrmTest is Test { market.totalSupplyAssets = 10 ether; assertGe( - irm.borrowRateView(marketParams, market), uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDiv(CURVE_STEEPNESS)) + irm.borrowRateView(marketParams, market), uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDivTo0(CURVE_STEEPNESS)) ); - assertGe(irm.borrowRate(marketParams, market), uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDiv(CURVE_STEEPNESS))); + assertGe(irm.borrowRate(marketParams, market), uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDivTo0(CURVE_STEEPNESS))); } function invariantLeMaxRateAtTarget() public { @@ -344,21 +344,21 @@ contract AdaptiveCurveIrmTest is Test { market.totalSupplyAssets = 10 ether; assertLe( - irm.borrowRateView(marketParams, market), uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMul(CURVE_STEEPNESS)) + irm.borrowRateView(marketParams, market), uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMulTo0(CURVE_STEEPNESS)) ); - assertLe(irm.borrowRate(marketParams, market), uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMul(CURVE_STEEPNESS))); + assertLe(irm.borrowRate(marketParams, market), uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMulTo0(CURVE_STEEPNESS))); } /* HELPERS */ function _expectedRateAtTarget(Id id, Market memory market) internal view returns (int256) { int256 rateAtTarget = irm.rateAtTarget(id); - int256 speed = ADJUSTMENT_SPEED.wMul(_err(market)); + int256 speed = ADJUSTMENT_SPEED.wMulTo0(_err(market)); uint256 elapsed = (rateAtTarget > 0) ? block.timestamp - market.lastUpdate : 0; int256 linearAdaptation = speed * int256(elapsed); int256 adaptationMultiplier = ExpLib.wExp(linearAdaptation); return (rateAtTarget > 0) - ? rateAtTarget.wMul(adaptationMultiplier).bound( + ? rateAtTarget.wMulTo0(adaptationMultiplier).bound( ConstantsLib.MIN_RATE_AT_TARGET, ConstantsLib.MAX_RATE_AT_TARGET ) : INITIAL_RATE_AT_TARGET; @@ -367,7 +367,7 @@ contract AdaptiveCurveIrmTest is Test { function _expectedAvgRate(Id id, Market memory market) internal view returns (uint256) { int256 rateAtTarget = irm.rateAtTarget(id); int256 err = _err(market); - int256 speed = ADJUSTMENT_SPEED.wMul(err); + int256 speed = ADJUSTMENT_SPEED.wMulTo0(err); uint256 elapsed = (rateAtTarget > 0) ? block.timestamp - market.lastUpdate : 0; int256 linearAdaptation = speed * int256(elapsed); int256 endRateAtTarget = int256(_expectedRateAtTarget(id, market)); @@ -378,7 +378,7 @@ contract AdaptiveCurveIrmTest is Test { avgBorrowRate = newBorrowRate; } else { // Safe "unchecked" cast to uint256 because linearAdaptation < 0 <=> newBorrowRate <= borrowRateAfterJump. - avgBorrowRate = uint256((int256(newBorrowRate) - int256(_curve(rateAtTarget, err))).wDiv(linearAdaptation)); + avgBorrowRate = uint256((int256(newBorrowRate) - int256(_curve(rateAtTarget, err))).wDivTo0(linearAdaptation)); } return avgBorrowRate; } @@ -386,9 +386,9 @@ contract AdaptiveCurveIrmTest is Test { function _curve(int256 rateAtTarget, int256 err) internal pure returns (uint256) { // Safe "unchecked" cast because err >= -1 (in WAD). if (err < 0) { - return uint256(((WAD - WAD.wDiv(CURVE_STEEPNESS)).wMul(err) + WAD).wMul(rateAtTarget)); + return uint256(((WAD - WAD.wDivTo0(CURVE_STEEPNESS)).wMulTo0(err) + WAD).wMulTo0(rateAtTarget)); } else { - return uint256(((CURVE_STEEPNESS - WAD).wMul(err) + WAD).wMul(rateAtTarget)); + return uint256(((CURVE_STEEPNESS - WAD).wMulTo0(err) + WAD).wMulTo0(rateAtTarget)); } } @@ -398,9 +398,9 @@ contract AdaptiveCurveIrmTest is Test { int256 utilization = int256(market.totalBorrowAssets.wDivDown(market.totalSupplyAssets)); if (utilization > TARGET_UTILIZATION) { - err = (utilization - TARGET_UTILIZATION).wDiv(WAD - TARGET_UTILIZATION); + err = (utilization - TARGET_UTILIZATION).wDivTo0(WAD - TARGET_UTILIZATION); } else { - err = (utilization - TARGET_UTILIZATION).wDiv(TARGET_UTILIZATION); + err = (utilization - TARGET_UTILIZATION).wDivTo0(TARGET_UTILIZATION); } } } diff --git a/test/ExpLibTest.sol b/test/ExpLibTest.sol index fb28857a..eee07127 100644 --- a/test/ExpLibTest.sol +++ b/test/ExpLibTest.sol @@ -63,7 +63,7 @@ contract ExpLibTest is Test { } function testWExpWMulMaxRate() public pure { - ExpLib.wExp(ExpLib.WEXP_UPPER_BOUND).wMul(ConstantsLib.MAX_RATE_AT_TARGET); + ExpLib.wExp(ExpLib.WEXP_UPPER_BOUND).wMulTo0(ConstantsLib.MAX_RATE_AT_TARGET); } function _wExpUnbounded(int256 x) internal pure returns (int256) { From 1516a1c1bf3f3f17693753350ac93f86b5b828e8 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Tue, 12 Dec 2023 10:26:15 +0100 Subject: [PATCH 15/35] chore: fmt --- test/AdaptiveCurveIrmTest.sol | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/test/AdaptiveCurveIrmTest.sol b/test/AdaptiveCurveIrmTest.sol index d3dbfb6b..0acd32c7 100644 --- a/test/AdaptiveCurveIrmTest.sol +++ b/test/AdaptiveCurveIrmTest.sol @@ -102,7 +102,9 @@ contract AdaptiveCurveIrmTest is Test { ); // The average value of exp((-50/365*x)) between 0 and 5 is approx. 0.7240. assertApproxEqRel( - irm.borrowRateView(marketParams, market), uint256((INITIAL_RATE_AT_TARGET / 4).wMulTo0(0.724 ether)), 0.1 ether + irm.borrowRateView(marketParams, market), + uint256((INITIAL_RATE_AT_TARGET / 4).wMulTo0(0.724 ether)), + 0.1 ether ); // Expected rate: 0.181%. assertApproxEqRel(irm.borrowRateView(marketParams, market), uint256(0.00181 ether) / 365 days, 0.1 ether); @@ -335,7 +337,9 @@ contract AdaptiveCurveIrmTest is Test { assertGe( irm.borrowRateView(marketParams, market), uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDivTo0(CURVE_STEEPNESS)) ); - assertGe(irm.borrowRate(marketParams, market), uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDivTo0(CURVE_STEEPNESS))); + assertGe( + irm.borrowRate(marketParams, market), uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDivTo0(CURVE_STEEPNESS)) + ); } function invariantLeMaxRateAtTarget() public { @@ -346,7 +350,9 @@ contract AdaptiveCurveIrmTest is Test { assertLe( irm.borrowRateView(marketParams, market), uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMulTo0(CURVE_STEEPNESS)) ); - assertLe(irm.borrowRate(marketParams, market), uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMulTo0(CURVE_STEEPNESS))); + assertLe( + irm.borrowRate(marketParams, market), uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMulTo0(CURVE_STEEPNESS)) + ); } /* HELPERS */ @@ -378,7 +384,8 @@ contract AdaptiveCurveIrmTest is Test { avgBorrowRate = newBorrowRate; } else { // Safe "unchecked" cast to uint256 because linearAdaptation < 0 <=> newBorrowRate <= borrowRateAfterJump. - avgBorrowRate = uint256((int256(newBorrowRate) - int256(_curve(rateAtTarget, err))).wDivTo0(linearAdaptation)); + avgBorrowRate = + uint256((int256(newBorrowRate) - int256(_curve(rateAtTarget, err))).wDivTo0(linearAdaptation)); } return avgBorrowRate; } From 4116ef86e93feee2ae3df62bf0e9dd4c4d7abb6e Mon Sep 17 00:00:00 2001 From: MathisGD Date: Tue, 12 Dec 2023 11:16:48 +0100 Subject: [PATCH 16/35] chore: fix compilation --- test/AdaptiveCurveIrmTest.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/AdaptiveCurveIrmTest.sol b/test/AdaptiveCurveIrmTest.sol index 629ba747..5e889b9b 100644 --- a/test/AdaptiveCurveIrmTest.sol +++ b/test/AdaptiveCurveIrmTest.sol @@ -417,7 +417,7 @@ contract AdaptiveCurveIrmTest is Test { // Safe "unchecked" cast because err >= -1 (in WAD). if (err < 0) { return - uint256(((WAD - WAD.wDivDown(ConstantsLib.CURVE_STEEPNESS)).wMulTo0(err) + WAD).wMulTo0(rateAtTarget)); + uint256(((WAD - WAD.wMulTo0(ConstantsLib.CURVE_STEEPNESS)).wMulTo0(err) + WAD).wMulTo0(rateAtTarget)); } else { return uint256(((ConstantsLib.CURVE_STEEPNESS - WAD).wMulTo0(err) + WAD).wMulTo0(rateAtTarget)); } From e308fa391978d124e1299b18b2b7a678c5a5289f Mon Sep 17 00:00:00 2001 From: MathisGD Date: Tue, 12 Dec 2023 11:39:53 +0100 Subject: [PATCH 17/35] chore: fmt --- test/AdaptiveCurveIrmTest.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/AdaptiveCurveIrmTest.sol b/test/AdaptiveCurveIrmTest.sol index 5e889b9b..1a7aa8b2 100644 --- a/test/AdaptiveCurveIrmTest.sol +++ b/test/AdaptiveCurveIrmTest.sol @@ -416,8 +416,7 @@ contract AdaptiveCurveIrmTest is Test { function _curve(int256 rateAtTarget, int256 err) internal pure returns (uint256) { // Safe "unchecked" cast because err >= -1 (in WAD). if (err < 0) { - return - uint256(((WAD - WAD.wMulTo0(ConstantsLib.CURVE_STEEPNESS)).wMulTo0(err) + WAD).wMulTo0(rateAtTarget)); + return uint256(((WAD - WAD.wMulTo0(ConstantsLib.CURVE_STEEPNESS)).wMulTo0(err) + WAD).wMulTo0(rateAtTarget)); } else { return uint256(((ConstantsLib.CURVE_STEEPNESS - WAD).wMulTo0(err) + WAD).wMulTo0(rateAtTarget)); } From 174936aee3a3192ea18ae3babe07cddf925b0523 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Mon, 11 Dec 2023 17:47:44 +0100 Subject: [PATCH 18/35] docs(irm): speed is not constant --- src/AdaptiveCurveIrm.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AdaptiveCurveIrm.sol b/src/AdaptiveCurveIrm.sol index 4e769b4b..51a9aa3d 100644 --- a/src/AdaptiveCurveIrm.sol +++ b/src/AdaptiveCurveIrm.sol @@ -126,8 +126,8 @@ contract AdaptiveCurveIrm is IAdaptiveCurveIrm { avgRateAtTarget = INITIAL_RATE_AT_TARGET; endRateAtTarget = INITIAL_RATE_AT_TARGET; } else { - // Note that the speed is assumed constant between two interactions, but in theory it increases because of - // interests. So the rate will be slightly underestimated. + // The speed is assumed constant between two updates, but it is in fact not constant because of interest. + // So the rate is underestimated. int256 speed = ADJUSTMENT_SPEED.wMulDown(err); // market.lastUpdate != 0 because it is not the first interaction with this market. // Safe "unchecked" cast because block.timestamp - market.lastUpdate <= block.timestamp <= type(int256).max. From f042fd27e07d6321328dca8d9f818929ef5c4b36 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Tue, 12 Dec 2023 11:44:44 +0100 Subject: [PATCH 19/35] docs(irm): future --- src/AdaptiveCurveIrm.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AdaptiveCurveIrm.sol b/src/AdaptiveCurveIrm.sol index 94808536..dd4ce162 100644 --- a/src/AdaptiveCurveIrm.sol +++ b/src/AdaptiveCurveIrm.sol @@ -95,7 +95,7 @@ contract AdaptiveCurveIrm is IAdaptiveCurveIrm { endRateAtTarget = ConstantsLib.INITIAL_RATE_AT_TARGET; } else { // The speed is assumed constant between two updates, but it is in fact not constant because of interest. - // So the rate is underestimated. + // So the rate will be underestimated. int256 speed = ConstantsLib.ADJUSTMENT_SPEED.wMulDown(err); // market.lastUpdate != 0 because it is not the first interaction with this market. // Safe "unchecked" cast because block.timestamp - market.lastUpdate <= block.timestamp <= type(int256).max. From b728864b3691ecb803e7e3a8c520b8291d2c4a2e Mon Sep 17 00:00:00 2001 From: MathisGD Date: Tue, 12 Dec 2023 12:23:41 +0100 Subject: [PATCH 20/35] test: fix test --- test/AdaptiveCurveIrmTest.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/AdaptiveCurveIrmTest.sol b/test/AdaptiveCurveIrmTest.sol index 1a7aa8b2..20a3fe83 100644 --- a/test/AdaptiveCurveIrmTest.sol +++ b/test/AdaptiveCurveIrmTest.sol @@ -416,7 +416,7 @@ contract AdaptiveCurveIrmTest is Test { function _curve(int256 rateAtTarget, int256 err) internal pure returns (uint256) { // Safe "unchecked" cast because err >= -1 (in WAD). if (err < 0) { - return uint256(((WAD - WAD.wMulTo0(ConstantsLib.CURVE_STEEPNESS)).wMulTo0(err) + WAD).wMulTo0(rateAtTarget)); + return uint256(((WAD - WAD.wDivTo0(ConstantsLib.CURVE_STEEPNESS)).wMulTo0(err) + WAD).wMulTo0(rateAtTarget)); } else { return uint256(((ConstantsLib.CURVE_STEEPNESS - WAD).wMulTo0(err) + WAD).wMulTo0(rateAtTarget)); } From 8fad62932e5982863d3ebb98ccbd83cad4771bae Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Tue, 12 Dec 2023 17:13:28 +0100 Subject: [PATCH 21/35] docs(constants): add APR --- src/libraries/adaptive-curve/ConstantsLib.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libraries/adaptive-curve/ConstantsLib.sol b/src/libraries/adaptive-curve/ConstantsLib.sol index 6d9b0cf0..a640ccc8 100644 --- a/src/libraries/adaptive-curve/ConstantsLib.sol +++ b/src/libraries/adaptive-curve/ConstantsLib.sol @@ -7,6 +7,7 @@ import {WAD_INT} from "../MathLib.sol"; /// @author Morpho Labs /// @custom:contact security@morpho.org library ConstantsLib { + /// @notice 1 bps = 0.01% (scaled by WAD). int256 public constant ONE_BPS = 0.0001 ether; /// @notice Curve steepness (scaled by WAD). @@ -21,9 +22,9 @@ library ConstantsLib { /// @notice Initial rate at target per second (scaled by WAD). int256 public constant INITIAL_RATE_AT_TARGET = 4_00 * ONE_BPS / 365 days; - /// @notice Minimum rate at target per second (scaled by WAD) (min APR is MAX_RATE_AT_TARGET / CURVE_STEEPNESS). + /// @notice Minimum rate at target per second (scaled by WAD) (0.025% min APR). int256 public constant MIN_RATE_AT_TARGET = 10 * ONE_BPS / 365 days; - /// @notice Maximum rate at target per second (scaled by WAD) (max APR is MAX_RATE_AT_TARGET * CURVE_STEEPNESS). + /// @notice Maximum rate at target per second (scaled by WAD) (800% max APR). int256 public constant MAX_RATE_AT_TARGET = 200_00 * ONE_BPS / 365 days; } From 19b3afb1ad6870afc921e6dbcb0efb0488773a28 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Wed, 13 Dec 2023 18:18:38 +0100 Subject: [PATCH 22/35] fix(foundry.toml): decrease optimization runs --- foundry.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/foundry.toml b/foundry.toml index 8318e07d..f3334ec6 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,6 +3,7 @@ src = "src" out = "out" test = "test" libs = ["lib"] +optimizer_runs = 999999 # Etherscan does not support verifying contracts with more optimization runs. [profile.default.fuzz] runs = 4096 From 87eb634e6d4ae8cf66cd599a8454445b38f82279 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Mon, 18 Dec 2023 10:45:25 +0100 Subject: [PATCH 23/35] docs(constants): add min/max APR --- src/libraries/adaptive-curve/ConstantsLib.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/adaptive-curve/ConstantsLib.sol b/src/libraries/adaptive-curve/ConstantsLib.sol index a640ccc8..22de9a06 100644 --- a/src/libraries/adaptive-curve/ConstantsLib.sol +++ b/src/libraries/adaptive-curve/ConstantsLib.sol @@ -19,7 +19,7 @@ library ConstantsLib { /// @notice Target utilization (scaled by WAD). int256 public constant TARGET_UTILIZATION = 0.9 ether; - /// @notice Initial rate at target per second (scaled by WAD). + /// @notice Initial rate at target per second (scaled by WAD) (1% min APR / 16% max APR). int256 public constant INITIAL_RATE_AT_TARGET = 4_00 * ONE_BPS / 365 days; /// @notice Minimum rate at target per second (scaled by WAD) (0.025% min APR). From 5baeaef0b6e9faf7138a085f7af495d85459d8b8 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Mon, 18 Dec 2023 14:57:52 +0100 Subject: [PATCH 24/35] refactor: constants --- src/libraries/adaptive-curve/ConstantsLib.sol | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/libraries/adaptive-curve/ConstantsLib.sol b/src/libraries/adaptive-curve/ConstantsLib.sol index 22de9a06..0f2d45ff 100644 --- a/src/libraries/adaptive-curve/ConstantsLib.sol +++ b/src/libraries/adaptive-curve/ConstantsLib.sol @@ -1,15 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {WAD_INT} from "../MathLib.sol"; - /// @title ConstantsLib /// @author Morpho Labs /// @custom:contact security@morpho.org library ConstantsLib { - /// @notice 1 bps = 0.01% (scaled by WAD). - int256 public constant ONE_BPS = 0.0001 ether; - /// @notice Curve steepness (scaled by WAD). int256 public constant CURVE_STEEPNESS = 4 ether; @@ -19,12 +14,15 @@ library ConstantsLib { /// @notice Target utilization (scaled by WAD). int256 public constant TARGET_UTILIZATION = 0.9 ether; - /// @notice Initial rate at target per second (scaled by WAD) (1% min APR / 16% max APR). - int256 public constant INITIAL_RATE_AT_TARGET = 4_00 * ONE_BPS / 365 days; + /// @notice Initial rate at target per second (scaled by WAD). + /// @dev Initial rate at target = 4% (rate between 1% and 16%). + int256 public constant INITIAL_RATE_AT_TARGET = 0.04 ether / int256(365 days); - /// @notice Minimum rate at target per second (scaled by WAD) (0.025% min APR). - int256 public constant MIN_RATE_AT_TARGET = 10 * ONE_BPS / 365 days; + /// @notice Minimum rate at target per second (scaled by WAD). + /// @dev Minimum rate at target = 0.1% (minimum rate = 0.025%). + int256 public constant MIN_RATE_AT_TARGET = 0.001 ether / int256(365 days); - /// @notice Maximum rate at target per second (scaled by WAD) (800% max APR). - int256 public constant MAX_RATE_AT_TARGET = 200_00 * ONE_BPS / 365 days; + /// @notice Maximum rate at target per second (scaled by WAD). + /// @dev Maximum rate at target = 200% (maximum rate = 800%). + int256 public constant MAX_RATE_AT_TARGET = 2.00 ether / int256(365 days); } From ce4d409ed13bae01a1f8bdbe9a1d9afe5db545be Mon Sep 17 00:00:00 2001 From: MathisGD Date: Mon, 18 Dec 2023 14:59:54 +0100 Subject: [PATCH 25/35] docs: minor improvements --- src/libraries/adaptive-curve/ConstantsLib.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libraries/adaptive-curve/ConstantsLib.sol b/src/libraries/adaptive-curve/ConstantsLib.sol index 0f2d45ff..eff767fd 100644 --- a/src/libraries/adaptive-curve/ConstantsLib.sol +++ b/src/libraries/adaptive-curve/ConstantsLib.sol @@ -6,13 +6,16 @@ pragma solidity ^0.8.0; /// @custom:contact security@morpho.org library ConstantsLib { /// @notice Curve steepness (scaled by WAD). + /// @dev Curve steepness = 4. int256 public constant CURVE_STEEPNESS = 4 ether; /// @notice Adjustment speed per second (scaled by WAD). + /// @dev Adjustment speed = 50/an. int256 public constant ADJUSTMENT_SPEED = int256(50 ether) / 365 days; /// @notice Target utilization (scaled by WAD). - int256 public constant TARGET_UTILIZATION = 0.9 ether; + /// @dev Target utilization = 90%. + int256 public constant TARGET_UTILIZATION = 0.90 ether; /// @notice Initial rate at target per second (scaled by WAD). /// @dev Initial rate at target = 4% (rate between 1% and 16%). From db8d2a6469818470e9dbeabd5371c2f54be33133 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Mon, 18 Dec 2023 15:00:47 +0100 Subject: [PATCH 26/35] chore: minor improvement --- src/libraries/adaptive-curve/ConstantsLib.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/adaptive-curve/ConstantsLib.sol b/src/libraries/adaptive-curve/ConstantsLib.sol index eff767fd..72fd6445 100644 --- a/src/libraries/adaptive-curve/ConstantsLib.sol +++ b/src/libraries/adaptive-curve/ConstantsLib.sol @@ -11,7 +11,7 @@ library ConstantsLib { /// @notice Adjustment speed per second (scaled by WAD). /// @dev Adjustment speed = 50/an. - int256 public constant ADJUSTMENT_SPEED = int256(50 ether) / 365 days; + int256 public constant ADJUSTMENT_SPEED = 50 ether / int256(365 days); /// @notice Target utilization (scaled by WAD). /// @dev Target utilization = 90%. From 2e5fe8e39e698d88fcf26f08ab77069fb51219c8 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Mon, 18 Dec 2023 15:01:29 +0100 Subject: [PATCH 27/35] docs: fix translation --- src/libraries/adaptive-curve/ConstantsLib.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/adaptive-curve/ConstantsLib.sol b/src/libraries/adaptive-curve/ConstantsLib.sol index 72fd6445..9df742c4 100644 --- a/src/libraries/adaptive-curve/ConstantsLib.sol +++ b/src/libraries/adaptive-curve/ConstantsLib.sol @@ -10,7 +10,7 @@ library ConstantsLib { int256 public constant CURVE_STEEPNESS = 4 ether; /// @notice Adjustment speed per second (scaled by WAD). - /// @dev Adjustment speed = 50/an. + /// @dev Adjustment speed = 50/year. int256 public constant ADJUSTMENT_SPEED = 50 ether / int256(365 days); /// @notice Target utilization (scaled by WAD). From 497e446db4db8c1d594fdfe8e9980070b9a7f60c Mon Sep 17 00:00:00 2001 From: MathisGD Date: Mon, 18 Dec 2023 15:02:35 +0100 Subject: [PATCH 28/35] chore: fmt --- src/libraries/adaptive-curve/ConstantsLib.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/adaptive-curve/ConstantsLib.sol b/src/libraries/adaptive-curve/ConstantsLib.sol index 9df742c4..38dcb215 100644 --- a/src/libraries/adaptive-curve/ConstantsLib.sol +++ b/src/libraries/adaptive-curve/ConstantsLib.sol @@ -15,7 +15,7 @@ library ConstantsLib { /// @notice Target utilization (scaled by WAD). /// @dev Target utilization = 90%. - int256 public constant TARGET_UTILIZATION = 0.90 ether; + int256 public constant TARGET_UTILIZATION = 0.9 ether; /// @notice Initial rate at target per second (scaled by WAD). /// @dev Initial rate at target = 4% (rate between 1% and 16%). @@ -27,5 +27,5 @@ library ConstantsLib { /// @notice Maximum rate at target per second (scaled by WAD). /// @dev Maximum rate at target = 200% (maximum rate = 800%). - int256 public constant MAX_RATE_AT_TARGET = 2.00 ether / int256(365 days); + int256 public constant MAX_RATE_AT_TARGET = 2.0 ether / int256(365 days); } From f84ba95193b15bbb18660d3b8a479db26489df44 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Mon, 18 Dec 2023 16:35:18 +0100 Subject: [PATCH 29/35] test: fix hardhat tests --- test/hardhat/irm/Irm.spec.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/test/hardhat/irm/Irm.spec.ts b/test/hardhat/irm/Irm.spec.ts index 3be2495b..b5004a93 100644 --- a/test/hardhat/irm/Irm.spec.ts +++ b/test/hardhat/irm/Irm.spec.ts @@ -50,13 +50,7 @@ describe("irm", () => { const AdaptiveCurveIrmFactory = await hre.ethers.getContractFactory("AdaptiveCurveIrm", admin); - irm = await AdaptiveCurveIrmFactory.deploy( - await admin.getAddress(), - 4000000000000000000n, - 1585489599188n, - 900000000000000000n, - 317097919n, - ); + irm = await AdaptiveCurveIrmFactory.deploy(await admin.getAddress()); const irmAddress = await irm.getAddress(); From dc56e0f1d64cb3338fab4773ef2fbf58a5121948 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Wed, 20 Dec 2023 09:14:26 +0100 Subject: [PATCH 30/35] docs(ifc): add natspecs --- src/interfaces/IAdaptiveCurveIrm.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/interfaces/IAdaptiveCurveIrm.sol b/src/interfaces/IAdaptiveCurveIrm.sol index 0af36b5c..0f2659a4 100644 --- a/src/interfaces/IAdaptiveCurveIrm.sol +++ b/src/interfaces/IAdaptiveCurveIrm.sol @@ -4,6 +4,10 @@ pragma solidity >=0.5.0; import {IIrm} from "../../lib/morpho-blue/src/interfaces/IIrm.sol"; import {Id} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; +/// @title IAdaptiveCurveIrm +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice Interface exposed by the AdaptiveCurveIrm. interface IAdaptiveCurveIrm is IIrm { /// @notice Address of Morpho. function MORPHO() external view returns (address); From 14942be55eb8bdaa5bd7e02056a68e19841485a1 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Wed, 20 Dec 2023 10:50:16 +0100 Subject: [PATCH 31/35] style: toZero --- src/AdaptiveCurveIrm.sol | 10 ++++----- src/libraries/MathLib.sol | 4 ++-- test/forge/AdaptiveCurveIrmTest.sol | 34 +++++++++++++++-------------- test/forge/ExpLibTest.sol | 2 +- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/AdaptiveCurveIrm.sol b/src/AdaptiveCurveIrm.sol index 7e170174..d7454660 100644 --- a/src/AdaptiveCurveIrm.sol +++ b/src/AdaptiveCurveIrm.sol @@ -82,7 +82,7 @@ contract AdaptiveCurveIrm is IAdaptiveCurveIrm { int256 errNormFactor = utilization > ConstantsLib.TARGET_UTILIZATION ? WAD - ConstantsLib.TARGET_UTILIZATION : ConstantsLib.TARGET_UTILIZATION; - int256 err = (utilization - ConstantsLib.TARGET_UTILIZATION).wDivTo0(errNormFactor); + int256 err = (utilization - ConstantsLib.TARGET_UTILIZATION).wDivToZero(errNormFactor); int256 startRateAtTarget = rateAtTarget[id]; @@ -96,7 +96,7 @@ contract AdaptiveCurveIrm is IAdaptiveCurveIrm { } else { // The speed is assumed constant between two updates, but it is in fact not constant because of interest. // So the rate will be underestimated. - int256 speed = ConstantsLib.ADJUSTMENT_SPEED.wMulTo0(err); + int256 speed = ConstantsLib.ADJUSTMENT_SPEED.wMulToZero(err); // market.lastUpdate != 0 because it is not the first interaction with this market. // Safe "unchecked" cast because block.timestamp - market.lastUpdate <= block.timestamp <= type(int256).max. int256 elapsed = int256(block.timestamp - market.lastUpdate); @@ -136,16 +136,16 @@ contract AdaptiveCurveIrm is IAdaptiveCurveIrm { /// ((C-1)*err + 1) * rateAtTarget else. function _curve(int256 _rateAtTarget, int256 err) private pure returns (int256) { // Non negative because 1 - 1/C >= 0, C - 1 >= 0. - int256 coeff = err < 0 ? WAD - WAD.wDivTo0(ConstantsLib.CURVE_STEEPNESS) : ConstantsLib.CURVE_STEEPNESS - WAD; + int256 coeff = err < 0 ? WAD - WAD.wDivToZero(ConstantsLib.CURVE_STEEPNESS) : ConstantsLib.CURVE_STEEPNESS - WAD; // Non negative if _rateAtTarget >= 0 because if err < 0, coeff <= 1. - return (coeff.wMulTo0(err) + WAD).wMulTo0(int256(_rateAtTarget)); + return (coeff.wMulToZero(err) + WAD).wMulToZero(int256(_rateAtTarget)); } /// @dev Returns the new rate at target, for a given `startRateAtTarget` and a given `linearAdaptation`. /// The formula is: max(min(startRateAtTarget * exp(linearAdaptation), maxRateAtTarget), minRateAtTarget). function _newRateAtTarget(int256 startRateAtTarget, int256 linearAdaptation) private pure returns (int256) { // Non negative because MIN_RATE_AT_TARGET > 0. - return startRateAtTarget.wMulTo0(ExpLib.wExp(linearAdaptation)).bound( + return startRateAtTarget.wMulToZero(ExpLib.wExp(linearAdaptation)).bound( ConstantsLib.MIN_RATE_AT_TARGET, ConstantsLib.MAX_RATE_AT_TARGET ); } diff --git a/src/libraries/MathLib.sol b/src/libraries/MathLib.sol index 413d2f0b..3e3dbd51 100644 --- a/src/libraries/MathLib.sol +++ b/src/libraries/MathLib.sol @@ -11,12 +11,12 @@ int256 constant WAD_INT = int256(WAD); /// @notice Library to manage fixed-point arithmetic on signed integers. library MathLib { /// @dev Returns the multiplication of `a` by `b` (in WAD) rounded towards 0. - function wMulTo0(int256 a, int256 b) internal pure returns (int256) { + function wMulToZero(int256 a, int256 b) internal pure returns (int256) { return a * b / WAD_INT; } /// @dev Returns the division of `a` by `b` (in WAD) rounded towards 0. - function wDivTo0(int256 a, int256 b) internal pure returns (int256) { + function wDivToZero(int256 a, int256 b) internal pure returns (int256) { return a * WAD_INT / b; } } diff --git a/test/forge/AdaptiveCurveIrmTest.sol b/test/forge/AdaptiveCurveIrmTest.sol index 5bf27c99..8d821340 100644 --- a/test/forge/AdaptiveCurveIrmTest.sol +++ b/test/forge/AdaptiveCurveIrmTest.sol @@ -73,7 +73,7 @@ contract AdaptiveCurveIrmTest is Test { assertApproxEqRel( irm.borrowRateView(marketParams, market), uint256( - (ConstantsLib.INITIAL_RATE_AT_TARGET * 4).wMulTo0( + (ConstantsLib.INITIAL_RATE_AT_TARGET * 4).wMulToZero( (1.9836 ether - 1 ether) * WAD / (ConstantsLib.ADJUSTMENT_SPEED * 5 days) ) ), @@ -82,7 +82,7 @@ contract AdaptiveCurveIrmTest is Test { // The average value of exp((50/365)*x) between 0 and 5 is approx. 1.4361. assertApproxEqRel( irm.borrowRateView(marketParams, market), - uint256((ConstantsLib.INITIAL_RATE_AT_TARGET * 4).wMulTo0(1.4361 ether)), + uint256((ConstantsLib.INITIAL_RATE_AT_TARGET * 4).wMulToZero(1.4361 ether)), 0.1 ether ); // Expected rate: 22.976%. @@ -104,7 +104,7 @@ contract AdaptiveCurveIrmTest is Test { assertApproxEqRel( irm.borrowRateView(marketParams, market), uint256( - (ConstantsLib.INITIAL_RATE_AT_TARGET / 4).wMulTo0( + (ConstantsLib.INITIAL_RATE_AT_TARGET / 4).wMulToZero( (0.5041 ether - 1 ether) * WAD / (-ConstantsLib.ADJUSTMENT_SPEED * 5 days) ) ), @@ -113,7 +113,7 @@ contract AdaptiveCurveIrmTest is Test { // The average value of exp((-50/365*x)) between 0 and 5 is approx. 0.7240. assertApproxEqRel( irm.borrowRateView(marketParams, market), - uint256((ConstantsLib.INITIAL_RATE_AT_TARGET / 4).wMulTo0(0.724 ether)), + uint256((ConstantsLib.INITIAL_RATE_AT_TARGET / 4).wMulToZero(0.724 ether)), 0.1 ether ); // Expected rate: 0.7240%. @@ -344,11 +344,11 @@ contract AdaptiveCurveIrmTest is Test { assertGe( irm.borrowRateView(marketParams, market), - uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDivTo0(ConstantsLib.CURVE_STEEPNESS)) + uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDivToZero(ConstantsLib.CURVE_STEEPNESS)) ); assertGe( irm.borrowRate(marketParams, market), - uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDivTo0(ConstantsLib.CURVE_STEEPNESS)) + uint256(ConstantsLib.MIN_RATE_AT_TARGET.wDivToZero(ConstantsLib.CURVE_STEEPNESS)) ); } @@ -359,11 +359,11 @@ contract AdaptiveCurveIrmTest is Test { assertLe( irm.borrowRateView(marketParams, market), - uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMulTo0(ConstantsLib.CURVE_STEEPNESS)) + uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMulToZero(ConstantsLib.CURVE_STEEPNESS)) ); assertLe( irm.borrowRate(marketParams, market), - uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMulTo0(ConstantsLib.CURVE_STEEPNESS)) + uint256(ConstantsLib.MAX_RATE_AT_TARGET.wMulToZero(ConstantsLib.CURVE_STEEPNESS)) ); } @@ -382,12 +382,12 @@ contract AdaptiveCurveIrmTest is Test { function _expectedRateAtTarget(Id id, Market memory market) internal view returns (int256) { int256 rateAtTarget = irm.rateAtTarget(id); - int256 speed = ConstantsLib.ADJUSTMENT_SPEED.wMulTo0(_err(market)); + int256 speed = ConstantsLib.ADJUSTMENT_SPEED.wMulToZero(_err(market)); uint256 elapsed = (rateAtTarget > 0) ? block.timestamp - market.lastUpdate : 0; int256 linearAdaptation = speed * int256(elapsed); int256 adaptationMultiplier = ExpLib.wExp(linearAdaptation); return (rateAtTarget > 0) - ? rateAtTarget.wMulTo0(adaptationMultiplier).bound( + ? rateAtTarget.wMulToZero(adaptationMultiplier).bound( ConstantsLib.MIN_RATE_AT_TARGET, ConstantsLib.MAX_RATE_AT_TARGET ) : ConstantsLib.INITIAL_RATE_AT_TARGET; @@ -396,7 +396,7 @@ contract AdaptiveCurveIrmTest is Test { function _expectedAvgRate(Id id, Market memory market) internal view returns (uint256) { int256 rateAtTarget = irm.rateAtTarget(id); int256 err = _err(market); - int256 speed = ConstantsLib.ADJUSTMENT_SPEED.wMulTo0(err); + int256 speed = ConstantsLib.ADJUSTMENT_SPEED.wMulToZero(err); uint256 elapsed = (rateAtTarget > 0) ? block.timestamp - market.lastUpdate : 0; int256 linearAdaptation = speed * int256(elapsed); int256 endRateAtTarget = int256(_expectedRateAtTarget(id, market)); @@ -408,7 +408,7 @@ contract AdaptiveCurveIrmTest is Test { } else { // Safe "unchecked" cast to uint256 because linearAdaptation < 0 <=> newBorrowRate <= borrowRateAfterJump. avgBorrowRate = - uint256((int256(newBorrowRate) - int256(_curve(rateAtTarget, err))).wDivTo0(linearAdaptation)); + uint256((int256(newBorrowRate) - int256(_curve(rateAtTarget, err))).wDivToZero(linearAdaptation)); } return avgBorrowRate; } @@ -416,9 +416,11 @@ contract AdaptiveCurveIrmTest is Test { function _curve(int256 rateAtTarget, int256 err) internal pure returns (uint256) { // Safe "unchecked" cast because err >= -1 (in WAD). if (err < 0) { - return uint256(((WAD - WAD.wDivTo0(ConstantsLib.CURVE_STEEPNESS)).wMulTo0(err) + WAD).wMulTo0(rateAtTarget)); + return uint256( + ((WAD - WAD.wDivToZero(ConstantsLib.CURVE_STEEPNESS)).wMulToZero(err) + WAD).wMulToZero(rateAtTarget) + ); } else { - return uint256(((ConstantsLib.CURVE_STEEPNESS - WAD).wMulTo0(err) + WAD).wMulTo0(rateAtTarget)); + return uint256(((ConstantsLib.CURVE_STEEPNESS - WAD).wMulToZero(err) + WAD).wMulToZero(rateAtTarget)); } } @@ -428,9 +430,9 @@ contract AdaptiveCurveIrmTest is Test { int256 utilization = int256(market.totalBorrowAssets.wDivDown(market.totalSupplyAssets)); if (utilization > ConstantsLib.TARGET_UTILIZATION) { - err = (utilization - ConstantsLib.TARGET_UTILIZATION).wDivTo0(WAD - ConstantsLib.TARGET_UTILIZATION); + err = (utilization - ConstantsLib.TARGET_UTILIZATION).wDivToZero(WAD - ConstantsLib.TARGET_UTILIZATION); } else { - err = (utilization - ConstantsLib.TARGET_UTILIZATION).wDivTo0(ConstantsLib.TARGET_UTILIZATION); + err = (utilization - ConstantsLib.TARGET_UTILIZATION).wDivToZero(ConstantsLib.TARGET_UTILIZATION); } } } diff --git a/test/forge/ExpLibTest.sol b/test/forge/ExpLibTest.sol index e378b0d7..ee61ce06 100644 --- a/test/forge/ExpLibTest.sol +++ b/test/forge/ExpLibTest.sol @@ -63,7 +63,7 @@ contract ExpLibTest is Test { } function testWExpWMulMaxRate() public pure { - ExpLib.wExp(ExpLib.WEXP_UPPER_BOUND).wMulTo0(ConstantsLib.MAX_RATE_AT_TARGET); + ExpLib.wExp(ExpLib.WEXP_UPPER_BOUND).wMulToZero(ConstantsLib.MAX_RATE_AT_TARGET); } function _wExpUnbounded(int256 x) internal pure returns (int256) { From 890b4cdabf3b9c8e6bfb10842e24a5958f335bc4 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Wed, 20 Dec 2023 11:26:11 +0100 Subject: [PATCH 32/35] style: xy --- src/libraries/MathLib.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libraries/MathLib.sol b/src/libraries/MathLib.sol index 413d2f0b..8cff8d17 100644 --- a/src/libraries/MathLib.sol +++ b/src/libraries/MathLib.sol @@ -10,13 +10,13 @@ int256 constant WAD_INT = int256(WAD); /// @custom:contact security@morpho.org /// @notice Library to manage fixed-point arithmetic on signed integers. library MathLib { - /// @dev Returns the multiplication of `a` by `b` (in WAD) rounded towards 0. - function wMulTo0(int256 a, int256 b) internal pure returns (int256) { - return a * b / WAD_INT; + /// @dev Returns the multiplication of `x` by `y` (in WAD) rounded towards 0. + function wMulTo0(int256 x, int256 y) internal pure returns (int256) { + return (x * y) / WAD_INT; } - /// @dev Returns the division of `a` by `b` (in WAD) rounded towards 0. - function wDivTo0(int256 a, int256 b) internal pure returns (int256) { - return a * WAD_INT / b; + /// @dev Returns the division of `x` by `y` (in WAD) rounded towards 0. + function wDivTo0(int256 x, int256 y) internal pure returns (int256) { + return (x * WAD_INT) / y; } } From 3b8624cfe204e1b7802231188b086ea89270f179 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Wed, 20 Dec 2023 13:28:35 +0100 Subject: [PATCH 33/35] chore: remove useless using --- src/AdaptiveCurveIrm.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/AdaptiveCurveIrm.sol b/src/AdaptiveCurveIrm.sol index 7e170174..166b3ad4 100644 --- a/src/AdaptiveCurveIrm.sol +++ b/src/AdaptiveCurveIrm.sol @@ -20,7 +20,6 @@ contract AdaptiveCurveIrm is IAdaptiveCurveIrm { using MathLib for int256; using UtilsLib for int256; using MorphoMathLib for uint128; - using MorphoMathLib for uint256; using MarketParamsLib for MarketParams; /* EVENTS */ @@ -95,7 +94,7 @@ contract AdaptiveCurveIrm is IAdaptiveCurveIrm { endRateAtTarget = ConstantsLib.INITIAL_RATE_AT_TARGET; } else { // The speed is assumed constant between two updates, but it is in fact not constant because of interest. - // So the rate will be underestimated. + // So the rate is always underestimated. int256 speed = ConstantsLib.ADJUSTMENT_SPEED.wMulTo0(err); // market.lastUpdate != 0 because it is not the first interaction with this market. // Safe "unchecked" cast because block.timestamp - market.lastUpdate <= block.timestamp <= type(int256).max. From 4db45edaf4c774a1bdde1fb05163400c2627ce77 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Thu, 21 Dec 2023 09:23:11 +0100 Subject: [PATCH 34/35] docs(constants): re-add missing natspec --- src/libraries/adaptive-curve/ConstantsLib.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/adaptive-curve/ConstantsLib.sol b/src/libraries/adaptive-curve/ConstantsLib.sol index 38dcb215..a2c09ad8 100644 --- a/src/libraries/adaptive-curve/ConstantsLib.sol +++ b/src/libraries/adaptive-curve/ConstantsLib.sol @@ -10,6 +10,8 @@ library ConstantsLib { int256 public constant CURVE_STEEPNESS = 4 ether; /// @notice Adjustment speed per second (scaled by WAD). + /// @dev The speed is per second, so the rate moves at a speed of ADJUSTMENT_SPEED * err each second (while being + /// continuously compounded). /// @dev Adjustment speed = 50/year. int256 public constant ADJUSTMENT_SPEED = 50 ether / int256(365 days); From 2bfc6c841c658535bfc7ad8c4ab9a51837ac9611 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Sat, 23 Dec 2023 09:37:08 +0100 Subject: [PATCH 35/35] chore: update morpho-blue --- lib/morpho-blue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/morpho-blue b/lib/morpho-blue index f463e40f..55d2d993 160000 --- a/lib/morpho-blue +++ b/lib/morpho-blue @@ -1 +1 @@ -Subproject commit f463e40f776acd0f26d0d380b51cfd02949c8c23 +Subproject commit 55d2d99304fb3fb930c688462ae2ccabb1d533ad