TWAP Oracle Remediation
Overview
Related Detector: TWAP Oracle
A time-weighted average price resists single-block manipulation, but a window that is too short — or a pool with insufficient observation cardinality — is still cheap to move, especially on L2s with fast blocks and centralized sequencing. The remediation is to size the window to the cost of manipulation, ensure the pool stores enough observations, and cross-check the TWAP against an independent source.
Recommended Fix
Before (Vulnerable)
// A very short window (or spot read dressed up as a TWAP) is manipulable
// by an attacker who controls a few consecutive blocks.
uint32 window = 60; // seconds
uint256 price = pool.consult(token, window);
After (Fixed — adequate window, cardinality, and a sanity bound)
uint32 constant WINDOW = 1800; // 30 minutes — sized to make manipulation costly
function price(IUniswapV3Pool pool, address token) external view returns (uint256 p) {
// Ensure the pool can actually serve the window.
(, , , , uint16 cardinality, , ) = pool.slot0();
require(cardinality >= requiredCardinality(WINDOW), "Grow observations");
p = _consultTwap(pool, token, WINDOW);
// Cross-check against an independent feed and bound the deviation.
uint256 ref = chainlinkFeed.latestAnswerChecked();
require(_within(p, ref, MAX_DEVIATION_BPS), "Oracle deviation");
}
Call increaseObservationCardinalityNext at deployment so the pool retains enough observations for the chosen window.
Common Mistakes
- Treating any TWAP as manipulation-proof; a short window on a thin pool is still movable within a handful of blocks.
- Reading a TWAP from a pool whose observation cardinality is too low to cover the window, silently falling back to a shorter effective average.
- Relying on a single oracle with no deviation bound against an independent source.