P17: Flash Loan Implementation

P17: Flash Loan Implementation

Project Overview

Attribute Value
Main Language Solidity
Alternative Languages Vyper
Difficulty Expert
Coolness Level Level 5: Pure Magic (Super Cool)
Business Potential The “Open Core” Infrastructure
Knowledge Area DeFi / Flash Loans
Main Book “Mastering Ethereum” by Andreas M. Antonopoulos & Gavin Wood
Prerequisites Projects 9 (ERC-20) & 10 (AMM/DEX)
Time Estimate 1-2 weeks

Learning Objectives

By completing this project, you will:

  1. Master transaction atomicity and understand why “all or nothing” execution enables capital-efficient borrowing without collateral
  2. Implement the callback pattern where the lending pool calls into the borrower contract, enabling complex multi-step operations within a single transaction
  3. Design arbitrage strategies that exploit price discrepancies across DEXs, understanding how flash loans democratize access to risk-free profits
  4. Implement robust security measures including reentrancy guards, repayment verification, and protection against malicious callback contracts
  5. Calculate and collect protocol fees on borrowed amounts, understanding how flash loan pools generate revenue
  6. Build multi-asset flash loan support enabling simultaneous borrowing of multiple tokens for complex DeFi operations
  7. Understand flash loan attack vectors and how protocols defend against oracle manipulation, governance attacks, and economic exploits
  8. Integrate with real DeFi protocols executing arbitrage across Uniswap, SushiSwap, Curve, and other AMMs

The Core Question You’re Answering

“How can you borrow millions of dollars with zero collateral, use it for profit, and return it—all in a single transaction?”

This question seems impossible in traditional finance. Banks require collateral, credit checks, and days of processing. But in DeFi, flash loans enable borrowing unlimited capital with zero upfront requirements—because the loan is atomic. If you can’t repay within the same transaction, the entire operation reverts as if it never happened.

Flash loans represent one of Web3’s most powerful primitives: they democratize access to capital. A developer with $0 to their name can execute the same arbitrage strategies as a hedge fund with billions. The blockchain doesn’t care about your credit score—only whether the transaction succeeds.


Deep Theoretical Foundation

Why Flash Loans Can Exist: Transaction Atomicity

In Ethereum (and most blockchains), transactions are atomic—they either completely succeed or completely fail. There’s no partial execution. This property, enforced at the EVM level, is what makes flash loans possible.

Traditional Loan:                     Flash Loan:
  Day 1: Apply for loan                Transaction {
  Day 3: Credit check                    1. Borrow $1M
  Day 7: Receive funds                   2. Use funds (arbitrage, etc.)
  Day 30+: Repay with interest           3. Repay $1M + fee
  Risk: Default                          If any step fails: REVERT
                                       }
                                       Risk: Zero (for lender)

The key insight: if repayment fails, the borrow never happened. From the lender’s perspective, there’s no credit risk—either they get repaid with fees, or the entire transaction reverts and their funds never left.

The Callback Pattern: How It Works

Flash loans use a callback pattern where the lending pool temporarily gives up control to the borrower:

                 Flash Loan Flow
    ================================================

    FlashLoanPool                    BorrowerContract
         |                                  |
         |  1. flashLoan(amount, data)     |
         |<---------------------------------|
         |                                  |
         |  2. Transfer funds               |
         |--------------------------------->|
         |                                  |
         |  3. executeOperation(...)        |
         |--------------------------------->|
         |                                  |
         |      (Borrower does stuff:       |
         |       - arbitrage                |
         |       - liquidate                |
         |       - collateral swap)         |
         |                                  |
         |  4. Borrower repays              |
         |<---------------------------------|
         |                                  |
         |  5. Verify balance >= before+fee |
         |  (if not, REVERT entire tx)      |
         |                                  |

This is inversion of control—the pool calls a function on an untrusted contract. This is inherently dangerous (reentrancy risk), which is why proper security measures are critical.

Flash Loan Economics

Flash loans charge small fees because the risk is minimal:

Protocol Fee Notes
Aave 0.09% Most popular flash loan source
dYdX 0% (was) Used internal accounting
Uniswap V3 0.05-1% Via flash swap mechanism
Balancer Variable Pool-specific fees

Example Arbitrage Math:

Borrow: 1,000 ETH from Aave
Fee: 0.9 ETH (0.09%)

Arbitrage opportunity:
  Buy ETH on DEX_A: 1000 ETH for 3,000,000 USDC
  Sell ETH on DEX_B: 1000 ETH for 3,050,000 USDC
  Gross profit: 50,000 USDC

Net profit after repaying:
  50,000 USDC - (0.9 ETH * 3000 USDC/ETH) = 50,000 - 2,700 = 47,300 USDC

Without flash loan (using own capital):
  Need $3M upfront, same $47,300 profit
  Much higher capital requirements for same return

Why Flash Loans Matter

Flash loans have several legitimate use cases:

  1. Arbitrage: Profit from price discrepancies across DEXs
  2. Collateral Swaps: Change loan collateral in one transaction
  3. Liquidations: Acquire funds to liquidate undercollateralized positions
  4. Self-Liquidation: Close your own position without selling assets first
  5. Governance Attacks: (concerning) Borrow governance tokens to vote

But they’ve also enabled massive exploits:

Attack Loss Mechanism
bZx (Feb 2020) $350K Oracle manipulation via flash loan
Harvest Finance (Oct 2020) $34M Price manipulation attack
Pancake Bunny (May 2021) $45M Flash loan + price manipulation
Cream Finance (Oct 2021) $130M Flash loan + oracle exploit
Beanstalk (Apr 2022) $182M Flash loan governance attack

Understanding flash loans means understanding both their power and their risks.

The Flash Loan Attack Pattern

Most flash loan exploits follow a similar pattern:

Standard Flash Loan Attack Vector
=================================

1. BORROW large amount (e.g., 100,000 ETH)
           |
           v
2. MANIPULATE price oracle
   - Swap in AMM to move price
   - Oracle reads manipulated price
           |
           v
3. EXPLOIT the manipulated price
   - Borrow at wrong collateral ratio
   - Liquidate at wrong price
   - Mint tokens at wrong rate
           |
           v
4. REVERSE manipulation
   - Swap back to restore price
   - Attacker keeps profit
           |
           v
5. REPAY flash loan + fee
   - Transaction completes
   - Protocol is drained

Concepts You Must Understand First

Before implementing flash loans, ensure you understand these foundational concepts:

1. EVM Transaction Atomicity

Source: “Mastering Ethereum” Chapter 6 - Transactions

Every Ethereum transaction is atomic. When you call a function:

  • All state changes happen, OR
  • Everything reverts to the pre-transaction state
function atomicExample() external {
    balances[msg.sender] -= 100;  // Change 1
    balances[recipient] += 100;    // Change 2
    require(someCondition);        // If fails, both changes revert
}

Why it matters: Flash loans exploit atomicity—if repayment fails, the borrow never occurred.

2. The Callback Pattern in Solidity

Source: Aave V3 Documentation

The callback pattern allows one contract to call a function on another:

interface IFlashLoanReceiver {
    function executeOperation(
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata premiums,
        address initiator,
        bytes calldata params
    ) external returns (bool);
}

Why it matters: The flash loan pool must call your contract and trust you to repay. You implement executeOperation to do your arbitrage/liquidation logic.

3. Reentrancy and Security

Source: “Mastering Ethereum” Chapter 9 - Smart Contract Security

When the pool calls your callback, you could try to re-enter the pool’s functions:

// DANGEROUS: Reentrancy attack
function executeOperation(...) external {
    // Flash loan pool called us with 100 ETH
    // Try to borrow again before repaying!
    pool.flashLoan(100 ether, "");  // Reentrancy!
}

Why it matters: Pools must use reentrancy guards to prevent recursive borrowing.

4. DEX/AMM Mechanics

Source: Project 10 (AMM/DEX) - You should have completed this

Understanding how AMMs like Uniswap work is essential:

  • Constant product formula: x * y = k
  • Price impact from large swaps
  • How to calculate optimal swap amounts

Why it matters: Most flash loan strategies involve DEX arbitrage.

5. Arbitrage Opportunities

Source: Real-world DeFi

Arbitrage profit exists when:

Price_DEX_A < Price_DEX_B (after accounting for fees)

Profit = (Price_B - Price_A) * Amount - GasCost - FlashFee

For profitable arbitrage:

  1. Price discrepancy must exceed combined fees
  2. Your transaction must execute before others spot it
  3. Price impact from your trade must not eliminate the spread

6. Gas Optimization for Complex Transactions

Flash loan arbitrage often involves:

  • Multiple DEX swaps
  • Token approvals
  • Complex math

Every operation costs gas. Your profit must exceed gas costs.

Typical flash loan arbitrage gas:
- Flash loan overhead: ~150,000 gas
- Each DEX swap: ~100,000-200,000 gas
- Token approvals: ~45,000 gas each
- Callback overhead: ~50,000 gas

Total: 400,000-800,000 gas
At 50 gwei: 0.02-0.04 ETH ($40-80 at $2000/ETH)

7. MEV and Transaction Ordering

Source: “Flash Boys 2.0” by Daian et al.

Miners/validators can:

  • See your pending transaction
  • Calculate if it’s profitable
  • Front-run or sandwich your trade

Why it matters: Profitable arbitrage is a competitive game. Others will try to steal your opportunity.


Questions to Guide Your Design

Pool Design Questions

  1. Should the pool hold ETH, ERC-20 tokens, or both?
  2. How do liquidity providers deposit and withdraw?
  3. Should LP tokens be minted to track shares?
  4. How do you prevent pool insolvency?
  5. Should there be a maximum flash loan amount?

Callback Interface Questions

  1. What information does the borrower need in the callback?
  2. How do you pass custom data to the borrower?
  3. Should the pool verify the borrower is a contract?
  4. How do you handle multiple simultaneous flash loans?
  5. What happens if the callback runs out of gas?

Fee Structure Questions

  1. What’s the optimal fee to balance usage and revenue?
  2. Should fees go to LPs, protocol treasury, or both?
  3. How do you handle fee calculation without overflow?
  4. Should fees be configurable by governance?
  5. How do competing pools affect fee economics?

Security Questions

  1. How do you prevent reentrancy during the callback?
  2. What if the borrower sends back a different token?
  3. How do you handle malicious callback code?
  4. Should flash loans be pausable in emergencies?
  5. How do you prevent manipulation of the pool itself?

Arbitrage Bot Questions

  1. How do you find profitable opportunities on-chain?
  2. Should you use off-chain price feeds or on-chain reserves?
  3. How do you calculate optimal trade amounts?
  4. How do you handle failed arbitrage gracefully?
  5. How do you compete with other MEV searchers?

Thinking Exercise: Paper Design

Before writing code, work through these scenarios on paper:

Exercise 1: Trace a Successful Arbitrage

Initial State:
- FlashLoanPool: 1000 ETH
- DEX_A (ETH/USDC): 100 ETH / 300,000 USDC (price = 3000 USDC/ETH)
- DEX_B (ETH/USDC): 100 ETH / 310,000 USDC (price = 3100 USDC/ETH)
- Arbitrage Bot: 0 ETH, 0 USDC

Questions:
1. How much ETH should the bot borrow?
2. What's the optimal trade size (considering slippage)?
3. What's the expected profit after fees?
4. Trace the exact balance changes at each step.

Work through it:

Step 1: Borrow X ETH from flash loan pool

  • Pool: (1000 - X) ETH
  • Bot: X ETH

Step 2: Buy USDC on DEX_B (sell ETH where ETH is expensive)

  • Wait, actually sell ETH on DEX_B to get USDC at better rate
  • Or… buy ETH on DEX_A where it’s cheaper, sell on DEX_B?

This gets complex. The optimal strategy depends on which DEX has the mispricing.

Exercise 2: What Happens When Arbitrage Fails?

Scenario: Bot borrows 100 ETH, but prices equalize before callback completes

1. Bot borrows 100 ETH (pool sends to bot)
2. Bot swaps 100 ETH for 300,000 USDC on DEX_A
3. Another transaction front-runs: moves DEX_B price down
4. Bot swaps 300,000 USDC for only 98 ETH on DEX_B
5. Bot tries to repay 100.09 ETH but only has 98 ETH
6. What happens?

Answer: The entire transaction reverts. Bot has 0 profit but also 0 loss. Pool never actually lost the 100 ETH—it’s as if the transaction never happened.

Exercise 3: Reentrancy Attack Vector

Malicious borrower contract:

function executeOperation(uint amount, uint fee, bytes data) external {
    // Pool just sent us 100 ETH
    // Pool is waiting for us to repay

    // Attack: Try to borrow again before repaying!
    pool.flashLoan(100 ether, "");

    // Now we have 200 ETH but pool thinks it only loaned 100
    // Can we repay just 100.09 and keep 100?
}

Question: How does your pool prevent this?

Answer: Use a reentrancy lock. If flashLoan is called while already in a flash loan, revert.


The Interview Questions They’ll Ask

Basic Understanding (Expect These First)

  1. What is a flash loan and why can it exist in DeFi but not traditional finance?

  2. Explain transaction atomicity in the EVM. How does it enable flash loans?

  3. Walk me through the complete flow of a flash loan transaction, from borrow to repayment.

  4. What’s the callback pattern? Why is it necessary for flash loans?

  5. If a flash loan isn’t repaid, what happens to the borrowed funds?

Technical Deep Dives

  1. How do you prevent reentrancy attacks in a flash loan pool?

  2. A flash loan pool has 1000 ETH. User requests 1001 ETH. What should happen?

  3. How do you verify that the borrower repaid the correct amount plus fee?

  4. What data would you pass in the callback’s bytes calldata data parameter?

  5. How would you implement flash loans for multiple tokens simultaneously?

Arbitrage & Strategy

  1. Given these DEX reserves, calculate the optimal arbitrage trade:
    • DEX_A: 100 ETH / 300,000 USDC
    • DEX_B: 80 ETH / 256,000 USDC
    • Flash loan fee: 0.09%
    • Swap fee: 0.3%
  2. Why might a profitable-looking arbitrage opportunity not actually be profitable?

  3. How do you compete with other arbitrage bots (MEV)?

  4. What’s the difference between a flash swap (Uniswap) and a flash loan (Aave)?

Security & Exploits

  1. Explain how flash loans enabled the bZx attack. What was the vulnerability?

  2. How can protocols protect against flash loan-powered governance attacks?

  3. What is oracle manipulation? How do flash loans make it easier?

  4. Should flash loan pools be pausable? What are the trade-offs?

  5. How would you design a protocol to be resistant to flash loan attacks?

Advanced Topics

  1. How do flash loans interact with MEV (Maximal Extractable Value)?

  2. Compare flash loans across Aave, dYdX, and Uniswap. What are the trade-offs?

  3. Could you implement cross-chain flash loans? What are the challenges?

  4. How do flash loans affect DeFi composability and systemic risk?


Hints in Layers

Layer 1: Basic Pool Structure

Start with a minimal flash loan pool for ETH:

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

interface IFlashLoanReceiver {
    function executeOperation(
        uint256 amount,
        uint256 fee,
        bytes calldata data
    ) external returns (bool);
}

contract FlashLoanPool {
    uint256 public constant FEE_PERCENTAGE = 9; // 0.09% = 9/10000

    receive() external payable {}

    function poolBalance() public view returns (uint256) {
        return address(this).balance;
    }

    function flashLoan(uint256 amount, bytes calldata data) external {
        // TODO: Implement
    }
}

Your task: Implement flashLoan with these steps:

  1. Record balance before
  2. Send ETH to borrower
  3. Call borrower’s callback
  4. Verify repayment

Layer 2: Complete Flash Loan Logic

function flashLoan(uint256 amount, bytes calldata data) external {
    require(amount <= address(this).balance, "Insufficient pool balance");

    uint256 balanceBefore = address(this).balance;
    uint256 fee = (amount * FEE_PERCENTAGE) / 10000;

    // Send funds to borrower
    (bool sent, ) = msg.sender.call{value: amount}("");
    require(sent, "ETH transfer failed");

    // Call borrower's callback
    bool success = IFlashLoanReceiver(msg.sender).executeOperation(
        amount,
        fee,
        data
    );
    require(success, "Callback failed");

    // Verify repayment
    require(
        address(this).balance >= balanceBefore + fee,
        "Flash loan not repaid"
    );

    emit FlashLoan(msg.sender, amount, fee);
}

What’s missing? Reentrancy protection!

Layer 3: Reentrancy Guard

contract FlashLoanPool {
    uint256 private _locked = 1;

    modifier nonReentrant() {
        require(_locked == 1, "Reentrancy detected");
        _locked = 2;
        _;
        _locked = 1;
    }

    function flashLoan(uint256 amount, bytes calldata data)
        external
        nonReentrant  // Add this!
    {
        // ... rest of implementation
    }
}

Now recursive flashLoan calls will revert.

Layer 4: Basic Arbitrage Bot

contract ArbitrageBot is IFlashLoanReceiver {
    address public pool;
    address public dexA;
    address public dexB;

    constructor(address _pool, address _dexA, address _dexB) {
        pool = _pool;
        dexA = _dexA;
        dexB = _dexB;
    }

    function executeArbitrage(uint256 amount) external {
        // Initiate flash loan
        IFlashLoanPool(pool).flashLoan(amount, "");
    }

    function executeOperation(
        uint256 amount,
        uint256 fee,
        bytes calldata
    ) external returns (bool) {
        require(msg.sender == pool, "Only pool can call");

        // 1. Buy tokens on DEX_A with borrowed ETH
        uint256 tokensReceived = IDex(dexA).swapETHForTokens{value: amount}();

        // 2. Sell tokens on DEX_B for ETH
        IERC20(token).approve(dexB, tokensReceived);
        uint256 ethReceived = IDex(dexB).swapTokensForETH(tokensReceived);

        // 3. Repay flash loan + fee
        uint256 amountOwed = amount + fee;
        require(ethReceived >= amountOwed, "Arbitrage not profitable");

        (bool sent, ) = pool.call{value: amountOwed}("");
        require(sent, "Repayment failed");

        // Profit stays in contract
        return true;
    }

    receive() external payable {}
}

Layer 5: Multi-Asset Flash Loans

interface IMultiAssetFlashLoanReceiver {
    function executeOperation(
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata fees,
        address initiator,
        bytes calldata params
    ) external returns (bool);
}

function flashLoan(
    address[] calldata assets,
    uint256[] calldata amounts,
    bytes calldata params
) external nonReentrant {
    require(assets.length == amounts.length, "Length mismatch");

    uint256[] memory balancesBefore = new uint256[](assets.length);
    uint256[] memory fees = new uint256[](assets.length);

    // Transfer all assets to borrower
    for (uint256 i = 0; i < assets.length; i++) {
        balancesBefore[i] = IERC20(assets[i]).balanceOf(address(this));
        fees[i] = (amounts[i] * FEE_PERCENTAGE) / 10000;

        require(
            balancesBefore[i] >= amounts[i],
            "Insufficient balance"
        );

        IERC20(assets[i]).transfer(msg.sender, amounts[i]);
    }

    // Execute callback
    require(
        IMultiAssetFlashLoanReceiver(msg.sender).executeOperation(
            assets,
            amounts,
            fees,
            msg.sender,
            params
        ),
        "Callback failed"
    );

    // Verify all repayments
    for (uint256 i = 0; i < assets.length; i++) {
        require(
            IERC20(assets[i]).balanceOf(address(this)) >=
                balancesBefore[i] + fees[i],
            "Flash loan not repaid"
        );
    }
}

Layer 6: Liquidity Provider Integration

contract FlashLoanPool is ERC20 {
    IERC20 public immutable asset;
    uint256 public constant FEE_PERCENTAGE = 9; // 0.09%

    constructor(address _asset) ERC20("FlashLP", "FLP") {
        asset = IERC20(_asset);
    }

    // LPs deposit assets and receive LP tokens
    function deposit(uint256 amount) external returns (uint256 shares) {
        uint256 totalAssets = asset.balanceOf(address(this));
        uint256 totalShares = totalSupply();

        if (totalShares == 0) {
            shares = amount;
        } else {
            shares = (amount * totalShares) / totalAssets;
        }

        asset.transferFrom(msg.sender, address(this), amount);
        _mint(msg.sender, shares);
    }

    // LPs burn LP tokens to withdraw assets + earned fees
    function withdraw(uint256 shares) external returns (uint256 amount) {
        uint256 totalAssets = asset.balanceOf(address(this));
        uint256 totalShares = totalSupply();

        amount = (shares * totalAssets) / totalShares;

        _burn(msg.sender, shares);
        asset.transfer(msg.sender, amount);
    }

    // Flash loan fees accumulate in pool, benefiting LPs
}

Layer 7: Advanced Security

contract SecureFlashLoanPool {
    // Reentrancy guard
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;
    uint256 private _status = NOT_ENTERED;

    // Emergency pause
    bool public paused;
    address public guardian;

    // Maximum loan percentage of pool
    uint256 public constant MAX_LOAN_PERCENTAGE = 9000; // 90%

    modifier nonReentrant() {
        require(_status == NOT_ENTERED, "ReentrancyGuard: reentrant call");
        _status = ENTERED;
        _;
        _status = NOT_ENTERED;
    }

    modifier whenNotPaused() {
        require(!paused, "Pool is paused");
        _;
    }

    function flashLoan(uint256 amount, bytes calldata data)
        external
        nonReentrant
        whenNotPaused
    {
        uint256 poolBalance = address(this).balance;

        // Prevent borrowing entire pool (manipulation protection)
        require(
            amount <= (poolBalance * MAX_LOAN_PERCENTAGE) / 10000,
            "Exceeds max loan"
        );

        // ... rest of implementation
    }

    function pause() external {
        require(msg.sender == guardian, "Only guardian");
        paused = true;
    }

    function unpause() external {
        require(msg.sender == guardian, "Only guardian");
        paused = false;
    }
}

Layer 8: Real DEX Integration

interface IUniswapV2Router {
    function swapExactETHForTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable returns (uint[] memory amounts);

    function swapExactTokensForETH(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
}

contract ProductionArbitrageBot is IFlashLoanReceiver {
    IFlashLoanPool public pool;
    IUniswapV2Router public routerA;  // e.g., Uniswap
    IUniswapV2Router public routerB;  // e.g., SushiSwap
    address public WETH;
    address public owner;

    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner");
        _;
    }

    constructor(
        address _pool,
        address _routerA,
        address _routerB,
        address _weth
    ) {
        pool = IFlashLoanPool(_pool);
        routerA = IUniswapV2Router(_routerA);
        routerB = IUniswapV2Router(_routerB);
        WETH = _weth;
        owner = msg.sender;
    }

    function executeArbitrage(
        uint256 loanAmount,
        address token,
        uint256 minProfit
    ) external onlyOwner {
        bytes memory data = abi.encode(token, minProfit);
        pool.flashLoan(loanAmount, data);
    }

    function executeOperation(
        uint256 amount,
        uint256 fee,
        bytes calldata data
    ) external returns (bool) {
        require(msg.sender == address(pool), "Only pool");

        (address token, uint256 minProfit) = abi.decode(
            data,
            (address, uint256)
        );

        // Build paths
        address[] memory pathBuy = new address[](2);
        pathBuy[0] = WETH;
        pathBuy[1] = token;

        address[] memory pathSell = new address[](2);
        pathSell[0] = token;
        pathSell[1] = WETH;

        // Buy tokens on DEX A
        uint[] memory amountsOut = routerA.swapExactETHForTokens{value: amount}(
            0, // Accept any amount (calculate properly in production!)
            pathBuy,
            address(this),
            block.timestamp + 300
        );

        // Approve and sell on DEX B
        IERC20(token).approve(address(routerB), amountsOut[1]);
        uint[] memory amountsBack = routerB.swapExactTokensForETH(
            amountsOut[1],
            0, // Accept any amount
            pathSell,
            address(this),
            block.timestamp + 300
        );

        uint256 ethReceived = amountsBack[1];
        uint256 amountOwed = amount + fee;

        require(ethReceived >= amountOwed + minProfit, "Insufficient profit");

        // Repay
        (bool sent, ) = address(pool).call{value: amountOwed}("");
        require(sent, "Repayment failed");

        return true;
    }

    // Withdraw profits
    function withdrawProfit() external onlyOwner {
        payable(owner).transfer(address(this).balance);
    }

    receive() external payable {}
}

ASCII Diagrams

Flash Loan Transaction Flow

                    FLASH LOAN TRANSACTION
    ======================================================

    TIME  |  POOL STATE  |  BORROWER STATE  |  ACTION
    ------|--------------|------------------|------------------
          |              |                  |
    T0    | 1000 ETH     | 0 ETH            | Initial state
          |              |                  |
          v              v                  v
    ------+==============+==================+-----------------
          |              |                  | flashLoan(100)
    T1    | 1000 ETH     | 0 ETH            | Borrow requested
          |   |          |                  |
          |   | 100 ETH  |                  |
          |   +--------->|                  |
    T2    | 900 ETH      | 100 ETH          | Funds sent
          |              |                  |
          |              |   +-------+      | executeOperation()
          |              |   | SWAP  |      |
          |              |   | 100E->|      |
          |              |   |305K $ |      |
    T3    |              | 305K USDC        | Bought on DEX_A
          |              |   +-------+      |
          |              |   | SWAP  |      |
          |              |   |305K$->|      |
          |              |   |102 E |      |
    T4    |              | 102 ETH          | Sold on DEX_B
          |              |                  |
          |              | 100.09 ETH       |
          |   <----------+   |              |
    T5    | 1000.09 ETH  | 1.91 ETH         | Repaid + fee
          |              |                  |
    ------+==============+==================+-----------------
                                            | TX SUCCESS
    RESULT: Pool gained 0.09 ETH fee
            Borrower gained 1.91 ETH profit
            All in ONE atomic transaction!

Callback Pattern Visualization

                    CALLBACK PATTERN
    ================================================

                        +-------------------+
                        |  FLASH LOAN POOL  |
                        |                   |
                        | balance: 1000 ETH |
                        +--------+----------+
                                 |
                 1. flashLoan(100 ETH, data)
                                 |
                        +--------v----------+
                        |    Check: Can we  |
                        |    lend 100 ETH?  |
                        +--------+----------+
                                 | YES
                                 |
                 2. Transfer 100 ETH
                        +--------v----------+
                        |     BORROWER      |
                        |    CONTRACT       |
                        +--------+----------+
                                 |
                 3. executeOperation(100, 0.09, data)
                                 |
        +------------------------+------------------------+
        |                        |                        |
   +----v----+             +-----v-----+            +-----v-----+
   | DEX A   |             | DEX B     |            | PROFIT    |
   | Buy     |   ------>   | Sell      |   ------>  | Calculate |
   | Tokens  |             | Tokens    |            |           |
   +---------+             +-----------+            +-----+-----+
                                                          |
                 4. Transfer 100.09 ETH back
                        +--------v----------+
                        |  FLASH LOAN POOL  |
                        |                   |
                        | Check: balance >= |
                        |   1000 + 0.09?    |
                        +--------+----------+
                                 |
                              SUCCESS

Arbitrage Opportunity Detection

                ARBITRAGE OPPORTUNITY DETECTION
    ========================================================

         DEX A (Uniswap)              DEX B (SushiSwap)
    +-----------------------+    +-----------------------+
    |  ETH / USDC Pool      |    |  ETH / USDC Pool      |
    |                       |    |                       |
    |  100 ETH              |    |  80 ETH               |
    |  300,000 USDC         |    |  256,000 USDC         |
    |                       |    |                       |
    |  Price: 3000 USDC/ETH |    |  Price: 3200 USDC/ETH |
    +-----------+-----------+    +-----------+-----------+
                |                            |
                |      PRICE DIFFERENCE      |
                |        $200 / ETH          |
                +-------------+--------------+
                              |
                    +---------v---------+
                    |  ARBITRAGE BOT    |
                    |                   |
                    | 1. Borrow ETH     |
                    | 2. Buy ETH on A   |
                    |    (cheaper)      |
                    | 3. Sell ETH on B  |
                    |    (expensive)    |
                    | 4. Repay + profit |
                    +-------------------+

    Calculation:
    +---------------------------------------------------------+
    | Borrow: 10 ETH from flash loan pool                     |
    | Buy: 10 ETH costs ~30,300 USDC on DEX_A (with slippage) |
    | Sell: 10 ETH returns ~31,800 USDC on DEX_B              |
    | Gross: 31,800 - 30,300 = 1,500 USDC                     |
    | Flash fee: 10 ETH * 0.09% * 3100 = ~2.79 USDC           |
    | Gas: ~500,000 gas * 50 gwei = 0.025 ETH = ~77.5 USDC    |
    | Net Profit: 1,500 - 2.79 - 77.5 = ~1,420 USDC           |
    +---------------------------------------------------------+

Flash Loan Attack Vector

            FLASH LOAN ORACLE MANIPULATION ATTACK
    ============================================================

    ATTACKER                     VULNERABLE               ORACLE
    CONTRACT                      PROTOCOL                (AMM-based)
        |                            |                        |
    1. Borrow 10,000 ETH             |                        |
        |-------------------------------->                    |
        |                            |                        |
    2. Swap 5,000 ETH -> USDC on AMM                          |
        |------------------------------------------------------>
        |                            |   Price crashes!       |
        |                            |   ETH: $3000 -> $2000  |
        |                            |                        |
    3. Use manipulated price         |                        |
        |--------------------------->|                        |
        |   "ETH collateral worth    |<-----------------------|
        |    less, liquidate!"       | Query price            |
        |                            |                        |
    4. Liquidate positions at bad price                       |
        |<---------------------------|                        |
        |   Receive discounted ETH   |                        |
        |                            |                        |
    5. Swap USDC -> ETH (restore)                             |
        |------------------------------------------------------>
        |                            |   Price restores       |
        |                            |                        |
    6. Repay 10,000 ETH + fee        |                        |
        |<-------------------------------                     |
        |                            |                        |
    7. Keep profit from liquidation  |                        |
        |   $$$$$$$$$                |                        |

    RESULT: Attacker profits from manipulated liquidations
            Victims lose collateral at unfair prices
            Attack cost: Only flash loan fee (~0.09%)

Real World Outcome

# ==============================================================
# FLASH LOAN IMPLEMENTATION - DEPLOYMENT & TESTING
# ==============================================================

# 1. SETUP: Deploy flash loan pool with initial liquidity
# --------------------------------------------------------------
$ forge create src/FlashLoanPool.sol:FlashLoanPool \
    --rpc-url $SEPOLIA_RPC \
    --private-key $PRIVATE_KEY

Deployer: 0xYourAddress
Deployed to: 0xFlashPoolAddress
Transaction hash: 0x...

# Fund the pool with 100 ETH
$ cast send $FLASH_POOL "" --value 100ether \
    --rpc-url $SEPOLIA_RPC \
    --private-key $PRIVATE_KEY

# Verify pool balance
$ cast balance $FLASH_POOL --rpc-url $SEPOLIA_RPC
100000000000000000000  # 100 ETH

# 2. DEPLOY: Arbitrage bot
# --------------------------------------------------------------
$ forge create src/ArbitrageBot.sol:ArbitrageBot \
    --constructor-args $FLASH_POOL $UNISWAP_ROUTER $SUSHI_ROUTER $WETH \
    --rpc-url $SEPOLIA_RPC \
    --private-key $PRIVATE_KEY

Deployed to: 0xArbBotAddress

# ==============================================================
# SCENARIO A: SUCCESSFUL ARBITRAGE
# ==============================================================

$ cast send $ARB_BOT "executeArbitrage(uint256,address,uint256)" \
    10ether \                           # Borrow 10 ETH
    $LINK_TOKEN \                       # Arbitrage LINK
    "100000000000000000" \              # Min profit 0.1 ETH
    --rpc-url $SEPOLIA_RPC \
    --private-key $PRIVATE_KEY

Transaction: 0xabc123...

# EXECUTION LOG:
[FlashLoanPool] flashLoan() called
  Borrower: 0xArbBotAddress
  Amount: 10.000000000000000000 ETH
  Fee: 0.009000000000000000 ETH (0.09%)
  Pool balance before: 100.000000000000000000 ETH

[ArbitrageBot] executeOperation() executing...
  Received: 10.000000000000000000 ETH

[UniswapRouter] swapExactETHForTokens()
  Input: 10 ETH
  Output: 2,950 LINK
  Path: WETH -> LINK
  Price: 295 LINK/ETH

[LINK] approve(SushiRouter, 2950)
  Spender: SushiSwap Router
  Amount: 2,950 LINK

[SushiRouter] swapExactTokensForETH()
  Input: 2,950 LINK
  Output: 10.250000000000000000 ETH
  Path: LINK -> WETH
  Price: 287.9 LINK/ETH

[ArbitrageBot] Profit calculation:
  ETH received: 10.250000000000000000
  Loan + fee: 10.009000000000000000
  Gross profit: 0.241000000000000000 ETH
  Min required: 0.100000000000000000 ETH
  CHECK PASSED

[ArbitrageBot] Repaying flash loan...
  Transfer: 10.009000000000000000 ETH -> FlashLoanPool

[FlashLoanPool] Verifying repayment...
  Pool balance after: 100.009000000000000000 ETH
  Required minimum: 100.009000000000000000 ETH
  CHECK PASSED

TRANSACTION SUCCESS!

Gas used: 485,234
Gas price: 25 gwei
Transaction cost: 0.012130850 ETH

# Final state:
Pool balance: 100.009 ETH (+0.009 ETH fee earned)
Bot balance: 0.228869150 ETH (0.241 - 0.012 gas)

# ==============================================================
# SCENARIO B: FAILED ARBITRAGE (REVERTS CLEANLY)
# ==============================================================

$ cast send $ARB_BOT "executeArbitrage(uint256,address,uint256)" \
    10ether \
    $LINK_TOKEN \
    "1000000000000000000" \              # Min profit 1 ETH (too high!)
    --rpc-url $SEPOLIA_RPC \
    --private-key $PRIVATE_KEY

Transaction: 0xdef456...
Status: REVERTED

# EXECUTION LOG:
[FlashLoanPool] flashLoan() called
  Borrower: 0xArbBotAddress
  Amount: 10.000000000000000000 ETH
  Fee: 0.009000000000000000 ETH

[ArbitrageBot] executeOperation() executing...
  Received: 10.000000000000000000 ETH

[UniswapRouter] swapExactETHForTokens()
  Input: 10 ETH
  Output: 2,950 LINK

[SushiRouter] swapExactTokensForETH()
  Input: 2,950 LINK
  Output: 10.250000000000000000 ETH

[ArbitrageBot] Profit calculation:
  ETH received: 10.250000000000000000
  Loan + fee: 10.009000000000000000
  Gross profit: 0.241000000000000000 ETH
  Min required: 1.000000000000000000 ETH
  INSUFFICIENT PROFIT - REVERTING

TRANSACTION REVERTED: "Insufficient profit"

# Result: ALL state changes undone
Pool balance: 100.009 ETH (unchanged)
Bot balance: 0.228869150 ETH (unchanged, minus gas for revert)

# ==============================================================
# SCENARIO C: MULTI-ASSET FLASH LOAN
# ==============================================================

# Deploy multi-asset pool
$ forge create src/MultiAssetFlashPool.sol:MultiAssetFlashPool \
    --rpc-url $SEPOLIA_RPC \
    --private-key $PRIVATE_KEY

# Fund with multiple tokens
$ cast send $USDC "transfer(address,uint256)" $MULTI_POOL 1000000000000 \
    --rpc-url $SEPOLIA_RPC --private-key $PRIVATE_KEY  # 1M USDC

$ cast send $DAI "transfer(address,uint256)" $MULTI_POOL \
    1000000000000000000000000 \
    --rpc-url $SEPOLIA_RPC --private-key $PRIVATE_KEY  # 1M DAI

$ cast send $WETH "deposit()" --value 100ether \
    --rpc-url $SEPOLIA_RPC --private-key $PRIVATE_KEY

$ cast send $WETH "transfer(address,uint256)" $MULTI_POOL \
    100000000000000000000 \
    --rpc-url $SEPOLIA_RPC --private-key $PRIVATE_KEY  # 100 WETH

# Execute multi-asset flash loan for collateral swap
$ cast send $COLLATERAL_SWAP_BOT \
    "executeCollateralSwap(address[],uint256[])" \
    "[$USDC,$WETH]" \
    "[500000000000,50000000000000000000]" \
    --rpc-url $SEPOLIA_RPC \
    --private-key $PRIVATE_KEY

# EXECUTION LOG:
[MultiAssetPool] Multi-asset flash loan initiated
  Asset 0: USDC - Amount: 500,000 - Fee: 450
  Asset 1: WETH - Amount: 50.0 - Fee: 0.045

[CollateralSwapBot] Executing collateral swap...
  1. Repaying Aave USDC debt with borrowed USDC
  2. Withdrawing ETH collateral from Aave
  3. Depositing borrowed WETH as new collateral
  4. Borrowing USDC against WETH
  5. Repaying flash loan

[MultiAssetPool] Verifying all repayments...
  USDC: 500,450 received (required: 500,450) PASS
  WETH: 50.045 received (required: 50.045) PASS

TRANSACTION SUCCESS!

Result: Swapped Aave position from ETH collateral to WETH collateral
        in single transaction with zero upfront capital!

# ==============================================================
# SCENARIO D: LIQUIDATION BOT
# ==============================================================

$ cast send $LIQUIDATION_BOT "executeLiquidation(address,address)" \
    $UNDERCOLLATERALIZED_USER \
    $USDC \
    --rpc-url $SEPOLIA_RPC \
    --private-key $PRIVATE_KEY

# EXECUTION LOG:
[LiquidationBot] Checking position health...
  User: 0xUndercollateralizedUser
  Collateral: 10 ETH ($30,000)
  Debt: 25,000 USDC
  Health Factor: 0.96 (< 1.0 = liquidatable!)

[FlashLoanPool] Flash loan: 12,500 USDC

[LiquidationBot] Executing liquidation...
  Repaying 12,500 USDC debt (50% of position)
  Receiving 4.17 ETH collateral (with 5% bonus)

[UniswapRouter] Selling 4.17 ETH...
  Received: 12,510 USDC

[LiquidationBot] Repaying flash loan...
  Amount: 12,511.25 USDC (including fee)

[FlashLoanPool] Repayment verified!

Profit: 12,510 - 12,511.25 = -1.25 USDC (loss due to unfavorable market)

Note: In production, bot would check profitability before executing!

# ==============================================================
# SCENARIO E: SECURITY - REENTRANCY ATTEMPT BLOCKED
# ==============================================================

$ forge test --match-test testReentrancyBlocked -vvvv

[PASS] testReentrancyBlocked()
  [MaliciousBot] Initiating flash loan...
  [FlashLoanPool] Entering nonReentrant
  [FlashLoanPool] Transferring 10 ETH to MaliciousBot
  [MaliciousBot] executeOperation called
  [MaliciousBot] Attempting reentrant flashLoan()...
  [FlashLoanPool] REVERT: "Reentrancy detected"
  [Test] Caught expected revert - security working!

# ==============================================================
# FINAL STATE SUMMARY
# ==============================================================

Flash Loan Pool Statistics:
  Total loans executed: 47
  Total volume: 1,234.56 ETH
  Total fees collected: 1.11 ETH
  Current balance: 101.11 ETH
  Utilization rate: 78%

Arbitrage Bot Statistics:
  Successful arbitrages: 32
  Failed (reverted): 15
  Total profit: 4.82 ETH
  Average profit per trade: 0.15 ETH
  Win rate: 68%

Top Arbitrage Routes:
  1. LINK: Uniswap -> SushiSwap (12 trades, 1.8 ETH profit)
  2. UNI: SushiSwap -> Uniswap (8 trades, 1.2 ETH profit)
  3. AAVE: Balancer -> Uniswap (7 trades, 0.9 ETH profit)

Complete Project Specification

Functional Requirements

  1. Flash Loan Pool
    • Accept deposits from liquidity providers
    • Issue LP tokens representing pool shares
    • Execute flash loans with callback pattern
    • Collect fees that accrue to LP holders
    • Support both ETH and ERC-20 tokens
  2. Flash Loan Execution
    • Transfer requested amount to borrower
    • Call borrower’s callback function
    • Verify repayment (principal + fee)
    • Revert entire transaction if repayment fails
  3. Arbitrage Bot
    • Execute cross-DEX arbitrage using flash loans
    • Support configurable minimum profit thresholds
    • Handle multiple token pairs
    • Gracefully revert unprofitable trades
  4. Security
    • Reentrancy protection on all state-changing functions
    • Maximum loan limits (percentage of pool)
    • Emergency pause functionality
    • Access control for admin functions
  5. Observability
    • Emit events for all flash loan operations
    • Track cumulative statistics (volume, fees)
    • Support off-chain monitoring

Non-Functional Requirements

  • Gas Efficiency: Flash loan execution < 200,000 gas overhead
  • Security: Pass security audit, resist known attack vectors
  • Reliability: Handle edge cases (zero amounts, max values)
  • Compatibility: Work with standard ERC-20 tokens, major DEXs

Solution Architecture

Module Structure

contracts/
├── core/
│   ├── FlashLoanPool.sol           # ETH flash loan pool
│   ├── ERC20FlashLoanPool.sol      # ERC-20 flash loan pool
│   └── MultiAssetFlashPool.sol     # Multi-token support
├── bots/
│   ├── ArbitrageBot.sol            # DEX arbitrage
│   ├── LiquidationBot.sol          # Aave/Compound liquidations
│   └── CollateralSwapBot.sol       # Collateral swapping
├── interfaces/
│   ├── IFlashLoanPool.sol
│   ├── IFlashLoanReceiver.sol
│   ├── IUniswapV2Router.sol
│   └── IERC20.sol
├── libraries/
│   ├── FlashLoanMath.sol           # Fee calculations
│   └── ArbitrageLib.sol            # Profit calculations
├── security/
│   ├── ReentrancyGuard.sol
│   └── Pausable.sol
└── test/
    ├── FlashLoanPool.t.sol
    ├── ArbitrageBot.t.sol
    ├── SecurityTests.t.sol
    └── mocks/
        ├── MockDEX.sol
        └── MockToken.sol

Core Interfaces

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

interface IFlashLoanPool {
    event FlashLoan(
        address indexed borrower,
        uint256 amount,
        uint256 fee
    );

    function flashLoan(
        uint256 amount,
        bytes calldata data
    ) external;

    function poolBalance() external view returns (uint256);
    function feePercentage() external view returns (uint256);
}

interface IFlashLoanReceiver {
    function executeOperation(
        uint256 amount,
        uint256 fee,
        bytes calldata data
    ) external returns (bool);
}

interface IMultiAssetFlashLoanReceiver {
    function executeOperation(
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata fees,
        address initiator,
        bytes calldata params
    ) external returns (bool);
}

Testing Strategy

Unit Tests

// test/FlashLoanPool.t.sol
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "../src/FlashLoanPool.sol";

contract FlashLoanPoolTest is Test {
    FlashLoanPool public pool;
    MockBorrower public borrower;

    function setUp() public {
        pool = new FlashLoanPool();
        borrower = new MockBorrower(address(pool));
        vm.deal(address(pool), 100 ether);
    }

    function test_FlashLoanSuccess() public {
        vm.deal(address(borrower), 1 ether); // For fee

        uint256 poolBefore = address(pool).balance;
        borrower.initiateFlashLoan(10 ether);
        uint256 poolAfter = address(pool).balance;

        // Pool should have gained the fee
        uint256 expectedFee = (10 ether * 9) / 10000;
        assertEq(poolAfter, poolBefore + expectedFee);
    }

    function test_FlashLoanInsufficientRepayment() public {
        BadBorrower bad = new BadBorrower(address(pool));

        vm.expectRevert("Flash loan not repaid");
        bad.initiateFlashLoan(10 ether);
    }

    function test_FlashLoanExceedsBalance() public {
        vm.expectRevert("Insufficient pool balance");
        borrower.initiateFlashLoan(1000 ether);
    }
}

Fuzz Tests

function testFuzz_FlashLoan(uint256 amount) public {
    // Bound to reasonable values
    amount = bound(amount, 0.01 ether, 90 ether);

    uint256 fee = (amount * 9) / 10000;
    vm.deal(address(borrower), fee + 0.01 ether);

    uint256 poolBefore = address(pool).balance;
    borrower.initiateFlashLoan(amount);

    assertEq(address(pool).balance, poolBefore + fee);
}

Security Tests

function test_ReentrancyBlocked() public {
    ReentrantBorrower attacker = new ReentrantBorrower(address(pool));
    vm.deal(address(attacker), 1 ether);

    vm.expectRevert("Reentrancy detected");
    attacker.attack(10 ether);
}

function test_PauseBlocksFlashLoans() public {
    pool.pause();

    vm.expectRevert("Pool is paused");
    borrower.initiateFlashLoan(1 ether);
}

Common Pitfalls & Debugging

Pitfall 1: Forgetting Reentrancy Guard

Problem: Attacker can recursively borrow before repaying.

Solution:

uint256 private _locked = 1;

modifier nonReentrant() {
    require(_locked == 1, "Reentrancy detected");
    _locked = 2;
    _;
    _locked = 1;
}

Pitfall 2: Integer Overflow in Fee Calculation

Problem: amount * feePercentage can overflow for large amounts.

Solution:

// Use mulDiv or check order of operations
uint256 fee = (amount * FEE_PERCENTAGE) / 10000;
// Or use OpenZeppelin's Math.mulDiv for safety

Pitfall 3: Not Checking Callback Return Value

Problem: Ignoring whether callback succeeded.

Solution:

bool success = IFlashLoanReceiver(msg.sender).executeOperation(...);
require(success, "Callback failed");

Pitfall 4: ETH Stuck in Borrower Contract

Problem: Borrower forgets to implement receive() or fallback().

Solution:

contract ArbitrageBot {
    receive() external payable {}
    // or
    fallback() external payable {}
}

Pitfall 5: Unprofitable After Gas

Problem: Arbitrage looks profitable but gas costs exceed profit.

Solution:

uint256 gasStart = gasleft();
// ... do operations ...
uint256 gasUsed = gasStart - gasleft();
uint256 gasCost = gasUsed * tx.gasprice;

require(
    profit > gasCost + minProfit,
    "Not profitable after gas"
);

Books That Will Help

Book Author Relevant Chapters Why It Helps
Mastering Ethereum Antonopoulos & Wood Ch 6: Transactions, Ch 9: Security Understand transaction atomicity and security patterns
How to DeFi: Advanced CoinGecko Ch 4: Flash Loans Real-world flash loan use cases and economics
Ethereum Smart Contract Development Marchioni Ch 8: Security Reentrancy and common vulnerabilities
Building Ethereum DApps Infante Ch 7: Security Contract interaction patterns

Additional Resources

Official Documentation

Flash Loan Attack Postmortems

MEV & Competition

Code References

Tools

  • Foundry - Solidity development framework
  • Tenderly - Transaction simulation and debugging
  • DeFi Llama - Track TVL and find arbitrage opportunities

Self-Assessment Checklist

Before moving to the next project, verify:

  • I can explain why flash loans can exist in DeFi but not traditional finance
  • I understand the callback pattern and can implement it securely
  • My flash loan pool correctly calculates and collects fees
  • Reentrancy attacks are blocked by my implementation
  • I can trace the complete flow of a flash loan transaction
  • I understand why failed flash loans revert cleanly with no risk
  • My arbitrage bot can execute profitable trades
  • I understand how flash loans enable price manipulation attacks
  • I can explain how protocols defend against flash loan exploits
  • My tests cover success, failure, and security edge cases
  • I understand the role of MEV in flash loan competition
  • I can implement multi-asset flash loans

Extensions and Challenges

Challenge 1: Implement Flash Loan Aggregator

Build a contract that routes flash loan requests to the cheapest provider:

function flashLoan(uint256 amount, bytes calldata data) external {
    // Check Aave, dYdX, Uniswap
    // Route to lowest fee provider
    // Handle fallback if primary fails
}

Challenge 2: Build MEV-Protected Arbitrage Bot

Integrate with Flashbots to submit bundles that can’t be front-run:

// Submit via Flashbots Protect RPC
// Include MEV share to validators
// Ensure atomic execution or nothing

Challenge 3: Implement Flash Loan Shield

Create a protocol feature that detects and blocks flash loan attacks:

modifier flashLoanShield() {
    require(
        !isFlashLoanContext(),
        "Flash loan detected"
    );
    _;
}

Challenge 4: Cross-Chain Flash Loans

Design a system for flash loans across L1 and L2:

// Borrow on Ethereum
// Bridge to Arbitrum
// Execute arbitrage on Arbitrum
// Bridge back
// Repay on Ethereum
// All "atomic" via message passing

What’s Next?

With flash loans mastered, you now understand one of DeFi’s most powerful primitives. You’ve learned how transaction atomicity enables risk-free borrowing, how the callback pattern works, and how to build profitable arbitrage strategies.

In Project 18: Block Explorer, you’ll shift from writing smart contracts to reading blockchain data. You’ll build an indexer that tracks transactions, decodes function calls, and presents on-chain activity—essential infrastructure for any DeFi user or developer.