# Oracle window misconfiguration off-by-one

Check TWAP/EMA window units, loop boundaries, and ring-buffer resize for wrong averaging

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

## How it works
An averaging oracle is only as correct as its window. The code assumes the window is measured in the right unit, over the right samples, with the boundary observation included. When any of those assumptions break — a period passed as a sample count, a loop that exits one observation early, a resize that leaves stale indices — the average is computed over the wrong set of data. The contract still returns a number, so nothing reverts; the price is simply wrong, and every decision built on it (rebalance, liquidation, funding) inherits the error.

## Where it shows up
- EMA/TWAP helpers taking a `period`/`window` argument: is it seconds or sample count? Cross-check against the actual update cadence [E2](https://bugbountylibrary.com/r/c4%3A2025-08-gte-perps-and-launchpad%3AM-03.md).
- Lookback loops comparing a timestamp from one observation while evaluating the tick of another [E1](https://bugbountylibrary.com/r/sherlock%3A2025-02-yieldoor%3AM-5.md).
- Ring-buffer resize paths (`setSampleMemory`-style) that change capacity without reindexing existing samples or fixing the counter modulo [E3](https://bugbountylibrary.com/r/c4%3A2021-11-malt%3AM-07.md).
- TODO comments or empty branches in shrink/grow logic — strong signal of unhandled index alignment [E3](https://bugbountylibrary.com/r/c4%3A2021-11-malt%3AM-07.md).
- Constants like `900` or `15 minutes` near an averaging call.

## How to review
The invariant: the observations averaged must be exactly those inside the intended time window, and each observation's value must be paired with its own timestamp. Concretely:
- Confirm the window unit matches the update cadence (900 samples at 10s = 2.5h, not 15min) [E2](https://bugbountylibrary.com/r/c4%3A2025-08-gte-perps-and-launchpad%3AM-03.md).
- In lookback loops, verify the timestamp gating the exit belongs to the same observation whose cumulative value was just used [E1](https://bugbountylibrary.com/r/sherlock%3A2025-02-yieldoor%3AM-5.md).
- For any resize, confirm existing entries are reindexed and the counter modulo stays consistent with the new capacity [E3](https://bugbountylibrary.com/r/c4%3A2021-11-malt%3AM-07.md).
- Compare the security doc's stated window against the code's actual span.

## Variants seen
- Off-by-one loop exit: boundary timestamp from second-most-recent observation, tick from most recent [E1](https://bugbountylibrary.com/r/sherlock%3A2025-02-yieldoor%3AM-5.md).
- Unit mismatch: seconds constant consumed as snapshot count, 10x window [E2](https://bugbountylibrary.com/r/c4%3A2025-08-gte-perps-and-launchpad%3AM-03.md).
- Ring-buffer resize without reindexing; shrink branch only sets `activeSamples` [E3](https://bugbountylibrary.com/r/c4%3A2021-11-malt%3AM-07.md).

## Impact and severity
Wrong averages led to rebalancing at bad prices [E1](https://bugbountylibrary.com/r/sherlock%3A2025-02-yieldoor%3AM-5.md), skewed funding and lagging mark price [E2](https://bugbountylibrary.com/r/c4%3A2025-08-gte-perps-and-launchpad%3AM-03.md), and near-zero or reverting exchange rates corrupting downstream stabilization [E3](https://bugbountylibrary.com/r/c4%3A2021-11-malt%3AM-07.md). Severity is high where the oracle gates value transfer; worse when the misconfiguration is silent and persistent rather than a one-off.

## Evidence
- E1: https://bugbountylibrary.com/r/sherlock%3A2025-02-yieldoor%3AM-5.md
- E2: https://bugbountylibrary.com/r/c4%3A2025-08-gte-perps-and-launchpad%3AM-03.md
- E3: https://bugbountylibrary.com/r/c4%3A2021-11-malt%3AM-07.md
