DEV Community

metadevdigital
metadevdigital

Posted on

Flash Loans: How Aave Turned Instant Liquidity Into a $25M Problem

Flash Loans: How Aave Turned Instant Liquidity Into a $25M Problem

cover

February 2020. bZx protocol got torn apart by a $55,000 flash loan attack—pocket change now, but it was the first time anyone weaponized atomic arbitrage on mainnet.1 Three years later, Euler Finance ate a $196 million loss using the same playbook.2 So yeah, flash loans are elegant as hell, but they'll also burn your protocol to the ground if you're not paranoid enough.

TL;DR: Aave's flash loans are uncollateralized loans that must be repaid (plus 0.05% fee) within the same transaction. They're powerful for arbitrage, liquidations, and MEV, but catastrophically dangerous if you assume prices remain stable or rely on external oracles without reentrancy guards. Always validate state changes mid-transaction; never trust balances or prices before the loan is repaid.

Why Would Anyone Give You Money They Can Take Back Mid-Transaction?

Ethereum transactions are atomic. Either they all succeed or they all fail—there's no halfway state on the blockchain.3 Aave figured out that this atomicity meant they could lend money with zero risk. If you can't repay within the same transaction, the whole thing reverts like it never happened.

The implementation is almost stupid in its simplicity. You call flashLoan(), Aave sends you tokens, invokes your callback function, checks that the tokens (plus a 0.05% fee) got returned, and if not—transaction gets nuked.

interface IFlashLoanReceiver {
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external returns (bytes32);
}
Enter fullscreen mode Exit fullscreen mode

Here's the actual flow: you call flashLoan() with an asset and amount. Aave transfers tokens to your contract, calls your executeOperation() callback, then verifies the tokens plus fee made it back into their vault.

// Simplified Aave flash loan logic
function flashLoan(
    address receiver,
    address token,
    uint256 amount,
    bytes calldata params
) external {
    uint256 balanceBefore = IERC20(token).balanceOf(address(this));
    uint256 amountOwed = amount + (amount * flashLoanPremium / 10000);

    IERC20(token).transfer(receiver, amount);

    IFlashLoanReceiver(receiver).executeOperation(
        token,
        amount,
        amountOwed - amount,
        msg.sender,
        params
    );

    // This is the critical check: if it fails, entire tx reverts
    require(
        IERC20(token).balanceOf(address(this)) >= amountOwed,
        "Flash loan not repaid"
    );
}
Enter fullscreen mode Exit fullscreen mode

The legitimate uses are actually pretty cool: DEX arbitrage, liquidations without needing capital upfront, swapping collateral on the fly.4 Problems start when someone builds a liquidator and forgets that their callback runs mid-transaction, not after everything settles.

The Oracle Manipulation Trap: A Case Study in Assumptions

What happens when you ask "what's the price of this asset" after you receive a flash loan but before you repay it? (hint: you're about to get wrecked)

Here's the vulnerable pattern:

// ❌ DO NOT DO THIS
contract VulnerableLiquidator is IFlashLoanReceiver {
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external override returns (bytes32) {
        // Receive flash loan of WETH
        // Check price on Uniswap V2
        uint256 ethPrice = getUniswapV2Price(WETH, USDC);

        // Problem: attacker could have manipulated Uniswap V2 in the same tx
        // Flash loan is fungible—they could have flash loaned the same assets
        // and created artificial price movement before your callback even runs

        // Liquidate a borrower based on this price
        liquidateBorrower(ethPrice);

        // Repay loan
        IERC20(asset).approve(address(lender), amount + premium);
        // ...
    }
}
Enter fullscreen mode Exit fullscreen mode

Euler Finance got destroyed exactly this way. An attacker flash loaned tokens, used those tokens to tank the price of another asset on Euler's internal oracle, triggered liquidations of healthy accounts, and walked off with millions—all one atomic transaction.5

The fix is boring and unsexy but it actually works: use Chainlink or other battle-hardened external price feeds that can't be manipulated mid-transaction.

// ✅ CORRECT: Use an oracle that doesn't suck
contract SafeLiquidator is IFlashLoanReceiver {
    IChainlinkOracle public oracle;

    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external override returns (bytes32) {
        // Chainlink prices are tamper-resistant because they:
        // 1) Update only between transactions (not mid-block)
        // 2) Derive from multiple independent sources
        // 3) Include time-delay mechanisms

        uint256 ethPrice = oracle.latestAnswer(WETH);

        // Liquidation proceeds with confidence
        liquidateBorrower(ethPrice);

        // Repay
        uint256 amountOwed = amount + premium;
        IERC20(asset).approve(address(lender), amountOwed);

        return keccak256("ERC3156FlashBorrower.onFlashLoan");
    }
}
Enter fullscreen mode Exit fullscreen mode

Reentrancy: The Thing Everyone Forgets About

Flash loans stack badly with reentrancy attacks because callbacks execute mid-transaction. Curve's withdrawal vulnerability happened because flash loans could manipulate liquidity and re-enter the withdrawal function before state got updated.6

Use checks-effects-interactions. Update your internal state before you make external calls. Don't even trust Aave with this rule.


Flash loans were legitimately cool—they democratized liquidations and arbitrage for anyone without a seven-figure balance. But they also exposed something uncomfortable: developers were treating "happened in the same block" as "happened safely." The $25M+ in attacks we've tracked down were mostly just people ignoring basic transaction-level constraints.

Are they dangerous? Yeah. The danger's not in Aave's code though (which is locked down tight). It's in developers who think atomic transactions are magic and that their price oracle is somehow exempt from being manipulated.



  1. bZx attack (February 2020): https://peckshield.medium.com/bzx-hack-full-post-mortem-with-detailed-fund-flow-and-exchange-timestamps-e563bf2b23f0 

  2. Euler Finance flash loan component of $196M exploit (March 2023): https://www.euler.finance/blog/euler-incident-report 

  3. EVM enforces this at the opcode level; REVERT throws away all state changes back to the transaction entry point. 

  4. Flash loans enabled the "$1.3 billion in liquidations without capital" narrative in 2021-2022, though this metric is misleading since you still need enough collateral to cover the 0.05% fee. 

  5. Euler's oracle multiplier tracked internal borrow/supply ratios, which attackers could artificially move via flash loans. See audit post-mortem. 

  6. Curve's August 2023 vulnerability involved a vyper compiler bug, but flash loans were a component of attack vectors in subsequent copycat exploits. 

Top comments (0)