# Flash Loan Flow
End-to-end execution flow for flash loans in Aave V3.
## Quick Reference
| Aspect | Details |
|--------|---------|
| **Entry Point** | `Pool.flashLoan(receiverAddress, assets, amounts, interestRateModes, onBehalfOf, params, referralCode)` |
| **Key Transformations** | [Flash Loan Premium](../transformations/index.md#flash-loan-premiums) |
| **State Changes** | `virtualUnderlyingBalance -= amount`, `accruedToTreasury += premium` |
| **Events Emitted** | `FlashLoan` |
---
## Flow Diagram
```mermaid
flowchart TD
%% Styling definitions
classDef validation fill:#ffcccc,stroke:#ff0000,stroke-width:2px
classDef transformation fill:#ccffcc,stroke:#00aa00,stroke-width:2px
classDef storage fill:#ccccff,stroke:#0000ff,stroke-width:2px
classDef event fill:#ffffcc,stroke:#aaaa00,stroke-width:2px
classDef error fill:#ff0000,stroke:#000000,color:#fff
classDef bridge fill:#ffccff,stroke:#aa00aa,stroke-width:2px
%% Entry point
Entry["Pool.flashLoan
receiverAddress, assets[],
amounts[], interestRateModes[],
onBehalfOf, params,
referralCode"] --> Validate["ValidationLogic
validateFlashloan"]
class Validate validation
%% Validation checks
subgraph Validation["1. Pre-Validation"]
direction TB
CheckLength{"assets.length ==
amounts.length?"}
CheckUnique{"Duplicate
assets?"}
CheckPaused{"Reserve
paused?"}
CheckActive{"Reserve
active?"}
CheckEnabled{"FlashLoan
enabled?"}
CheckLiquidity{"Sufficient
liquidity?"}
end
Validate --> CheckLength
CheckLength --> CheckUnique
CheckUnique --> CheckPaused
CheckPaused --> CheckActive
CheckActive --> CheckEnabled
CheckEnabled --> CheckLiquidity
%% Transfer funds to receiver
CheckLiquidity --> Transfer["IERC20
transferUnderlyingTo
Pool -> Receiver"]
subgraph TransferPhase["2. Fund Transfer"]
direction TB
CalcPremium["TRANSFORMATION
totalPremium =
amount.percentMulCeil
'flashLoanPremium'"]
class CalcPremium transformation
UpdateVirtual["STORAGE UPDATE
virtualUnderlyingBalance
-= amount"]
class UpdateVirtual storage
TransferTo["AToken.transfer
UnderlyingTo
receiverAddress"]
end
Transfer --> CalcPremium
CalcPremium --> UpdateVirtual
UpdateVirtual --> TransferTo
%% Execute callback
TransferTo --> Callback["IFlashLoanReceiver
executeOperation"]
subgraph CallbackPhase["3. Receiver Callback"]
direction TB
Execute["Receiver executes
arbitrary logic
with borrowed funds"]
MustRepay["Receiver must:
- Approve Pool for
amount + premium
- Return true"]
class MustRepay validation
end
Callback --> Execute
Execute --> MustRepay
%% Repayment handling
MustRepay --> CheckMode{"interestRateMode
== NONE?"}
%% Mode 0: Repay with funds
CheckMode -->|Yes| Repay["_handleFlashLoanRepayment"]
subgraph RepayPhase["4a. Repayment Flow"]
direction TB
CalcTotal["TRANSFORMATION
amountPlusPremium =
amount + totalPremium"]
class CalcTotal transformation
UpdateTreasury["STORAGE UPDATE
accruedToTreasury +=
premium scaled"]
class UpdateTreasury storage
UpdateRates["ReserveLogic
updateInterestRates
AndVirtualBalance"]
PullFunds["IERC20
safeTransferFrom
receiver -> aToken"]
end
Repay --> CalcTotal
CalcTotal --> UpdateTreasury
UpdateTreasury --> UpdateRates
UpdateRates --> PullFunds
PullFunds --> Event["EMIT
FlashLoan"]
class Event event
%% Mode 2: Take on debt
CheckMode -->|No| Borrow["BorrowLogic
executeBorrow"]
subgraph BorrowPhase["4b. Debt Mode"]
direction TB
ValidateBorrow["ValidationLogic
validateBorrow
collateral check"]
class ValidateBorrow validation
OpenDebt["STORAGE UPDATE
Mint variable debt
to onBehalfOf"]
class OpenDebt storage
NoPremium["premium = 0
No fee when
taking debt"]
end
Borrow --> ValidateBorrow
ValidateBorrow --> OpenDebt
OpenDebt --> NoPremium
NoPremium --> Event
%% Error annotations
%% CRITICAL: All validations must pass before any transfers
%% CRITICAL: Receiver MUST return true from executeOperation
%% CRITICAL: Receiver MUST approve Pool for amount + premium
%% Link styles for critical paths
linkStyle 0 stroke:#ff0000,stroke-width:3px
linkStyle 20 stroke:#ff0000,stroke-width:3px
```
---
## Step-by-Step Execution
### 1. Entry Point
**File:** `contracts/protocol/pool/Pool.sol`
```solidity
function flashLoan(
address receiverAddress,
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata interestRateModes,
address onBehalfOf,
bytes calldata params,
uint16 referralCode
) public virtual override {
DataTypes.FlashloanParams memory flashParams = DataTypes.FlashloanParams({
user: _msgSender(),
receiverAddress: receiverAddress,
assets: assets,
amounts: amounts,
interestRateModes: interestRateModes,
interestRateStrategyAddress: RESERVE_INTEREST_RATE_STRATEGY,
onBehalfOf: onBehalfOf,
params: params,
referralCode: referralCode,
flashLoanPremium: _flashLoanPremium,
addressesProvider: address(ADDRESSES_PROVIDER),
pool: address(this),
userEModeCategory: _usersEModeCategory[onBehalfOf],
isAuthorizedFlashBorrower: IACLManager(ADDRESSES_PROVIDER.getACLManager()).isFlashBorrower(
_msgSender()
)
});
FlashLoanLogic.executeFlashLoan(
_reserves,
_reservesList,
_eModeCategories,
_usersConfig[onBehalfOf],
flashParams
);
}
```
### 2. Execute Flash Loan
**File:** `contracts/protocol/libraries/logic/FlashLoanLogic.sol`
```solidity
function executeFlashLoan(
mapping(address => DataTypes.ReserveData) storage reservesData,
mapping(uint256 => address) storage reservesList,
mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories,
DataTypes.UserConfigurationMap storage userConfig,
DataTypes.FlashloanParams memory params
) external {
// The usual action flow (cache -> updateState -> validation -> changeState -> updateRates)
// is altered to (validation -> user payload -> cache -> updateState -> changeState -> updateRates) for flashloans.
// This is done to protect against reentrance and rate manipulation within the user specified payload.
ValidationLogic.validateFlashloan(reservesData, params.assets, params.amounts);
FlashLoanLocalVars memory vars;
vars.totalPremiums = new uint256[](params.assets.length);
vars.receiver = IFlashLoanReceiver(params.receiverAddress);
vars.flashloanPremium = params.isAuthorizedFlashBorrower ? 0 : params.flashLoanPremium;
for (uint256 i = 0; i < params.assets.length; i++) {
vars.currentAmount = params.amounts[i];
vars.totalPremiums[i] = DataTypes.InterestRateMode(params.interestRateModes[i]) ==
DataTypes.InterestRateMode.NONE
? vars.currentAmount.percentMulCeil(vars.flashloanPremium)
: 0;
reservesData[params.assets[i]].virtualUnderlyingBalance -= vars.currentAmount.toUint128();
IAToken(reservesData[params.assets[i]].aTokenAddress).transferUnderlyingTo(
params.receiverAddress,
vars.currentAmount
);
}
require(
vars.receiver.executeOperation(
params.assets,
params.amounts,
vars.totalPremiums,
params.user,
params.params
),
Errors.InvalidFlashloanExecutorReturn()
);
for (uint256 i = 0; i < params.assets.length; i++) {
vars.currentAsset = params.assets[i];
vars.currentAmount = params.amounts[i];
if (
DataTypes.InterestRateMode(params.interestRateModes[i]) == DataTypes.InterestRateMode.NONE
) {
_handleFlashLoanRepayment(
reservesData[vars.currentAsset],
DataTypes.FlashLoanRepaymentParams({
user: params.user,
asset: vars.currentAsset,
interestRateStrategyAddress: params.interestRateStrategyAddress,
receiverAddress: params.receiverAddress,
amount: vars.currentAmount,
totalPremium: vars.totalPremiums[i],
referralCode: params.referralCode
})
);
} else {
// If the user chose to not return the funds, the system checks if there is enough collateral and
// eventually opens a debt position
BorrowLogic.executeBorrow(
reservesData,
reservesList,
eModeCategories,
userConfig,
DataTypes.ExecuteBorrowParams({
asset: vars.currentAsset,
interestRateStrategyAddress: params.interestRateStrategyAddress,
user: params.user,
onBehalfOf: params.onBehalfOf,
amount: vars.currentAmount,
interestRateMode: DataTypes.InterestRateMode(params.interestRateModes[i]),
referralCode: params.referralCode,
releaseUnderlying: false,
oracle: IPoolAddressesProvider(params.addressesProvider).getPriceOracle(),
userEModeCategory: IPool(params.pool).getUserEMode(params.onBehalfOf).toUint8(),
priceOracleSentinel: IPoolAddressesProvider(params.addressesProvider)
.getPriceOracleSentinel()
})
);
// no premium is paid when taking on the flashloan as debt
emit IPool.FlashLoan(
params.receiverAddress,
params.user,
vars.currentAsset,
vars.currentAmount,
DataTypes.InterestRateMode(params.interestRateModes[i]),
0,
params.referralCode
);
}
}
}
```
### 3. Validation Checks
**File:** `contracts/protocol/libraries/logic/ValidationLogic.sol`
```solidity
function validateFlashloan(
mapping(address => DataTypes.ReserveData) storage reservesData,
address[] memory assets,
uint256[] memory amounts
) internal view {
require(assets.length == amounts.length, Errors.InconsistentFlashloanParams());
for (uint256 i = 0; i < assets.length; i++) {
for (uint256 j = i + 1; j < assets.length; j++) {
require(assets[i] != assets[j], Errors.InconsistentFlashloanParams());
}
validateFlashloanSimple(reservesData[assets[i]], amounts[i]);
}
}
function validateFlashloanSimple(
DataTypes.ReserveData storage reserve,
uint256 amount
) internal view {
DataTypes.ReserveConfigurationMap memory configuration = reserve.configuration;
require(!configuration.getPaused(), Errors.ReservePaused());
require(configuration.getActive(), Errors.ReserveInactive());
require(configuration.getFlashLoanEnabled(), Errors.FlashloanDisabled());
require(IERC20(reserve.aTokenAddress).totalSupply() >= amount, Errors.InvalidAmount());
}
```
### 4. IFlashLoanReceiver Interface
**File:** `contracts/flashloan/interfaces/IFlashLoanReceiver.sol`
```solidity
interface IFlashLoanReceiver {
/**
* @notice Executes an operation after receiving the flash-borrowed assets
* @dev Ensure that the contract can return the debt + premium, e.g., has
* enough funds to repay and has approved the Pool to pull the total amount
* @param assets The addresses of the flash-borrowed assets
* @param amounts The amounts of the flash-borrowed assets
* @param premiums The fee of each flash-borrowed asset
* @param initiator The address of the flashloan initiator
* @param params The byte-encoded params passed when initiating the flashloan
* @return True if the execution of the operation succeeds, false otherwise
*/
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external returns (bool);
function ADDRESSES_PROVIDER() external view returns (IPoolAddressesProvider);
function POOL() external view returns (IPool);
}
```
### 5. Handle Flash Loan Repayment
**File:** `contracts/protocol/libraries/logic/FlashLoanLogic.sol`
```solidity
function _handleFlashLoanRepayment(
DataTypes.ReserveData storage reserve,
DataTypes.FlashLoanRepaymentParams memory params
) internal {
uint256 amountPlusPremium = params.amount + params.totalPremium;
DataTypes.ReserveCache memory reserveCache = reserve.cache();
reserve.updateState(reserveCache);
reserve.accruedToTreasury += params
.totalPremium
.getATokenMintScaledAmount(reserveCache.nextLiquidityIndex)
.toUint128();
reserve.updateInterestRatesAndVirtualBalance(
reserveCache,
params.asset,
amountPlusPremium,
0,
params.interestRateStrategyAddress
);
IERC20(params.asset).safeTransferFrom(
params.receiverAddress,
reserveCache.aTokenAddress,
amountPlusPremium
);
emit IPool.FlashLoan(
params.receiverAddress,
params.user,
params.asset,
params.amount,
DataTypes.InterestRateMode.NONE,
params.totalPremium,
params.referralCode
);
}
```
---
## Amount Transformations
### Flash Loan Premium Calculation
```
Input Amount
↓
amount = 1000 * 10^18 // 1000 tokens
↓
flashLoanPremium = 9 // 0.09% in bps (default: 0.09%)
↓
totalPremium = amount.percentMulCeil(flashLoanPremium)
= (amount * flashLoanPremium + 9999) / 10000
= (1000 * 10^18 * 9 + 9999) / 10000
= 0.9 * 10^18 // ~0.9 tokens
↓
amountPlusPremium = amount + totalPremium
= 1000.9 * 10^18
```
**Key Points:**
- Premium is calculated using `percentMulCeil` (ceiling division for rounding up)
- Premium is waived for authorized flash borrowers (checked via ACLManager)
- When taking flash loan as debt (`interestRateMode != 0`), premium is 0
- Premium is accrued to treasury and minted as aTokens
### Interest Rate Modes
| Mode | Value | Description |
|------|-------|-------------|
| `NONE` | 0 | Must repay flash loan + premium in same transaction |
| `VARIABLE` | 2 | Flash loan amount converted to variable debt for `onBehalfOf` |
---
## Event Details
### FlashLoan Event
```solidity
event FlashLoan(
address indexed target, // Flash loan receiver contract
address initiator, // Transaction initiator (msg.sender)
address indexed asset, // Asset flash borrowed
uint256 amount, // Amount flash borrowed
DataTypes.InterestRateMode interestRateMode, // 0 (repay) or 2 (debt)
uint256 premium, // Premium paid (0 if debt mode)
uint16 indexed referralCode // Referral code
);
```
---
## Error Conditions
| Error | Condition | File |
|-------|-----------|------|
| `InconsistentFlashloanParams` | `assets.length != amounts.length` | ValidationLogic.sol |
| `InconsistentFlashloanParams` | Duplicate assets in array | ValidationLogic.sol |
| `ReservePaused` | Reserve is paused | ValidationLogic.sol |
| `ReserveInactive` | Reserve is not active | ValidationLogic.sol |
| `FlashloanDisabled` | Flash loans disabled for reserve | ValidationLogic.sol |
| `InvalidAmount` | Requested amount exceeds available liquidity | ValidationLogic.sol |
| `InvalidFlashloanExecutorReturn` | `executeOperation()` returns false | FlashLoanLogic.sol |
---
## Related Flows
- [Borrow Flow](./borrow.md) - When flash loan is taken as debt (interestRateMode = 2)
- [Supply Flow](./supply.md) - Flash loan premium minted to treasury
- [Liquidation Flow](./liquidation.md) - Common use case for flash loans
---
## Source File Locations
```
contracts/protocol/pool/Pool.sol
contracts/protocol/libraries/logic/FlashLoanLogic.sol
contracts/protocol/libraries/logic/ValidationLogic.sol
contracts/flashloan/interfaces/IFlashLoanReceiver.sol
contracts/protocol/libraries/types/DataTypes.sol
```