security/oracle-price-manipulation

Oracle & Price Manipulation Vulnerability Patterns

ethereumsecurity-guide👥 Communityconfidence highhealth -1%
v1.0.0·Updated 3/26/2026

Distilled from 300+ Code4rena audit findings. Oracle-related vulnerabilities form the largest category of critical findings in the dataset (~64 files). Every DeFi protocol that touches prices, collateral, or asset valuation is exposed to this class of attack.

Category Overview

Oracle manipulation attacks follow two primary vectors:

  1. Spot price manipulation — flash loans move an on-chain price right before a price read
  2. Oracle configuration bugs — missing validation allows stale, out-of-range, or admin-controlled prices to be accepted as valid

Off-Chain / Admin-Controlled Values

1. Trust in Admin-Supplied Critical Values Without Validation

Pattern: A protocol allows an admin or off-chain actor to set a value (e.g., expectedPayout, targetPrice, collateralRatio) that directly controls how much users receive or owe. No on-chain validation bounds this value against market reality.

Real-world example: A lottery contract sets expectedPayout off-chain. An admin can set it to drain the prize pool in a single claim. The contract treats admin input as ground truth.

Fix: For any value that determines fund flows:

  • Compute it on-chain from raw data when possible
  • If admin-supplied, validate against on-chain oracle bounds: require(adminValue >= oracle.price() * 0.95 && adminValue <= oracle.price() * 1.05)
  • Apply time-locks before accepting new values

2. Missing Min/Max Price Circuit Breakers

Pattern: Chainlink aggregators for many assets (especially newly listed tokens) have minAnswer and maxAnswer bounds configured off-chain. If the asset's price drops below minAnswer, Chainlink continues to report minAnswer rather than the true price. Protocols that only check answer > 0 will consume the artificially high floor price, over-valuing collateral and enabling under-collateralized borrowing.

Vulnerable pattern:

(, int256 price,,,) = chainlinkFeed.latestRoundData();
require(price > 0, "invalid price");
return uint256(price);

Fix:

(, int256 price,, uint256 updatedAt,) = chainlinkFeed.latestRoundData();
require(price > int256(MIN_PRICE) && price < int256(MAX_PRICE), "price out of circuit breaker bounds");
require(block.timestamp - updatedAt <= MAX_PRICE_AGE, "stale price");
return uint256(price);

3. Stale Price Acceptance

Pattern: latestRoundData() is called but the updatedAt timestamp is not checked. During periods of network congestion or Chainlink node downtime, the feed may not update for hours. A protocol accepting stale prices can be exploited when the true market price has moved significantly.

Fix: Enforce a maximum age: require(block.timestamp - updatedAt <= 3600, "price older than 1h"). The appropriate maximum age depends on the asset's volatility and the protocol's risk tolerance (typically 1–24 hours for most assets).

4. Single-Oracle Dependency

Pattern: Using only one oracle source creates a single point of failure. If the oracle is compromised, goes offline, or reports incorrect data, the protocol has no fallback.

Fix: Use a primary oracle with a secondary fallback. Compare the two values: require(abs(primary - secondary) / primary < 0.02, "oracle deviation > 2%"). If they diverge, pause the protocol.


On-Chain Spot Price Manipulation

5. AMM Spot Price as Collateral Oracle

Pattern: A protocol uses the current spot price from a Uniswap V2 or V3 pool (ratio of reserves) as the canonical price for collateral valuation. Flash loans can temporarily push this price to any value within a single transaction, allowing an attacker to borrow against inflated collateral.

Fix: Never use AMM spot prices for lending/minting decisions. Use:

  • Chainlink for widely-listed assets
  • TWAP (Time-Weighted Average Price) with a window ≥ 30 minutes for long-tail assets

6. Inadequate TWAP Window

Pattern: A protocol uses a TWAP oracle but with a window that is too short (e.g., 1–5 minutes). A well-capitalized attacker can sustain the price manipulation for the duration of the TWAP window, making the manipulation "average out" to the desired manipulated price.

Fix: TWAP windows should be:

  • Minimum 30 minutes for volatile assets
  • 1–4 hours for collateral in conservative lending protocols
  • Document and justify the chosen window

Cross-Chain and Multi-Chain Issues

7. Hardcoded Block-Time Constants

Pattern: Oracle-related logic uses block count–based timeouts (e.g., MIN_AUCTION_DURATION = 7200 blocks) that assume Ethereum's ~12s block time. On BSC (~3s) or Avalanche (~2s), this window is 4–6× shorter than intended, enabling attacks that depend on duration (e.g., Dutch auctions, grace periods, TWAP accumulation).

Fix: Use block.timestamp for duration-based logic. If block counts are necessary, make the constant configurable or use chain-specific deployments.

8. Cross-Chain Price Discrepancies

Pattern: A protocol operating on multiple chains uses different oracle configurations per chain. Price discrepancies between chains can be exploited via cross-chain arbitrage if the protocol doesn't account for bridging costs and delays.

Fix: Ensure oracle configurations are consistent across deployments. Apply conservative staleness thresholds that account for potential chain-specific delays.


Manipulation via Protocol's Own State

9. Self-Referential Price Calculation

Pattern: A protocol calculates the price of its own token or derivative using a formula that can be manipulated by interacting with the protocol itself. For example, a vault token priced as totalAssets / totalSupply can be manipulated by donating assets or burning tokens.

Fix: Add minimum liquidity requirements, fee-on-deposit mechanisms, and track virtual balances separately from actual token balances.


Audit Checklist

  • Every latestRoundData() call: are minAnswer, maxAnswer, and updatedAt validated?
  • Are AMM spot prices used anywhere for collateral valuation? → Replace with TWAP or Chainlink
  • TWAP windows: are they ≥ 30 minutes for volatile assets?
  • Single oracle dependency: is there a fallback?
  • Admin-supplied values: are they bounded against on-chain market prices?
  • Block-number–based time constants: are they correct for all target chains?
  • Is there a circuit breaker / pause if oracle values deviate unexpectedly?
  • Is the protocol's own token used as collateral, creating reflexive risk?