> For the complete documentation index, see [llms.txt](https://docs.curvance.com/app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.curvance.com/app/developer-docs/lending-protocol/dynamic-interest-rate-model.md).

# Dynamic Interest Rate Model

### Overview

The Dynamic Interest Rate Model is a sophisticated interest rate mechanism designed to efficiently balance supply and demand within Curvance lending markets. It builds upon traditional "jump rate" models while introducing dynamic elements that automatically respond to changing market conditions. &#x20;

### Core Components

#### Interest Rate Structure

The model uses two interest-rate slopes and one utilization threshold:

* **Base Rate:** Applied linearly while utilization is at or below `vertexStart`.
* **Vertex Rate:** Applied only to utilization above `vertexStart`. This slope is multiplied by `vertexMultiplier`.
* **Vertex Start:** The utilization threshold where the model switches from the base slope to the vertex slope.

#### Dynamic Vertex Multiplier

The heart of the model is the Vertex Multiplier - a dynamic coefficient that adjusts based on market conditions:

* **Default Value:** Starts at 1.0 (WAD).
* **Maximum Value:** Capped at vertexMultiplierMax.

<figure><img src="/files/oaF6KJS15RnJNzTqAUOo" alt=""><figcaption></figcaption></figure>

### Data Flow

1. Market Utilization → Calculated as `(debt * WAD) / (assetsHeld + debt)`, or `0` when `debt == 0`.
2. Utilization → Drives interest rate calculations through base and vertex formulas.
3. Interest Rates → Applied to borrowers and distributed to lenders (minus protocol fees).
4. Vertex Multiplier → Dynamically adjusted when the linked BorrowableCToken calls `adjustedBorrowRate(...)`. Governance configuration updates can also reset it to `WAD` or clamp it to a lower `vertexMultiplierMax`. In normal cToken flow, `BorrowableCToken._accrueIfNeeded()` calls the IRM when `block.timestamp >= vestingEnd`, then uses the returned `ADJUSTMENT_RATE` to schedule the next vesting window.
5. Decay Mechanism → Applied lazily during multiplier updates. It reduces elevated multiplier values per adjustment and floors the result at `WAD`.

### State Machine

The Vertex Multiplier operates as a state machine with the following transitions:

#### States and Transitions

* Initialization State
  * **Starting point:** `vertexMultiplier = WAD` when the IRM is deployed or when governance updates the IRM with `vertexReset = true`.
  * **The IRM does not store a next-update timestamp.** The linked BorrowableCToken stores vesting timing and calls `adjustedBorrowRate(...)` when an accrual crosses the current vesting window.
* Adjustment States
  * Based on utilization relative to thresholds:
    * **At or below `decreaseThresholdEnd`:** maximum negative adjustment plus decay.
    * **Above `decreaseThresholdEnd` and at or below `vertexStart`:** scaled negative adjustment plus decay. At exactly `vertexStart`, the scaled negative adjustment is zero, so only decay applies.
    * **Above `vertexStart` and at or below `increaseThresholdStart`:** decay only.
    * **Above `increaseThresholdStart`:** positive adjustment plus decay.
* Transition Conditions
  * Updates occur when the linked token calls the model; the model returns ADJUSTMENT\_RATE as the cadence hint.
  * Rate update only possible when properly linked to a BorrowableCToken.

#### Decay Mechanism

The decay feature introduces a downward sloping characteristic:

* When the multiplier is elevated, a constant negative velocity is applied.
* This occurs regardless of positive/negative acceleration from utilization.
  * If the multiplier is elevated due to high utilization, it naturally decays over time to prevent interest rates from remaining too high indefinitely.
  * Decay subtracts a fraction of the current multiplier each adjustment, pulling it downward toward 1.0 (floored at WAD).
* Creates natural incentives for borrowers while protecting against liquidity crunches.

<figure><img src="/files/SyX3DzcvDSKt3QITm4xA" alt=""><figcaption></figcaption></figure>

### Configuration Parameters

The model is governed by several parameters that define its behavior:

* **Base Parameters:**
  * **`baseRatePerSecond`:** Stored per-second base slope, in WAD, used when utilization is at or below `vertexStart`.
  * **`vertexRatePerSecond`:** Stored per-second vertex slope, in WAD, applied only to utilization above `vertexStart` and multiplied by `vertexMultiplier`.
  * **`vertexStart`:** Utilization point, stored in WAD, where the model switches from the base slope to the vertex slope.
* **Adjustment Controls:**
  * **ADJUSTMENT\_RATE:** The fixed 10-minute interval returned to the linked cToken for scheduling vesting windows. The IRM does not run on its own timer; updates happen when accrual calls cross a vesting window.
  * **adjustmentVelocity:** Maximum rate of multiplier change per update.
  * **vertexMultiplierMax:** Maximum allowed value for multiplier.
* **Threshold Parameters:**
  * **`increaseThresholdStart`:** Utilization point above `vertexStart` where positive multiplier adjustments begin. It is derived as halfway between `vertexStart` and 100% utilization.
  * **`decreaseThresholdEnd`:** Utilization point below `vertexStart` where negative multiplier adjustments max out. An elevated multiplier starts decreasing as soon as utilization is at or below `vertexStart`.
  * **`decayPerAdjustment`:** Rate, in BPS, used to compute `decay = multiplier * decayPerAdjustment / BPS` on each multiplier update. The resulting `decay` amount is subtracted from the multiplier.

### Design Benefits

1. **Responsive to Market Conditions:**
   1. High utilization leads to increased rates, attracting lenders.
   2. Sustained high rates encourage borrowers to repay.
2. S**elf-Balancing:**
   1. Creates a feedback loop that stabilizes market liquidity.
   2. Raises rates as utilization remains elevated, but it does not guarantee that liquidity crunches cannot occur.
3. **Growth Incentives:**
   1. The decay mechanism can lower an elevated vertex multiplier over later adjustment windows.
   2. Stable markets can trend back toward the base multiplier as accrual-triggered updates apply decay.
4. **Gas Optimization:**
   1. `DynamicIRM` stores `vertexMultiplier` as a standalone `uint256` and stores configuration in `ratesConfig`. If you mention bit packing, scope it to the linked cToken's `_vestingData`, which packs vesting rate, vesting end, last vesting claim, and debt index.
   2. Efficient math calculations for model computation.

### Practical Example

If a market experiences sustained high utilization:

1. Interest rates will gradually increase as the Vertex Multiplier rises.
2. This attracts new lenders while encouraging borrowers to repay.
3. As utilization decreases, rates begin to fall (but not immediately due to the multiplier).
4. The decay mechanism can help rates normalize when utilization is low enough for decay to dominate any positive multiplier adjustment. If utilization remains above `increaseThresholdStart`, the positive adjustment can keep the multiplier elevated or push it higher, subject to `vertexMultiplierMax`.

This creates a more stable, responsive system compared to fixed-rate models while protecting the protocol from potential liquidity crises.

***

## Math Equations

**Constants:**

* **WAD** = 1e18
* **BPS** = 10\_000
* **WAD\_BPS** = 1e22

1. **Utilization Rate Calculation**

$$
Utilization Rate = Debt / (AssetsHeld + Debt)
$$

```solidity
    function utilizationRate(
        uint256 assetsHeld,
        uint256 debt
    ) public pure returns (uint256 r) {
        // Utilization rate is 0 when there are no outstanding debt.
        r = debt == 0 ? 0 : _mulDiv(debt, WAD, assetsHeld + debt);
    }
```

***

2. **Base Interest Rate Calculation**

$$
Base Rate = (Utilization \* baseRatePerSecond) / WAD
$$

```solidity
    function _baseRate(
        uint256 util,
        uint256 baseRatePerSecond
    ) internal pure returns (uint256 r) {
        r = _mulDiv(util, baseRatePerSecond, WAD);
    }
```

***

3. **Vertex Interest Rate Calculation**

$$
VertexInterestRate =
(vertexStart \* baseRatePerSecond) / WAD
\+ ((util - vertexStart) \* vertexRatePerSecond \* vertexMultiplier) / WAD^2
$$

```solidity
    function _vertexRate(
        uint256 util,
        uint256 baseRatePerSecond,
        uint256 vertexRatePerSecond,
        uint256 vertexStart,
        uint256 multiplier
    ) internal pure returns (uint256 r) {
        r = _mulDiv(vertexStart, baseRatePerSecond, WAD) +
            _mulDiv(
            util - vertexStart,
            vertexRatePerSecond * multiplier,
            WAD_SQUARED
        );
    }
```

***

4. **Final Borrow Interest Rate Calculation**

**If `util <= vertexStart`:**&#x20;

$$
BorrowRate = (util \* baseRatePerSecond) / WAD
$$

**If `util > vertexStart`:**

$$
BorrowRate =
(vertexStart \* baseRatePerSecond) / WAD
\+ ((util - vertexStart) \* vertexRatePerSecond \* vertexMultiplier) / WAD^2
$$

```solidity
    function borrowRate(
        uint256 assetsHeld,
        uint256 debt
    ) public view returns (uint256 r) {
        uint256 util = utilizationRate(assetsHeld, debt);
        // Cache from storage since we only need to query a new config values.
        RatesConfig storage c = ratesConfig;
        uint256 vertexStart = c.vertexStart;

        if (util <= vertexStart) {
            return _baseRate(util, c.baseRatePerSecond);
        }

        r = _vertexRate(
            util,
            c.baseRatePerSecond,
            c.vertexRatePerSecond,
            vertexStart,
            vertexMultiplier
        );
    }
```

***

5. **Vertex Multiplier Adjustment (Above Vertex)**

**decay:**

$$decay = (multiplier \* decayPerAdjustment) / BPS$$

**start:**&#x20;

$$start = decreaseThresholdEnd \* INFLECTIONDENOMINATION$$

**Case 1: Low Utilization (utilization <= start)**

$$
newMultiplier = max((multiplier - decay), WAD)
$$

**Case 2: High Utilization (utilization > start)**

$$
shift = ((util - start ) \* WAD) / (WAD - start)
$$

$$
adjustedShift = WADBPS+ (shift \* adjustmentVelocity)
$$

$$
newMultiplier = (multiplier \* adjustedShift) / WADBPS - decay
$$

$$
finalResult = min(max(newMultiplier, WAD), vertexMultiplierMax)
$$

```solidity
    function _updateAboveVertex(
        RatesConfig memory c,
        uint256 util,
        uint256 multiplier
    ) internal pure returns (uint256 newMultiplier) {
        // Calculate decay rate.
        uint256 decay = _mulDiv(multiplier, c.decayPerAdjustment, BPS);

        if (util <= (_INFLECTION_DENOMINATION * uint256(c.increaseThresholdStart))) {
            newMultiplier = multiplier - decay;

            // Check if decay rate sends new rate below 1.
            return newMultiplier < WAD ? WAD : newMultiplier;
        }

        // Apply a positive multiplier to the current multiplier based on
        // `util` vs `increaseThresholdStart` and `WAD`.
        // Then apply decay effect.
        newMultiplier = _positiveShift(
            multiplier, // `multiplier` in `WAD`.
            c.adjustmentVelocity, // `adjustmentVelocity` in `BPS`.
            decay, // `decay` in `multiplier` aka `WAD`.
            util, // `current` in `WAD`.
            _INFLECTION_DENOMINATION * uint256(c.increaseThresholdStart) // `start` convert to WAD to match util.
        );

        // Update and return with adjustment and decay rate applied.
        // Its theorectically possible for the multiplier to be below 1
        // due to decay, so we need to check like in below vertex.
        if (newMultiplier < WAD) {
            return WAD;
        }

        // Make sure `newMultiplier` is not above `vertexMultiplierMax`.
        newMultiplier = newMultiplier < c.vertexMultiplierMax
            ? newMultiplier
            : c.vertexMultiplierMax;
    }
```

```solidity
    function _positiveShift(
        uint256 multiplier,
        uint256 adjustmentVelocity,
        uint256 decay,
        uint256 current, // `util`.
        uint256 start // `increaseThresholdStart`.
    ) internal pure returns (uint256 result) {
        // We do not need to check for current >= end, since we know util is
        // the absolute maximum utilization is 100%, and thus current == end.
        // Which will result in WAD result for `shift`.
        // Thus, this will be bound between [0, WAD].
        uint256 shift = _mulDiv(current - start, WAD, WAD - start);

        // Apply `shift` result to adjustment velocity.
        // Then add 100% on top for final adjustment value to `multiplier`.
        // We use WAD_BPS here since `shift` is in `WAD` for max precision but
        // `adjustmentVelocity` is in BPS since it does not need greater
        // precision due to being configured in `BPS`.
        shift = WAD_BPS + (shift * adjustmentVelocity);

        // Apply positive `shift` effect to `currentMultiplier`, and
        // adjust for 1e36 precision. Then apply decay effect.
        result = _mulDiv(multiplier, shift, WAD_BPS) -  decay;
    }
```

***

6. **Vertex Multiplier Adjustment (Below Vertex)**

**decay:**

$$decay = (multiplier \* decayPerAdjustment) / BPS$$

**end:**

$$end = decreaseThresholdEnd \* INFLECTIONDENOMINATION$$

**Case 1: Very Low Utilization (utilization <= end)**

$$
newMultiplier = max((multiplier \* BPS) / (BPS + adjustmentVelocity) - decay, WAD)
$$

**Case 2: Moderate Utilization (utilization > end)**

$$
shift = ((vertexStart - util) \* WAD / (vertexStart - end)
$$

$$
adjustedShift = WADBPS + (shift \* adjustmentVelocity)
$$

$$
newMultiplier = (multiplier \* WADBPS) / adjustedShift - decay
$$

$$
result = max(newMultiplier, WAD)
$$

```solidity
    function _updateBelowVertex(
        RatesConfig memory c,
        uint256 util,
        uint256 multiplier
    ) internal pure returns (uint256 newMultiplier) {
        // Calculate decay rate.
        uint256 decay = _mulDiv(multiplier, c.decayPerAdjustment, BPS);

        // Convert `decreaseThresholdEnd` to `WAD` to be in same terms as
        // `util`, no precision loss as a result.
        if (util <= (_INFLECTION_DENOMINATION * uint256(c.decreaseThresholdEnd))) {
            // Apply maximum adjustVelocity reduction (shift = 1).
            // We only need to adjust for `BPS` precision since `shift`
            // is not used here.
            // currentMultiplier / (1 + adjustmentVelocity) = newMultiplier.
            newMultiplier = _mulDiv(
                multiplier,
                BPS,
                BPS + c.adjustmentVelocity
            ) - decay;

            // Check if decay rate sends multiplier below 1.
            return newMultiplier < WAD ? WAD : newMultiplier;
        }

        // Apply a negative multiplier to the current multiplier based on
        // `util` vs `vertexStart` and `decreaseThresholdEnd`.
        // Then apply decay effect.
        newMultiplier = _negativeShift(
            multiplier, // `multiplier` in `WAD`.
            c.adjustmentVelocity, // `adjustmentVelocity` in `BPS`.
            decay, // `decay` in `multiplier` aka `WAD`.
            util, // `current` in `WAD`.
            c.vertexStart, // `start` in `WAD`.
            _INFLECTION_DENOMINATION * uint256(c.decreaseThresholdEnd) // `end` convert to WAD to match util/vertexStart.
        );

        // Update and return with adjustment and decay rate applied.
        // But first check if new rate sends multiplier below 1.
        return newMultiplier < WAD ? WAD : newMultiplier;
    }
```

```solidity
    function _negativeShift(
        uint256 multiplier,
        uint256 adjustmentVelocity,
        uint256 decay,
        uint256 current, // `util`.
        uint256 start, // `vertexStart`.
        uint256 end // `decreaseThresholdEnd`.
    ) internal pure returns (uint256 result) {
        // Calculate linear curve multiplier. We know that current > end,
        // based on pre conditional checks.
        // Thus, this will be bound between [0, WAD].
        uint256 shift = _mulDiv(start - current, WAD, start - end);

        // Apply `shift` result to adjustment velocity.
        // Then add 100% on top for final adjustment value to `multiplier`.
        // We use WAD_BPS here since `shift` is in `WAD` for max precision but
        // `adjustmentVelocity` is in BPS since it does not need greater
        // precision due to being configured in `BPS`.
        shift = WAD_BPS + (shift * adjustmentVelocity);

        // Apply negative `shift` effect to `currentMultiplier`, and
        // adjust for 1e36 precision. Then apply decay effect.
        result = _mulDiv(multiplier, WAD_BPS, shift) -  decay;
    }
```

***

## User-Facing View Functions

#### predictedBorrowRate()

**Contract:** DynamicIRM

**Description:** Calculates the current borrow rate per second with updated vertex multiplier applied, predicting what the rate will be after the next adjustment.

**Function signature:**

```solidity
function predictedBorrowRate(
    uint256 assetsHeld,
    uint256 debt
    ) public view returns (uint256 result)
```

<table><thead><tr><th width="129">Type</th><th width="173">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assetsHeld</td><td>The amount of underlying assets held in the pool.</td></tr><tr><td>uint256</td><td>debt</td><td>The amount of outstanding debt in the pool.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="147">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The borrow rate percentage per second, in <code>WAD</code>.</td></tr></tbody></table>

***

#### borrowRate()

**Contract:** DynamicIRM

**Description:** Calculates the current borrow rate per second based on current market conditions.

**Function signature:**

<pre class="language-solidity"><code class="lang-solidity">function borrowRate(
<strong>    uint256 assetsHeld,
</strong>    uint256 debt
) public view returns (uint256 r)
</code></pre>

<table><thead><tr><th width="124">Type</th><th width="181">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assetsHeld</td><td>The amount of underlying assets held in the pool.</td></tr><tr><td>uint256</td><td>debt</td><td>The amount of outstanding debt in the pool.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="137">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The borrow interest rate percentage, per second, in <code>WAD</code>.</td></tr></tbody></table>

***

#### supplyRate()

**Contract:** DynamicIRM

**Description:** Calculates the current supply rate per second for frontend data querying. It should not be used for on-chain execution.

**Function signature:**

```solidity
function supplyRate(
    uint256 assetsHeld,
    uint256 debt,
    uint256 interestFee
) public view returns (uint256 r)
```

<table><thead><tr><th width="124">Type</th><th width="181">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assetsHeld</td><td>The amount of underlying assets held in the pool.</td></tr><tr><td>uint256</td><td>debt</td><td>The amount of outstanding debt in the pool.</td></tr><tr><td>uint256</td><td>interestFee</td><td>The current interest rate protocol fee for the market token, in <code>BPS</code>.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="137">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The supply interest rate percentage, per second, in <code>WAD</code>.</td></tr></tbody></table>

***

#### utilizationRate()

**Contract:** DynamicIRM

**Description:** Calculates market utilization as `(debt * WAD) / (assetsHeld + debt)`. Returns `0` when `debt == 0`.&#x20;

**Function signature:**

```solidity
function utilizationRate(
    uint256 assetsHeld,
    uint256 debt
) public pure returns (uint256 r)
```

<table><thead><tr><th width="124">Type</th><th width="181">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assetsHeld</td><td>The amount of underlying assets held in the pool.</td></tr><tr><td>uint256</td><td>debt</td><td>The amount of outstanding debt in the pool.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="137">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The utilization rate between [0, WAD].</td></tr></tbody></table>

***

#### vertexMultiplier()

**Contract:** DynamicIRM

**Description:** Public getter for the dynamic value applied to `vertexRatePerSecond`, in `WAD`. The multiplier is dynamically adjusted by `adjustedBorrowRate(...)`, can be reset or clamped by governance configuration updates, is floored at `WAD`, and is capped by `vertexMultiplierMax`.

**Function signature:**

```solidity
// uint256 public vertexMultiplier;
function vertexMultiplier() public view returns (uint256);
```

**Return data:**

<table><thead><tr><th width="137">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The current vertex multiplier, in <code>WAD</code>.</td></tr></tbody></table>

***

#### linkedToken()

**Contract:** DynamicIRM

**Description:** The borrowable Curvance token linked to this interest rate&#x20;model contract.

**Function signature:**

```solidity
function linkedToken() external view returns (address result);
```

**Return data:**

<table><thead><tr><th width="137">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The linked borrowableCToken address.</td></tr></tbody></table>

***


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.curvance.com/app/developer-docs/lending-protocol/dynamic-interest-rate-model.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
