Skip to content
Quantix Security Labs
Technical analysis

Flash loan attacks: how they work, what they cost, how to stop them

A flash loan removes the capital barrier from an attack, not the flaw behind it. How the loans work, a fully worked example of the economics, four public incidents, and the defences that change the arithmetic.

Written by
Quantix Research, DeFi Security
Published
Reading time
11 minutes
Category
DeFi Security

Technical analysis. Built from the public sources in the bibliography. Incident details are as reported there; no new discovery is claimed.

Illustration: one transaction laid out as a tray: a borrowed stack, a pool pushed out of balance, a lending market exploited at the moved price, and the loan repaid with the difference kept.
Contents
  1. What a flash loan is
  2. Anatomy of an attack
  3. A worked example
  4. Deeper liquidity, a TWAP, an external feed
  5. The profitability inequality
  6. The loan is rarely the vulnerability
  7. Four public incidents
  8. Detection signals
  9. Review checklist and invariant tests
  10. Conclusions
  11. Recommendations
  12. Bibliography

A flash loan attack is an exploit whose capital is borrowed and repaid inside the same transaction. The loan is not the flaw. It removes the barrier that used to shelter protocols with an economic weakness, the attacker’s need for money: anything that would pay with a hundred million dollars held for one transaction is open to anyone who can write the contract.

This analysis covers what flash loans cost, an attack computed step by step on a model pool and lending market, the inequality that decides whether it pays, the flaws it funds, four public incidents, and detection and review. Oracle designs are compared in our analysis of oracle designs and are not repeated here.

What a flash loan is

A flash loan is an uncollateralised loan that must be repaid, with its fee, before the transaction that took it ends. The lender sends the funds, calls the borrower’s contract, then collects repayment; if that fails, the whole transaction reverts, loan included. ERC-3156 standardises the pattern with an onFlashLoan callback.

FlashLender.sol (illustrative, ERC-3156 style)solidity
// Simplified, illustrative. Not production code.// ERC-3156-style lender: lend, call back, collect, or revert everything.bytes32 constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan"); function flashLoan(IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data)    external    returns (bool){    uint256 fee = flashFee(token, amount);    IERC20(token).safeTransfer(address(receiver), amount);                          // 1. lend    bytes32 ok = receiver.onFlashLoan(msg.sender, token, amount, fee, data);         // 2. borrower logic runs here    if (ok != CALLBACK_SUCCESS) revert CallbackFailed();    IERC20(token).safeTransferFrom(address(receiver), address(this), amount + fee); // 3. collect, or revert all    return true;}

Atomicity makes the loan riskless on both sides: the lender cannot end the transaction unpaid, and a borrower whose plan fails loses only the gas of a reverted transaction. The capital costs the fee alone, so borrowing 2 million of a stablecoin for one transaction costs between nothing and about 6,000.

SourceMechanismFeeNotes
Aave v3flashLoan or flashLoanSimple, callback executeOperation0.05%Changeable by governance (FLASHLOAN_PREMIUM_TOTAL); waived for authorised flash borrowers in flashLoan, which can also open a debt instead of repaying.
Balancerv3: the Vault’s transient unlock0%The v2 fee, set by governance, was always zero; v3 is fee-free by design.
Uniswap v2 flash swapTokens sent first, then uniswapV2Call0.3% LP feeSame-token repayment of at least the amount ÷ 0.997, an effective 0.3009%.
Flash-loan fees as stated in each project’s documentation, October 2026

Anatomy of an attack

The threat model is an unprivileged attacker that deploys a contract, borrows from any flash lender and orders its own calls within one transaction, but cannot hold a price across blocks for free. Almost every attack has five phases.

Anatomy of a flash loan attack on the model lending marketFour lifelines: flash lender, attacker contract, a constant-product pool of token X and stablecoin Y, and a lending market that values X at the pool’s spot price. All six steps run inside one transaction, with steps 2 to 5 executing in the attacker’s callback. Step 1, borrow: the flash lender lends 2,006,018 Y and calls the attacker back. Step 2, manipulate: the attacker swaps 2,006,018 Y for 666.67 X, moving the spot price from 2,000 to 4,500. Step 3, exploit: the attacker deposits 400 X, which the market values at 1,800,000 Y. Step 4, exploit: the attacker borrows 1,350,000 Y, which is 75 percent of 1,800,000. Step 5, unwind: the attacker swaps the 666.67 X back for 1,995,996 Y, and the spot price returns to about 2,004. Step 6, repay: the attacker repays 2,007,021 Y, the principal plus a 0.05 percent premium. If the repayment fails, steps 1 to 5 are undone. Left behind: 1,350,000 Y of debt against 400 X worth 800,000 Y; the attacker keeps 538,975 Y before gas.Flash lenderlends, calls backAttacker contractlogic runs in callbackPool X/Yx·y = k, fee 0.3%Lending marketreads pool spot price1borrow2manipulate3exploit4exploit5unwind6repaylend 2,006,018 Ythen call the attacker backswap 2,006,018 Y for 666.67 Xspot 2,000 → 4,500deposit 400 X, valued at 1,800,000 Yborrow 1,350,000 Y = 75% × 1,800,000swap 666.67 X for 1,995,996 Yspot 4,500 → 2,004repay 2,007,021 Yprincipal + 0.05% premiumOne transaction. If step 6 cannot pay principal and premium, steps 1 to 5 are undone and only gas is lost.Left behind: 1,350,000 Y of debt against 400 X worth 800,000 Y. The attacker keeps 538,975 Y before gas.
The worked example below as calls inside one transaction. Amounts are rounded for display.
  1. Borrow the capital; execution continues in the attacker’s callback.
  2. Manipulate a value another protocol reads: a spot price, an exchange rate, a balance, a vote count.
  3. Exploit the dependent protocol while the value is wrong: borrow, mint, redeem, vote or liquidate.
  4. Unwind the manipulation to recover most of the capital, for example by trading back.
  5. Repay principal and fee; the remainder is profit.

The vulnerability sits in the third phase: the dependent protocol accepted a value one transaction could change and restore.

A worked example

Consider a model with the following parameters. Token X is a volatile asset with a fair price of 2,000; Y is a stablecoin worth 1. Values are in Y.

  • Pool. Constant product: x₀ = 2,000 X and y₀ = 4,000,000 Y, so k = 8 × 10⁹ and the spot price y / x is 2,000. The 0.30% fee on each swap’s input is paid to liquidity providers outside the reserves, so x·y = k holds exactly.
  • Lending market. Values X at the pool’s spot price and lends up to 75% of that value, at most 1,350,000 Y in total against X.
  • Attacker. Holds 400 X bought at the fair price (800,000 Y), to be abandoned as collateral; pays a 0.05% flash premium and an assumed 100 Y of gas; aims to multiply the spot price by m = 2.25 (√m = 1.5).

Since the spot price is y / x = y² / k, multiplying it by m means leaving y₁ = y₀ × √m and x₁ = k / y₁. Selling Δx back, net of fee, moves the X reserve to x₂ = x₁ + Δx and pays out y₁ − k / x₂. Values are computed exactly and rounded for display.

StepComputationResult
1. Swap neededy₁ = 4,000,000 × 1.5; x₁ = 8 × 10⁹ ÷ 6,000,000y₁ = 6,000,000; x₁ = 1,333.33 X
2. Input and feeyIn = (6,000,000 − 4,000,000) ÷ 0.9972,006,018.05, of which 6,018.05 is fee
3. X receivedxOut = 2,000 − 1,333.33666.67 X
4. Price impactSpot y₁ ÷ x₁; average fill yIn ÷ xOutSpot 2,000 → 4,500 (+125%); average fill 3,009.03 (+50.45%)
5. Collateral as the market values it400 × 4,5001,800,000 (fair value 800,000)
6. Amount borrowable75% × 1,800,000, within the cap1,350,000
7. Unwindx₂ = 1,333.33 + 0.997 × 666.67 = 1,998; yOut = 6,000,000 − 8 × 10⁹ ÷ 1,9981,995,996.00 (fee 2.00 X)
8. Unwind lossyIn − yOut10,022.06
9. Flash premium0.05% × 2,006,018.051,003.01
10. Net profit1,350,000 − 800,000 − 10,022.06 − 1,003.01 − 100538,874.93
The attack on the model, step by step (values in Y unless marked X)

The cash reconciles: 1,995,996.00 + 1,350,000 = 3,345,996.00 Y, less 2,007,021.06 repaid, 800,000 of abandoned collateral and 100 of gas, leaves 538,874.93. The market’s shortfall of 550,000 is split between the attacker (538,974.93 before gas), the liquidity providers (10,022.06) and the flash lender (1,003.01).

The round trip costs exactly its two fees: 6,018.05 Y in, and 2.00 X out, for which the pool would have paid 4,004.00 Y. Uniswap v2 keeps fees in the reserves, where the attacker recovers part of them, so there the round trip would cost about 8,012 Y. Nor need the 400 X be the attacker’s own: bought on a deep venue inside the transaction, they add only 400 Y of premium.

Deeper liquidity, a TWAP, an external feed

Change one parameter at a time. Every amount in a constant-product round trip scales with the reserves, so ten times the depth means ten times the fees and premium, for the same extracted value.

ConfigurationPrice readExtractableFees, premium, gasNet at ×2.25Attacker’s best move
Base pool, spot price4,500550,00011,125+538,875≈ +1,220,000 at about ×40
Pool 10× deeper, spot price4,500550,000110,351+439,649≈ +790,000 at about ×8
Pool 60× deeper, spot price4,500550,000661,604−111,604≈ −109,000 at about ×2.1; no move pays
Base pool, TWAP or external feed2,000011,125−11,125No same-transaction move
The attack under four configurations. Net is for a ×2.25 move with 400 X; the last column lets the attacker choose the move and the collateral. Whole Y, rounded.

Depth alone is a weak defence for a spot price. Ten times the liquidity cuts the profit at the same move by less than a fifth, and a bigger move then pays better, because the value extracted per unit of collateral grows with the price move while the swap needed grows with its square root. The attack fails at every move only once the pool is about 50 times deeper: roughly 200 million Y per side, or 150 times the borrow cap.

A price that one transaction cannot move removes the extractable value. Under a TWAP or an external feed the 400 X are still worth 2,000 each, 600,000 of borrowing is an ordinary loan, and the swaps simply cost 11,125. Moving such a price means holding it across blocks, which a flash loan cannot fund; that cost is the subject of our primer on the cost of manipulation.

The profitability inequality

A flash-funded attack is profitable when

V > C + F + G

where V is the value extracted while the protocol is misled, C the cost of moving and restoring what it reads (here the round-trip fees), F the flash-loan fee and G gas. In the base case, V is about 49 times C + F + G. The loan supplies only F; its real effect is that the right-hand side carries no cost of capital and no market risk.

DefenceTerm it changesEffect in the model
TWAP or external feedV falls to zero within one transaction; C becomes a multi-block cost flash loans cannot fundNet −11,125
Deviation bound d against a referenceV: the price read rises by a factor of at most 1 + dNothing to extract while 0.75 × (1 + d) < 1, that is d < 33.3%
Deeper liquidityC rises in proportionProfitable at 10×; at no move from about 50×
Borrow cap BV is at most B × (1 − 1 / (LTV × m))V below 1,350,000 at any move
Lower loan-to-valueV is zero until m > 1 / LTVAt 75%, a rise of more than a third
What each defence changes in the inequality, with the model’s parameters

One caveat: a deviation bound stops borrowing against inflated collateral, but a price pushed down by almost d can still liquidate honest positions within d of their threshold, and the liquidation bonus becomes V.

The loan is rarely the vulnerability

Every term except F belongs to the protocol under attack. “Flash loan attack” names the funding, not the flaw, and each underlying flaw has a defence that works whether or not a loan is involved.

Underlying flawWhat the loan suppliesDefence
Spot-price oracleCapital to move reserves and restore themA price one transaction cannot move, bounded against a reference; exposure caps
Donation-sensitive accountingCapital to inflate a balance or build a large positionInternal accounting instead of balanceOf; virtual shares; a solvency check on every path that reduces collateral
Voting power read in the current blockTokens for one transactionPast checkpoints (ERC-5805); a delay and timelock before execution
Reentrancy, including read-onlyCapital that makes a re-entered call payChecks-effects-interactions; reentrancy guards; guarded views
Underlying flaws behind flash-funded attacks and the defence that addresses each

Donation-sensitive accounting is the weakness behind vault inflation attacks; borrowed voting power is examined in our analysis of governance attacks. For the price class, the fix belongs in the market.

ModelMarket.sol: spot price replaced, exposure capped (illustrative)diff
 // Simplified, illustrative. Not production code. function collateralPrice() public view returns (uint256) {-    (uint112 rx, uint112 ry, ) = pool.getReserves();-    return uint256(ry) * 1e18 / rx;                 // spot: any trade in this transaction moves it+    uint256 p = _feedPrice();                       // external feed, sign and age checked+    uint256 ref = _twap(TWAP_WINDOW);                // a reference one transaction cannot move+    if (_deviationBps(p, ref) > MAX_DEVIATION_BPS) revert PriceDeviation();+    return p; }  function borrow(uint256 amount) external {     uint256 value = collateral[msg.sender] * collateralPrice() / 1e18;     require(debt[msg.sender] + amount <= value * LTV_BPS / 10_000, "LTV");+    require(totalDebt + amount <= BORROW_CAP, "cap"); // bounds what any mispricing can extract     debt[msg.sender] += amount;+    totalDebt += amount;     stable.safeTransfer(msg.sender, amount); }

Four public incidents

Each incident below was funded with flash loans; in each, the flaw lay elsewhere. Figures are those of the cited sources, at prices of the time.

DateProtocolChainUnderlying flawReported loss
18 Feb 2020bZxEthereumsUSD collateral priced from Uniswap and Kyber, which the attacker had just moved with a 7,500 ETH flash loan from bZx itselfAttacker profit 2,381 ETH, about US$0.63M (Qin et al.)
26 Oct 2020Harvest FinanceEthereumShares priced from the live value of assets in Curve’s Y pool; a 3% arbitrage check missed a move of about 1%About US$33.8M (Harvest)
17 Apr 2022BeanstalkEthereumVoting power from same-transaction deposits; an emergency function executed proposals with two-thirds of the votesAbout US$181M; about US$77M in non-Bean assets taken (Immunefi)
13 Mar 2023Euler FinanceEthereumA donation function with no solvency check: an account could make itself insolvent and liquidate itself at a discountAbout US$197M (CertiK)
Four flash-funded incidents, their underlying flaw and the loss reported by the cited source

A first bZx attack, three days earlier, used a 10,000 ETH flash loan; Qin et al. note that it “could have been avoided if bZx checked the negative equity and reverted the transaction”, a check that existed but was not invoked. In Euler’s case, CertiK’s analysis of one transaction shows a 30 million DAI flash loan from Aave funding the position that was then donated into insolvency.

Detection signals

An attack of this shape leaves a recognisable trace in its own transaction. The strongest signal combines:

  • a call to a known flash lender followed by a callback, large relative to the pool traded next;
  • two opposite swaps in one pool, so that its spot price leaves its recent range and returns;
  • between them, a borrow, mint, redeem, vote or liquidation in a protocol that reads that pool;
  • an account that ends the transaction with debt above its collateral valued at a reference price.

Arbitrage and liquidation bots produce the first two signals all day; the last two mark an attack. A monitor that decodes each transaction touching the protocol, measures how far each price it reads moves within it, and compares borrowing prices with a reference feed can flag the combination in the same block. That transaction is final, but attacks repeat: Harvest’s post-mortem records 17 transactions against one vault within four minutes and 13 against another within three, so a pause after the first few would have limited the loss. Earlier signals are covered in our analysis of suspicious on-chain behaviour, and Security Monitoring follows recorded incidents through these stages.

Review checklist and invariant tests

Treat flash loans as a given, and ask what one transaction with unlimited capital could do to each value the protocol trusts.

  • List every price, rate, balance and vote weight the protocol reads; mark those one transaction can change.
  • For each, write the inequality at current liquidity, caps and loan-to-value, with capital free.
  • Check that every path that reduces collateral or adds debt, donations included, ends with a solvency check.
  • Check that votes come from a checkpoint before the proposal, and that nothing executes in the voting transaction.
  • Check external calls made mid-update, including reads of this protocol’s views by others.

Each item maps to an invariant: the price used does not move within a transaction when reserves do; no account ends a transaction undercollateralised at the reference price; a donation raises nobody’s redeemable value or borrowing power; counted votes never exceed the checkpointed supply. The first is a short fuzz test.

PriceIndependence.t.sol (illustrative)solidity
// Simplified, illustrative. Not production code.// Foundry fuzz test against the model pool and market described in this article.contract PriceIndependenceTest is Test {    ModelPool pool;    ModelMarket market;    address alice = makeAddr("alice");     function setUp() public {        // deploy the pool (2,000 X and 4,000,000 Y) and the market; alice deposits 400 X    }     function testFuzz_tradeDoesNotMoveBorrowingPower(uint256 yIn) public {        yIn = bound(yIn, 1e18, 100_000_000e18);        uint256 before = market.maxBorrow(alice);         deal(address(pool.tokenY()), address(this), yIn);        pool.tokenY().approve(address(pool), yIn);        pool.swapExactYForX(yIn, address(this));     // moves the spot price inside this transaction         assertEq(market.maxBorrow(alice), before, "borrowing power follows the spot price");    }}

Against the spot-price market, any swap changes the result, so the test fails on its first input; against the fixed market it holds for every swap size. The solvency invariant needs a stateful handler that swaps, deposits, borrows and donates in any order, and catches combinations nobody wrote down.

Conclusions

A flash loan pays for an attack; it does not break a protocol. Free capital for one transaction turns any value that can be moved and restored within a transaction into a liability, and any safety margin that assumed attackers need money into none.

In the model, the attacker’s costs were 2% of what it extracted, and ten times the liquidity still left a large profit; the attack stopped paying only with a pool about 50 times deeper, or once the market stopped reading a price one transaction could move. The defences that matter change the protocol: what it reads, how much it lets anyone take, and when it counts a balance or a vote.

Recommendations

Protocol developers

  1. Value collateral, shares and votes only from inputs one transaction cannot move.
  2. End every function that reduces collateral or adds debt with a solvency check.
  3. Bound each price against an independent reference, and size borrow caps so that extractable value stays below the cost of moving it.

Risk and monitoring

  1. Recompute the inequality whenever liquidity, caps or loan-to-value change, with capital free.
  2. Alert on a flash borrow, a round trip through a pool the protocol reads and a borrow or mint in between; keep a pause that acts within minutes.

Reviewers

  1. Assume unlimited single-transaction capital; never accept “requires large capital” as a mitigation.
  2. Fuzz for price independence within a transaction and for solvency at a reference price.

Bibliography

Primary sources: standards, official documentation, incident reports and papers.

  1. 1Flash Loans (Aave V3)(opens in a new tab)Aave documentation
  2. 2Protocol Fee Operations: flash loan fees(opens in a new tab)Balancer documentation
  3. 3Flash Swaps (Uniswap v2)(opens in a new tab)Uniswap developer documentation
  4. 4ERC-3156: Flash Loans(opens in a new tab)Ethereum Improvement Proposals
  5. 5Attacking the DeFi Ecosystem with Flash Loans for Fun and Profit(opens in a new tab)Qin, Zhou, Livshits and Gervais, arXiv:2003.03810
  6. 6Harvest Flashloan Economic Attack Post-Mortem(opens in a new tab)Harvest Finance, October 2020
  7. 7Hack Analysis: Beanstalk Governance Attack, April 2022(opens in a new tab)Immunefi
  8. 8Euler Finance Incident Analysis(opens in a new tab)CertiK, March 2023

Applied on this site

Assessments, case studies and tools that apply this publication.

Want this question asked of your code?

Quantix reviewers apply the same reasoning to production systems. Share what you are building and when it ships.