Back to Blog

Flash Loan Attack Playbook: How Attackers Think and How to Defend

|Odin Scan Team
Flash Loan Attack Playbook: How Attackers Think and How to Defend

Flash loans changed the economics of smart contract exploitation. Before flash loans, exploiting a price manipulation bug required capital. The attacker needed millions in real tokens to move a price. After flash loans, the capital requirement dropped to zero. Anyone can borrow $100 million for one transaction, use it to exploit a vulnerability, and repay it, all atomically.

This did not create new vulnerability classes. It made existing ones catastrophically worse. A price manipulation bug that would have cost an attacker $10 million to exploit now costs them nothing but gas fees.

Understanding how attackers construct flash loan attacks is the first step to defending against them.


The Flash Loan Primitive

A flash loan lets you borrow any amount of tokens with no collateral, as long as you repay the full amount (plus a small fee) within the same transaction. If you do not repay, the entire transaction reverts as if nothing happened.

// Simplified flash loan flow
function executeFlashLoan() external {
    // 1. Borrow $100M USDC from Aave
    aave.flashLoan(address(this), USDC, 100_000_000e6, "");

    // 2. This callback executes with $100M in our contract
    // ... do anything with the capital ...

    // 3. Repay the loan + fee (automatic at end of callback)
    // If we cannot repay, the entire transaction reverts
}

function executeOperation(
    address asset,
    uint256 amount,
    uint256 premium,
    address initiator,
    bytes calldata params
) external returns (bool) {
    // We have $100M USDC here
    // Execute the attack logic
    // ...

    // Approve repayment
    IERC20(asset).approve(address(aave), amount + premium);
    return true;
}

The key insight: for the duration of the callback, the attacker has unlimited capital. Any vulnerability that can be exploited with enough capital becomes exploitable by anyone.


Attack Pattern 1: Price Oracle Manipulation

The most common flash loan attack pattern. The attacker uses borrowed capital to manipulate a price source, then profits from a protocol that reads that manipulated price.

Step-by-step construction:

1. Flash borrow 50,000 ETH from Aave
2. Sell 50,000 ETH into the WETH/USDC Uniswap V2 pool
   -> Pool price of ETH drops massively
3. Protocol X uses this pool as a price oracle
   -> Protocol X now thinks ETH is worth $100 instead of $2,000
4. On Protocol X, deposit $100 USDC as collateral
   -> Protocol X values this at $100
5. Borrow 1 ETH from Protocol X
   -> Protocol X values this at $100 (manipulated price)
   -> Collateral ratio appears sufficient
6. Repay the flash loan (sell the 1 ETH elsewhere for $2,000)
7. Profit: ~$1,900 per ETH borrowed, minus gas and flash loan fees

Why it works: The protocol uses a spot AMM price that can be moved within a single transaction. The attacker moves it, exploits it, and moves it back.

Defense:

// WRONG: spot price from AMM pool
function getPrice() internal view returns (uint256) {
    (uint112 r0, uint112 r1,) = pair.getReserves();
    return uint256(r1) * 1e18 / uint256(r0); // manipulable in one tx
}

// RIGHT: Chainlink oracle with staleness check
function getPrice() internal view returns (uint256) {
    (, int256 price,, uint256 updatedAt,) = feed.latestRoundData();
    require(price > 0, "Invalid price");
    require(block.timestamp - updatedAt <= MAX_STALENESS, "Stale");
    return uint256(price);
}

// ALSO RIGHT: TWAP over a meaningful window
function getPrice() internal view returns (uint256) {
    return oracle.consult(token, 1e18, TWAP_PERIOD); // 30 min+ window
}

Chainlink feeds cannot be manipulated by flash loans because they aggregate prices from multiple off-chain sources. TWAPs resist manipulation because they average prices over a time window that spans multiple blocks.


Attack Pattern 2: Governance Flash Loan

The attacker borrows governance tokens, acquires voting power, and passes a malicious proposal in one transaction.

Step-by-step:

1. Flash borrow 10M governance tokens
2. Delegate voting power to attacker address
3. Create and vote on a malicious proposal
4. If the governance system allows instant execution: execute the proposal
5. Repay the flash loan

Why it works (when it works): Some governance systems use current token balance for voting power instead of historical snapshots. If quorum can be reached and a proposal can be created, voted on, and executed in the same block, the attack succeeds.

Defense: Use getPastVotes() with a snapshot at the proposal creation block. Require a minimum proposal delay. Add a timelock between approval and execution.


Attack Pattern 3: Collateral Inflation

The attacker uses flash-borrowed funds to inflate the value of collateral they already hold, borrows against the inflated collateral, and exits.

Step-by-step:

1. Deposit 100 XYZ tokens as collateral in lending protocol
2. Flash borrow $10M USDC
3. Buy XYZ tokens with the USDC, pumping the price
4. Lending protocol now values the 100 XYZ collateral at 10x
5. Borrow $500K against the inflated collateral
6. Sell the XYZ tokens bought in step 3 (pushing price back down)
7. Repay the flash loan from the sale proceeds
8. Keep the $500K. Never repay the lending protocol. The collateral is now worthless.

Defense: Same as Pattern 1: do not use manipulable price sources. Additionally, implement borrow caps per asset and per user to limit maximum exposure even if the oracle is compromised.


Attack Pattern 4: Liquidity Drain

The attacker uses flash-borrowed capital to drain a protocol's liquidity through arbitrage or fee manipulation.

Step-by-step example (vault donation attack):

1. Be the first depositor in a new vault with 1 wei of token
2. Receive 1 share
3. Flash borrow 1,000,000 tokens
4. "Donate" 1,000,000 tokens to the vault (direct transfer, not deposit)
5. Vault now has 1,000,001 tokens and 1 share
   -> pricePerShare = 1,000,001
6. Another user deposits 999,999 tokens
   -> shares = 999,999 * 1 / 1,000,001 = 0 (rounds down)
   -> User gets 0 shares, their deposit is lost
7. Withdraw 1 share = 2,000,000 tokens
8. Repay flash loan, keep the profit

Defense:

// Prevent first-depositor manipulation
function deposit(uint256 assets) external returns (uint256 shares) {
    if (totalSupply() == 0) {
        // Mint dead shares to address(1) to prevent manipulation
        shares = assets - MINIMUM_SHARES;
        _mint(address(1), MINIMUM_SHARES);
    } else {
        shares = assets * totalSupply() / totalAssets();
    }
    require(shares > 0, "Zero shares");
    _mint(msg.sender, shares);
}

Also known as the "virtual shares" or "dead shares" pattern. ERC-4626 vaults should always implement this.


Attack Pattern 5: Liquidation Manipulation

The attacker uses flash loans to artificially trigger liquidations and profit from the liquidation bonus.

Step-by-step:

1. Identify a user with a healthy but borderline position (105% collateral ratio)
2. Flash borrow the collateral token
3. Sell it on DEXes to push the price down
4. User's position is now underwater at the manipulated price
5. Liquidate the user's position, claiming the liquidation bonus
6. Repay the flash loan
7. Profit from the liquidation bonus minus market impact

Defense: Use manipulation-resistant oracle prices for liquidation decisions. Implement a grace period before liquidation is executable. Set liquidation thresholds with enough buffer that temporary price manipulation cannot trigger liquidation of well-collateralized positions.


The Common Thread

Every flash loan attack follows the same structure:

  1. Borrow capital for free
  2. Manipulate something the protocol trusts (price, voting power, liquidity)
  3. Exploit the protocol's reaction to the manipulated input
  4. Unwind the manipulation
  5. Repay the loan

The vulnerability is never the flash loan itself. It is always the thing being manipulated: the oracle, the governance snapshot, the vault accounting, the liquidation logic.

If your protocol uses any value that can be changed within a single transaction (spot prices, current balances, current supply ratios), that value is flash-loan-manipulable. Design your protocol so that every economically important decision uses values that resist single-transaction manipulation.


The Flash Loan Defense Checklist

  • No spot AMM prices used for any lending, borrowing, or collateral decision
  • All price oracles use Chainlink feeds or TWAPs with 30+ minute windows
  • Governance voting uses historical balance snapshots, not current balances
  • ERC-4626 vaults implement dead shares or virtual shares to prevent first-depositor attacks
  • Liquidation uses manipulation-resistant oracle prices with adequate buffer
  • Borrow caps limit maximum exposure per asset
  • Protocol invariants hold even when prices move 10x in a single block

Odin Scan flags flash-loan-vulnerable patterns automatically: spot AMM price usage, current-balance governance voting, first-depositor-vulnerable vaults, and unprotected liquidation logic. Start your free trial and find these patterns before an attacker does.