Standardized Flash Loan Interface

OperationsFlash Loans → Standardized Interface (Composer)

While flash loan implementations are nearly all the same, the explicit usage and interface vary.

In this section we cover the flash loans provided by:

  • Balancer V2 and forks
  • Aave V2 & V3 and forks
  • Morpho Blue

Flash Loan Parameters

The following parameters need to be provided for Aave V2, V3 and Morpho Blue:

Parameter Structure

Offset Length (bytes) Type Description
0 20 address Asset contract address to borrow
20 20 address Flash loan pool contract address
40 16 uint128 Amount to borrow (in asset decimals)
56 2 uint16 paramsLength + 1 (total parameter length)
58 1 uint8 Pool identifier for validation
59 + paramsLength paramsLength bytes Packed composer operations to execute

Key Technical Details

Validation Logic

Since flash loans use callbacks, we need poolId to validate that the callback was triggered by a trusted Aave or Morpho pool.

Parameters Structure

params is a packed set of composer operations that will be executed during the flash loan.

Important Considerations

  • Re-entrancy: The composer re-enters itself during flash loan execution
  • Caller Forwarding: The caller address is forwarded from the original call source
  • Pool Validation: The validation logic is hard-coded, so only a limited set of pools are allowed
  • DEX Limitations: Some flash loan sources are DEXs (e.g., Balancer V2, Uniswap V4) - this means swaps through these DEXs are not possible during the flash loan due to re-entrancy protection

Solidity Example: USDC Loop in Aave V3

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

// select 1M USDC
uint128 amount = uint128(1000000.0e6);

// Inner operations: deposit USDC and borrow USDC
bytes memory innerOperations = abi.encodePacked(
    // Deposit USDC to Aave V3
    uint8(ComposerCommands.LENDING),
    uint8(LenderOps.DEPOSIT),
    uint16(AAVE_V3_ID),
    USDC, // asset
    amount, // amount
    uint8(0), // interest rate mode (0 = variable)
// Borrow USDC from Aave V3
    uint8(ComposerCommands.LENDING),
    uint8(LenderOps.BORROW),
    uint16(AAVE_V3_ID),
    USDC, // asset
    amount, // amount
    uint8(0) // interest rate mode (0 = variable)
);
// Main flash loan operation
bytes memory operation = abi.encodePacked(
    uint8(ComposerCommands.FLASH_LOAN),
    uint8(FlashLoanIds.AAVE_V3),
    USDC, // asset to borrow
    AAVE_V3_POOL, // flash loan pool
    amount, // amount to borrow
    uint16(innerOperations.length + 1), // params length + 1
    uint8(0), // poolId for validation
    innerOperations // packed operations to execute
);
// Execute the flash loan
composer.deltaCompose(operation);

TypeScript Example (using viem)

export function encodeFlashLoanLoop(composerAddress: `0x${string}`, amount: bigint): `0x${string}` {
    // Inner operations: deposit and borrow
    const innerOperations =
        encodePacked(
            ["uint8", "uint8", "uint16", "address", "uint128", "uint8"],
            [
                ComposerCommands.LENDING,
                LenderOps.DEPOSIT,
                AAVE_V3_ID,
                USDC_ADDRESS,
                amount,
                0, // interest rate mode (variable)
            ]
        ) +
        encodePacked(
            ["uint8", "uint8", "uint16", "address", "uint128", "uint8"],
            [
                ComposerCommands.LENDING,
                LenderOps.BORROW,
                AAVE_V3_ID,
                USDC_ADDRESS,
                amount,
                0, // interest rate mode (variable)
            ]
        ).slice(2) // Remove '0x' prefix

    // Main flash loan operation
    const operation = encodePacked(
        [
            "uint8", // ComposerCommands.FLASH_LOAN
            "uint8", // FlashLoanIds.AAVE_V3
            "address", // asset
            "address", // pool
            "uint128", // amount
            "uint16", // paramsLength + 1
            "uint8", // poolId
            "bytes", // inner operations
        ],
        [
            ComposerCommands.FLASH_LOAN,
            FlashLoanIds.AAVE_V3,
            USDC_ADDRESS,
            AAVE_V3_POOL,
            amount,
            innerOperations.length / 2 + 1, // length in bytes + 1
            0, // poolId
            innerOperations,
        ]
    )

    return operation
}

// Usage example
const operationData = encodeFlashLoanLoop(
    "0x...", // composer address
    1000000n
)

// Call the composer
await publicClient.writeContract({
    address: composerAddress,
    abi: parseAbi(["function deltaCompose(bytes data)"]),
    functionName: "deltaCompose",
    args: [operationData],
})

Notes

Pool ID Validation

The poolId parameter is crucial for security. Each flash loan provider has specific pool IDs:

  • Morpho Blue: Pool ID 0 for the main Morpho Blue contract
  • Aave V3: Pool IDs vary by deployment (e.g., 0 for mainnet)
  • Aave V2: Pool IDs vary by deployment (e.g., 7 for Granary)

Re-entrancy Considerations

When using DEX-based flash loans (Balancer V2, Uniswap V4), be aware that:

  • You cannot perform swaps on the same DEX during the flash loan
  • Re-entrancy guards prevent nested operations on these protocols
  • Use alternative DEXs for any swaps needed within the flash loan

results matching ""

    No results matching ""