Understanding oracle manipulation: designs and their failure modes
Spot prices, time-weighted averages, push feeds and pull feeds fail in different ways. A comparison of oracle designs, the assumptions each makes, and the checks a consuming contract must add.
- Written by
- Quantix Research, DeFi Security
- Published
- Reading time
- 7 minutes
- Category
- DeFi Security
Technical analysis. Built from the public sources in the bibliography. Incident details are as reported there; no new discovery is claimed.

Contents
- What an oracle is asked to answer
- Four designs, four failure modes
- Spot prices: one transaction is enough
- Time-weighted averages: cost grows with time
- Push feeds: staleness and the sequencer
- Pull feeds: the caller chooses the update
- Derived prices: LP tokens and vault shares
- Choosing and combining
- Conclusions
- Recommendations
- Bibliography
A lending market, a derivatives exchange or a stablecoin needs to know what an asset is worth, and the blockchain cannot tell it. The answer comes from an oracle, and the protocol inherits every weakness of the oracle it chooses. Price oracle manipulation is one of the attack classes systematised by Zhou et al. in their survey of DeFi attacks, and it recurs throughout public incident reports.
This analysis compares the four main oracle designs in use today, the assumption each one makes, how each has been or can be manipulated, and the checks a consuming contract has to add. It complements our primer on the cost of manipulation, which develops the economic inequality that decides whether a given design is safe enough.
What an oracle is asked to answer
A protocol usually wants a fair market price: the price at which a meaningful amount of the asset could be sold now. Every oracle answers a narrower question. A spot price answers “what is the marginal price in this pool right now”. A time-weighted average answers “what was the average price in this pool over the last window”. A push feed answers “what did a set of reporters agree the price was at their last update”. A pull feed answers “what did a set of publishers sign at some recent moment”. Manipulation happens in the gap between the question asked and the question answered.
Four designs, four failure modes
Spot prices: one transaction is enough
An automated market maker’s reserves imply a price, and reading it is cheap and always available. It is also the easiest price to move. An attacker can borrow a large amount with a flash loan, trade against the pool to move the reserves, interact with the protocol that reads them, and trade back, all within one transaction. The attacker pays fees and slippage on two trades, and nothing else.
// Simplified, illustrative. Do not price collateral this way.function spotPrice(IUniswapV2Pair pair) internal view returns (uint256) { (uint112 r0, uint112 r1, ) = pair.getReserves(); return (uint256(r1) * 1e18) / r0; // movable and restorable within the same transaction}Spot prices are acceptable only where the value that can be extracted at a manipulated price is smaller than the cost of moving it, which for collateral pricing is almost never the case.
Time-weighted averages: cost grows with time
Uniswap v2 pairs maintain cumulative price accumulators that add the current price multiplied by the time elapsed at the first interaction in each block. A consumer that records the accumulator at two moments can compute the average price between them. Uniswap v3 accumulates the tick, so averages are geometric means, and pools keep a configurable history of observations.
Because the accumulator is updated at the start of a block with the price from the previous one, moving the price within a transaction does not affect the average. To move a time-weighted average, an attacker must hold the manipulated price across blocks for a meaningful fraction of the window, while arbitrageurs trade against it. The cost rises with the window length and the pool’s liquidity.
Two cases weaken this protection. In pools with little liquidity, holding a price for the whole window may be cheap. And a party that produces consecutive blocks can hold a manipulated price between them without arbitrage in between, which reduces the cost of moving short windows. Longer windows trade manipulation resistance for responsiveness: a 30-minute average lags a genuine crash by up to 30 minutes, during which the protocol may accept collateral at a price the market no longer pays.
Push feeds: staleness and the sequencer
In a push design, a network of independent oracle nodes collects prices from data sources off-chain, aggregates them and writes a new value on-chain when the price deviates beyond a threshold or when a heartbeat interval elapses. Chainlink data feeds are the most widely used example. Manipulating such a feed requires moving the market price across the venues its sources aggregate, which is far more expensive than moving one pool.
The characteristic failures are elsewhere. A feed can be stale: if no update has occurred within the heartbeat, the consumer is reading an old value, possibly from before a large move. On rollups, if the sequencer is down, feeds may not update while some users can still act through other paths, so Chainlink publishes sequencer uptime feeds and recommends a grace period after the sequencer restarts. And a feed reports a market price, so if that market itself is thin or manipulable, the feed faithfully reports a manipulated price.
// Illustrative consumer of a push feed with the AggregatorV3Interface.uint256 constant MAX_AGE = 3_600; // set from the feed's documented heartbeatuint256 constant GRACE_PERIOD = 3_600; // after an L2 sequencer restarts function _price() internal view returns (uint256) { // L2 only: the sequencer uptime feed answers 0 when up, 1 when down. (, int256 status, uint256 startedAt, , ) = sequencerUptimeFeed.latestRoundData(); if (status != 0) revert SequencerDown(); if (block.timestamp - startedAt < GRACE_PERIOD) revert GracePeriodNotOver(); (, int256 answer, , uint256 updatedAt, ) = priceFeed.latestRoundData(); if (answer <= 0) revert InvalidPrice(); if (block.timestamp - updatedAt > MAX_AGE) revert StalePrice(); return uint256(answer) * 10 ** (18 - priceFeed.decimals());}Pull feeds: the caller chooses the update
In a pull design, publishers sign price updates off-chain at high frequency, and anyone can submit a signed update to the on-chain contract, which verifies it before storing it. Pyth’s pull model works this way: the user’s transaction typically includes the update it needs and pays a small fee, and the consumer then reads the price with a maximum age. Updates also carry a confidence interval reflecting disagreement between publishers.
The new risk is selection. Any recent signed update is valid, so a caller who can choose between several updates within the freshness window can submit the one most favourable to them. A consumer that accepts prices up to a minute old effectively lets the caller pick any price from that minute. The defences are tight age limits, using the confidence interval to widen the price against the user (or to refuse it when the interval is wide), and, for sensitive actions, requiring the update to be newer than the last action the user took.
Derived prices: LP tokens and vault shares
Many collateral assets are themselves claims on pools: liquidity provider tokens, vault shares, staking derivatives. Their price is derived by combining an underlying price with an exchange rate computed by another contract. If that exchange rate can be moved by a trade or a donation, the derived price is as manipulable as a spot price, however robust the underlying feed. Pricing an AMM liquidity token by its current reserves is the classic example; robust approaches value the position from the pool’s invariant and external prices instead. Vault share prices that can be inflated by donation, discussed in our ERC-4626 primer, and values read mid-call, discussed in the read-only reentrancy primer, are two further routes.
Choosing and combining
| Design | Cost of manipulation driven by | Characteristic failure | Consumer must check |
|---|---|---|---|
| Spot price | Fees and slippage of two trades in one pool | Moved and restored within one transaction | Do not use for collateral |
| TWAP | Pool liquidity × window length | Thin pools; lag behind real moves | Window length; pool liquidity; observation history |
| Push feed | Moving the market across aggregated venues | Stale values; sequencer downtime | Answer > 0; age against heartbeat; sequencer status |
| Pull feed | Publisher consensus, plus caller’s choice of update | Favourable update selection | Tight maximum age; confidence interval |
| Derived price | The weakest input in the derivation | Exchange rate moved by trade or donation | Manipulation resistance of every input |
No design is safe in every context, and robust protocols combine them: a primary feed with a secondary source used as a bound, deviation checks that pause sensitive actions when sources disagree, and circuit breakers that stop new borrowing when a price moves more than a set amount in a short period. The oracle integrity assessment case study and the Veyra Finance research assessment show these combinations evaluated against a modelled lending protocol.
Conclusions
Each oracle design answers a narrower question than “what is this worth”, and each is manipulated or fails in the gap: spot prices within a transaction, averages in thin pools and across controlled blocks, push feeds through staleness and downtime, pull feeds through the caller’s choice of update, and derived prices through their weakest input.
The consuming contract, not the oracle, is responsible for closing those gaps with checks that match the design it chose.
Recommendations
Protocol developers
- Never price collateral from a spot reserve ratio or any exchange rate that a trade or donation can move.
- For push feeds, check answer sign, age against the feed’s heartbeat and, on rollups, sequencer status with a grace period.
- For pull feeds, use a tight maximum age and apply the confidence interval conservatively.
Risk and governance
- Size borrow caps and collateral factors to the cost of manipulating each asset’s price source, not only its volatility.
- Add a secondary source as a bound and a circuit breaker for sudden moves.
Reviewers
- Trace every price the protocol consumes to its origin and identify which party can move it, and how cheaply.
- Test each consumer with a stale, zero, negative and extreme price.
Bibliography
Primary sources: standards, official documentation, incident reports and papers.
- 1Data Feeds(opens in a new tab)Chainlink documentation
- 2L2 Sequencer Uptime Feeds(opens in a new tab)Chainlink documentation
- 3Oracles (Uniswap v2)(opens in a new tab)Uniswap documentation
- 4Price oracles (Uniswap v3)(opens in a new tab)Uniswap documentation
- 5Pull updates(opens in a new tab)Pyth Network documentation
- 6SoK: Decentralized Finance (DeFi) Attacks(opens in a new tab)Zhou et al., arXiv:2208.13035
Applied on this site
Assessments, case studies and tools that apply this publication.







