Aave V3 Flash Loans
Flash Loans are a hallmark feature of DeFi protocols like Aave, enabling users (typically smart contracts) to borrow large amounts of assets without providing any upfront collateral, provided the loan amount plus a premium (fee) is returned within the same transaction. This atomic guarantee makes them powerful tools for arbitrage, liquidations, collateral swaps, debt refinancing, and other complex strategies.
Aave V3 offers two main flash loan functions exposed via the Pool.sol contract:
flashLoanSimple(receiverAddress, asset, amount, params, referralCode): Borrows a single asset.flashLoan(receiverAddress, assets[], amounts[], interestRateModes[], onBehalfOf, params, referralCode): Borrows multiple assets simultaneously, with an option to open debt positions instead of repaying immediately (Mode 2).
We'll primarily trace the flashLoanSimple flow for clarity, as flashLoan builds upon the same core principles but adds iteration and the debt-opening mode.
The Entry Point: Pool.flashLoanSimple()
A flash loan is initiated by calling flashLoanSimple (or flashLoan) on the Pool.sol contract.
// File: /src/contracts/protocol/pool/Pool.sol
/// @inheritdoc IPool
function flashLoanSimple(
address receiverAddress, // The contract that will receive funds and execute logic
address asset, // The asset being borrowed
uint256 amount, // The amount of the asset to borrow
bytes calldata params, // Arbitrary data passed to the receiver's callback
uint16 referralCode // Optional referral code
) public virtual override {
// Prepare parameters for the logic library
DataTypes.FlashloanSimpleParams memory flashParams = DataTypes.FlashloanSimpleParams({
receiverAddress: receiverAddress,
asset: asset,
amount: amount,
params: params,
referralCode: referralCode,
flashLoanPremiumToProtocol: _flashLoanPremiumToProtocol, // Get premium % from storage
flashLoanPremiumTotal: _flashLoanPremiumTotal // Get total premium % from storage
});
// Delegate execution to the FlashLoanLogic library, passing the specific reserve data
FlashLoanLogic.executeFlashLoanSimple(_reserves[asset], flashParams);
}
Similar to other Pool functions, flashLoanSimple acts mainly as a dispatcher:
- It receives the request parameters.
- It retrieves the current flash loan premium percentages (
_flashLoanPremiumTotal,_flashLoanPremiumToProtocol) fromPoolStorage. - It packages these, along with the function arguments, into a
DataTypes.FlashloanSimpleParamsstruct. - It fetches the specific
ReserveDatafor the requestedassetfrom the_reservesmapping (defined inPoolStorage). - It delegates the entire operation to the static
executeFlashLoanSimplefunction within theFlashLoanLogiclibrary, passing the reserve data and the parameters struct.
Core Execution: FlashLoanLogic.executeFlashLoanSimple()
This function within FlashLoanLogic.sol orchestrates the main steps of the flash loan.
// File: /src/contracts/protocol/libraries/logic/FlashLoanLogic.sol (Conceptual Structure)
function executeFlashLoanSimple(
DataTypes.ReserveData storage reserve,
DataTypes.FlashloanSimpleParams memory params
) internal {
// 1. Validation
ValidationLogic.validateFlashloanSimple(reserve, params.amount);
// 2. Calculate Premiums
uint256 totalPremium = params.amount.percentMul(params.flashLoanPremiumTotal);
uint256 premiumToProtocol = totalPremium.percentMul(params.flashLoanPremiumToProtocol);
// 3. Transfer Funds to Receiver
IAToken(reserve.aTokenAddress).transferUnderlyingTo(params.receiverAddress, params.amount);
// 4. Execute Receiver's Logic (Callback)
bool success = IFlashLoanSimpleReceiver(params.receiverAddress).executeOperation(
params.asset,
params.amount,
totalPremium,
msg.sender, // initiator
params.params // pass through user-provided data
);
require(success, Errors.INVALID_FLASHLOAN_EXECUTOR_RETURN);
// 5. Handle Repayment
_handleFlashLoanRepayment(
reserve,
DataTypes.FlashLoanRepaymentParams({
amount: params.amount,
totalPremium: totalPremium,
flashLoanPremiumToProtocol: premiumToProtocol,
asset: params.asset,
receiverAddress: params.receiverAddress,
referralCode: params.referralCode
})
);
}
Let's break down these steps:
-
Validation (
ValidationLogic.validateFlashloanSimple):- Checks basic conditions using
ValidationLogic.sol:- Is the reserve active, not paused? (
ReserveConfiguration.sol) - Is flash lending enabled for this asset? (
ReserveConfiguration.sol) - Is the requested
amountvalid (e.g., does the reserve have enough liquidity if virtual accounting isn't active)? ChecksIERC20(aTokenAddress).totalSupply().
- Is the reserve active, not paused? (
- Checks basic conditions using
-
Calculate Premiums:
- The
totalPremiumis calculated as a percentage (flashLoanPremiumTotal) of the borrowedamountusingPercentageMath.percentMul. - The portion of the premium destined for the Aave Treasury (
premiumToProtocol) is calculated as a percentage (flashLoanPremiumToProtocol) of thetotalPremium.
- The
-
Transfer Funds to Receiver:
- The core transfer happens here. The function calls
transferUnderlyingToon the reserve'sATokencontract (IAToken.sol, implemented inAToken.sol). - The
ATokencontract holds the actual underlying liquidity and transfers the requestedamountto theparams.receiverAddress.
- The core transfer happens here. The function calls
-
Execute Receiver's Logic (Callback):
- Crucially, the
FlashLoanLogicnow calls theexecuteOperationfunction on theparams.receiverAddress. - This receiver contract must implement the
IFlashLoanSimpleReceiver(orIFlashLoanReceiverfor multi-asset loans) interface. - The
executeOperationfunction receives the asset, amount, calculated premium, the original initiator's address, and the arbitraryparamsdata provided in the initialflashLoanSimplecall. - This is where the user's custom flash loan logic resides (e.g., execute arbitrage trades, interact with other protocols, etc.).
- The
executeOperationmust returntrueto signal successful execution and readiness to repay. If it returnsfalseor reverts, the entire flash loan transaction reverts.
- Crucially, the
-
Handle Repayment (
_handleFlashLoanRepayment):- After the receiver's
executeOperationsuccessfully returnstrue,FlashLoanLogicproceeds to secure repayment. This is handled by the internal_handleFlashLoanRepaymentfunction. - This function only handles the standard repayment case (Mode 0) because
flashLoanSimpledoesn't support opening debt positions. - Pull Repayment: It transfers
amount + totalPremiumof the underlying asset from theparams.receiverAddressback to the reserve'sATokencontract. This is done usingIERC20(asset).safeTransferFrom. Important: The receiver contract must have approved thePool'sATokenaddress to spend at leastamount + totalPremiumbeforeexecuteOperationreturns. - Notify AToken: It calls
IAToken(aTokenAddress).handleRepayment(receiverAddress, receiverAddress, amount + totalPremium)to inform the aToken that the funds have been returned. The aToken can then update its internal accounting. - Update Reserve State: It calls
reserve.updateState(reserveCache)(usingReserveLogic.sol) to accrue any interest that might have occurred during the flash loan execution (though typically negligible within a single transaction). - Distribute Premium to Suppliers: It calculates the premium portion for suppliers (
totalPremium - premiumToProtocol). It then usesreserve.cumulateToLiquidityIndex(inReserveLogic.sol) to effectively increase theliquidityIndexbased on this supplier premium, distributing the fee proportionally among all aToken holders over time. - Send Premium to Treasury: It calls
IAToken(aTokenAddress).mintToTreasury(premiumToProtocol, reserve.liquidityIndex)to mint new aTokens representing the protocol's share of the premium directly to the treasury address associated with the aToken. - Emit Event: Finally, back in
Pool.sol(afterFlashLoanLogicreturns), theFlashLoanevent is emitted.
- After the receiver's
The Receiver Contract (IFlashLoanSimpleReceiver)
Any contract intending to receive a simple flash loan must implement this interface:
// File: /src/contracts/misc/flashloan/interfaces/IFlashLoanSimpleReceiver.sol
interface IFlashLoanSimpleReceiver {
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address initiator,
bytes calldata params
) external returns (bool);
// Helper functions to get context
function ADDRESSES_PROVIDER() external view returns (IPoolAddressesProvider);
function POOL() external view returns (IPool);
}
The developer's core task is implementing executeOperation:
- Perform desired actions using the received
amountofasset. - Use the
paramsdata if needed for context. - Critically: Ensure the contract has enough
asset(either from the flash loan proceeds or pre-existing balance) to coveramount + premium. - Critically: Ensure the contract approves the reserve's
ATokenaddress (obtainable viaIPool(POOL()).getReserveData(asset).aTokenAddress) to spendamount + premiumof theasset. This approval must happen before returningtrue. - Return
true.
The FlashLoanReceiverBase.sol contract provides a convenient base, pre-populating ADDRESSES_PROVIDER and POOL.
Multi-Asset Flash Loans (Pool.flashLoan)
The flashLoan function follows a similar pattern but iterates through arrays (assets, amounts, interestRateModes).
- Validation:
ValidationLogic.validateFlashloanchecks each asset individually. - Transfers: Transfers happen sequentially for each asset.
- Callback: Calls
IFlashLoanReceiver.executeOperation, passing arrays of assets, amounts, and premiums. - Repayment:
- Mode 0: Handles repayment similarly to
flashLoanSimplebut iterates through the assets, pulling backamounts[i] + premiums[i]for each. - Mode 2 (Open Variable Debt): If
interestRateModes[i] == 2, instead of pulling repayment for that specific asset/amount, it callsBorrowLogic.executeBorrowwithreleaseUnderlying: falseandonBehalfOfset to the address provided in the initialflashLoancall. This performs standard borrow validation (HF, LTV, etc.) and mints debt tokens for theonBehalfOfuser. No premium is paid for assets where debt is opened. TheonBehalfOfaddress must not be thereceiverAddressin this mode to prevent contracts from assigning debt to themselves unexpectedly.
- Mode 0: Handles repayment similarly to
Security & Conclusion
Flash Loans are incredibly powerful but require careful implementation in the receiver contract. The primary risks are:
- Reentrancy: While Aave V3 core contracts are generally protected, the receiver's
executeOperationmust be wary of reentering the Aave Pool or other protocols in unsafe ways. - Incorrect Repayment: Failing to have sufficient funds or approve the repayment will cause the entire transaction to revert.
- Logic Errors: Any bug in the receiver's
executeOperationcould lead to loss of funds or failed operations.
In summary, Aave V3's flash loan mechanism provides temporary, uncollateralized liquidity by transferring funds to a receiver, invoking the receiver's custom logic via executeOperation, and then atomically reclaiming the funds plus a premium (or opening a debt position). The Pool acts as the entry point, delegating the complex orchestration of validation, transfer, callback, and repayment/debt creation to the FlashLoanLogic library and interacting with AToken, ValidationLogic, ReserveLogic, and potentially BorrowLogic.

