A flash loan is a loan between lender and borrower smart contracts that must be repaid, plus an optional fee, before the end of the transaction. This ERC specifies interfaces for lenders to accept flash loan requests, and for borrowers to take temporary control of the transaction within the lender execution. The process for the safe execution of flash loans is also specified.
The current state of the flash loan ecosystem is fragmented and lacks standardization, leading to several challenges for both lenders and borrowers. The absence of a common interface results in increased integration efforts, as each flash loan provider implements its own unique approach. This lack of standardization is expected to become more problematic as the ecosystem grows, requiring more resources to maintain compatibility.
A comprehensive analysis of the existing flash loan protocols reveals significant differences in their implementations, including:
To address these inconsistencies and promote a more efficient and accessible flash loan ecosystem, this ERC specifies a standardized interface that encompasses the maximum flexibility required by both lenders and borrowers. By consolidating the various approaches into a unified standard, this proposal aims to streamline the integration process, enabling borrowers to seamlessly switch between flash lenders without the need for code modifications.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
Under this standard a flash loan is a loan of an amount
of an ERC-20 asset
from a lender
. This loan can remain open for the span of a single flash
call in the lender
.
This amount
plus a fee
defined by the lender
in the same asset
must be repaid before the end of flash
call at a repayment receiver
address defined by the lender
.
The flash
function is called by the initiator
, who defines the loan receiver, the callback receiver, the callback function, the asset
and the amount
.
When the initiator
calls flash
in a lender
. The lender
will then transfer the amount
of asset
to the loan receiver.
The lender
, after transferring amount
of asset
to the loan receiver, will execute the callback function on the callback receiver. The lender
will include in this callback function call a number of parameters related to the loan as defined in this standard.
The amount
and fee
need to be transferred to a repayment receiver
before the end of the flash
call. The fee
can be set to zero asset
.
The callback function can return any arbitrary data which will be received by the initiator
as the return value of the flash
call.
The lender decides which assets
to support. The lender can decide to support all possible assets.
A lender
MUST implement the ERC-7399 interface.
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.6.0 <0.9.0;
import { IERC20 } from "./IERC20.sol";
interface IERC7399 {
/// @dev The amount of currency available to be lent.
/// @param asset The loan currency.
/// @return The amount of `asset` that can be borrowed.
function maxFlashLoan(
address asset
) external view returns (uint256);
/// @dev The fee to be charged for a given loan. Returns type(uint256).max if the loan is not possible.
/// @param asset The loan currency.
/// @param amount The amount of assets lent.
/// @return The amount of `asset` to be charged for the loan, on top of the returned principal.
function flashFee(
IERC20 asset,
uint256 amount
) external view returns (uint256);
/// @dev Initiate a flash loan.
/// @param loanReceiver The address receiving the flash loan
/// @param asset The asset to be loaned
/// @param amount The amount to loaned
/// @param data The ABI encoded user data
/// @param callback The address and signature of the callback function
/// @return result ABI encoded result of the callback
function flash(
address loanReceiver,
ERC20 asset,
uint256 amount,
bytes calldata data,
/// @dev callback. This is a combination of the callback receiver address, and the signature of callback
/// function. It is encoded packed as 20 bytes + 4 bytes.
/// @dev the return of the callback function is not encoded in the parameter, but must be `returns (bytes
/// memory)` for compliance with the standard.
/// @param initiator The address that called this function
/// @param paymentReceiver The address that needs to receive the amount plus fee at the end of the callback
/// @param asset The asset to be loaned
/// @param amount The amount to loaned
/// @param fee The fee to be paid
/// @param data The ABI encoded data to be passed to the callback
/// @return result ABI encoded result of the callback
function(address, address, IERC20, uint256, uint256, bytes memory) external returns (bytes memory) callback
)
external
returns (bytes memory);
}
The maxFlashLoan
function MUST return the maximum available loan for asset
. The maxFlashLoan
function MUST NOT revert. If no flash loans for the specified asset
are possible, the value returned MUST be zero.
The flashFee
function MUST return the fee charged for a loan of amount
asset
. The flashFee
function MUST NOT revert. If a flash loan for the specified asset
and amount
is not possible, the value returned MUST be type(uint256).max
.
The flash
function MUST execute the callback passed on as an argument.
bytes memory result = callback(msg.sender, address(this), asset, amount, _fee, data);
The flash
function MUST transfer amount
of asset
to loan receiver before executing the callback.
The flash
function MUST include msg.sender
as the initiator
in the callback.
The flash
function MUST NOT modify the asset
, amount
and data
parameter received, and MUST pass them on to the callback.
The flash
function MUST include a fee
argument in the callback with the fee to pay for the loan on top of the principal, ensuring that fee == flashFee(asset, amount)
.
Before the end of the callback, the asset
balance of payment receiver
MUST have increased by amount + fee
from the amount at the beginning of the callback, or revert if this is not true.
The return of the flash
function MUST be the same as the return from the callback.
A callback receiver of flash loans MUST implement one or more external functions with the following arguments and return value:
/// @dev This function can have any name and be overloaded.
/// @param initiator The address that called this function
/// @param paymentReceiver The address that needs to receive the amount plus fee at the end of the callback
/// @param asset The asset to be loaned
/// @param amount The amount to loaned
/// @param fee The fee to be paid
/// @param data The ABI encoded data to be passed to the callback
/// @return result ABI encoded result of the callback
function(address, address, IERC20, uint256, uint256, bytes memory) external returns (bytes memory) callback;
The interfaces described in this ERC have been chosen as to cover the known flash lending use cases, while allowing for safe and gas efficient implementations.
maxFlashLoan
and flashFee
return numerical values on impossible loans to allow sorting lenders without having to deal with reverts.
maxFlashLoan
returns a value that is consistent with an impossible loan when the lender
is not able to serve the loan.
flashFee
returns a value that is consistent with an impossible loan when the lender
is not able to serve the loan.
flash
has been chosen as a function name as a verb which is descriptive enough, unlikely to clash with other functions in the lender
, and including both the use cases in which the assets lent are held or minted by the lender
.
Existing flash lenders all provide flash loans of several asset types from the same contract. Providing a asset
parameter in both the flash
and callback functions matches closely the observed functionality.
A bytes calldata data
parameter is included for the initiator
to pass arbitrary information to the receiver
. The receiver
can pass arbitrary information back to the initiator
using the bytes memory
return value.
A initiator
will often be required in the callback function, which the lender
knows as msg.sender
. An alternative implementation which would embed the initiator
in the data
parameter by the caller would require an additional mechanism for the receiver to verify its accuracy, and is not advisable.
A loan receiver is taken as a parameter to allow flexibility on the implementation of separate loan initiators, loan receivers, and callback receivers. This parameter is not passed on to the callback receiver on the grounds that it will be often the same as callback receiver and when not, it can be encoded in the data
by the initiator
.
A payment receiver
allows for the same flexibility on repayments as in borrows. Control flow and asset flow are independent.
The amount
will be required in the callback function, which the lender
took as a parameter. An alternative implementation which would embed the amount
in the data
parameter by the caller would require an additional mechanism for the receiver to verify its accuracy, and is not advisable.
A fee
will often be calculated in the callback function, which the callback receiver must be aware of for repayment. Passing the fee
as a parameter instead of appended to data
is simple and effective.
Arbitrary callback functions on callback receivers allows to implement different behaviours to flash loans on callback receivers without the need for encoding a function router using the data
argument. A function call type is 24 bytes of which the first 20 bytes are the target address and the last 4 bytes are the function signature.
The amount + fee
are pushed to the payment receiver
to allow for the segregation of asset and control flows. While a "pull" architecture is more prevalent, "push" architectures are also common. For those cases where the lender
can't implement a "push" architecture, a simple wrapper contract can offer this proposal's external interface, while using liquidity from the lender
using a "pull" architecture.
This EIP is a successor of ERC-3156. While not directly backwards compatible, a wrapper contract offering this proposal's external interface with liquidity obtained from an ERC-3156 flash lender
is trivial to implement.
The arguments of the flash loan callbacks are expected to reflect the conditions of the flash loan, but cannot be trusted unconditionally. They can be divided in two groups, that require different checks before they can be trusted to be genuine.
initiator
, asset
and amount
refer to a past transaction that might not have happened if the caller of the callback decides to lie. fee
might be false or calculated incorrectly. data
might have been manipulated by the caller.initiator
, asset
, amount
and fee
are genuine a reasonable pattern is to verify that the callback caller is in a whitelist of verified flash lenders. Since often the caller of flash
will also be receiving the callback this will be trivial. In all other cases flash lenders will need to be approved if the arguments in the callback are to be trusted.data
is genuine, in addition to the check in point 1, it is recommended to verify that the initiator
belongs to a group of trusted addresses. Trusting the lender
and the initiator
is enough to trust that the contents of data
are genuine.Any receiver
that repays the amount
and fee
received as arguments needs to include in the callback a mechanism to verify that the initiator and lender
are trusted.
Alternatively, the callback receiver can implement permissioned functions that set state variables indicating that a flash loan has been initiated and what to expect as amount
and fee
.
Alternatively, the callback receiver can verify that amount
was received by the loanReceiver
and use its own heuristics to determine if a fee
is fair and the loan repaid, or the transaction reverted.
The typical quantum of assets involved in flash mint transactions will give rise to new innovative attack vectors.
The supply of a flash-mintable asset can be easily manipulated, so oracles that take the supply of the flash-mintable asset into account must either discount amounts that were flash-minted, produce data that is averaged over time, or find some other solution to the varying supply.
If the flash mint provider does not place any limits on the amount of flash mintable assets in a transaction, then anyone can flash mint $2^256-1$ amount of assets.
The protocols on the receiving end of the flash mints will need to ensure their contracts can handle this, either by using a compiler that embeds overflow protection in the smart contract bytecode, or by setting explicit checks.
The coupling of flash minting with business specific features in the same platform can easily lead to unintended consequences.
Assume a smart contract that flash lends its native asset. The same smart contract borrows from a third party when users burn the native asset. This pattern would be used to aggregate in the smart contract the collateralized debt of several users into a single account in the third party. The flash mint could be used to cause the lender
to borrow to its limit, and then pushing interest rates in the underlying lender
, liquidate the flash lender
:
lender
a very large amount of FOO.lender
to borrow from underwriter
all the way to its borrowing limit.underwriter
, making lender
undercollateralized.lender
for profit.Copyright and related rights waived via CC0.