# Stale rate in same-block accrual

Rate or index consumed by burn/withdraw/default math without refresh after an intra-block state change

_Area: DeFi accounting and math bugs. Seen in 3 disclosed reports; latest 2025-11-05. Updated 2026-10-24._

## How it works
A rate, index or share price is cached and refreshed only on discrete events (a `drip`, `update_rate`, an interest accrual). The code assumes the rate is fixed for the block, so it skips the refresh when the timestamp already matches, or it mixes a live balance with a stale preview. That assumption breaks when something else in the same block moves the underlying state: a default, a liquidation, a donation, a penalty transfer, or a drip on a sibling contract. The burn or withdraw math then uses the old, higher rate while the pool settles at the new, lower one, so the caller receives more value than the state supports.

## Where it shows up
- Early-return guards like `if (now == last_update_timestamp) return;` on a rate/index refresh.
- Withdraw or burn paths that read a cached rate instead of recomputing it.
- Two independently accruing contracts (debt fee vs savings yield) updated by separate drip calls [E1](https://bugbountylibrary.com/r/h1%3A665798.md).
- Profit/loss formulas mixing a live balance with a stale `previewRedeem` or similar [E3](https://bugbountylibrary.com/r/cantina%3Amorpho_jul2025%3A3.1.3.md).
- Any path that raises a balance without raising the idle/accounted balance (donations, penalties) [E3](https://bugbountylibrary.com/r/cantina%3Amorpho_jul2025%3A3.1.3.md).

## How to review
List every state variable that can change intra-block (defaults, liquidations, oracle updates, donations, sibling drips) and check whether each cached rate is refreshed before it is consumed by burn, withdraw or default math. The invariant: the rate used to compute shares burned must equal the rate the pool applies when settling that same operation, even if the rate changed earlier in the block. For E3, check that the profit formula compares balance against a freshly computed asset value, not a stale preview, and that `balance > idleBalance` cannot be read as profit. Trace the timestamp-equality shortcut against the intra-block mutator list.

## Variants seen
- Deposit-then-drip-then-exit in one tx captures yield on DAI never held over time [E1](https://bugbountylibrary.com/r/h1%3A665798.md).
- Same-block default drops `b_rate`; withdraw burns too few b_tokens at the stale higher rate, leaving the vault insolvent [E2](https://bugbountylibrary.com/r/c4%3A2025-02-blend-v2-audit-certora-formal-verification%3AM-11.md).
- Live balance vs stale `previewRedeem` flips a loss into reported profit, mispricing same-block depositors/withdrawers [E3](https://bugbountylibrary.com/r/cantina%3Amorpho_jul2025%3A3.1.3.md).

## Impact and severity
Riskless yield extraction [E1](https://bugbountylibrary.com/r/h1%3A665798.md), vault insolvency from under-burned shares [E2](https://bugbountylibrary.com/r/c4%3A2025-02-blend-v2-audit-certora-formal-verification%3AM-11.md), and mispriced shares from premature loss accounting [E3](https://bugbountylibrary.com/r/cantina%3Amorpho_jul2025%3A3.1.3.md). Severity rises when the stale rate feeds a burn amount or solvency check, and when the triggering state change is cheap or attacker-induced within one transaction.

## Evidence
- E1: https://bugbountylibrary.com/r/h1%3A665798.md
- E2: https://bugbountylibrary.com/r/c4%3A2025-02-blend-v2-audit-certora-formal-verification%3AM-11.md
- E3: https://bugbountylibrary.com/r/cantina%3Amorpho_jul2025%3A3.1.3.md
