From c3154c9764c434bce9e5f7e52befec7d67022a70 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Mon, 6 Nov 2023 11:26:26 +0100 Subject: [PATCH 1/4] refactor(math): revert to int256 --- src/SpeedJumpIrm.sol | 45 +++++++++++++++++++++------------------ src/libraries/MathLib.sol | 5 ++--- test/SpeedJumpIrmTest.sol | 31 ++++++++++++++------------- 3 files changed, 42 insertions(+), 39 deletions(-) diff --git a/src/SpeedJumpIrm.sol b/src/SpeedJumpIrm.sol index c3911441..44ac5492 100644 --- a/src/SpeedJumpIrm.sol +++ b/src/SpeedJumpIrm.sol @@ -36,14 +36,14 @@ contract AdaptativeCurveIrm is IIrm { address public immutable MORPHO; /// @notice Curve steepness (scaled by WAD). /// @dev Verified to be greater than 1 at construction. - uint256 public immutable CURVE_STEEPNESS; + 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 ethers / 365 days. - uint256 public immutable ADJUSTMENT_SPEED; + int256 public immutable ADJUSTMENT_SPEED; /// @notice Target utilization (scaled by WAD). /// @dev Verified to be strictly between 0 and 1 at construction. - uint256 public immutable TARGET_UTILIZATION; + int256 public immutable TARGET_UTILIZATION; /// @notice Initial rate at target (scaled by WAD). uint256 public immutable INITIAL_RATE_AT_TARGET; @@ -63,24 +63,27 @@ contract AdaptativeCurveIrm is IIrm { /// @param initialRateAtTarget The initial rate at target (scaled by WAD). constructor( address morpho, - uint256 curveSteepness, - uint256 adjustmentSpeed, - uint256 targetUtilization, + int256 curveSteepness, + int256 adjustmentSpeed, + int256 targetUtilization, uint256 initialRateAtTarget ) { require(morpho != address(0), ErrorsLib.ZERO_ADDRESS); - require(curveSteepness <= uint256(type(int256).max), ErrorsLib.INPUT_TOO_LARGE); - require(curveSteepness >= WAD, ErrorsLib.INPUT_TOO_SMALL); - require(adjustmentSpeed <= uint256(type(int256).max), ErrorsLib.INPUT_TOO_LARGE); - require(targetUtilization < WAD, ErrorsLib.INPUT_TOO_LARGE); + require(curveSteepness <= type(int256).max, ErrorsLib.INPUT_TOO_LARGE); + require(curveSteepness >= WAD_INT, ErrorsLib.INPUT_TOO_SMALL); + require(adjustmentSpeed <= type(int256).max, ErrorsLib.INPUT_TOO_LARGE); + require(targetUtilization < WAD_INT, ErrorsLib.INPUT_TOO_LARGE); require(targetUtilization > 0, ErrorsLib.ZERO_INPUT); require(initialRateAtTarget >= MIN_RATE_AT_TARGET, ErrorsLib.INPUT_TOO_SMALL); require(initialRateAtTarget <= MAX_RATE_AT_TARGET, ErrorsLib.INPUT_TOO_LARGE); MORPHO = morpho; - CURVE_STEEPNESS = curveSteepness; - ADJUSTMENT_SPEED = adjustmentSpeed; - TARGET_UTILIZATION = targetUtilization; + // Safe "unchecked" cast. + CURVE_STEEPNESS = int256(curveSteepness); + // Safe "unchecked" cast. + ADJUSTMENT_SPEED = int256(adjustmentSpeed); + // Safe "unchecked" cast. + TARGET_UTILIZATION = int256(targetUtilization); INITIAL_RATE_AT_TARGET = initialRateAtTarget; } @@ -110,12 +113,12 @@ contract AdaptativeCurveIrm is IIrm { /// @dev Returns avgBorrowRate and newRateAtTarget. /// @dev Assumes that the inputs `marketParams` and `id` match. function _borrowRate(Id id, Market memory market) private view returns (uint256, uint256) { - uint256 utilization = - market.totalSupplyAssets > 0 ? market.totalBorrowAssets.wDivDown(market.totalSupplyAssets) : 0; + // Safe "unchecked" cast because market.totalBorrowAssets <= type(uint128).max. + int256 utilization = + int256(market.totalSupplyAssets > 0 ? market.totalBorrowAssets.wDivDown(market.totalSupplyAssets) : 0); - uint256 errNormFactor = utilization > TARGET_UTILIZATION ? WAD - TARGET_UTILIZATION : TARGET_UTILIZATION; - // Safe "unchecked" int256 casts because utilization <= WAD, TARGET_UTILIZATION < WAD and errNormFactor <= WAD. - int256 err = (int256(utilization) - int256(TARGET_UTILIZATION)).wDivDown(int256(errNormFactor)); + int256 errNormFactor = utilization > TARGET_UTILIZATION ? WAD_INT - TARGET_UTILIZATION : TARGET_UTILIZATION; + int256 err = (utilization - TARGET_UTILIZATION).wDivDown(errNormFactor); uint256 startRateAtTarget = rateAtTarget[id]; @@ -123,7 +126,6 @@ contract AdaptativeCurveIrm is IIrm { if (startRateAtTarget == 0) { return (_curve(INITIAL_RATE_AT_TARGET, err), INITIAL_RATE_AT_TARGET); } else { - // Safe "unchecked" cast because ADJUSTMENT_SPEED <= type(int256).max. // 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); @@ -166,9 +168,10 @@ contract AdaptativeCurveIrm is IIrm { function _curve(uint256 _rateAtTarget, int256 err) private view returns (uint256) { // Safe "unchecked" cast because err >= -1 (in WAD). if (err < 0) { - return uint256((WAD - WAD.wDivDown(CURVE_STEEPNESS)).wMulDown(err) + WAD_INT).wMulDown(_rateAtTarget); + return + uint256((WAD_INT - WAD_INT.wDivDown(CURVE_STEEPNESS)).wMulDown(err) + WAD_INT).wMulDown(_rateAtTarget); } else { - return uint256((CURVE_STEEPNESS - WAD).wMulDown(err) + WAD_INT).wMulDown(_rateAtTarget); + return uint256((CURVE_STEEPNESS - WAD_INT).wMulDown(err) + WAD_INT).wMulDown(_rateAtTarget); } } } diff --git a/src/libraries/MathLib.sol b/src/libraries/MathLib.sol index ee792787..887acd0d 100644 --- a/src/libraries/MathLib.sol +++ b/src/libraries/MathLib.sol @@ -44,9 +44,8 @@ library MathLib { } } - function wMulDown(uint256 a, int256 b) internal pure returns (int256) { - require(a <= uint256(type(int256).max)); - return int256(a) * b / WAD_INT; + function wMulDown(int256 a, int256 b) internal pure returns (int256) { + return a * b / WAD_INT; } function wDivDown(int256 a, int256 b) internal pure returns (int256) { diff --git a/test/SpeedJumpIrmTest.sol b/test/SpeedJumpIrmTest.sol index ceda1b8b..0975b496 100644 --- a/test/SpeedJumpIrmTest.sol +++ b/test/SpeedJumpIrmTest.sol @@ -16,9 +16,9 @@ contract AdaptativeCurveIrmTest is Test { event BorrowRateUpdate(Id indexed id, uint256 avgBorrowRate, uint256 rateAtTarget); - uint256 internal constant CURVE_STEEPNESS = 4 ether; - uint256 internal constant ADJUSTMENT_SPEED = 50 ether / uint256(365 days); - uint256 internal constant TARGET_UTILIZATION = 0.9 ether; + int256 internal constant CURVE_STEEPNESS = 4 ether; + int256 internal constant ADJUSTMENT_SPEED = 50 ether / int256(365 days); + int256 internal constant TARGET_UTILIZATION = 0.9 ether; uint256 internal constant INITIAL_RATE_AT_TARGET = 0.01 ether / uint256(365 days); AdaptativeCurveIrm internal irm; @@ -174,14 +174,18 @@ contract AdaptativeCurveIrmTest is Test { Market memory market; market.totalBorrowAssets = 9 ether; market.totalSupplyAssets = 10 ether; - assertGt(irm.borrowRate(marketParams, market), irm.MIN_RATE_AT_TARGET().wDivDown(CURVE_STEEPNESS)); + assertGt( + irm.borrowRate(marketParams, market), uint256(int256(irm.MIN_RATE_AT_TARGET()).wDivDown(CURVE_STEEPNESS)) + ); } function invariantMaxRateAtTarget() public { Market memory market; market.totalBorrowAssets = 9 ether; market.totalSupplyAssets = 10 ether; - assertLt(irm.borrowRate(marketParams, market), irm.MAX_RATE_AT_TARGET().wMulDown(CURVE_STEEPNESS)); + assertLt( + irm.borrowRate(marketParams, market), uint256(int256(irm.MAX_RATE_AT_TARGET()).wMulDown(CURVE_STEEPNESS)) + ); } function _expectedRateAtTarget(Id id, Market memory market) internal view returns (uint256) { @@ -218,24 +222,21 @@ contract AdaptativeCurveIrmTest is Test { function _curve(uint256 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_INT).wMulDown(rateAtTarget); + return uint256((WAD_INT - WAD_INT.wDivDown(CURVE_STEEPNESS)).wMulDown(err) + WAD_INT).wMulDown(rateAtTarget); } else { - return uint256((CURVE_STEEPNESS - WAD).wMulDown(err) + WAD_INT).wMulDown(rateAtTarget); + return uint256((CURVE_STEEPNESS - WAD_INT).wMulDown(err) + WAD_INT).wMulDown(rateAtTarget); } } - function _err(Market memory market) internal pure returns (int256) { + function _err(Market memory market) internal pure returns (int256 err) { if (market.totalSupplyAssets == 0) return -1 ether; - uint256 utilization = market.totalBorrowAssets.wDivDown(market.totalSupplyAssets); - int256 err; + int256 utilization = int256(market.totalBorrowAssets.wDivDown(market.totalSupplyAssets)); + if (utilization > TARGET_UTILIZATION) { - // Safe "unchecked" cast because |err| <= WAD. - err = int256((utilization - TARGET_UTILIZATION).wDivDown(WAD - TARGET_UTILIZATION)); + err = (utilization - TARGET_UTILIZATION).wDivDown(WAD_INT - TARGET_UTILIZATION); } else { - // Safe "unchecked" casts because utilization <= WAD and TARGET_UTILIZATION <= WAD. - err = (int256(utilization) - int256(TARGET_UTILIZATION)).wDivDown(int256(TARGET_UTILIZATION)); + err = (utilization - TARGET_UTILIZATION).wDivDown(TARGET_UTILIZATION); } - return err; } } From fff19bef6b1952ffc08286ac8fc111e44c94f426 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Mon, 6 Nov 2023 16:19:15 +0100 Subject: [PATCH 2/4] refactor(irm): expect uint256 inputs --- src/SpeedJumpIrm.sol | 16 ++++++++-------- test/SpeedJumpIrmTest.sol | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/SpeedJumpIrm.sol b/src/SpeedJumpIrm.sol index 44ac5492..6b46b9bc 100644 --- a/src/SpeedJumpIrm.sol +++ b/src/SpeedJumpIrm.sol @@ -63,16 +63,16 @@ contract AdaptativeCurveIrm is IIrm { /// @param initialRateAtTarget The initial rate at target (scaled by WAD). constructor( address morpho, - int256 curveSteepness, - int256 adjustmentSpeed, - int256 targetUtilization, + uint256 curveSteepness, + uint256 adjustmentSpeed, + uint256 targetUtilization, uint256 initialRateAtTarget ) { require(morpho != address(0), ErrorsLib.ZERO_ADDRESS); - require(curveSteepness <= type(int256).max, ErrorsLib.INPUT_TOO_LARGE); - require(curveSteepness >= WAD_INT, ErrorsLib.INPUT_TOO_SMALL); - require(adjustmentSpeed <= type(int256).max, ErrorsLib.INPUT_TOO_LARGE); - require(targetUtilization < WAD_INT, ErrorsLib.INPUT_TOO_LARGE); + require(curveSteepness <= uint256(type(int256).max), ErrorsLib.INPUT_TOO_LARGE); + require(curveSteepness >= WAD, ErrorsLib.INPUT_TOO_SMALL); + require(adjustmentSpeed <= uint256(type(int256).max), ErrorsLib.INPUT_TOO_LARGE); + require(targetUtilization < WAD, ErrorsLib.INPUT_TOO_LARGE); require(targetUtilization > 0, ErrorsLib.ZERO_INPUT); require(initialRateAtTarget >= MIN_RATE_AT_TARGET, ErrorsLib.INPUT_TOO_SMALL); require(initialRateAtTarget <= MAX_RATE_AT_TARGET, ErrorsLib.INPUT_TOO_LARGE); @@ -113,7 +113,7 @@ contract AdaptativeCurveIrm is IIrm { /// @dev Returns avgBorrowRate and newRateAtTarget. /// @dev Assumes that the inputs `marketParams` and `id` match. function _borrowRate(Id id, Market memory market) private view returns (uint256, uint256) { - // Safe "unchecked" cast because market.totalBorrowAssets <= type(uint128).max. + // Safe "unchecked" cast because the utilization is smaller than 1 (scaled by WAD). int256 utilization = int256(market.totalSupplyAssets > 0 ? market.totalBorrowAssets.wDivDown(market.totalSupplyAssets) : 0); diff --git a/test/SpeedJumpIrmTest.sol b/test/SpeedJumpIrmTest.sol index 0975b496..134d0892 100644 --- a/test/SpeedJumpIrmTest.sol +++ b/test/SpeedJumpIrmTest.sol @@ -26,7 +26,7 @@ contract AdaptativeCurveIrmTest is Test { function setUp() public { irm = - new AdaptativeCurveIrm(address(this), CURVE_STEEPNESS, ADJUSTMENT_SPEED, TARGET_UTILIZATION, INITIAL_RATE_AT_TARGET); + new AdaptativeCurveIrm(address(this), uint256(CURVE_STEEPNESS), uint256(ADJUSTMENT_SPEED), uint256(TARGET_UTILIZATION), INITIAL_RATE_AT_TARGET); vm.warp(90 days); } From 34194587578a8b03a08ed25d8d0fbbc425f66fd4 Mon Sep 17 00:00:00 2001 From: MathisGD <74971347+MathisGD@users.noreply.github.com> Date: Thu, 9 Nov 2023 11:30:05 +0100 Subject: [PATCH 3/4] docs: comment adjustment speed non negative Signed-off-by: MathisGD <74971347+MathisGD@users.noreply.github.com> --- src/SpeedJumpIrm.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SpeedJumpIrm.sol b/src/SpeedJumpIrm.sol index 6b46b9bc..3194e090 100644 --- a/src/SpeedJumpIrm.sol +++ b/src/SpeedJumpIrm.sol @@ -40,6 +40,7 @@ contract AdaptativeCurveIrm is IIrm { /// @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 ethers / 365 days. + /// @dev Verified to be non-negative at construction. int256 public immutable ADJUSTMENT_SPEED; /// @notice Target utilization (scaled by WAD). /// @dev Verified to be strictly between 0 and 1 at construction. From bcc4cf164ba927eab35dac4f1fc1f41483210cf3 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Thu, 9 Nov 2023 11:41:46 +0100 Subject: [PATCH 4/4] chore: fmt --- src/SpeedJumpIrm.sol | 4 ++-- test/SpeedJumpIrmTest.sol | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/SpeedJumpIrm.sol b/src/SpeedJumpIrm.sol index 89ff66ea..750b12ff 100644 --- a/src/SpeedJumpIrm.sol +++ b/src/SpeedJumpIrm.sol @@ -168,8 +168,8 @@ contract AdaptativeCurveIrm is IIrm { /// ((C-1)*err + 1) * rateAtTarget else. function _curve(uint256 _rateAtTarget, int256 err) private view returns (uint256) { // Safe "unchecked" cast of _rateAtTarget because _rateAtTarget <= MAX_RATE_AT_TARGET. - int256 steeringCoeff = - (err < 0 ? WAD_INT - WAD_INT.wDivDown(CURVE_STEEPNESS) : CURVE_STEEPNESS - WAD_INT).wMulDown(int256(_rateAtTarget)); + int256 steeringCoeff = (err < 0 ? WAD_INT - WAD_INT.wDivDown(CURVE_STEEPNESS) : CURVE_STEEPNESS - WAD_INT) + .wMulDown(int256(_rateAtTarget)); // Safe "unchecked" cast of _rateAtTarget because _rateAtTarget <= MAX_RATE_AT_TARGET. // Safe "unchecked" cast of the result because r >= 0. return uint256(steeringCoeff.wMulDown(err) + int256(_rateAtTarget)); diff --git a/test/SpeedJumpIrmTest.sol b/test/SpeedJumpIrmTest.sol index f163ba7a..2b948ac6 100644 --- a/test/SpeedJumpIrmTest.sol +++ b/test/SpeedJumpIrmTest.sol @@ -225,10 +225,12 @@ contract AdaptativeCurveIrmTest is Test { // Safe "unchecked" cast because err >= -1 (in WAD). if (err < 0) { return uint256( - (WAD_INT - WAD_INT.wDivDown(CURVE_STEEPNESS)).wMulDown(int256(rateAtTarget)).wMulDown(err) + int256(rateAtTarget) + (WAD_INT - WAD_INT.wDivDown(CURVE_STEEPNESS)).wMulDown(int256(rateAtTarget)).wMulDown(err) + + int256(rateAtTarget) ); } else { - return uint256((CURVE_STEEPNESS - WAD_INT).wMulDown(int256(rateAtTarget)).wMulDown(err) + int256(rateAtTarget)); + return + uint256((CURVE_STEEPNESS - WAD_INT).wMulDown(int256(rateAtTarget)).wMulDown(err) + int256(rateAtTarget)); } }