# Stale oracle cache bypasses guards

Cache or TTL fast-path returning prices before freshness, enabled-flag, or pool-update checks run

_Area: DeFi oracles, liquidations and flash loans. Seen in 2 disclosed reports; latest 2026-08-20. Updated 2026-11-01._

## How it works
A price is read from a cache, TTL store, or external pool's cumulative accumulator that was last written at some earlier moment. The code trusts that stored value and returns it before the checks that make a price safe — freshness, an enabled/disabled flag, or an explicit refresh of the source pool — ever execute. The assumption that breaks is "a cached price is equivalent to a freshly validated price." It is not: the cache records the past, while the guards describe the present. When the fast path short-circuits ahead of the guards, the guards never run for that read.

## Where it shows up
- Batch or vector price accessors with a cache branch that `continue`s before per-asset config is loaded [E2](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-25.md).
- A single-asset path that rejects disabled assets while the batch path does not — same contract, different guard order [E2](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-25.md).
- TWAP readers that pull cumulative prices from an external pool without first triggering that pool's internal update [E1](https://bugbountylibrary.com/r/sherlock%3A2023-12-ubiquity%3AM-1.md).
- State (bins, TWAP) advanced after the value transfer rather than before, so a cheap or zero-amount call can move it first [E3](https://bugbountylibrary.com/r/c4%3A2022-12-stealth-project%3AM-03.md).
- Any `if cached { return }` placed above authorization, enablement, or freshness logic.

## How to review
For every price read, trace the exact order of operations and confirm the invariant: **no price is returned to a consumer until freshness, enablement, and source-refresh checks have run on that same call path.** Concretely: diff single-item and batch accessors line by line and confirm identical guard ordering [E2](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-25.md); for external TWAPs, confirm the integration calls whatever update function the source pool requires before reading cumulatives [E1](https://bugbountylibrary.com/r/sherlock%3A2023-12-ubiquity%3AM-1.md); confirm state that prices depend on is updated before, not after, the swap/value step [E3](https://bugbountylibrary.com/r/c4%3A2022-12-stealth-project%3AM-03.md).

## How finders spotted it
- Read the TWAP update function and noticed it read the Curve metapool's cumulatives without invoking the metapool's own `_update()` [E1](https://bugbountylibrary.com/r/sherlock%3A2023-12-ubiquity%3AM-1.md).
- Read the batch endpoint, saw the cache `continue` precede the `config.enabled` check, then compared to the single-asset path that rejects disabled assets [E2](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-25.md).
- Noticed `_moveBins` ran after the swap, reasoned a zero-amount swap could advance state cheaply, and confirmed with two balance-comparison scenarios [E3](https://bugbountylibrary.com/r/c4%3A2022-12-stealth-project%3AM-03.md).

## Variants seen
- External-pool TWAP stale because the source's internal update was never called [E1](https://bugbountylibrary.com/r/sherlock%3A2023-12-ubiquity%3AM-1.md).
- TTL cache fast-path skipping a disabled-asset guard on a batch endpoint [E2](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-25.md).
- Post-swap state update letting a null swap freeze bin position at a stale price [E3](https://bugbountylibrary.com/r/c4%3A2022-12-stealth-project%3AM-03.md).

## Impact and severity
Mint/redeem outside intended price thresholds with potential value extraction [E1](https://bugbountylibrary.com/r/sherlock%3A2023-12-ubiquity%3AM-1.md); continued borrowing against collateral whose oracle was disabled [E2](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-25.md); trades executed at a stale bin position for better-than-fair pricing [E3](https://bugbountylibrary.com/r/c4%3A2022-12-stealth-project%3AM-03.md). Impact rises when the bypassed guard is a risk control (disabled asset) rather than a mere efficiency cache, and when the stale price feeds a lending or mint path where value can be extracted repeatedly.

## Evidence
- E1: https://bugbountylibrary.com/r/sherlock%3A2023-12-ubiquity%3AM-1.md
- E2: https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-25.md
- E3: https://bugbountylibrary.com/r/c4%3A2022-12-stealth-project%3AM-03.md
