Docs / Application security

Field guide · Smart contract security

Smart contract security

Price manipulation: why a flash loan fakes the number

By Abhimanyu Gupta, Founder & Principal Operator

On this page

A decentralised exchange prices two tokens by their ratio, so a large enough trade moves the price. If another contract reads that price and lends against it, the reader can be fed a number the attacker chose, and a flash loan means they need none of their own money to do it.

Read with a purpose. Never act on an instantaneous price you can move in one transaction; use a time-weighted average or an aggregated oracle, and always guard the reading for staleness and sanity.

At a glance

The bug
A contract asks a market "what is this worth right now?" and trusts the answer, when the answer can be moved on purpose within the same transaction
The lever
A flash loan borrows millions with no collateral, as long as it is repaid before the transaction ends, so the attacker's own capital is not the limit
The target
Spot price from a single pool: a large trade moves it, the victim reads the moved price, the attacker trades back
The fix
Do not read an instantaneous price you can move. Use a time-weighted average, or an independent oracle with multiple sources
The habit
For any price a contract acts on, ask: could someone move this within one transaction, and would my contract believe them?

The price a contract cannot trust

A decentralised exchange holds two tokens in a pool and prices them by their ratio. That is elegant and it is also the weakness: the price is whatever the current balances say it is, and a large enough trade changes the balances, so a large enough trade changes the price. If another contract reads that price at the wrong instant and lends against it, or mints against it, or liquidates against it, the reader can be fed a number the attacker chose.

This is the single most costly bug class in DeFi. It is not a coding slip like a missing modifier; it is a design mistake about which numbers can be trusted, and it has drained hundreds of millions across dozens of protocols.

Why a flash loan removes the "you'd need to be rich" defence

The instinct is that moving a large pool's price would take enormous capital, so only a whale could do it. A flash loan removes that objection entirely. It lends you an arbitrary amount with no collateral, on one condition: you repay it, plus a fee, before the transaction finishes. If you cannot, the whole transaction reverts as if it never happened, so the lender is never at risk. The attacker borrows millions, uses them for the length of one transaction, and repays from the profit, all atomically.

one transaction, all or nothing 1. Borrow 2. Move price 3. Victim readsthe moved price 4. Profit 5. Repay

The vulnerable read

The tell is a contract that derives a price from a pool's live balances, right now, and acts on it in the same call.

Vulnerable.solsolidity
// Price = how much USDC the pool holds per token. Moves with every trade.
function spotPrice() public view returns (uint256) {
    uint256 tokenReserve = token.balanceOf(address(pool));
    uint256 usdcReserve  = usdc.balanceOf(address(pool));
    return (usdcReserve * 1e18) / tokenReserve;
}

function borrowAgainst(uint256 collateralTokens) external {
    uint256 value = collateralTokens * spotPrice() / 1e18;
    _lend(msg.sender, value);   // lends against a price the caller can move
}

The attacker flash-borrows, swaps a huge amount into the pool to spike spotPrice(), calls borrowAgainst to borrow far more than their collateral is really worth, swaps back to unwind the price, and repays the flash loan. The protocol is left holding collateral worth a fraction of what it lent.

The fix: do not read a price you can move in one block

The defence is to price against something that cannot be shoved within a single transaction. There are two standard answers, and serious protocols use both.

ApproachWhy it resists manipulation
Time-weighted average price (TWAP)Averages the price over many blocks, so a one-block spike barely moves it. Manipulating it means holding the price off-market for a sustained time, which is expensive and arbitraged away.
An independent oracle (e.g. a decentralised price feed)Aggregates many off-chain and on-chain sources and reports a signed value, so a single pool's balances are not the input at all.
Better.solsolidity
// Read a value that is not the instantaneous balance of one pool.
function priceUSD() public view returns (uint256) {
    // A decentralised feed returns a recent, aggregated, signed price.
    (, int256 answer, , uint256 updatedAt, ) = priceFeed.latestRoundData();
    require(answer > 0, "bad price");
    require(block.timestamp - updatedAt < 1 hours, "stale price");
    return uint256(answer);
}

An oracle is only as good as the checks around it. Even a good feed can be stale, paused, or report a value from a market that has itself gapped. Always check that the timestamp is recent, that the value is positive and inside a sane band, and think about what happens on the day the feed stops updating. "Trust the oracle" without those guards is its own vulnerability.

The short version. A single pool's spot price is a number an attacker can move within one transaction, and a flash loan means they do not need their own capital to do it. Any contract that lends, mints or liquidates against that number can be fed a lie. Price against a time-weighted average or an independent aggregated oracle instead, and guard the reading for staleness and sanity. The question to carry into every review: could someone move this price in one transaction, and would my contract believe them?

References & further reading

  1. Uniswap, Oracles and TWAP. How time-weighted average prices are built and why they resist single-block manipulation.
  2. Chainlink, Using Data Feeds. Reading an aggregated price feed, including the staleness and round checks.
  3. Ethereum community, Oracles. Why on-chain contracts cannot see off-chain prices on their own, and the trust that introduces.
  4. Ethereum community, Smart contract security. The broader security context.
All guides Web app penetration testing

Want this tested on you?

Reading about it is one thing. Seeing it proven on your own systems is another.