Orca Crypto
Menu
Start Here
Learn
Chains
Exchanges
Markets
Tools
Safety
Glossary
More
Our tokens Book a session
Advanced · Protocol risk

Oracles: how prices get onchain and how they fail

Every liquidation, every perp fill and every stablecoin mint starts with one number read from one contract.

Loading live prices
By the Orca Crypto teamUpdated 2026-09-28How we check this14 min readAdvanced
The short answer

A smart contract cannot look up a price, so every lending market, perp and stablecoin reads one from an oracle. Push oracles such as Chainlink write a new price when it moves past a deviation threshold or a heartbeat expires. Pull oracles such as Pyth let the user submit a signed price in the same transaction. AMM TWAPs average a pool’s own price over time. Each fails differently: staleness, manipulation of a thin source market, or a price that is accurate while the asset behind it is not.

A contract cannot look up a price

The EVM is deterministic. Every node must compute the same result from the same inputs, so a contract can only read what is already onchain: balances, storage, other contracts. It cannot call an exchange API. Anything a protocol knows about the outside world, it knows because some contract wrote it there, and that contract is the oracle.

A lending market uses that number to decide whether your loan is healthy. A perp exchange uses it to mark positions and trigger liquidations. A CDP stablecoin uses it to decide how much you can mint. So the question for any protocol is never just "which oracle" but three sharper ones: how accurate is the number, how fresh is it, and how expensive is it to fake? There are three designs in wide use, and they trade those properties differently.

DesignWho writes the priceFreshnessMain failure
Push feed (Chainlink Data Feeds)An oracle network, on a schedule and on price movesSeconds to hours, by designStaleness between updates; a thin source market
Pull oracle (Pyth Core, Chainlink Data Streams)Whoever needs it, inside their own transactionSub second offchain; onchain only when pulledThe caller chooses which valid update to submit
AMM TWAP (Uniswap v3 observations)Nobody: it is derived from swapsLags by the averaging windowManipulation of a thin pool; lag in a real crash

Push feeds: deviation thresholds and heartbeats

A Chainlink data feed is a consumer facing proxy contract pointing to an aggregator that receives reports from a network of node operators. The aggregator only updates when one of two triggers fires: the offchain price has moved by more than the deviation threshold, or the heartbeat has elapsed since the last update. In quiet markets the heartbeat does the work; in volatile ones the deviation trigger fires constantly.

Feed on EthereumDeviation thresholdHeartbeat
ETH / USD0.5%3,600 s (1 hour)
BTC / USD0.5%3,600 s (1 hour)
DAI / USD0.25%3,600 s (1 hour)
USDC / USD0.25%82,800 s (23 hours)
USDT / USD0.25%86,400 s (24 hours)

Values from Chainlink’s feed directory as of September 2026; the Data Feeds documentation explains the triggers and the proxy and aggregator split. Two consequences follow directly. First, the onchain price can sit up to one threshold away from the market with nothing wrong: with ETH near 2,680 dollars, a 0.5% threshold means the feed may lag by about 13.40 dollars before it is obliged to move. Any protocol whose liquidation buffer is thinner than that is relying on luck. Second, "stale" is feed specific. On September 29, 2026 the USDC / USD feed had last updated about 22 hours earlier, which is perfectly healthy for a 23 hour heartbeat. A lending market that rejected any price older than one hour would have frozen USDC for most of the day.

solidity
// Reading a Chainlink push feed defensively (Solidity)
(, int256 answer, , uint256 updatedAt, ) = feed.latestRoundData();
require(answer > 0, "no price");
// maxAge is set PER FEED: its heartbeat plus a margin.
// On Ethereum, ETH / USD has a 3,600 s heartbeat; USDC / USD has 82,800 s.
require(block.timestamp - updatedAt <= maxAge, "stale price");
uint8 dec = feed.decimals();   // 8 for the USD pairs above

Chainlink also groups feeds by market pricing risk, from low to high market risk, with separate categories for custom and new token feeds, based on things like volume concentrated on one venue and cross rate dependencies. A feed is only as good as the market it reads, and the docs say so plainly. The legacy minAnswer and maxAnswer bounds in aggregators are, per Chainlink’s documentation, no longer used on most feeds, a change with a history you will meet below.

Pull oracles: the user brings the price

A pull oracle inverts the flow. Prices are produced and signed offchain continuously (Pyth publishes about every 400 milliseconds), and nothing is written onchain until someone needs it. The protocol’s own transaction fetches a signed update, pays a small fee, and the oracle contract verifies the signatures before storing it. The price you liquidate against is milliseconds old instead of up to a heartbeat old.

solidity
// Pull model: the caller brings a signed update and pays for it (Solidity)
uint fee = pyth.getUpdateFee(priceUpdate);        // priceUpdate fetched from Hermes
pyth.updatePriceFeeds{value: fee}(priceUpdate);   // verified and stored onchain
PythStructs.Price memory p = pyth.getPriceNoOlderThan(priceId, 60);  // max age, seconds
// value = p.price * 10^p.expo    confidence = p.conf * 10^p.expo

Verification is where the trust lives. Pyth Core upgraded on August 26, 2026: an update is now accepted when at least 3 of 5 independently operated routers have signed the Merkle root that contains it. Before the upgrade the signers were Wormhole guardians with a 13 of 19 quorum over roots produced by Pythnet, which is being shut down. The contract interface did not change, which is exactly why an integrator should know it happened. The offchain Hermes service now requires an API key; the onchain contracts stay permissionless. Pyth’s upgrade notes set out the old and new signer sets side by side.

Pull designs add two things to think about. Every Pyth price carries a confidence interval, and Pyth’s own best practices say to value collateral at the low end and debt at the high end: bitcoin at 50,000 ± 1,000 dollars counts as 49,000 when you are lending against it. And because anyone can submit any validly signed update inside the allowed age, a caller can choose the one that suits them. The defense is a strict maximum age on every read, the 60 in the snippet above. Chainlink’s Data Streams product uses the same pull pattern for latency sensitive markets such as perps.

Push feed

Price written by the oracle network

  • Always a price onchain for anyone to read
  • Simple to integrate: one view call
  • Can lag the market by a full deviation threshold
  • Someone pays gas for every update, needed or not

Pull oracle

Price written by the user who needs it

  • Fresh to the second at the moment of use
  • Confidence interval published with each price
  • No price onchain until somebody pulls one
  • Caller can pick among valid updates: enforce a max age

TWAP oracles and what they cost to move

An AMM already has a price: the ratio of its reserves (see AMM math). Reading that spot price directly is the classic mistake, because a single flash loan funded swap can push it anywhere for the length of one transaction. Uniswap v3 instead accumulates the current tick every second, so a contract can read the average over any window (the oracle docs give the details). Subtract two cumulative values, divide by the elapsed time, and you have the mean tick:

average tick = (tickCumulativet2 − tickCumulativet1) ÷ (t2 − t1)    TWAP = 1.0001average tickThe mean of ticks is the geometric mean of prices. A pool can store up to 65,535 observations.
solidity
// A 30 minute TWAP from a Uniswap v3 pool (Solidity sketch)
uint32[] memory ago = new uint32[](2);
ago[0] = 1800;  ago[1] = 0;                     // 30 minutes ago, and now
(int56[] memory tc, ) = pool.observe(ago);      // cumulative tick at each point
int24 avgTick = int24((tc[1] - tc[0]) / 1800);  // arithmetic mean of the tick
// price = 1.0001 ** avgTick, a geometric mean of the price over the window.
// Uniswap's OracleLibrary also rounds negative ticks down; copy it, do not improvise.

The attacker’s problem is time. To move a TWAP over window W by a factor (1 + X) while holding a distorted price for only t seconds, the spot price has to move much further:

required spot multiple = (1 + X)W ÷ tBecause the TWAP averages the logarithm of the price over the window.
Distorted price held forSpot move needed to shift a 30 minute TWAP by 20%
1 block (12 s)about 750 billion times
2 blocks (24 s)about 870,000 times
5 blocks (60 s)about 237 times
10 blocks (2 min)about 15 times
30 blocks (6 min)about 2.5 times

Computed from the formula above. A one block distortion needs absurd capital; the danger is in holding a price for several blocks, which normally fails because arbitrageurs trade it back in between. Under proof of stake, a validator who proposes consecutive blocks can keep them out. Uniswap’s own analysis of the USDC / WETH 0.05% pool put the one block version of this 20% attack at a swap of about 709 billion dollars and the two block version at about 978 million: still prohibitive, no longer absurd. Wide range liquidity raises the cost sharply, and thin pools are cheap at any length. The flip side of manipulation resistance is lag: a 30 minute TWAP of a token that really does fall 40% in five minutes is still reporting a much higher price, which is its own route to bad debt in a lending market.

How oracles fail

Manipulated source

The oracle faithfully reports a market that has been pushed. Thin order books, one dominant venue, or a small AMM pool are the usual targets. Mango Markets, below, is the canonical case.

Stale or frozen price

The number is old, or pinned at a bound. In May 2022 the LUNA feed used by some lenders stopped at a 0.10 dollar floor while LUNA traded at fractions of a cent, and borrowers deposited near worthless LUNA at the floor price.

Right price, wrong asset

A wrapped, bridged or staked token priced by what it should be worth. If the backing breaks, the oracle keeps quoting par while the collateral is impaired.

Keys and plumbing

Feeds have owners who can repoint them, layer 2 sequencers go down, decimals get mixed up. On Ethereum the ETH / USD feed proxy was owned by a 4 of 9 multisig as of September 2026.

The LUNA case cost Venus Protocol on BNB Chain more than 11 million dollars and Blizz Finance on Avalanche about 8 million. Both relied on a reputable oracle; the gap was in what their contracts did when the reported price stopped tracking reality. Layer 2s add a specific hazard: if the sequencer halts, prices go stale for everyone while only some users can still transact. Chainlink publishes sequencer uptime feeds for this, and its reference integration waits out a grace period (3,600 seconds in the example) after the sequencer returns before trusting prices again. The broader taxonomy of these bugs is in smart contract vulnerability classes.

Case study: Mango Markets, October 2022

On October 11, 2022, a trader opened a long position of more than 400 million MNGO perpetual contracts on Mango Markets at about 4 cents, roughly 19 million dollars of exposure according to the CFTC’s complaint. He then bought MNGO spot on the thin venues the oracle read from. The price on Mango went from about 0.04 to 0.54 dollars, more than 13 times, in about 30 minutes.

Now the arithmetic. Every one cent rise added at least 4 million dollars of unrealized profit to the long, so a 50 cent rise put more than 200 million dollars of paper gains in the account. Mango counted unrealized profit as collateral, and the account borrowed everything the protocol would lend: more than 110 million dollars of other assets. When MNGO fell back, the collateral evaporated and the loans stayed. According to the CFTC, about 67 million dollars was later returned and roughly 47 million kept.

The oracle was not wrong
The oracle did its job: it reported the price at which MNGO was trading. The failure was letting a position in a thinly traded asset be marked at that price and borrowed against without limit. Size caps relative to real liquidity would have stopped it at any oracle.

US regulators and prosecutors brought civil and criminal cases, and a jury convicted in April 2024. In May 2025 the trial judge vacated those convictions, finding among other things that the government had not proved false statements to a permissionless protocol. Whatever the courts decide, the design lesson stands.

Circuit breakers, fallbacks and caps

A well designed integration assumes the oracle will sometimes be wrong and bounds the damage:

  • Per feed staleness checks tied to that feed’s heartbeat, not one number for all.
  • Cross checks between two independent sources, pausing when they diverge beyond a band.
  • Price caps. Aave, for example, reads USDC through a capped adapter (its documentation’s example ceiling is 1.04 dollars) and prices liquid staking tokens by their exchange rate with a cap on how fast that rate may grow, so a spike cannot inflate collateral.
  • Supply and borrow caps sized to the liquidity of the market the oracle reads, the defense Mango lacked.
  • Sequencer checks on layer 2s, with a grace period after recovery.

Every one of these is a tradeoff. A fallback feed is a second attack surface. A circuit breaker that freezes the last good price is precisely the LUNA failure. A common compromise is to pause new borrowing while liquidations keep running on the best available price, and to document exactly when that happens.

How a protocol should choose, and how you can check

Choosing an oracle comes down to matching three numbers. The liquidity behind the price should dwarf the largest position the protocol will accept. The maximum drift between updates (the deviation threshold, or the TWAP lag) should be well inside the gap between the loan to value limit and the liquidation threshold. And the governance of the feed (who can change it, how fast) should be no weaker than the protocol’s own.

  1. Find the oracle address

    Docs usually list it. On Aave, getSourceOfAsset(asset) on the AaveOracle contract returns the feed per asset; as of September 2026 USDC on Ethereum read from a contract describing itself as Capped USDC / USD. On Morpho, the oracle is one of the immutable parameters of each market.

  2. Read it on an explorer

    Call description(), decimals() and latestRoundData(). Compare updatedAt with the feed’s heartbeat, and the answer with the market price.

  3. Check who controls it

    Read owner() on the proxy. If it is a multisig, note the threshold. Check whether the protocol can swap the oracle, and behind what delay. The guide on admin keys and timelocks shows how.

  4. Size the source market

    Look at the depth of the venues or pool the price comes from. If a few million dollars could move it and the market accepts tens of millions of that collateral, you have found the risk before an attacker did.

Common questions

What is the difference between a push oracle and a pull oracle?

A push oracle writes prices onchain itself, on a schedule and whenever the price moves past a threshold, so a price is always there to read but may lag. A pull oracle signs prices offchain and lets the user submit one inside the transaction that needs it, so the price is fresh but only exists onchain when someone pulls it.

Why is the spot price of a DEX pool a bad oracle?

Because it can be moved for the length of one transaction with a flash loan and moved back before anyone else trades. A time weighted average forces an attacker to hold the distorted price for many seconds against arbitrage, which on a deep pool costs far more than the attack can earn.

How stale is too stale for a Chainlink price?

It depends on the feed. Compare the time since updatedAt with that feed’s heartbeat. On Ethereum, ETH / USD updates at least hourly while USDC / USD has a 23 hour heartbeat as of September 2026, so one staleness limit for every asset is either too loose for one or too tight for the other.

Was Mango Markets hacked?

No code was exploited. The trader used the protocol as designed: he pushed up the price of a thin token that the oracle read, and borrowed against the resulting paper profit. The weakness was allowing unlimited borrowing against a position in an illiquid asset.

Can I see which oracle a lending market uses?

Yes. Most protocols list oracle addresses in their docs, and you can confirm onchain by reading the market or oracle contract on a block explorer. Then read the feed itself: its description, last update time and owner.

Kept on this device only. Nothing is sent anywhere.

Where to go next

Next: Inside a lending protocol: rate curves, liquidations and bad debt

Guide 10 of 16 in the Advanced level. Your place is kept on this device, so you can pick up exactly here next time.

Share this page

Was this page useful?