Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Post cantina #106

Merged
merged 61 commits into from
Dec 25, 2023
Merged
Show file tree
Hide file tree
Changes from 57 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
12ce108
feat(ifc): add adaptive curve irm interface
Rubilmax Nov 27, 2023
01b77ff
refactor(ifc): move natspecs to interfaces
Rubilmax Nov 28, 2023
a04b426
Merge pull request #101 from morpho-org/feat/natspecs-interfaces
MathisGD Dec 1, 2023
c6a5579
chore: make params constants
MathisGD Dec 3, 2023
654819b
feat: new inital rate at target
MathisGD Dec 3, 2023
7ba35f6
feat: constants lib
MathisGD Dec 4, 2023
e1b8f5e
test: fix test with new initial rate
MathisGD Dec 4, 2023
20d4835
chore: fmt
MathisGD Dec 4, 2023
14cc003
fix(constants): lower max rate at target
Rubilmax Dec 7, 2023
ed7f842
Merge pull request #100 from morpho-org/feat/interfaces
MerlinEgalite Dec 7, 2023
e17490c
docs(constants): updated values
Rubilmax Dec 7, 2023
ae47e0b
fix(constants): increase clarity
Rubilmax Dec 8, 2023
643304a
refactor: rename math operations
QGarchery Dec 8, 2023
b7e534a
Merge remote-tracking branch 'origin/post-cantina' into chore/constants
MathisGD Dec 10, 2023
143bc6d
fix(constants): change def
Rubilmax Dec 11, 2023
07deb50
test: constants ranges
MathisGD Dec 12, 2023
b449f69
style: naming
MathisGD Dec 12, 2023
1516a1c
chore: fmt
MathisGD Dec 12, 2023
d596b3b
Merge pull request #103 from morpho-org/chore/constants
MathisGD Dec 12, 2023
1da844c
Merge remote-tracking branch 'origin/post-cantina' into refactor/rena…
MathisGD Dec 12, 2023
4116ef8
chore: fix compilation
MathisGD Dec 12, 2023
e308fa3
chore: fmt
MathisGD Dec 12, 2023
174936a
docs(irm): speed is not constant
Rubilmax Dec 11, 2023
73d62b8
Merge branch 'post-cantina' of github.com:morpho-labs/morpho-blue-per…
Rubilmax Dec 12, 2023
f042fd2
docs(irm): future
Rubilmax Dec 12, 2023
b728864
test: fix test
MathisGD Dec 12, 2023
37dce62
Merge pull request #107 from morpho-org/refactor/rename-math-operations
MathisGD Dec 12, 2023
503776c
Merge branch 'post-cantina' of github.com:morpho-labs/morpho-blue-per…
Rubilmax Dec 12, 2023
7f1ec18
Merge pull request #108 from morpho-org/docs/speed-constant
Rubilmax Dec 12, 2023
c0f8fa8
Merge branch 'post-cantina' of github.com:morpho-labs/morpho-blue-per…
Rubilmax Dec 12, 2023
8fad629
docs(constants): add APR
Rubilmax Dec 12, 2023
19b3afb
fix(foundry.toml): decrease optimization runs
Rubilmax Dec 13, 2023
160c2b3
Merge pull request #109 from morpho-org/fix/optimization-runs
Rubilmax Dec 14, 2023
d40f1b4
Merge branch 'post-cantina' of github.com:morpho-labs/morpho-blue-per…
Rubilmax Dec 18, 2023
87eb634
docs(constants): add min/max APR
Rubilmax Dec 18, 2023
5baeaef
refactor: constants
MathisGD Dec 18, 2023
ce4d409
docs: minor improvements
MathisGD Dec 18, 2023
db8d2a6
chore: minor improvement
MathisGD Dec 18, 2023
2e5fe8e
docs: fix translation
MathisGD Dec 18, 2023
497e446
chore: fmt
MathisGD Dec 18, 2023
ee3600a
Merge pull request #110 from morpho-org/fix/constants-1
MathisGD Dec 18, 2023
b408425
Merge pull request #105 from morpho-org/fix/constants
MathisGD Dec 18, 2023
d909034
Merge branch 'main' into post-cantina-merge
MathisGD Dec 18, 2023
f84ba95
test: fix hardhat tests
MathisGD Dec 18, 2023
d21d721
Merge branch 'main' of github.com:morpho-labs/morpho-blue-periphery i…
Rubilmax Dec 18, 2023
aaa2c16
Merge pull request #111 from morpho-org/post-cantina-merge
MerlinEgalite Dec 19, 2023
ecfe5b7
Merge branch 'post-cantina' of github.com:morpho-labs/morpho-blue-per…
Rubilmax Dec 20, 2023
dc56e0f
docs(ifc): add natspecs
Rubilmax Dec 20, 2023
14942be
style: toZero
MathisGD Dec 20, 2023
dcdc270
Merge pull request #113 from morpho-org/docs/interface
Rubilmax Dec 20, 2023
890b4cd
style: xy
MathisGD Dec 20, 2023
3b8624c
chore: remove useless using
MathisGD Dec 20, 2023
f47e981
Merge pull request #114 from morpho-org/style/to-zero
MerlinEgalite Dec 20, 2023
0046ff7
Merge branch 'post-cantina' into chore/remove-useless-using
MathisGD Dec 20, 2023
ecba68b
Merge branch 'post-cantina' into style/x-y
MathisGD Dec 20, 2023
a3257ec
Merge pull request #116 from morpho-org/chore/remove-useless-using
MathisGD Dec 20, 2023
d57f290
Merge pull request #115 from morpho-org/style/x-y
MathisGD Dec 21, 2023
4db45ed
docs(constants): re-add missing natspec
Rubilmax Dec 21, 2023
ba2caaa
Merge pull request #117 from morpho-org/docs/constants
MerlinEgalite Dec 21, 2023
2bfc6c8
chore: update morpho-blue
MerlinEgalite Dec 23, 2023
101c261
Merge pull request #118 from morpho-org/chore/update-mb
MathisGD Dec 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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 security@morpho.org
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 security@morpho.org
/// @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 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";
}
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 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) {
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;
}
}
28 changes: 20 additions & 8 deletions src/libraries/adaptive-curve/ConstantsLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,27 @@ pragma solidity ^0.8.0;
/// @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 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 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