Skip to content
HomeAboutServicesGuidesContact

Guide

Oracle manipulation and stale feeds

Two different failures, routinely treated as one. An attacker moving a price and a feed that stopped moving need opposite defences.

In short

  • Manipulation and staleness are opposite problems. One is an attacker moving a number; the other is a number that stopped moving while the market did not.
  • A time-weighted average defends against the first and makes the second worse, because averaging over a window is a deliberate decision to lag.
  • Most stale-price losses require no attacker at all. The feed halts, the consuming contract keeps using the last value, and ordinary traders take the other side.
  • A price that cannot be trusted should stop the system rather than be used anyway. Defaulting through a failed feed converts an availability problem into a solvency one.

Two failures, one label

Oracle risk is usually discussed as a single category, and the two things inside it behave nothing alike.

In manipulation, an attacker spends capital to move a reported price, acts against the distorted figure, and restores it. The price is wrong because someone made it wrong, briefly and deliberately.

In staleness, nobody does anything. A feed stops updating, the consuming contract keeps reading the last value it received, and the market moves away from it. The price is wrong because it stopped being right.

In plain terms: the first is an attack and the second is a malfunction, and a defence built for one can make the other worse. Treating them as one category is how a protocol ends up protected against the rarer failure and exposed to the common one.

Manipulation: reading a pool as a price

The classic form is a contract that reads the reserve ratio of an on-chain pool and treats it as the market price of the asset. That ratio is not a price. It is the current state of one venue, and a venue can be traded against.

An attacker borrows capital, trades heavily into the pool to move the ratio, triggers whatever the protocol does with that figure — a loan against inflated collateral, a liquidation at a favourable rate, a mispriced redemption — then trades back and repays. The whole sequence completes in one transaction.

Flash loans did not create this vulnerability. They removed the capital requirement that made it impractical, which turned a theoretical weakness affecting well-funded attackers into a routine one available to anyone.

In plain terms: if a number can be moved by trading, and your protocol acts on that number within the same transaction, the number is an input the attacker controls rather than a fact about the world.

Where TWAP helps, and where it does not

A time-weighted average price samples over a window rather than reading an instant, so a single-block distortion is diluted by every other block in the window. Against the attack above, that is effective and it is the standard answer.

It is not free, and the cost is in three places.

A window long enough to resist manipulation is long enough to lag a real move. During genuine volatility the average reports a price the market left some minutes ago, which is precisely when liquidations need to be accurate.

On a thin pool, the averaging does not help enough. The cost of holding a distorted price across a window scales with the liquidity an attacker has to fight, and on a low-liquidity pair that cost can stay within reach.

And an average still has to be read. A consuming contract that computes a correct TWAP and then fails to check how old the underlying observations are has moved the staleness problem rather than solved it.

In plain terms: TWAP is a trade of responsiveness for resistance. It is the right trade for most lending collateral and the wrong one for anything that must act on the current price.

Staleness: the failure with no attacker

A reporting feed publishes on a heartbeat, updating at least every interval and sooner if the price deviates past a threshold. The consuming contract receives a value and a timestamp saying when it was set.

The defect is simple and extremely common: the contract reads the value and ignores the timestamp. If the feed halts, the contract keeps returning the last known figure as though it were current, and every operation priced against it is priced against the past.

No exploit is required. Ordinary traders take the profitable side of a stale quote because it is profitable, and the loss lands on whoever is on the other side, usually liquidity providers who never agreed to that risk.

A review of ours reproduced this on a fork: a thirty-minute feed halt while the market moved twelve percent, with the pool accepting swaps at the stale rate throughout. Nothing in the contract was broken, and the entire window was a free option for anybody paying attention.

In plain terms: check the timestamp, and check the round the answer came from. A value with no freshness test is a value from an unknown moment.

What to do when the price cannot be trusted

The decision that matters is what happens on failure, and it is usually made by omission.

Defaulting through means continuing to operate with the last value, which is the behaviour a contract has if nobody wrote the alternative. It keeps the protocol available and prices every operation wrongly for as long as the condition lasts.

Pausing means refusing to act while the input is untrustworthy. Deposits, swaps and liquidations stop until a fresh value arrives. The protocol becomes unavailable, which is visible, complained about, and recoverable.

In plain terms: an availability problem is survivable and a solvency problem is not. A protocol that halts on a stale feed has an incident; a protocol that keeps trading on one has a loss.

The same reasoning applies to deviation. A new value that differs from the previous one by an implausible margin is more likely to be a malfunction or a manipulation than a real move, and a sanity bound that rejects it costs almost nothing.

The oracle is a trust assumption, not a fact

Every price feed is a set of parties and a set of rules about what happens when they disagree or stop. Those are assumptions, and they belong in the same register as admin keys rather than in the register of inputs.

Three questions define the assumption. Who can update the feed, and what would it take for them to publish a wrong value. What happens if they simply stop. And if several sources are aggregated, how is disagreement resolved — a median tolerates one bad source, an average does not.

A protocol depending on a feed inherits every one of those answers. A system can be flawless at the contract level and still be exactly as trustworthy as the weakest party in its price path.

What to ask of your own system

Five questions, answerable without a review.

Every place we read a price: which of them read an instantaneous value, and could that value be moved by a trade in the same transaction?

Where we use an average, how long is the window, and what does the protocol do during a real move that outruns it?

Do we check the timestamp and the round on every read, or only on some?

On a stale or implausible value, does the protocol pause or carry on? If nobody wrote that branch, it carries on.

If the feed stopped permanently this afternoon, what is the sequence of events, and who loses money first?

Questions

We use a major reporting network. Is manipulation still a concern?

Direct manipulation of a well-aggregated feed is hard and expensive, so the risk shifts rather than disappearing. What remains is staleness during network stress, the deviation threshold allowing a real gap before an update fires, and any secondary source used as a fallback being weaker than the primary.

Is a TWAP always safer than a spot price?

No. It is more resistant to manipulation and less accurate during real movement. For liquidation thresholds that lag can be the more expensive property, which is why the window length is a design decision rather than a default.

How long is too long for a price to be stale?

It depends on the volatility of what is being priced, not on a universal figure. The usable rule is the feed own heartbeat plus a grace window, because that is the interval the publisher committed to. Anything older is outside what was promised.

Can monitoring catch this after deployment?

Better than most classes. A feed that stops updating, or publishes a value far from other venues, is visible in real time and a rule can fire on it. That is the case for monitoring alongside a review rather than instead of one.

The work behind this

Related reading