diff --git a/appendices/glossary.md b/appendices/glossary.md
index 4268fc86..90a64086 100644
--- a/appendices/glossary.md
+++ b/appendices/glossary.md
@@ -54,6 +54,10 @@ An order to buy at or below a stated price. The buyer's side of an order book.
A recent block's hash, included in every transaction to bound its lifetime. When it expires, the transaction can never land.
+### borrow accumulation factor
+
+A single number per reserve that starts at 1.0 and only grows, compounding the borrow rate over elapsed slots. A borrower's live debt is their stored principal multiplied by it, so interest accrues for everyone without any loan being touched. The lending example calls the field `borrow_accumulation_factor`; see the *Lending and Borrowing* chapter.
+
### built-in address
An address chosen rather than computed: the System Program, the token programs, and the sysvars each sit at a fixed byte string the runtime treats as that thing. Neither a [public key](#address) somebody holds the private key for nor a [PDA](#pda-program-derived-address) derived from seeds, so a program uses it as a constant to compare against. Whether one happens to decode to a point on the ed25519 curve is an accident of the bytes and carries no meaning.
@@ -106,10 +110,6 @@ A permissionless instruction handler that performs maintenance work nobody owns
A binary tree keyed on the first bit where entries differ. Used for order books because insertion never shifts other entries and walking it yields price-time priority for free. See the *Order Book Exchange* chapter.
-### cumulative interest index
-
-A single monotonically growing number per reserve that starts at 1.0 and compounds the interest rate over time. Debts are stored scaled by the index at borrow time, so one multiplication accrues interest for every borrower at once. See the *Lending and Borrowing* chapter.
-
### decimals
The number of minor units in one major unit of a token, declared on the mint. USDC has 6, SOL has 9. Programs must read decimals, never assume them.
@@ -444,7 +444,7 @@ A token account whose authority is a program's PDA, where a program holds funds
### WAD
-A fixed-point convention: values scaled by 10^18, so 1.0 is stored as a 1 followed by eighteen zeros. The scale used for interest indexes and rate math. Borrowed from Ethereum, where DappHub's `ds-math` library introduced it and MakerDAO made it standard, at 18 decimals because that is what Ether and most ERC-20 tokens use. Now the usual word for 18-decimal fixed point on every chain, Solana included.
+A fixed-point convention: values scaled by 10^18, so 1.0 is stored as a 1 followed by eighteen zeros. The scale used for accumulation factors and rate math. Borrowed from Ethereum, where DappHub's `ds-math` library introduced it and MakerDAO made it standard, at 18 decimals because that is what Ether and most ERC-20 tokens use. Now the usual word for 18-decimal fixed point on every chain, Solana included.
### wallet
diff --git a/building-financial-products-in-solana/figures/lending-account-map.svg b/building-financial-products-in-solana/figures/lending-account-map.svg
index d128f1b1..c9b96582 100644
--- a/building-financial-products-in-solana/figures/lending-account-map.svg
+++ b/building-financial-products-in-solana/figures/lending-account-map.svg
@@ -264,8 +264,8 @@
USDC reserve
available_liquidity: 1,250
- borrowed_amount_scaled: 750
- cumulative_borrow_rate_index: 1.0
+ borrowed_principal: 750
+ borrow_accumulation_factor: 1.0
accumulated_protocol_fees: 0
@@ -310,8 +310,8 @@
NVDAx reserve
available_liquidity: 5
- borrowed_amount_scaled: 0
- cumulative_borrow_rate_index: 1.0
+ borrowed_principal: 0
+ borrow_accumulation_factor: 1.0
accumulated_protocol_fees: 0
@@ -356,8 +356,8 @@
SPCXx reserve
available_liquidity: 1
- borrowed_amount_scaled: 3
- cumulative_borrow_rate_index: 1.0
+ borrowed_principal: 3
+ borrow_accumulation_factor: 1.0
accumulated_protocol_fees: 0
diff --git a/building-financial-products-in-solana/figures/lending-borrow-ltv.svg b/building-financial-products-in-solana/figures/lending-borrow-ltv.svg
index b0dc42bc..7671d6d4 100644
--- a/building-financial-products-in-solana/figures/lending-borrow-ltv.svg
+++ b/building-financial-products-in-solana/figures/lending-borrow-ltv.svg
@@ -203,8 +203,8 @@
USDC reserve
available_liquidity: 250
- borrowed_amount_scaled: 750
- cumulative_borrow_rate_index: 1.0
+ borrowed_principal: 750
+ borrow_accumulation_factor: 1.0
accumulated_protocol_fees: 0
@@ -246,8 +246,8 @@
NVDAx reserve
available_liquidity: 5
- borrowed_amount_scaled: 0
- cumulative_borrow_rate_index: 1.0
+ borrowed_principal: 0
+ borrow_accumulation_factor: 1.0
accumulated_protocol_fees: 0
diff --git a/building-financial-products-in-solana/figures/lending-deposit-shares.svg b/building-financial-products-in-solana/figures/lending-deposit-shares.svg
index 8e536322..70d7c65f 100644
--- a/building-financial-products-in-solana/figures/lending-deposit-shares.svg
+++ b/building-financial-products-in-solana/figures/lending-deposit-shares.svg
@@ -149,8 +149,8 @@
USDC reserve
available_liquidity: 1,000
- borrowed_amount_scaled: 0
- cumulative_borrow_rate_index: 1.0
+ borrowed_principal: 0
+ borrow_accumulation_factor: 1.0
accumulated_protocol_fees: 0
diff --git a/building-financial-products-in-solana/figures/lending-liquidation.svg b/building-financial-products-in-solana/figures/lending-liquidation.svg
index cbff658d..e067e54c 100644
--- a/building-financial-products-in-solana/figures/lending-liquidation.svg
+++ b/building-financial-products-in-solana/figures/lending-liquidation.svg
@@ -246,8 +246,8 @@
USDC reserve
available_liquidity: 1,250
- borrowed_amount_scaled: 750
- cumulative_borrow_rate_index: 1.0
+ borrowed_principal: 750
+ borrow_accumulation_factor: 1.0
accumulated_protocol_fees: 0
@@ -317,8 +317,8 @@
SPCXx reserve
available_liquidity: 3.25
- borrowed_amount_scaled: 0.75
- cumulative_borrow_rate_index: 1.0
+ borrowed_principal: 0.75
+ borrow_accumulation_factor: 1.0
accumulated_protocol_fees: 0
diff --git a/building-financial-products-in-solana/figures/lending-rate-curve.svg b/building-financial-products-in-solana/figures/lending-rate-curve.svg
index ab37c74c..84950d0a 100644
--- a/building-financial-products-in-solana/figures/lending-rate-curve.svg
+++ b/building-financial-products-in-solana/figures/lending-rate-curve.svg
@@ -27,8 +27,8 @@
past the kink the rate spikes,
pushing borrowers to repay
and pulling depositors in
-
- the index compounds at each refresh
+
+ the factor compounds at each refresh
@@ -44,6 +44,6 @@
refreshes, over five weeks of slots
1.0
≈ 1.0107
- Bob’s debt = scaled amount × index: $750 → ≈ $758
+ Bob’s debt = principal × factor: $750 → ≈ $758
one number reprices every loan in the reserve
diff --git a/building-financial-products-in-solana/lending.md b/building-financial-products-in-solana/lending.md
index c03c5f0d..c482a4ec 100644
--- a/building-financial-products-in-solana/lending.md
+++ b/building-financial-products-in-solana/lending.md
@@ -62,15 +62,15 @@ Read her position carefully, because everything later hangs on it: **Carol's deb
## Interest: One Index, No Transfers
-Weeks pass, and the loans accrue interest without a single token moving. The mechanism is the **cumulative borrow-rate index**, and it is the most reusable idea in this chapter.
+Weeks pass, and the loans accrue interest without a single token moving. The mechanism is the **borrow accumulation factor**, and it is the most reusable idea in this chapter.
-Each reserve stores one number that starts at 1.0 and only grows. On every `refresh_reserve()`, the program computes the current borrow rate, multiplies it by the slots elapsed, and grows the index: new index = old index × (1 + rate per slot × elapsed slots). A loan is stored not as an amount but as a *scaled* amount: the amount divided by the index at borrow time. The live debt is always scaled amount × current index, rounded *up*, because debt rounds against the debtor. One multiplication per refresh serves every borrower in the reserve simultaneously; nobody's loan is ever visited individually. The moving of actual tokens is consolidated to the moments people repay or redeem, exactly like the order book's unsettled balances but across time instead of across fills.
+Each reserve stores one number that starts at 1.0 and only grows. On every `refresh_reserve()`, the program computes the current borrow rate, multiplies it by the slots elapsed, and grows the factor: new factor = old factor × (1 + rate per slot × elapsed slots). Rather than updating every loan, the reserve stores each borrower's `borrowed_principal` and grows the one shared `borrow_accumulation_factor`. A live debt is that principal times the current factor, rounded *up*, because debt rounds against the debtor. No loan is ever visited individually. The moving of actual tokens is consolidated to the moments people repay or redeem, exactly like the order book's unsettled balances but across time instead of across fills.
The rate itself comes from a **kinked utilization curve**. Utilization is borrowed ÷ (available + borrowed). Below the kink at 80% utilization, the rate climbs gently from 2% toward 20% APR; above it, steeply toward 150%. The kink is the reserve begging for liquidity: when nearly everything is lent out, the rate spikes, which simultaneously pushes borrowers to repay and pulls new suppliers in with yield. It is monetary policy in fifteen lines of integer math.
Of the interest that accrues, the reserve factor (10%) is skimmed into `accumulated_protocol_fees`, floored, in the suppliers' favor; the rest belongs to the suppliers through the rising share price.
-
+
## The Shorted Asset Rallies and the Position Becomes Liquidatable
@@ -101,7 +101,7 @@ Dave is one instance of a pattern this market runs on. Some work in a financial
## Repayment, Fees, and Yield: Everyone Leaves Whole
-Five weeks in, Bob's cash crunch has passed. His $750 debt has quietly become about $758 through the index. He runs `repay_obligation_liquidity()` (anyone may repay any obligation, so there is no owner check; over-repayment clamps to the debt, and any sub-unit of principal lingers with the borrower rather than being forgiven by rounding). Then `withdraw_obligation_collateral()` releases his NVDAx shares (the withdraw math rounds *up* the borrow power being removed, so a withdrawal can never squeak past the health check by a rounding sub-unit), and `redeem_reserve_collateral()` turns shares back into his five NVDAx. Total cost of five weeks' liquidity: about eight dollars, and he never sold his NVIDIA.
+Five weeks in, Bob's cash crunch has passed. His $750 debt has quietly become about $758 through the factor. He runs `repay_obligation_liquidity()` (anyone may repay any obligation, so there is no owner check; over-repayment clamps to the debt, and any sub-unit of principal lingers with the borrower rather than being forgiven by rounding). Then `withdraw_obligation_collateral()` releases his NVDAx shares (the withdraw math rounds *up* the borrow power being removed, so a withdrawal can never squeak past the health check by a rounding sub-unit), and `redeem_reserve_collateral()` turns shares back into his five NVDAx. Total cost of five weeks' liquidity: about eight dollars, and he never sold his NVIDIA.
Maria runs `collect_protocol_fees()` whenever she likes and receives the accumulated 10% of interest, about 78 cents (`NothingToCollect` guards the empty case, and `protocol_fees_accrue_and_owner_can_collect` pins the flow). Of every dollar of interest Bob paid, ninety cents went to the suppliers, ten to the house.
@@ -124,7 +124,7 @@ Every number in this program flows through `mul_div_floor()` or `mul_div_ceil()`
- Total liquidity = available + borrowed-with-interest − the house's accumulated fees, and the share supply is a claim on exactly that; the exchange rate cannot be moved by donating tokens to the vault (pinned by `raw_token_donation_does_not_inflate_exchange_rate`).
- No action may leave an obligation past its allowed borrow value, and every valuation is fresh (same transaction) or refused.
- Loan-to-value ≤ liquidation threshold in every valid config, so there is always a buffer between the most you can borrow and the point you can be liquidated.
-- The index never decreases, and debt computed from it never rounds in the borrower's favor.
+- The factor never decreases, and debt computed from it never rounds in the borrower's favor.
- Liquidation can only shrink an unhealthy position toward health, by at most the close factor, at exactly the bonus.
## The Failure Modes: The Liquidation Engine Is Only as Honest as Its Prices
@@ -147,7 +147,7 @@ Strip the branding from a commercial bank and what remains is: pooled deposits e
The concepts you now have:
- **Shares as the yield mechanism.** Interest swells the pool while the share supply stays put, so each share redeems for more; no per-lender bookkeeping exists or needs to.
-- **The cumulative index.** One multiplication accrues interest for every borrower simultaneously; debts are stored scaled and revalued on touch. The same shape prices any claim on a growing pool.
+- **The accumulation factor.** Store each borrower's principal and one shared factor, and compute a debt by multiplying them. The same shape prices any claim on a growing pool.
- **Conservative-by-construction valuation.** Collateral floors, debt ceils, so every health check errs against the borrower, and no rounding sub-unit can squeak a position past a boundary.
- **Liquidation as a paid correction.** The close factor caps each call at part of the debt, so the borrower keeps what health does not require; the bonus pays the liquidator; the health gate means only positions already past the threshold can be touched.
- **Cranks.** Permissionless, idempotent maintenance whose funds destinations come from stored state, and whose reliability comes from someone being paid.
diff --git a/financial-programming-concepts-in-solana/the-arithmetic-of-money.md b/financial-programming-concepts-in-solana/the-arithmetic-of-money.md
index 49c9f9c9..7554c799 100644
--- a/financial-programming-concepts-in-solana/the-arithmetic-of-money.md
+++ b/financial-programming-concepts-in-solana/the-arithmetic-of-money.md
@@ -75,9 +75,9 @@ Check them **after the math, not just before.** A check on the way in catches ba
One warning from the same file: an invariant that can hold vacuously is not an invariant. `new_k >= old_k` passes trivially when `old_k` is zero, which is why the empty pool needs its own explicit guard. Know the degenerate values of every property you assert.
-## One Index Compounds Interest for Every Borrower at Once
+## One Accumulation Factor Compounds Interest for Every Borrower
-Interest is multiplication repeated over time, and the naive implementation, looping over every borrower every slot, is impossible onchain. The standard solution is the **cumulative index**: one fixed-point number per market that starts at 1.0 and grows by the current rate each time anyone touches the market. New index = old index × (1 + rate per slot × elapsed slots). A debt is stored divided by the index at borrow time, and its live value is that scaled figure times the current index. One multiplication serves every borrower simultaneously, interest compounds across updates automatically, and no tokens move until someone actually repays or withdraws. The Lending chapter runs this machine in production; the same shape prices any share of a growing pool.
+Interest is multiplication repeated over time, and the naive implementation, looping over every borrower every slot, is impossible onchain. The standard solution is the **accumulation factor**: one fixed-point number per market that starts at 1.0 and grows by the current rate each time anyone touches the market. New factor = old factor × (1 + rate per slot × elapsed slots). Instead of updating every debt, the market stores each borrower's principal and grows the one shared factor; a live debt is that principal multiplied by the current factor. Interest compounds across updates automatically, and no tokens move until someone actually repays or withdraws. The lending example calls the field `borrow_accumulation_factor`. The Lending chapter runs this machine in production; the same shape prices any share of a growing pool.
Two numerical points complete the picture:
@@ -92,4 +92,4 @@ Rates themselves are policy, not arithmetic, but one curve shows up everywhere:
- Multiply before divide, promote intermediates to u128, and check every operation with a named overflow error.
- Rounding is a policy decision: floor what the program pays, ceil what the user owes, round once, and keep health checks conservative.
- Assert invariants after the math, and know the degenerate values where an invariant can hold vacuously.
-- One cumulative index compounds interest for every borrower at once, and a utilization curve is monetary policy in fifteen lines of integer math.
+- One accumulation factor compounds interest for every borrower at once, and a utilization curve is monetary policy in fifteen lines of integer math.
diff --git a/solana-building-blocks/time.md b/solana-building-blocks/time.md
index 2e8d3564..c9d5dc54 100644
--- a/solana-building-blocks/time.md
+++ b/solana-building-blocks/time.md
@@ -11,15 +11,15 @@ Solana has two clocks: a slot counter that nobody can bend, and a wall clock tha
The **slot** counts block opportunities, roughly 400ms apart. Slots only go up, at a rate nobody can meaningfully bend, and programs read the current one from the **Clock sysvar**, a system account the runtime keeps fresh.
-Two programs in this book accrue value over time, and both count it in slots: the lending market accrues interest on debt, and the perpetual futures venue accrues funding on open positions. The lending market carries one number per reserve, a **cumulative index** that grows each time anyone touches the market:
+Two programs in this book accrue value over time, and both count it in slots: the lending market accrues interest on debt, and the perpetual futures venue accrues funding on open positions. The lending market carries one number per reserve, a **borrow accumulation factor** that grows each time anyone touches the market:
```
-new index = old index × (1 + rate per slot × elapsed slots)
+new factor = old factor × (1 + rate per slot × elapsed slots)
```
-Elapsed slots is the current slot minus the slot of the last refresh, which the reserve stores. Rather than recalculating every borrower's debt, the market stores each borrower's principal and updates the one shared index. A borrower's current debt is their principal multiplied by that index. The *Arithmetic of Money* chapter covers how to build that index safely. The index advances by slots, not seconds.
+Elapsed slots is the current slot minus the slot of the last refresh, which the reserve stores. Rather than recalculating every borrower's debt, the market stores each borrower's principal and updates the one shared factor. A borrower's current debt is their principal multiplied by that factor. The *Arithmetic of Money* chapter covers how to build that factor safely. The factor advances by slots, not seconds.
-The perpetual futures venue does the same for funding, storing `cumulative_funding` alongside `last_funding_slot`. Both are indexes, both advance by elapsed slots, and both decide what somebody owes. They count slots because a trader who could stretch a slot could stretch what they owe.
+The perpetual futures venue does the same for funding, storing `cumulative_funding` alongside `last_funding_slot`. Both accumulate, both advance by elapsed slots, and both decide what somebody owes. They count slots because a trader who could stretch a slot could stretch what they owe.
## Timestamps Are Validator-Reported
@@ -62,7 +62,12 @@ A clock gives a program seconds, so counting days means dividing by 86,400. That
elapsed days = (now − time_started) / 86,400
```
-`contribute()` requires that count to be *less than* the duration. `refund()` requires it to be *at least* the duration. One expression, compared both ways, so there is no gap and no overlap between "you may still contribute" and "you may now refund": at the moment the count reaches the duration, contributions turn off and refunds turn on, by arithmetic rather than by a second stored flag anybody has to keep in step.
+Both handlers compare that one count against the same duration:
+
+- `contribute()` requires it to be *less than* the duration.
+- `refund()` requires it to be *at least* the duration.
+
+One expression, compared both ways, so there is no gap and no overlap between "you may still contribute" and "you may now refund". At the moment the count reaches the duration, contributions turn off and refunds turn on, by arithmetic rather than by a second stored flag anybody has to keep in step.
The trap is what "seven days" then means: the window ends at the first tick of day seven, not at the end of the seventh day.
@@ -80,7 +85,7 @@ A halt is a rare event: at the time of writing, block production has not stopped
No block is produced during a halt, so the slot count does not move. Wall-clock time does. The first block after a restart carries a slot roughly where the last one left off and a timestamp hours later. Three consequences:
-- **Per-slot accrual pauses with the chain.** The lending market's index advances by elapsed slots, so a nine-hour halt accrues no interest: nobody is charged for a market they could not reach. A fee accruing by timestamp charges the full nine hours.
+- **Per-slot accrual pauses with the chain.** The lending market's factor advances by elapsed slots, so a nine-hour halt accrues no interest: nobody is charged for a market they could not reach. A fee accruing by timestamp charges the full nine hours.
- **A window counted in seconds refuses the old price. One counted in slots accepts it, which is the dangerous half.** The vault strategy refuses a price older than sixty seconds, so its first call after a halt fails until a publisher posts again. The lending market's 25-slot window sees a price stamped one slot ago and, on the slot count alone, would accept a number nine hours old. A price stamped one slot ago is not always a price from four hundred milliseconds ago.
- **A deadline can pass with nobody able to act on it.** The fundraiser's day count runs through a halt like any other nine hours, so a contributor with an hour left finds the window shut when the chain returns. That program survives it because its refund path has no deadline; a design whose only exit was a narrow window would have stranded them.
@@ -112,8 +117,8 @@ Every program in Part II that touches time is in this table, with the two questi
## What You Learnt
- Solana has two clocks: the **slot**, whose progression the runtime guarantees, and the **unix timestamp**, which validators report.
-- Four questions decide the clock, asked in order: order of arrival takes a counter of your own, the age of a published number takes whatever unit its publisher stamped, a real-world time you promised a person takes the wall clock, and everything else takes the slot count.
-- To accrue interest or funding without touching every account, store each account's principal and one shared **cumulative index** that advances by elapsed slots. A current balance is the principal multiplied by the index.
+- Three questions decide the clock, asked in order: order of arrival takes a counter of your own, the age of a published number takes whatever unit its publisher stamped, and a real-world time you promised a person takes the wall clock. Three noes leave the slot count.
+- To accrue interest or funding without touching every account, store each account's principal and one shared **accumulation factor** that advances by elapsed slots. A current balance is the principal multiplied by the factor.
- A price needs a freshness window, each consumer sets its own, and the unit follows the source: slots where the program chooses, seconds where the feed published seconds.
- A deadline counted in whole days floors, so both sides of it must be computed from the same expression, and the window ends at the first tick of the last day. Say which edge you meant when you write the terms down.
- Where a clock would decide something valuable and neither clock is safe enough, count instead: the order book settles who queued first with a sequence number, and keeps its timestamp for display only.