How does an off-chain price get on-chain — and how is it attacked?
A smart contract is deterministic and cannot reach outside the chain — it cannot query an exchange or read a website. Yet every lending market, every liquidation and every derivative needs a price. Resolving that contradiction creates DeFi's most persistent attack surface.
Why the problem exists at all
Every node must re-execute a transaction and reach the same result. If a contract could make a network call, two nodes could receive different answers and consensus would break. So external data cannot be pulled. It must be pushed in by an ordinary transaction that someone pays for.
That someone is an oracle: not magic, just an address that has been given the right to write a number into a contract's storage, and a set of rules about who may do so.
The three designs
- Off-chain reporter networks. Independent operators observe prices across many venues, sign their values, and a contract aggregates them — typically as a median — publishing on a deviation threshold or a heartbeat. Corrupting a majority of reporters is one attack path, and rarely the cheapest. The others are moving the source markets the honest reporters are faithfully reading, exploiting the aggregation rule itself, and compromising whoever may add or remove a transmitter. The residual risk is staleness: between updates, the on-chain price is simply old.
- On-chain spot price from a pool. Read the reserve ratio of an AMM directly. Instantaneous, trustless, free — and the source of nearly every oracle exploit in the record, for the reason below.
- Time-weighted average price (TWAP). Average an on-chain price over a window. Manipulating an average across many blocks costs vastly more than manipulating one instant; the trade-off is lag, which is precisely what a liquidation engine cannot afford.
Why a spot price from a pool is manipulable — with the arithmetic
From Unit 2, an AMM's spot price is k ÷ x². So moving the reserve moves the price quadratically:
new spot ÷ old spot = (x ÷ x_new)²
Worked. A pool holds 1,000 X and 2,000,000 USDC; k = 2,000,000,000; spot = 2,000. An attacker buys 900 X, leaving 100:
new USDC reserve = 2,000,000,000 ÷ 100 = 20,000,000 new spot = 20,000,000 ÷ 100 = 200,000
The reported price is now 100× reality — consistent with (1,000 ÷ 100)² = 100. Any contract reading that pool as its oracle now believes token X is worth $200,000.
Flash loans remove the capital requirement
That attack cost about $18 million of USDC. Historically, that constraint was the defence. Composability removed it.
A flash loan exploits atomicity: borrow up to whatever the pool is holding, with no collateral, on the condition that it is repaid within the same transaction — plus a fee, and the gas for everything you did in between. If it is not, the whole transaction reverts and it is as if nothing happened — so the lender is never at risk and the loan needs no underwriting. It is a genuinely novel primitive with no traditional-finance equivalent.
It also means the capital barrier to manipulation is roughly zero. The attack template:
- Flash-borrow a very large sum.
- Trade into a thin pool to distort its spot price.
- Call a victim protocol that uses that pool as its oracle — borrow against wildly overvalued collateral, mint against a false price, or liquidate healthy positions.
- Reverse the pool trade.
- Repay the flash loan and keep the difference.
All five steps in one transaction. No capital, no counterparty risk, and no bug in the victim's code — its logic was correct, its input was false.
The documented record
- February 2020, bZx — two flash-loan-assisted manipulations, roughly $350,000 and $650,000. Small sums; the template was the news.
- October 2020, Harvest Finance — roughly $24 million, via manipulating a stablecoin pool's price with flash-loaned capital.
- October 2022, Mango Markets — roughly $110 million, by manipulating the price of a thinly traded token used to value collateral. It led to US criminal proceedings.
The class has recurred for years, across chains and designs, and it will keep recurring wherever a price is read from a venue that the reader can also trade in.
The defences, and their limits
Multiple independent sources with a median; TWAPs over meaningful windows; deviation circuit breakers; deep-liquidity venues only; caps on borrowing against any one asset. Each raises the cost of an attack. None reduces it to zero, and each brings its own failure mode — a TWAP is stale, a circuit breaker can freeze liquidations exactly when they are needed, a reporter network is a trust assumption.
The transferable question is short: where does this contract get its price, and how much would it cost me to move that number for one block? If the answer is "less than the value it protects," the design is unsound regardless of how well the rest is written.
Try it now
- Take the (x ÷ x_new)² relationship and compute the manipulated spot price when an attacker removes 50%, 80% and 95% of a pool's reserve. Note how the last one behaves — it does not degrade, it detonates.
- Read a price off each chart below, build a hypothetical $5 million pool from them, and compute the USDC required to move its spot price tenfold. Then ask what a flash loan does to that requirement, given that the money need only exist for the length of one transaction.
- Find any protocol's documentation on its price feed. Identify which of the three designs it uses, the update frequency or deviation threshold, and what happens if the feed goes stale.