Every DeFi protocol that lends, liquidates, mints or settles has a line of code that reads a number and believes it. That number is the price. From an attacker's point of view it is the most valuable input in the system, because everything downstream — collateral ratios, redemption amounts, liquidation thresholds — is a deterministic function of it. Move the number and you do not need to break the code. The code will pay you correctly for a world that does not exist.

Most large flash-loan exploits were not bugs in arithmetic or access control. They were protocols that asked a manipulable source what an asset was worth and acted on an answer the attacker had written.

This article is for engineers building anything that consumes a price on-chain, and for the people who decide whether that integration is safe enough to put money behind. We cover what each oracle design promises, how each is attacked, the checks that belong between the feed and your logic, and how our auditors test the whole path.

The oracle is a trust assumption, not a utility

An oracle is a party you have decided to trust to tell you the truth about something your contract cannot observe. Every design moves that trust somewhere — onto a committee of node operators, onto the liquidity depth of a pool, onto a keeper you run yourself — and the first job of a review is to say where it went.

Write the assumption down in one sentence: "We trust that the median of these reporters, updated at least every heartbeat, is within one percent of the real market price." Then ask what it costs an attacker to make that false, what the protocol does when it is false, and who finds out. Most oracle vulnerabilities are a failure to ask the second question.

A taxonomy of price sources

Each design makes a different promise and fails in a different way.

Push feeds

Off-chain reporters observe markets, aggregate, and write a value on-chain on a heartbeat or when the price moves past a deviation threshold. The value you read is the last thing they wrote, which may be seconds or hours old. Push feeds resist single-transaction manipulation because the attacker cannot write to them, but they can be stale or paused, and the committee can be wrong or compromised.

Pull or on-demand feeds

The consumer fetches a signed price report off-chain and submits it in the transaction that uses it; the contract verifies signature and timestamp. The data is fresh, but freshness becomes your responsibility: reject reports older than your tolerance, and make sure the submitter cannot pick a favourable report from a window of recent ones. An attacker who can choose between a report from twelve seconds ago and one from now will choose whichever liquidates you.

Time-weighted average prices

An AMM pool accumulates its price over time, and a TWAP contract reads the accumulator at two points and divides. Moving it requires holding the pool at a distorted price across multiple blocks, which costs capital and exposes the attacker to arbitrage. TWAPs resist flash loans but lag the market, and that lag is itself an attack surface during fast moves.

On-chain AMM spot

Reading getReserves() and dividing one reserve by the other. This is not an oracle; it is the state of a pool that anyone can change within the same transaction for the cost of a swap fee. We still find it in production code every year, usually inherited from a fork or added as a "fallback".

Custom keepers and self-reported prices

A protocol-run bot posting prices from an exchange API, or an admin key setting a value by hand. This concentrates all oracle trust in one key and one server. It is sometimes the only option for a long-tail asset, but it must be labelled as what it is: a centralised feed whose compromise is a total loss.

Do not read prices from manipulable sources

Flash-loan spot manipulation, step by step

  • Borrow. The attacker takes an uncollateralised flash loan of a large amount of token A, repayable within the transaction.
  • Distort. They swap most of it into token B through the pool your protocol reads. The reserves shift and the spot price of B in terms of A rises sharply.
  • Exploit. They interact with your protocol, which reads the pool, concludes B is worth far more than a moment ago, and lets them borrow against it at the inflated valuation, mints them synthetic assets, or settles a position in their favour.
  • Restore. They swap B back into A, returning the pool to roughly its original state and recovering most of the capital.
  • Repay. They repay the loan plus fee and keep the difference, which came out of your reserves.

Every step is atomic. There is no window for arbitrageurs, no time for a keeper to react, and no price history that reveals the spike except the one block in which it happened. The cost is swap fees and the loan fee; the gain is bounded only by how much your protocol will pay out at the distorted price. The pre-conditions are a price derived from pool state in the same transaction, a payout that scales with that price, and enough flash-loan liquidity to move the pool. The first is the one you control.

TWAP manipulation economics

TWAPs remove the atomic version of the attack but not the attack. The attacker must hold the pool at a distorted ratio for a meaningful share of the averaging window, paying arbitrageurs the whole time. Two conditions make this affordable: a short window and a thin pool. A thirty-minute TWAP on a shallow pool on an L2 with cheap blocks can be moved for far less than the position it unlocks.

Multi-block attacks are the realistic form. A builder controlling consecutive block proposals sets the pool to a distorted price at the end of one block and leaves it there through the start of the next, so the accumulator records the distortion with no opportunity for anyone else to trade. The economics change from "pay arbitrageurs for thirty minutes" to "control two adjacent blocks". If two blocks are a large share of your window, you are running a spot oracle.

The defence is longer windows, deeper pools, and cross-checking the TWAP against an independent source rather than treating it as a replacement for a committee feed.

Sanity-check everything you receive

A push feed removes the flash-loan problem and introduces a different one: the number you read may be old, frozen, or wrong, and the contract will not tell you unless you ask. Every read should pass through a validation layer before it touches your logic.

Staleness and heartbeat checks

Every feed has a maximum update interval. If the returned timestamp is older than that interval plus a margin, the read should revert or degrade. Three recurring mistakes: one threshold for every feed when heartbeats differ by asset; checking the wrong field so the check always passes; and a threshold so generous that a feed can be hours behind during the volatility that matters. The threshold is per feed, derived from the published heartbeat, and tested.

Deviation thresholds and circuit breakers

Compare the new value with the previous one, or with a second source. If the move exceeds what the asset could plausibly do in that interval, pause the affected operation. A circuit breaker that halts liquidations on a fifty percent one-block move costs minutes of downtime in the rare legitimate case and prevents a total loss in the manipulated one. The pause should be loud: an event, an alert, and a process for resuming.

Also handle the min and max answer bounds some feed contracts enforce internally. If a feed clamps at a floor, a real crash below that floor is reported as the floor, and your protocol values collapsed collateral at a price that no longer exists.

Zero, negative, and round-number answers

A feed returning zero, a negative integer, or the same value for many consecutive rounds is reporting a failure state, not a price. Each should be an explicit revert path. Treating a zero as "free" has been the mechanism behind more than one drained lending market.

Decimals, inversion, and the arithmetic around the oracle

Some of the most embarrassing oracle bugs are about units, not trust. Feeds report with a fixed number of decimals that varies by feed and does not match the token's own. Multiplying an eight-decimal price by an eighteen-decimal balance without normalising puts you off by ten orders of magnitude, and the direction decides whether users get nothing or everything.

Pair inversion is the second trap. A feed quoted as A per B is the reciprocal of B per A; if the feed address is later switched to the opposite orientation, every valuation becomes nonsense. We check that orientation is enforced rather than assumed, and that derived prices — A per C from A per B and B per C — handle decimals at each step.

Bridged assets deserve their own feed decision. A bridged token carries the bridge's solvency risk and can depeg while the canonical feed stays flat. Pricing it with the canonical feed is a decision to make deliberately, not inherit silently.

L2 sequencer status

On rollups, the sequencer can go down. While it is down, push feeds cannot update and every price freezes. When it returns, transactions are processed against out-of-date prices, and users whose positions became liquidatable during the outage have had no chance to act.

The mitigation is a sequencer uptime feed: before trusting any price, check that the sequencer is up and has been up for longer than a grace period, and otherwise reject price-sensitive operations. We find this missing in many L2 deployments ported from mainnet, because the mainnet code never needed it.

Pricing LP tokens and other derived assets

Accepting an LP token as collateral means valuing something whose composition changes with every trade. The naive approach — price each reserve, sum, divide by LP supply — reintroduces spot manipulation through the back door, because the reserves are exactly what a flash loan distorts.

Fair LP pricing instead computes what the reserves would be at externally observed prices, assuming the invariant holds, and values the LP token from that. For constant-product pools this follows from the invariant and two external prices; stable-swap and concentrated-liquidity pools need per-implementation review. A yield-bearing wrapper adds another layer: its exchange rate is itself a value an attacker may move, typically by donating to the underlying vault.

A related failure is circularity: collateral priced from a pool whose other side is the protocol's own debt token means the protocol is pricing its own liabilities.

Oracle governance and feed addresses

Where does the feed address live, and who can change it? If it is immutable, nobody can, which is safe until a deprecated feed bricks the market. If it sits behind an admin setter, the admin — or whoever phishes the admin — can point your protocol at a contract that returns any number they like, bypassing every sanity check above because they control both value and timestamp.

Feed changes should go through a timelock long enough for users to exit, emit events that monitoring watches, and validate the new address at set-time: decimals match, description matches, a fresh round exists. Where the feed is upgradeable by its operator, that upgrade process is part of your threat model.

Fail safe, not open

When the oracle path cannot produce a value you can defend, the protocol must refuse to act, not act on a default. A revert on staleness is annoying. Continuing with a stale or zero price is a loss event.

Design the degraded modes deliberately. Deposits and repayments are safe with no price at all; borrows, liquidations and redemptions should be blocked. A good design lets users reduce their risk when the oracle is unavailable and never lets anyone increase their claim on the protocol.

How we test oracle integrations

On an audit, the oracle review is a distinct workstream.

  • Source inventory. Every place a price enters the system, including indirect reads through other protocols' view functions, classified by the taxonomy above.
  • Manipulation cost. For each manipulable source, what it costs to move the price enough to profit, given pool depth and the protocol's payout function. If cost is below gain, that is a Critical finding whether or not we build the full exploit.
  • Validation review. We read the staleness, bounds, deviation and sequencer checks line by line, then write tests that feed the contract a stale round, a zero answer, a negative answer, a frozen round ID, mismatched decimals and a flipped pair, and confirm each reverts where it should.
  • Fork simulation. Where a source is manipulable, we build the attack on a mainnet fork: flash loan, distortion, interaction, restore, repay.
  • Governance paths. Who can change every feed address, and what timelock and monitoring sit around that path.
  • Dependency graph. Every cycle and every derived asset priced from pool state.

Practical next steps

Write the trust sentence for every asset you price. Replace any read of pool reserves with a committee feed or a long-window TWAP cross-checked against one. Add per-feed staleness bounds, a deviation circuit breaker, explicit handling for zero and clamped answers, and a sequencer check on every L2 deployment. Put feed address changes behind a timelock with monitoring. Then write the failing tests before we do.

Oracle integrations are reviewed on every DeFi engagement alongside our audit checklist. Scope is on the services page; reach the team on Telegram.