# Stale oracle bounds and decay

Cached oracle limits or lazy price decay not refreshed before use or migration

_Area: DeFi oracles, liquidations and flash loans. Seen in 3 disclosed reports; latest 2025-02-18. Updated 2026-10-30._

## How it works
A price or bound is computed once and reused, but the thing it depends on can change. The code assumes a value read earlier (at construction, at last rebalance) still describes the current world. When the dependency moves — a Chainlink proxy swaps its aggregator, a Dutch-auction price should have decayed over idle epochs, a feeder's quote drifts from market — the stored value is stale and the protocol acts on a wrong price.

## Where it shows up
- Constructor reads of `aggregator().minAnswer`/`maxAnswer`/`decimals` stored in state [E2](https://bugbountylibrary.com/r/sherlock%3A2022-11-isomorph%3AM-11.md)
- Price decay applied only inside rebalance/modifyLiquidity, never on read or before a terminal action [E3](https://bugbountylibrary.com/r/cantina%3Adoppler%3A3.2.3.md)
- Migration or settlement paths that consume the last stored price without recomputing [E3](https://bugbountylibrary.com/r/cantina%3Adoppler%3A3.2.3.md)
- Oracle feeders backed by a single DEX pool or updated infrequently [E1](https://bugbountylibrary.com/r/dhl%3A20240223-compounduni.md)
- Bounds checks that revert when a fresh aggregator's limits differ from cached ones [E2](https://bugbountylibrary.com/r/sherlock%3A2022-11-isomorph%3AM-11.md)

## How to review
For every cached price, bound, or decayed value, ask: what external state does it depend on, and can that state change between the read and the use? The invariant: any value used to price a borrow, swap, or migration must be derived from current on-chain state at the point of use, or the code must detect and refresh staleness. Check that aggregator-specific values are read per-call from the proxy [E2](https://bugbountylibrary.com/r/sherlock%3A2022-11-isomorph%3AM-11.md), that decay is applied lazily on read so idle epochs still count [E3](https://bugbountylibrary.com/r/cantina%3Adoppler%3A3.2.3.md), and that the oracle price is sanity-checked against an independent market reference [E1](https://bugbountylibrary.com/r/dhl%3A20240223-compounduni.md).

## Variants seen
- Cached Chainlink aggregator bounds break after proxy aggregator swap [E2](https://bugbountylibrary.com/r/sherlock%3A2022-11-isomorph%3AM-11.md)
- Inactivity decay skipped for epochs with no rebalance, migration at stale high price [E3](https://bugbountylibrary.com/r/cantina%3Adoppler%3A3.2.3.md)
- Feeder returns stale/undervalued price enabling cheap borrow [E1](https://bugbountylibrary.com/r/dhl%3A20240223-compounduni.md)

## Impact and severity
Undervalued borrows and profitable swaps drained value (~$439k in E1); migration executed at the wrong price in E3; priceLiquidity reverts in E2. Severity rises when the stale price gates borrowing or a one-shot migration, and when no independent price check exists.

## Why fixes fail
Refreshing only on state-changing calls leaves read-only or terminal paths stale [E3](https://bugbountylibrary.com/r/cantina%3Adoppler%3A3.2.3.md); reading bounds once at deploy ignores later proxy changes [E2](https://bugbountylibrary.com/r/sherlock%3A2022-11-isomorph%3AM-11.md).

## Evidence
- E1: https://bugbountylibrary.com/r/dhl%3A20240223-compounduni.md
- E2: https://bugbountylibrary.com/r/sherlock%3A2022-11-isomorph%3AM-11.md
- E3: https://bugbountylibrary.com/r/cantina%3Adoppler%3A3.2.3.md
