S Price: $0.52222 (+7.28%)

Contract Diff Checker

Contract Name:
IRMAdaptiveCurve

Contract Source Code:

// SPDX-License-Identifier: MIT
// Copyright (c) 2023 Morpho Association

pragma solidity ^0.8.0;

import {IIRM} from "evk/InterestRateModels/IIRM.sol";
import {ExpLib} from "./lib/ExpLib.sol";

/// @title IRMAdaptiveCurve
/// @custom:contact [email protected]
/// @author Euler Labs (https://www.eulerlabs.com/).
/// @author Adapted from Morpho Labs (https://github.com/morpho-org/morpho-blue-irm/).
/// @notice A Linear Kink IRM that adjusts the rate at target utilization based on time spent above/below it.
/// @dev This implementation intentionally leaves variables names, units and ExpLib unchanged from original.
/// Returned rates are extended to RAY per second to be compatible with the EVK.
contract IRMAdaptiveCurve is IIRM {
    /// @dev Unit for internal precision.
    int256 internal constant WAD = 1e18;
    /// @dev Unit for internal precision.
    int256 internal constant YEAR = int256(365.2425 days);
    /// @notice The name of the IRM.
    string public constant name = "IRMAdaptiveCurve";
    /// @notice The utilization rate targeted by the model.
    /// @dev In WAD units.
    int256 public immutable TARGET_UTILIZATION;
    /// @notice The initial interest rate at target utilization.
    /// @dev In WAD per second units.
    /// When the IRM is initialized for a vault this is the rate at target utilization that is assigned.
    int256 public immutable INITIAL_RATE_AT_TARGET;
    /// @notice The minimum interest rate at target utilization that the model can adjust to.
    /// @dev In WAD per second units.
    int256 public immutable MIN_RATE_AT_TARGET;
    /// @notice The maximum interest rate at target utilization that the model can adjust to.
    /// @dev In WAD per second units.
    int256 public immutable MAX_RATE_AT_TARGET;
    /// @notice The steepness of the interest rate line.
    /// @dev In WAD units.
    int256 public immutable CURVE_STEEPNESS;
    /// @notice The speed at which the rate at target is adjusted up or down.
    /// @dev In WAD per second units.
    /// For example, with `2e18 / 24 hours` the model will 2x `rateAtTarget` if the vault is fully utilized for a day.
    int256 public immutable ADJUSTMENT_SPEED;

    /// @notice Internal cached state of the interest rate model.
    struct IRState {
        /// @dev The current rate at target utilization.
        uint144 rateAtTarget;
        /// @dev The previous utilization rate of the vault.
        int64 lastUtilization;
        /// @dev The timestamp of the last update to the model.
        uint48 lastUpdate;
    }

    /// @notice Get the internal cached state of a vault's irm.
    mapping(address => IRState) internal irState;

    error InvalidParams();

    /// @notice Deploy IRMAdaptiveCurve.
    /// @param _TARGET_UTILIZATION The utilization rate targeted by the interest rate model.
    /// @param _INITIAL_RATE_AT_TARGET The initial interest rate at target utilization.
    /// @param _MIN_RATE_AT_TARGET The minimum interest rate at target utilization that the model can adjust to.
    /// @param _MAX_RATE_AT_TARGET The maximum interest rate at target utilization that the model can adjust to.
    /// @param _CURVE_STEEPNESS The steepness of the interest rate line.
    /// @param _ADJUSTMENT_SPEED The speed at which the rate at target utilization is adjusted up or down.
    constructor(
        int256 _TARGET_UTILIZATION,
        int256 _INITIAL_RATE_AT_TARGET,
        int256 _MIN_RATE_AT_TARGET,
        int256 _MAX_RATE_AT_TARGET,
        int256 _CURVE_STEEPNESS,
        int256 _ADJUSTMENT_SPEED
    ) {
        // Validate parameters.
        if (_TARGET_UTILIZATION <= 0 || _TARGET_UTILIZATION > 1e18) {
            revert InvalidParams();
        }
        if (_INITIAL_RATE_AT_TARGET < _MIN_RATE_AT_TARGET || _INITIAL_RATE_AT_TARGET > _MAX_RATE_AT_TARGET) {
            revert InvalidParams();
        }
        if (_MIN_RATE_AT_TARGET < 0.001e18 / YEAR || _MIN_RATE_AT_TARGET > 10e18 / YEAR) {
            revert InvalidParams();
        }
        if (_MAX_RATE_AT_TARGET < 0.001e18 / YEAR || _MAX_RATE_AT_TARGET > 10e18 / YEAR) {
            revert InvalidParams();
        }
        if (_CURVE_STEEPNESS < 1.01e18 || _CURVE_STEEPNESS > 100e18) {
            revert InvalidParams();
        }
        if (_ADJUSTMENT_SPEED < 2e18 / YEAR || _ADJUSTMENT_SPEED > 1000e18 / YEAR) {
            revert InvalidParams();
        }

        TARGET_UTILIZATION = _TARGET_UTILIZATION;
        INITIAL_RATE_AT_TARGET = _INITIAL_RATE_AT_TARGET;
        MIN_RATE_AT_TARGET = _MIN_RATE_AT_TARGET;
        MAX_RATE_AT_TARGET = _MAX_RATE_AT_TARGET;
        CURVE_STEEPNESS = _CURVE_STEEPNESS;
        ADJUSTMENT_SPEED = _ADJUSTMENT_SPEED;
    }

    /// @inheritdoc IIRM
    function computeInterestRate(address vault, uint256 cash, uint256 borrows) external returns (uint256) {
        if (msg.sender != vault) revert E_IRMUpdateUnauthorized();

        int256 utilization = _calcUtilization(cash, borrows);

        // If this is the first call then use the current utilization instead of the lastUtilization from storage.
        int256 lastUtilization = irState[vault].lastUpdate == 0 ? utilization : irState[vault].lastUtilization;
        (uint256 rate, uint256 rateAtTarget) = computeInterestRateInternal(vault, lastUtilization);

        irState[vault] = IRState(uint144(rateAtTarget), int64(utilization), uint48(block.timestamp));
        return rate * 1e9; // Extend rate to RAY/sec for EVK.
    }

    /// @inheritdoc IIRM
    function computeInterestRateView(address vault, uint256 cash, uint256 borrows) external view returns (uint256) {
        int256 utilization = _calcUtilization(cash, borrows);
        (uint256 rate,) = computeInterestRateInternal(vault, utilization);
        return rate * 1e9; // Extend rate to RAY/sec for EVK.
    }

    /// @notice Perform computation of the new rate at target without mutating state.
    /// @param vault Address of the vault to compute the new interest rate for.
    /// @param cash Amount of assets held directly by the vault.
    /// @param borrows Amount of assets lent out to borrowers by the vault.
    /// @return The new rate at target utilization in RAY units.
    function computeRateAtTargetView(address vault, uint256 cash, uint256 borrows) external view returns (uint256) {
        int256 utilization = _calcUtilization(cash, borrows);
        (, uint256 rateAtTarget) = computeInterestRateInternal(vault, utilization);
        return rateAtTarget * 1e9; // Extend rate to RAY/sec for EVK.
    }

    /// @notice Get the timestamp of the last update for a vault.
    /// @param vault Address of the vault to get the last update timestamp for.
    /// @return The last update timestamp.
    function getLastUpdateTimestamp(address vault) external view returns (uint256) {
        return irState[vault].lastUpdate;
    }

    /// @notice Compute the new interest rate and rate at target utilization of a vault.
    /// @param vault Address of the vault to compute the new interest rate for.
    /// @return The new interest rate at current utilization.
    /// @return The new interest rate at target utilization.
    function computeInterestRateInternal(address vault, int256 utilization) internal view returns (uint256, uint256) {
        // Calculate the normalized distance between current utilization and target utilization.
        // `err` is normalized to [-1, +1] where -1 is 0% util, 0 is at target and +1 is 100% util.
        int256 errNormFactor = utilization > TARGET_UTILIZATION ? WAD - TARGET_UTILIZATION : TARGET_UTILIZATION;
        int256 err = (utilization - TARGET_UTILIZATION) * WAD / errNormFactor;

        IRState memory state = irState[vault];
        int256 startRateAtTarget = int256(uint256(state.rateAtTarget));
        int256 endRateAtTarget;

        if (startRateAtTarget == 0) {
            // First interaction.
            endRateAtTarget = 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 always underestimated.
            int256 speed = ADJUSTMENT_SPEED * err / WAD;

            // Calculate the adaptation parameter.
            int256 elapsed = int256(block.timestamp - state.lastUpdate);
            int256 linearAdaptation = speed * elapsed;

            if (linearAdaptation == 0) {
                endRateAtTarget = startRateAtTarget;
            } else {
                endRateAtTarget = _newRateAtTarget(startRateAtTarget, linearAdaptation);
            }
        }
        return (uint256(_curve(endRateAtTarget, err)), uint256(endRateAtTarget));
    }

    /// @notice Calculate the interest rate according to the linear kink model.
    /// @param rateAtTarget The current interest rate at target utilization.
    /// @param err The distance between the current utilization and the target utilization, normalized to `[-1, +1]`.
    /// @dev rate = ((1-1/C)*err + 1) * rateAtTarget if err < 0
    ///             (C-1)*err + 1) * rateAtTarget else.
    /// @return The new interest rate at current utilization.
    function _curve(int256 rateAtTarget, int256 err) internal view returns (int256) {
        // Non negative because 1 - 1/C >= 0, C - 1 >= 0.
        int256 coeff = err < 0 ? WAD - WAD * WAD / CURVE_STEEPNESS : CURVE_STEEPNESS - WAD;
        // Non negative if rateAtTarget >= 0 because if err < 0, coeff <= 1.
        return ((coeff * err / WAD) + WAD) * rateAtTarget / WAD;
    }

    /// @notice Calculate the new interest rate at target utilization by applying an adaptation.
    /// @param startRateAtTarget The current interest rate at target utilization.
    /// @param linearAdaptation The adaptation parameter, used as a power of `e`.
    /// @dev Applies exponential growth/decay to the current interest rate at target utilization.
    /// Formula: `rateAtTarget = startRateAtTarget * e^linearAdaptation` bounded to min and max.
    /// @return The new interest rate at target utilization.
    function _newRateAtTarget(int256 startRateAtTarget, int256 linearAdaptation) internal view returns (int256) {
        int256 rateAtTarget = startRateAtTarget * ExpLib.wExp(linearAdaptation) / WAD;
        if (rateAtTarget < MIN_RATE_AT_TARGET) return MIN_RATE_AT_TARGET;
        if (rateAtTarget > MAX_RATE_AT_TARGET) return MAX_RATE_AT_TARGET;
        return rateAtTarget;
    }

    /// @notice Calculate the utilization rate, given cash and borrows from the vault.
    /// @param cash Amount of assets held directly by the vault.
    /// @param borrows Amount of assets lent out to borrowers by the vault.
    /// @return The utilization rate in WAD.
    function _calcUtilization(uint256 cash, uint256 borrows) internal pure returns (int256) {
        int256 totalAssets = int256(cash + borrows);
        if (totalAssets == 0) return 0;
        return int256(borrows) * WAD / totalAssets;
    }
}

// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity >=0.8.0;

/// @title IIRM
/// @custom:security-contact [email protected]
/// @author Euler Labs (https://www.eulerlabs.com/)
/// @notice Interface of the interest rate model contracts used by EVault
interface IIRM {
    error E_IRMUpdateUnauthorized();

    /// @notice Perform potentially state mutating computation of the new interest rate
    /// @param vault Address of the vault to compute the new interest rate for
    /// @param cash Amount of assets held directly by the vault
    /// @param borrows Amount of assets lent out to borrowers by the vault
    /// @return Then new interest rate in second percent yield (SPY), scaled by 1e27
    function computeInterestRate(address vault, uint256 cash, uint256 borrows) external returns (uint256);

    /// @notice Perform computation of the new interest rate without mutating state
    /// @param vault Address of the vault to compute the new interest rate for
    /// @param cash Amount of assets held directly by the vault
    /// @param borrows Amount of assets lent out to borrowers by the vault
    /// @return Then new interest rate in second percent yield (SPY), scaled by 1e27
    function computeInterestRateView(address vault, uint256 cash, uint256 borrows) external view returns (uint256);
}

// SPDX-License-Identifier: MIT
// Copyright (c) 2023 Morpho Association
pragma solidity ^0.8.0;
/// @title ExpLib
/// @custom:contact [email protected]
/// @author Adapted from Morpho Labs
/// (https://github.com/morpho-org/morpho-blue-irm/blob/a824ce06a53f45f12d0ffedb51abd756896b29fa/src/adaptive-curve-irm/libraries/ExpLib.sol)
/// @notice Library to approximate the exponential function.

library ExpLib {
    int256 internal constant WAD = 1e18;
    /// @dev ln(2).
    int256 internal constant LN_2_INT = 0.693147180559945309e18;
    /// @dev ln(1e-18).
    int256 internal constant LN_WEI_INT = -41.446531673892822312e18;
    /// @dev Above this bound, `wExp` is clipped to avoid overflowing when multiplied with 1e18.
    /// @dev This upper bound corresponds to: ln(type(int256).max / 1e36) (scaled by WAD, floored).
    int256 internal constant WEXP_UPPER_BOUND = 93.859467695000404319e18;
    /// @dev The value of wExp(`WEXP_UPPER_BOUND`).
    int256 internal constant WEXP_UPPER_VALUE = 57716089161558943949701069502944508345128.422502756744429568e18;
    /// @dev Returns an approximation of exp.

    function wExp(int256 x) internal pure returns (int256) {
        unchecked {
            // If x < ln(1e-18) then exp(x) < 1e-18 so it is rounded to zero.
            if (x < LN_WEI_INT) return 0;
            // `wExp` is clipped to avoid overflowing when multiplied with 1e18.
            if (x >= WEXP_UPPER_BOUND) return WEXP_UPPER_VALUE;
            // Decompose x as x = q * ln(2) + r with q an integer and -ln(2)/2 <= r <= ln(2)/2.
            // q = x / ln(2) rounded half toward zero.
            int256 roundingAdjustment = (x < 0) ? -(LN_2_INT / 2) : (LN_2_INT / 2);
            // Safe unchecked because x is bounded.
            int256 q = (x + roundingAdjustment) / LN_2_INT;
            // Safe unchecked because |q * ln(2) - x| <= ln(2)/2.
            int256 r = x - q * LN_2_INT;
            // Compute e^r with a 2nd-order Taylor polynomial.
            // Safe unchecked because |r| < 1e18.
            int256 expR = WAD + r + (r * r) / WAD / 2;
            // Return e^x = 2^q * e^r.
            if (q >= 0) return expR << uint256(q);
            else return expR >> uint256(-q);
        }
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):