SC04:2026 - Flash Loan–Facilitated Attacks

Description

Flash loan–facilitated attacks describe exploits where an attacker uses uncollateralized, same-transaction borrowing (flash loans) to amplify an underlying vulnerability into a profitable, protocol-draining attack. Flash loans are not inherently vulnerable—they are a legitimate DeFi primitive—but they grant attackers arbitrarily large, transient capital within a single transaction. Any protocol that makes trust assumptions based on capital at risk or historical position size is exposed when an attacker can temporarily hold massive balances without putting their own funds at risk.

This affects all contract types that assume economic constraints or proportional exposure: lending protocols (governance vote weight, liquidation thresholds, collateral ratios), AMMs (share minting, arbitrage, oracle skew), yield vaults (deposit/withdraw/share accounting), governance (vote buying, flash-loan governance attacks), NFT and token valuation (floor price, collateral valuation), and cross-protocol composability where one contract’s state is influenced by another’s liquidity. On non-EVM chains, similar concepts exist (e.g., transient large positions within a single block or batch).

Few areas to focus on:

Attackers construct batched transactions that:

  1. Borrow large capital via flash loan (Aave, dYdX, Uniswap V3 flash, or equivalent).
  2. Manipulate protocol state, prices, or accounting using the borrowed funds.
  3. Extract profit (drain liquidity, take under-collateralized loans, skew governance).
  4. Repay the flash loan in the same transaction, keeping the profit.

When underlying weaknesses in business logic (SC02), oracles (SC03), arithmetic (SC07), or access control (SC01) exist, flash loans act as a force multiplier, turning small bugs into catastrophic exploits.

Example (Vulnerable Use of Flash Loans with Rounding Bug)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IFlashLender {
    function flashLoan(
        address receiver,
        uint256 amount,
        bytes calldata data
    ) external;
}

contract VulnerablePool {
    uint256 public totalShares;
    uint256 public totalAssets;

    mapping(address => uint256) public sharesOf;

    // Vulnerable: naive share minting with truncation benefit to sender
    function deposit(uint256 assets) external {
        uint256 shares;
        if (totalShares == 0) {
            shares = assets;
        } else {
            shares = (assets * totalShares) / totalAssets;
        }

        totalAssets += assets;
        totalShares += shares;
        sharesOf[msg.sender] += shares;
    }

    // No safeguard against flash-loan boosted deposit/withdraw loops
}

Issues:

Example (Mitigated Design Considerations)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract SaferPool {
    uint256 public totalShares;
    uint256 public totalAssets;
    uint256 public lastUpdateBlock;

    mapping(address => uint256) public sharesOf;

    error TooFrequentInteraction();

    modifier rateLimited() {
        // Simple example: limit high-impact operations to once per block
        if (lastUpdateBlock == block.number) revert TooFrequentInteraction();
        _;
        lastUpdateBlock = block.number;
    }

    function deposit(uint256 assets) external rateLimited {
        require(assets > 0, "zero assets");

        uint256 shares;
        if (totalShares == 0) {
            shares = assets;
        } else {
            // Use rounding that errs in favor of the protocol and is formally analyzed
            shares = (assets * totalShares + totalAssets - 1) / totalAssets;
        }

        totalAssets += assets;
        totalShares += shares;
        sharesOf[msg.sender] += shares;
    }
}

Security Improvements:

Note: Real protocols should use stronger defense-in-depth mechanisms rather than relying solely on per-block limits.

2025 Case Studies

Best Practices & Mitigations