ROOTVIELROOTVIEL
← Knowledge Base

Web3 & smart contracts

Partial14 min

Flash-Loan-Assisted Attacks

A flash loan lends an enormous, uncollateralised sum for the duration of a single transaction, on the sole condition that it is repaid before the transaction ends. It is not itself a bug — it is an amplifier that lets anyone wield whale-sized capital for one atomic block of logic, turning a latent flaw (a spot-price oracle, a weak vault invariant, a snapshot-based vote) into a profitable, capital-free exploit.

Classification

Domain
Web3 & smart contracts
ROOTVIEL support
Partial — Core is built; parts are not yet wired end-to-end.
Analysis family
RootRaven
CWE
CWE-345, CWE-840
OWASP
SC07, SC02

Definition

A flash loan is a loan that is borrowed and repaid within one atomic transaction: the lending contract transfers funds to the borrower, calls back into the borrower’s code, and then requires that the balance (plus a fee) has been returned by the time control comes back. If repayment fails, the entire transaction reverts and it is as if the loan never happened — so the lender takes no credit risk and demands no collateral.

A flash-loan-assisted attack is therefore not a vulnerability in the flash loan. It is the exploitation of some other weakness — a manipulable price source, a vault whose share math breaks at extreme ratios, a governance system that reads voting power from current balance — using flash-loaned capital to reach a magnitude of state change that ordinary capital could not. The flash loan removes the "you need to be a whale" precondition; the bug does the rest.

Context

Flash loans matter anywhere a protocol’s behaviour depends on a quantity an attacker can move within a single transaction and then move back.

  • ▸Spot-price oracles: a DEX reserve ratio read as a price can be skewed by a flash-financed swap, then read by a victim contract at the skewed value.
  • ▸Collateral valuation: lending markets that price collateral from a manipulable source let a borrower inflate collateral or deflate debt for one transaction.
  • ▸Vault share math: deposit/redeem logic that misbehaves at extreme balances (share inflation) becomes reachable when the attacker can momentarily own most of the pool.
  • ▸Governance snapshots: systems that count votes from current token balance let a borrower acquire a majority for one block.
  • ▸Any single-transaction invariant: logic that assumes "no one can hold this much at once" is false in a world with flash loans.

Solidity & EVM fundamentals

Atomicity is the enabling property. An Ethereum transaction executes as all-or-nothing: if any call in the chain reverts, every state change rolls back. A flash lender exploits this by sending funds first and enforcing repayment with a require at the end of the same call frame — because a failure reverts the transfer too, the lender is safe, and the borrower gets to run arbitrary logic in between with the borrowed balance.

The second fundamental is that reading state within a transaction sees the mid-transaction world. A contract that computes a price from reserve0 / reserve1 of an AMM pair reads whatever those reserves are at that instant — including after a flash-financed swap has skewed them. The manipulation and the victim read happen in the same atomic unit, so there is no block in between for arbitrage or oracles to correct the value.

Atomic transaction
All state changes in a transaction commit together or not at all; any revert undoes everything, which is what makes an uncollateralised same-transaction loan safe for the lender.
Flash loan vs. flash swap
A flash loan lends a token to be repaid in-transaction; a flash swap (e.g. Uniswap) lets you receive output tokens and pay later in the same call. Both grant momentary capital.
Spot price
An instantaneous ratio read from current reserves — cheap to read and trivially manipulable within one transaction; contrast with a time-weighted average.
TWAP
Time-Weighted Average Price: a price averaged over a window of blocks, so a single-transaction swap cannot move it meaningfully. A standard mitigation for the oracle leg.
Amplifier, not root cause
The flash loan supplies magnitude; the exploited defect (manipulable oracle, broken invariant) is the actual vulnerability.

Root cause

The root cause is always a protocol invariant that implicitly assumes capital is scarce or that a within-transaction state reading is trustworthy. Common forms: trusting a spot price that can be moved in the same transaction; computing shares, collateral, or rewards from a balance an attacker can momentarily dominate; or deriving authority (votes) from a snapshot taken inside the attacking transaction.

Blaming the flash loan is a category error. Removing flash loans would only raise the capital bar — a sufficiently funded actor could perform the same manipulation with their own money. The durable fix is to make the invariant hold regardless of how much capital passes through a single transaction: price from a manipulation-resistant source, enforce conservation that does not break at extremes, and derive authority from state that cannot be acquired-and-returned atomically.

How the attack works, step by step

The archetype is oracle manipulation financed by a flash loan, all inside one transaction so repayment is guaranteed before it ends.

  1. 1Borrow: the attacker takes a large flash loan of a token from a lending pool, receiving the funds and control via a callback.
  2. 2Distort: they use the borrowed capital to skew the manipulable state — e.g. a huge swap that moves a DEX pair’s reserve ratio, changing the spot price the victim contract will read.
  3. 3Exploit: they call the victim protocol, which reads the distorted state (over-valuing collateral, under-pricing shares, counting borrowed votes) and grants them more than it should.
  4. 4Reverse & profit: they undo the distortion (swap back) and keep the surplus extracted from the victim.
  5. 5Repay: they return the flash loan plus fee from the proceeds; because this all happens atomically, if any step failed the whole transaction would revert and the loan would be void.

Vulnerable example

A synthetic lending contract that prices collateral from a DEX pair’s spot reserves. Any actor who can move those reserves within a transaction — trivially, with a flash loan — can borrow against a mis-valued deposit. The flaw is the spot-price read, not the loan.

synthetic — do not use; spot-price oracle, flash-manipulable
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; interface IPair { function getReserves() external view returns (uint112, uint112); } contract SpotPricedLender { IPair public pair; // COLLATERAL / STABLE pair mapping(address => uint256) public collateral; // units of COLLATERAL // UNSAFE: price is the instantaneous reserve ratio. // A flash-loaned swap moves reserves within the same tx, // so this returns whatever the attacker set it to. function price() public view returns (uint256) { (uint112 r0, uint112 r1) = pair.getReserves(); return uint256(r1) * 1e18 / uint256(r0); // STABLE per COLLATERAL } // UNSAFE: borrow limit derived from a manipulable price. function maxBorrow(address user) public view returns (uint256) { return collateral[user] * price() / 1e18; // inflated when price is pumped } }

Impact

  • ▸Drained lending markets: borrowing far beyond true collateral value, leaving the protocol with bad debt.
  • ▸Pool extraction: redeeming mispriced shares or swapping at a manipulated price to siphon reserves.
  • ▸Governance capture: momentarily acquiring majority voting power to pass or execute a malicious proposal (see governance attacks).
  • ▸Capital-free scale: because the loan is uncollateralised, the attacker risks only gas and the loan fee, dramatically widening the set of actors who can attempt the exploit.
Note

The flash loan multiplies the impact of whatever underlying bug exists; it does not create impact on its own. A protocol with no manipulable invariant is not made exploitable merely by the existence of flash loans.

Detection — static, dynamic, symbolic, fuzzing, formal

Detection targets the underlying weakness (does a security-critical decision depend on manipulable, within-transaction state?) and then confirms exploitability by financing the manipulation in a controlled run.

Static analysis
Flags security decisions that read spot reserves / instantaneous balances, and the absence of a TWAP or manipulation-resistant source. A SIGNAL that the invariant may be flash-manipulable, not proof it is profitable.
Dynamic (local-fork) testing
The strongest practical method: fork real liquidity state locally, take a flash loan from a forked pool, run the manipulation, and measure net profit. A profitable run is a reproduced exploit for that state and block.
Symbolic execution
Can reason about whether a manipulated input makes a branch profitable, but the DeFi composition (external pools, price curves) is usually too large to model fully; results are partial and often UNKNOWN.
Fuzzing
Invariant fuzzing with a flash-loan harness searches for call sequences (including borrowed capital) that break a value-conservation or solvency invariant; a break is a reproducible counterexample.
Formal verification
Can prove the invariant holds under a modelled price source (e.g. that borrow power is monotone in a TWAP), but only under the model’s assumptions about the oracle — it cannot prove the real oracle is manipulation-resistant.
Note

Because the exploit depends on real external liquidity, a local FORK of mainnet state is often required to make the evidence meaningful; a bare local chain with synthetic pools can prove the mechanism but not the economic feasibility.

How autonomous agents investigate it

An autonomous investigator separates the two questions — "is there a manipulable invariant?" and "is manipulating it profitable after costs?" — and answers the second only by execution against realistic liquidity.

  1. 1Observe: find security-critical reads of spot prices or current balances and the authority they grant (borrow power, share price, votes); mark each as a candidate.
  2. 2Hypothesise: state the end-to-end claim ("flash-borrow X, swap to move price by Δ, borrow against inflated collateral, swap back, repay, net profit P").
  3. 3Experiment: on a local fork of real liquidity, execute the full borrow→distort→exploit→reverse→repay sequence, measuring reserves and the attacker’s token balances at each step.
  4. 4Collect evidence: the realised profit net of loan fee and gas, and the victim-state change — all from execution on the fork.
  5. 5Decide: promote to a finding only when a real run netted positive against realistic liquidity; a mechanism that works only on synthetic pools, or whose profit is negative after costs, is UNKNOWN or informational.
Partial

The model is good at assembling the multi-step attack and choosing swap sizes; it must not estimate profitability — that is measured on the fork, because real liquidity depth and fees determine whether the manipulation pays.

How RootRaven analyzes it

RootRaven proves a flash-loan-assisted finding only by executing the whole attack on a disposable LOCAL blockchain or a LOCAL fork of real state — never against a live network — and confirming net value extraction with a deterministic, AI-free verifier. The model composes the candidate attack sequence and swap sizes; the verifier, not the model, decides whether the attacker ended richer and whether the victim invariant actually broke.

Observation
Spot-price reads and balance-derived authority are marked as candidate manipulable invariants — SIGNALS, not findings.
Hypothesis
A full attack claim with magnitudes: borrow amount, swap to move the invariant, the exploit call, reversal, repayment, and expected net profit.
Experiment
Run the complete sequence on a local fork of real liquidity (or a local chain for mechanism-only cases), capturing reserves and balances step by step. No live-network attack mode.
Evidence
Measured profit net of fee and gas, and the recorded distortion of the victim’s state — raw numbers from the fork.
Verification
A deterministic checker confirms attacker end-balance > start-balance (net costs) and that the targeted invariant moved — no model judgement.
Finding
Promoted only on a profitable, reproduced run against realistic liquidity; synthetic-only success, or negative-after-cost profit, stays UNKNOWN or informational.
Partial

Support is "partial" for this class: economic exploits depend on real liquidity depth, fork fidelity, and block context, so reproduction is harder and sometimes inconclusive. RootRaven keeps the caveat — a non-reproduced manipulable invariant is reported as UNKNOWN, never as "safe", and a successful lab run is scoped to the forked state and block it used.

Mitigation

  • ▸Price from manipulation-resistant sources: a TWAP over a sufficient window, or a robust external oracle, instead of a spot reserve ratio.
  • ▸Aggregate multiple independent price sources and bound acceptable deviation, so a single pool cannot move the valuation.
  • ▸Make critical invariants hold at any single-transaction magnitude: conservation checks, deposit seeding, and share math that does not break when one actor momentarily dominates a pool.
  • ▸Derive authority from state that cannot be atomically acquired and returned — e.g. historical snapshot blocks, vote-escrow, or a delay between acquiring tokens and using their power.
  • ▸Add economic friction where appropriate: per-block action limits, deposit/withdraw delays, and caps that blunt single-transaction manipulation.
  • ▸Do not try to "block flash loans" (e.g. rejecting contract callers) — it is brittle, breaks composability, and does not stop a well-funded attacker; fix the invariant instead.

Corrected example — Solidity, why, and limits

The corrected lender prices collateral from a TWAP rather than the instantaneous reserve ratio, so a single-transaction swap cannot move the value a borrow decision sees. The decisive change is the price source, not any check on who called.

synthetic — TWAP-based valuation
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; interface ITwapOracle { // Returns an average price over a trailing window of blocks. function consult(address token, uint256 amountIn) external view returns (uint256); } contract TwapLender { ITwapOracle public oracle; mapping(address => uint256) public collateral; address public collateralToken; // WHY: a time-weighted average cannot be moved meaningfully by a // single-transaction (flash-financed) swap, because it averages over // many blocks. The attacker can skew the spot pool, but not this value. function maxBorrow(address user) public view returns (uint256) { return oracle.consult(collateralToken, collateral[user]); } }
Note

Limits of the fix: a TWAP resists single-transaction manipulation but is not unconditionally safe — it lags the real price (bad during legitimate volatility), it can still be moved by an attacker willing to hold a distortion across several blocks (multi-block manipulation), and its resistance depends on the pool having deep, liquid reserves. A TWAP mitigates the oracle leg; it does not fix a vault whose share math breaks at extremes, which needs its own invariant.

Verifying the fix

Verify by attempting the same flash-financed manipulation against the fixed contract on a local fork and confirming it is no longer profitable. The sketches are conceptual Foundry-style tests over forked liquidity.

conceptual — Foundry fork test: manipulation no longer pays
// Fork real liquidity locally, then try the attack against the TWAP lender. function test_flashManipulationUnprofitable() public { uint256 before = token.balanceOf(attacker); vm.prank(attacker); uint256 borrowedPower = lender.maxBorrow(attacker); // read under TWAP _flashSwapToSkewSpot(); // move the SPOT pool hard uint256 skewedPower = lender.maxBorrow(attacker); // WHY it holds: TWAP barely moves within one tx. assertApproxEqRel(skewedPower, borrowedPower, 1e16); // ~1% tolerance _reverseSwapAndRepay(); assertLe(token.balanceOf(attacker), before); // no net gain }
Note

A passing fork test supports "the single-transaction manipulation did not pay against this liquidity at this block" — explicitly scoped. It does not rule out multi-block manipulation, thin-liquidity conditions, or a different exploit path, and it is not a general guarantee.

Limitations

  • ▸Economic dependence: profitability hinges on real liquidity depth, fees, and gas, so a bare local chain can show the mechanism but not feasibility — a local fork is usually required.
  • ▸Fork fidelity: results are scoped to the forked state and block; a different block or liquidity profile may change the outcome.
  • ▸Not the loan’s fault: the finding is about the underlying invariant; "it uses a flash loan" is never itself the vulnerability.
  • ▸Multi-block manipulation: a fix that stops single-transaction attacks may still fall to an attacker who holds a distortion over several blocks.
  • ▸Composition blow-up: symbolic/formal coverage is limited by the size of the external DeFi composition, so many paths remain UNKNOWN.
  • ▸Maturity caveat: this class is "partial" — RootRaven reproduces some economic exploits but reports non-reproduced manipulable invariants as UNKNOWN, not safe.

References

  1. [1]Aave docs — Flash Loans (atomic, same-transaction borrowing)
  2. [2]Uniswap V2 docs — Flash Swaps and TWAP oracles
  3. [3]Uniswap V3 docs — Oracle (time-weighted average price)
  4. [4]OWASP Smart Contract Top 10 — Flash Loan Attacks & Price Oracle Manipulation
  5. [5]MITRE CWE-345 (Insufficient Verification of Data Authenticity), CWE-840 (Business Logic Errors)
  6. [6]Foundry Book — forking mainnet state for tests

References point to public, well-known technical sources for further study.

Related articles