Skip to content

Commit

Permalink
Merge pull request #106 from morpho-org/post-cantina
Browse files Browse the repository at this point in the history
Post cantina
  • Loading branch information
MerlinEgalite authored Dec 25, 2023
2 parents 73eddf9 + 101c261 commit a7d9cce
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 167 deletions.
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/morpho-blue
Submodule morpho-blue updated 63 files
+51 −0 .github/workflows/certora.yml
+1 −1 .github/workflows/foundry.yml
+4 −0 .gitignore
+1 −1 README.md
+389 −0 certora/LICENSE
+287 −0 certora/README.md
+14 −0 certora/confs/AccrueInterest.conf
+9 −0 certora/confs/AssetsAccounting.conf
+9 −0 certora/confs/ConsistentState.conf
+13 −0 certora/confs/ExactMath.conf
+13 −0 certora/confs/Health.conf
+9 −0 certora/confs/LibSummary.conf
+9 −0 certora/confs/Liveness.conf
+14 −0 certora/confs/RatioMath.conf
+12 −0 certora/confs/Reentrancy.conf
+9 −0 certora/confs/Reverts.conf
+12 −0 certora/confs/Transfer.conf
+48 −0 certora/dispatch/ERC20NoRevert.sol
+42 −0 certora/dispatch/ERC20Standard.sol
+45 −0 certora/dispatch/ERC20USDT.sol
+6 −0 certora/gambit.conf
+92 −0 certora/harness/MorphoHarness.sol
+17 −0 certora/harness/MorphoInternalAccess.sol
+35 −0 certora/harness/TransferHarness.sol
+138 −0 certora/specs/AccrueInterest.spec
+126 −0 certora/specs/AssetsAccounting.spec
+294 −0 certora/specs/ConsistentState.spec
+123 −0 certora/specs/ExactMath.spec
+108 −0 certora/specs/Health.spec
+26 −0 certora/specs/LibSummary.spec
+386 −0 certora/specs/Liveness.spec
+184 −0 certora/specs/RatioMath.spec
+61 −0 certora/specs/Reentrancy.spec
+179 −0 certora/specs/Reverts.spec
+82 −0 certora/specs/Transfer.spec
+1 −1 foundry.toml
+1 −1 lib/forge-std
+48 −34 src/Morpho.sol
+3 −2 src/interfaces/IIrm.sol
+17 −14 src/interfaces/IMorpho.sol
+3 −0 src/libraries/ErrorsLib.sol
+10 −7 src/libraries/EventsLib.sol
+4 −3 src/libraries/SafeTransferLib.sol
+3 −0 src/libraries/SharesMathLib.sol
+1 −1 src/libraries/UtilsLib.sol
+6 −7 src/libraries/periphery/MorphoBalancesLib.sol
+9 −7 test/forge/BaseTest.sol
+1 −1 test/forge/helpers/SigUtils.sol
+12 −0 test/forge/integration/AccrueInterestIntegrationTest.sol
+17 −1 test/forge/integration/AuthorizationIntegrationTest.sol
+3 −3 test/forge/integration/BorrowIntegrationTest.sol
+5 −0 test/forge/integration/CallbacksIntegrationTest.sol
+22 −20 test/forge/integration/CreateMarketIntegrationTest.sol
+66 −4 test/forge/integration/LiquidateIntegrationTest.sol
+3 −2 test/forge/integration/OnlyOwnerIntegrationTest.sol
+12 −0 test/forge/integration/SupplyCollateralIntegrationTest.sol
+12 −0 test/forge/integration/SupplyIntegrationTest.sol
+1 −1 test/forge/integration/WithdrawCollateralIntegrationTest.sol
+5 −3 test/forge/invariant/MorphoInvariantTest.sol
+14 −0 test/forge/libraries/SafeTransferLibTest.sol
+31 −1 test/hardhat/Morpho.spec.ts
+0 −288 test/morpho_tests.tree
+1 −1 tsconfig.json
77 changes: 19 additions & 58 deletions src/AdaptiveCurveIrm.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
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";
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";
Expand All @@ -15,11 +16,10 @@ import {MathLib as MorphoMathLib} from "../lib/morpho-blue/src/libraries/MathLib
/// @title AdaptiveCurveIrm
/// @author Morpho Labs
/// @custom:contact [email protected]
contract AdaptiveCurveIrm is IIrm {
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 */
Expand All @@ -29,63 +29,22 @@ contract AdaptiveCurveIrm is IIrm {

/* 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.
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.
int256 public immutable ADJUSTMENT_SPEED;

/// @notice Target utilization (scaled by WAD).
/// @dev Verified to be strictly between 0 and 1 at construction.
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.
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 */

/// @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 */
Expand Down Expand Up @@ -119,8 +78,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).wDivToZero(errNormFactor);

int256 startRateAtTarget = rateAtTarget[id];

Expand All @@ -129,12 +90,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);
// The speed is assumed constant between two updates, but it is in fact not constant because of interest.
// So the rate is always underestimated.
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);
Expand Down Expand Up @@ -172,18 +133,18 @@ 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;
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.wMulDown(err) + WAD).wMulDown(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.wMulDown(ExpLib.wExp(linearAdaptation)).bound(
return startRateAtTarget.wMulToZero(ExpLib.wExp(linearAdaptation)).bound(
ConstantsLib.MIN_RATE_AT_TARGET, ConstantsLib.MAX_RATE_AT_TARGET
);
}
Expand Down
18 changes: 18 additions & 0 deletions src/interfaces/IAdaptiveCurveIrm.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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";

/// @title IAdaptiveCurveIrm
/// @author Morpho Labs
/// @custom:contact [email protected]
/// @notice Interface exposed by the AdaptiveCurveIrm.
interface IAdaptiveCurveIrm is IIrm {
/// @notice Address of Morpho.
function MORPHO() external view returns (address);

/// @notice Rate at target utilization.
/// @dev Tells the height of the curve.
function rateAtTarget(Id id) external view returns (int256);
}
9 changes: 0 additions & 9 deletions src/libraries/ErrorsLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,9 @@ pragma solidity ^0.8.0;
/// @custom:contact [email protected]
/// @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";
}
10 changes: 6 additions & 4 deletions src/libraries/MathLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ int256 constant WAD_INT = int256(WAD);
/// @custom:contact [email protected]
/// @notice Library to manage fixed-point arithmetic on signed integers.
library MathLib {
function wMulDown(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 wMulToZero(int256 x, int256 y) internal pure returns (int256) {
return (x * y) / WAD_INT;
}

function wDivDown(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 wDivToZero(int256 x, int256 y) internal pure returns (int256) {
return (x * WAD_INT) / y;
}
}
30 changes: 22 additions & 8 deletions src/libraries/adaptive-curve/ConstantsLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,29 @@ pragma solidity ^0.8.0;
/// @author Morpho Labs
/// @custom:contact [email protected]
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 Curve steepness (scaled by WAD).
/// @dev Curve steepness = 4.
int256 public constant CURVE_STEEPNESS = 4 ether;

/// @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 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);

/// @notice Maximum curve steepness allowed (scaled by WAD).
int256 internal constant MAX_CURVE_STEEPNESS = 100 ether;
/// @notice Target utilization (scaled by WAD).
/// @dev Target utilization = 90%.
int256 public constant TARGET_UTILIZATION = 0.9 ether;

/// @notice Maximum adjustment speed allowed (scaled by WAD).
int256 internal constant MAX_ADJUSTMENT_SPEED = int256(1_000 ether) / 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).
/// @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).
/// @dev Maximum rate at target = 200% (maximum rate = 800%).
int256 public constant MAX_RATE_AT_TARGET = 2.0 ether / int256(365 days);
}
Loading

0 comments on commit a7d9cce

Please sign in to comment.