Abstract

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.

Motivation

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:

  • Inconsistent syntax for initiating flash loans across different platforms.
  • Variations in the relationship between the loan receiver and the callback receiver, with some protocols allowing different addresses for each role while others do not.
  • Divergent repayment mechanisms, with some lenders pulling the principal and fee from the loan receiver and others requiring the loan receiver to manually return the funds.
  • Disparities in the treatment of flash minting, where some lenders allow the creation of any amount of their native asset without charging a fee, effectively permitting flash loans bounded by computational constraints rather than asset ownership limitations.

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.

Specification

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.

Lender Specification

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.

Receiver Specification

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;

Rationale

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.

Backwards Compatibility

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.

Security Considerations

Verification of callback arguments

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.

  1. No arguments can be assumed to be genuine without some kind of verification. 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.
  2. To trust that the value of 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.
  3. To trust that the value of 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.

Flash lending security considerations

Automatic approvals

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.

Flash minting external security considerations

The typical quantum of assets involved in flash mint transactions will give rise to new innovative attack vectors.

Spot Oracle Manipulation

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.

Arithmetic Overflow and Underflow

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.

Flash minting internal security considerations

The coupling of flash minting with business specific features in the same platform can easily lead to unintended consequences.

Treasury Draining

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:

  1. Flash mint from lender a very large amount of FOO.
  2. Redeem FOO for BAR, causing lender to borrow from underwriter all the way to its borrowing limit.
  3. Trigger a debt rate increase in underwriter, making lender undercollateralized.
  4. Liquidate the lender for profit.

Copyright and related rights waived via CC0.