In Aave V3, the liquidity index converts scaled aToken units into a supplied-asset balance, while the variable borrow index converts scaled debt units into an accrued debt balance. They use different rates and accrual rules. Compare each position with its own reserve's normalized index at the same block; a stored index can lag the balance returned by a fresh read.
This explanation follows Aave V3.7 Origin at revision cff15de, inspected on 28 October 2026. Check the implementation used by the particular chain and market before applying its rounding rules. The calculations below describe that V3 reference source.
Give supply and debt separate multipliers
A supplier's aToken balance and a borrower's variable-debt balance can both increase without either person submitting a transaction. The protocol can retain scaled units and apply an accumulating reserve index when a balance is read.
The aToken balance getter uses normalized income. The separate variable-debt balance getter uses normalized variable debt. An adapter that saves one generic field called “Aave index” loses information needed to reconstruct both.
| Position | Stored account quantity | Matching reserve multiplier |
|---|---|---|
| Supplied asset | Scaled aToken balance | Normalized liquidity index |
| Variable-rate borrowing | Scaled variable-debt balance | Normalized variable borrow index |
In human-readable units, the relationship is scaled quantity multiplied by the matching index. In raw arithmetic, divide that product by the ray scale, 10^27. This scale is independent of the underlying token's decimals: a six-decimal token still uses a ray-scaled index. WadRayMath defines the scales and multiplication routines.
For this revision, TokenMath rounds the final aToken balance down and the final variable-debt balance up. Those are balance-read rules; minting, burning and transferring have their own rounding paths. A spreadsheet using ordinary decimal rounding can therefore disagree by a base unit even when it selects the correct index.
The scaled-balance comparison explains the account-unit distinction in more detail. Here, the additional requirement is to select two different multipliers and advance each correctly through time.
A stored checkpoint is different from a current projection
The Pool source exposes both kinds of evidence. Its reserve-data getter returns stored indices and the last-update timestamp. Its normalized-income and normalized-variable-debt getters calculate the reserve's ongoing values for the read's block timestamp.
These view reads do not persist a new checkpoint. Reading the same reserve at a later block can return larger balances while its stored index fields remain unchanged.
Within one reserve, both accrual paths use its last-update timestamp, but supply uses the liquidity rate and variable debt uses the variable borrow rate. At the checkpoint's timestamp, normalized getters return the stored indices. Stored updates also have guards: the liquidity branch requires a nonzero liquidity rate, while the variable-borrow branch requires nonzero aggregate scaled variable debt. A positive base borrow rate alone does not advance the stored debt index. These distinctions follow ReserveLogic's read and update paths.
For positions in different assets, keep each reserve's timestamp separately. A supply position in one asset and a debt position in another do not share a universal accrual checkpoint merely because they appear on the same account screen.
Reconstruct both sides of one illustrative reserve
The following constructed example uses fictitious asset U with six decimals. One supplier holds 20,000 scaled aToken units; a separate borrower owes 8,000 scaled debt units. Neither account adds, removes or transfers units during the example. The borrower's collateral is outside this calculation, so this is not a health-factor example.
At time t0, the stored liquidity index is 1.10 and the stored variable borrow index is 1.25. The supplied claim is therefore 22,000 U, and debt is 10,000 U. These are reconstructed opening balances; neither scaled quantity is a principal amount deposited or borrowed at t0.
Assume liquidity APR is 6% and variable borrow APR is 12% for the first interval. These are invented inputs. Aggregate pool positions and the rate curve are omitted, so the example does not claim to derive those rates from utilization.
Each interval lasts 2,628,000 seconds: 30 days and 10 hours, exactly one twelfth of the 365-day year used by the pinned MathUtils implementation.
For liquidity, the interval factor is 1 + 0.06 / 12 = 1.005. Multiply the opening index by that factor to get 1.1055. The supplier's claim becomes 20,000 × 1.1055 = 22,110 U.
For variable debt, this revision uses a polynomial approximation to exponential accrual. Define x as annual decimal rate multiplied by elapsed seconds divided by seconds per year. Ignoring intermediate integer rounding, its factor is 1 + x + x²/2 + x³/6; the code performs the corresponding ray operations. It is not an exact exponential or a daily-compounding calculation.
Here x is 0.01, producing a factor of approximately 1.010050166667. Applying it to the opening borrow index gives approximately 1.262562708333. The debt balance becomes 10,100.501667 U after the specified final ceiling.
Advance the checkpoint before using new rates
At t1, an unrelated supplier's transaction updates the reserve. It changes neither observed account's scaled units. The reference supply execution path updates reserve state before calculating subsequent rates. For this fixture, stipulate that the resulting rates become 3% liquidity APR and 9% variable borrow APR.
The first interval still uses 6% and 12%. Replacing them retrospectively with the newly displayed rates would produce the wrong opening-to-t1 accrual. Immediately after the t1 checkpoint, stored and normalized indices agree because no additional time has passed.
Let another equal interval pass without a further update. The liquidity factor is now 1.0025, and the borrow calculation uses x equal to 0.0075.
| Reconstructed quantity | t0 | t1 | t2 |
|---|---|---|---|
| Current liquidity index | 1.10 | 1.1055 | 1.10826375 |
| Current variable borrow index | 1.25 | 1.262562708333 | 1.272067526996 |
| Supplier's claim, U | 22,000.000000 | 22,110.000000 | 22,165.275000 |
| Borrower's debt, U | 10,000.000000 | 10,100.501667 | 10,176.540216 |
Index decimals are shortened for display. Balance results use the full ray calculations and six underlying decimals, independently checked against high-precision decimal calculations.
At t1 just before the transaction, the normalized reads already produce the t1 values, while stored indices remain 1.10 and 1.25. The transaction saves the t1 checkpoint. At t2, stored indices remain at t1, but normalized reads project forward using 3% and 9%. An account need not transact for its reported balance to include that interval.
Three mistakes the ledger makes visible
First, substituting the liquidity index into the debt calculation gives 8,000 × 1.1055 = 8,844 U at t1. That is 1,256.501667 U below the correctly accrued debt and even below the opening 10,000 U liability. The arithmetic is valid; the chosen multiplier is wrong. A balance declining without a repayment is an immediate reason to inspect the mapping.
Second, comparing the absolute indices does not measure either person's holding-period return. The relevant first-interval ratios are 1.1055 divided by 1.10 for supply and approximately 1.262562708333 divided by 1.25 for debt. Each comparison needs its matching opening index. If a position's scaled units change, a single opening-to-closing ratio also needs a dated flow ledger.
The broader receipt-token accounting guide shows how withdrawals and transfers can reverse the apparent meaning of a quantity change. Do not apply an index again to a visible balance that already includes it.
Third, “linear liquidity accrual” describes an interval's calculation, not one unbroken simple-interest formula across all checkpoints. In a separate illustrative supply-only comparison, hold 6% constant for two of our intervals. Starting with 22,000 U, one unsplit linear interval gives 22,220 U. Two checkpointed intervals multiply by 1.005 twice and give 22,220.55 U. Checkpoint splitting changes this constructed calculation by 0.55 U. Reconstruct the actual update sequence rather than choosing an arbitrary compounding frequency.
The utilization and borrow-APR guide addresses how rates arise. An index instead carries accumulated effects of the rates and intervals already applied; today's APR alone cannot reconstruct that history.
Build a reconciliation that can fail visibly
Use a fixed block tag for every read in one snapshot. For older states, use a provider capable of serving those historical contract reads; silently falling back to the latest block defeats the comparison.
| Worksheet field | Evidence to save |
|---|---|
| Identity | Chain, Pool, underlying, aToken, debt token and implementation revision |
| Observation | Block number, block hash and timestamp |
| Account units | Both scaled balances, token decimals and visible balances |
| Checkpoint | Both stored indices, both stored rates and reserve update timestamp |
| Current projection | Normalized income and normalized variable debt at that block |
| Changes between snapshots | Supplies, withdrawals, borrowing, repayments, transfers and liquidation effects |
Reproduce each visible balance from its scaled quantity and matching normalized index. Compare raw integers before formatting decimals. If it fails, classify the difference: wrong token, wrong index, mixed blocks, different implementation, missed account flow or incorrect rounding. Altering the saved input to make the totals agree hides the evidence needed to diagnose the problem.
Keep the conclusion narrow. These calculations establish indexed quantities, not available withdrawal cash, a market sale price or a dollar value. Likewise, supply caps and borrow caps are separate constraints; index growth does not authorize another operation.
For the illustrative t1 snapshot, the reproducible pair is 22,110.000000 U supplied and 10,100.501667 U owed by the separate accounts. Save both matching indices and the observation block with those balances. That record makes the next comparison checkable.
