Skip to main content
How borrower interest accrues, where protocol revenue goes, and how the pool socializes bad debt. Driven by supply_index and borrow_index. Read first: Markets. The pool never iterates accounts. It stores supply and debt as scaled amounts and applies interest to everyone at once by advancing two indexes, supply_index and borrow_index. A position’s actual balance is always derived from its scaled amount and the current index.

Scaled balances

Every supply and debt amount is stored as a scaled RAY value. The actual amount is the scaled value multiplied by the relevant index:
Supply positions and totals use supply_index; debt positions and totals use borrow_index. RAY is 10^27. Both indexes start at RAY (1.0) when a market is created and only ever rise from interest — except the supply index, which a bad-debt cleanup can lower (see below). Views reconstruct actual supplied and borrowed amounts on demand from the current index and the asset’s decimals.

Utilization

Utilization is the fraction of supplied liquidity that is currently borrowed, in RAY:
When supplied is zero, utilization is zero. When selecting a rate it is clamped to at most 1 RAY. Utilization drives the borrow rate and, through it, the supply rate.

Borrow-rate model

The annual borrow rate is a piecewise-linear curve with two kinksmid_utilization and optimal_utilization — producing three regions. It starts at base_borrow_rate and adds slope1, then slope2, then slope3 as utilization climbs (calculate_borrow_rate):
The final rate is capped at max_borrow_rate (≤ 2·RAY). Parameters must satisfy base ≤ slope1 ≤ slope2 ≤ slope3 ≤ max and 0 < mid < optimal < 1 RAY, so the curve is non-decreasing.
max_utilization is a separate hard cap, not a rate kink. When a borrow or withdraw would push post-state utilization above it, the call reverts with #127 UtilizationAboveMax (unless max_utilization ≥ 1 RAY). It bounds how much of the pool can be borrowed. It does not change the rate curve.Mainnet uses four distinct settings today, not one — read the live values rather than assuming:

Accrual

On every mutating pool call, and on update_indexes, the pool advances the borrow index by compounding the rate over the elapsed time. The rate compounds per millisecond. Elapsed time is processed in chunks of at most one year each, and each chunk uses an 8-term Taylor approximation of e^x:
The borrow index is capped at 1e36 (MAX_BORROW_INDEX_RAY) — a 1e9x growth budget from its starting RAY.
The ceiling is a silent clamp, not an error. At MAX_BORROW_INDEX_RAY the borrow index simply stops moving, accrued interest reads as zero, and debt stops growing with no revert and no event. Suppliers stop earning. A market approaching it has to be detected off-chain. Reaching it takes about 11 years pinned at the protocol’s 200% APR cap, or ~70 years at 30%.
Every dropped Taylor term is positive, so the truncation always under-accrues interest — never over-accrues — and the bias collapses as the rate falls (2.4e-4 relative shortfall at 200% APR, 1.2e-12 at 20%).

Splitting interest

The interest accrued on outstanding debt is split by reserve_factor into a protocol fee and supplier rewards (calculate_supplier_rewards):
The supplier share lifts supply_index. Every supplier’s position appreciates at once, with no per-account write. The update is conservative. If the index move cannot express the full reward, the leftover is recorded as revenue rather than lost. The supply index shares the borrow index’s 1e36 ceiling, and accrual never lowers it. The protocol fee is added to revenue and held as a scaled supply claim. The display supply APR follows directly (calculate_deposit_rate):
Worked example. Say utilization is 80%, the borrow rate is 5%, and the reserve factor is 1000 BPS (10%). Then:
That is a 3.6% supply rate against a 5% borrow rate.

Protocol revenue

Protocol revenue is not a separate token balance. It is held as a scaled supply claim in revenue, bounded by total supply (0 ≤ revenue ≤ supplied). It appreciates with the supply index until claimed. On claim_revenue the pool does four things: sync the indexes, reconstruct the claimable amount, cap it at available cash, and burn enough revenue shares to cover the payout. It transfers to its owner, the controller, which forwards the proceeds to the configured accumulator. See Controller ABI.

Recapitalization

There is no add_rewards entrypoint. The one external top-up path is recapitalize(payer, hub_asset, amount) on the controller. Anyone can call it. It applies only up to the market’s actual shortfall, refunds the excess, and returns the amount actually applied. Credit is measured, not requested. It is not pause-gated.

Bad debt

When an account’s total collateral is ≤ $5 WAD (BAD_DEBT_USD_THRESHOLD = 5·WAD) and its debt exceeds that collateral, the position is unrecoverable. Cleanup runs either inline during a liquidation, or when anyone calls clean_bad_debt. Both do the same four things:
  1. Seize all of the account’s remaining supply and debt.
  2. Remove the account and burn its position NFT.
  3. Lower the affected market’s supply index to absorb the shortfall.
  4. Emit CleanBadDebtEvent.
Step 3 is the write-down:
remaining_value is the total supplied value less the bad debt, capped at that total. The two floors compound. That makes the written-down index at most the single-step value, never more. The extra truncation falls on suppliers, never on the protocol. The result is clamped at SUPPLY_INDEX_FLOOR_RAW = RAY / 1_000 (0.001) — not at WAD. The floor keeps the index above zero, so the division in calculate_scaled_supply can never trap. That floor also caps share inflation. At the floor, a deposit mints 1,000× the shares it would mint at index 1.0. No amount of socialization can push a share below one thousandth of a token — that 1,000× is the whole budget. The cost is deposit headroom. A market written down to the floor can accept only a thousandth of the usual maximum single deposit. Only suppliers of that one market absorb the loss. CleanBadDebtEvent records the cleared totals. See Liquidations.

Fixed-point units

Multiply and divide helpers use half-up rounding by default, flooring where it protects the protocol and ceiling for amounts a user owes.

Next

Markets

What a market is and how its rate model is configured.

Risk parameters

Reserve factor, caps, dust floors, and the rate-model constraints.

Liquidations

How bad debt is triggered and socialized into the supply index.