Interest accrual and reserves
How interest is booked, how it is split between lenders and reserves, and what "paid in full" means.
One accrual
Each time accrueInterest() runs, it charges the whole window since the last booking at the
current rate:
Where:
- —
totalBorrowAssetsbefore the booking, USDG base units. - — the borrow rate from the curve, at the moment of booking, applied to the whole window.
- — seconds since
lastAccrued. - — 31,536,000 seconds.
One multiplication chain, one division, so it rounds once. Source:
InterestRateModel · 0x8390…D741.
The split
Where is the reserve factor in effect: 10% whenever nothing has been stored, which is the
case on every mainnet market, and a real 0% when 0 has been set explicitly (see
effective reserve factor). The reserve
share rounds down and lenders take the remainder, so
exactly — no unit is charged to a borrower and credited to
nobody. Source: InterestRateModel · 0x8390…D741, InterestRateModel · 0x8390…D741.
Booking
Source: LendingPoolRouter. Three guards come first:
| Case | What happens |
|---|---|
| Nothing | |
lastAccrued moves forward, nothing is charged — a window with no debt is not billed to the next borrower | |
Returns without moving lastAccrued, so the window stays open and is charged in full later |
A borrow closes the window regardless: it accrues, then sets lastAccrued to now before the
new debt is added. Without that, a market whose only debt is dust could keep a window open
indefinitely under the third guard, and the next borrower would be charged the whole carried period
on fresh debt. What a borrow drops is the floored remainder, under one base unit.
The third guard is what stops interest from being truncated away by frequent calls. There is no stored remainder, though: each booking still drops the fraction of a base unit below . So "paid in full" means to within less than one base unit (0.000001 USDG) per booking.
The shortest window that books anything is
which is 0.07 s in the illustrative market: there, every accrual books something. It grows as debt and the rate shrink.
Who triggers it
accrueInterest() is public, permissionless and not pausable: anyone can call it. It also runs
inside borrow, repay, liquidation, supplying and withdrawing collateral, the vault's supply and
withdrawal of liquidity, and every Earn vault deposit, mint, withdraw and redeem, which accrue every
market first.
Every market prices at its vault's utilisation, which
the vault stores and moves only at a checkpoint, after every market has accrued at the old value.
The pools call the vault's permissionless accrueInterestAll() (accrue every market, then
checkpoint) before and after every borrow, repay and liquidation; the vault's own deposit, withdraw
and allocation calls accrue every market at their start and checkpoint at their end.
accrueInterestAll() is not paused with the vault, so a paused vault never blocks a repay or a
liquidation. Between two checkpoints every market's rate is constant, so the order in which markets
accrue does not change what any of them is charged. Between bookings, interest is
owed but not yet written to storage; views such as totalBorrowAssets show the last booked figure.
Worked example
On mainnet at block 93419456 (timestamp 1792496007) only WETH/USDG had debt: = 50000840,
last booked at 1792495666, 341 seconds earlier, at = 7119808825931708:
3 base units, all to supply ( to reserves):
calculateInterest(router, 341) returned (3, 3, 0), and the vault's pendingInterest(pool) 3.
NVDA/USDG and SPY/USDG had no debt, so their accruals only move lastAccrued forward (the
guard above). Amounts that small hide the rounding, so the example uses the
illustrative market, last booked 7 days earlier:
With 20010000000 and 23759375000000000 (from
the curve):
| pending | reserve | to supply | |
|---|---|---|---|
| Illustrative NVDA/USDG | 9.117741 | 0.911774 | 8.205967 |
All in USDG. For a year-long window the same formula gives 475425093 / 427882584 / 47542509 —
475.425093 USDG of interest, 427.882584 to supply and 47.542509 to reserves. That is what
calculateInterest(router, 31536000) would return in that state.
Compounding
Because each booking grows , the next booking charges interest on interest. Compounding happens at the frequency of accruals, not continuously and not on a schedule. The effect on the annual figure is on APR and APY.
Reserves
Reserves sit in the market as cash, and they count toward its solvency: a withdrawal of liquidity and a borrow are both allowed only while
Source: LendingPoolRouter, LendingPoolRouter.
The owner can take reserves out only while that still holds afterwards
(LendingPoolRouter). If bad debt ever exceeds a market's supply,
the excess is charged to reserves — see bad debt.