ERC-7399 Flash loan standard interface (⚡ Flash Loans ⚡)
Review · Vaults & DeFi · created 2023-07-25
Unifies the request, callback, and repayment process for flash loans, which are borrowed and repaid within a single transaction, into one interface. Borrowers can switch between multiple lenders without modifying their code.
Why it is needed
A flash loan is a loan that lets you borrow a large amount without collateral, provided you repay it along with a fee within the same transaction. Right now, every provider of this feature does it differently. The syntax of the function that initiates the loan differs, some providers let you separate the address that receives the funds from the address that receives the callback while others don't, and repayment methods differ too, with some lenders pulling funds from the borrower and others requiring the borrower to send them directly. Handling of flash minting, where a lender mints its own token on the spot to lend it out, also differs. As a result, borrowers must write separate integration code for each lender, and the cost of maintaining compatibility grows as the ecosystem grows. This standard bundles the flexibility needed by both lenders and borrowers into a single interface, letting borrowers freely switch lenders without modifying their code.
What to implement
function(address, address, IERC20, uint256, uint256, bytes memory) external returns (bytes memory) callback;: The function signature that a callback receiver must implement. It takes an initiator, a payment receiver, an asset, an amount, a fee, and data, and returns arbitrary data. The name is free to choose and can be overloaded.
In depth
Separation of the four roles
This standard involves four roles. The initiator is the party that calls the lender's flash function, and it decides the asset and amount, the loan receiver, and the callback receiver and callback function. The loan receiver is the address that receives the borrowed asset, and the callback receiver is the address holding the function that the lender calls after sending the asset. The payment receiver is set by the lender and is the address that receives the principal and fee. Separating the addresses through which assets flow from the address that executes code allows control flow and asset flow to be designed independently. The loan receiver address is not passed as a callback argument, because it is usually the same as the callback receiver, and when it differs the initiator can pass it along in data.
Query functions do not revert
maxFlashLoan must return the maximum amount that can be borrowed for the given asset and must not revert. It must return 0 if loans of that asset are unavailable. flashFee must return the fee for borrowing the given amount and must likewise not revert, returning type(uint256).max if the loan is unavailable. The reason numbers are returned instead of reverting even in the unavailable case is to let borrowers query multiple lenders, compare terms, and sort them without handling reverts. The fee can be set to 0, and the lender decides for itself which assets to support, including supporting all assets.
Rules that the flash function must follow
flash must transfer amount of asset to the loan receiver before executing the callback. The callback must set msg.sender as the initiator and pass through the received asset, amount, and data unchanged. The fee argument in the callback must equal flashFee(asset, amount). Before the callback finishes, the asset balance of the payment receiver must have increased by amount + fee compared to the start of the callback, otherwise it must revert. The return value of flash must equal the return value of the callback. The reason initiator and amount are passed as separate arguments rather than mixed into data is that putting them in data would require the receiver to have a separate mechanism to verify their authenticity.
The callback is specified with a 20 byte address and a 4 byte signature
The callback argument of flash is encoded as 24 bytes, combining the 20 byte address of the callback receiver and the 4 byte function signature of the callback. The callback function can have any name and can be overloaded, but its argument layout must be (initiator, paymentReceiver, asset, amount, fee, data) and its return type must be bytes memory. Because the function to call can itself be specified, the callback receiver can have different functions for different purposes without needing routing information inside data to indicate which action to perform. Since the callback's return value is passed through flash back to the initiator unchanged, the receiver can also return arbitrary information to the initiator.
Repayment is push based
Repayment is a push model in which the borrower sends the principal and fee to the payment receiver. The pull model, where the lender draws funds from the borrower, is more common, but the push model allows asset flow and control flow to be separated. A lender that can only implement the pull model can deploy a simple wrapper contract that exposes this standard's external interface while internally pulling liquidity using the pull model. The function was named flash because it has clear meaning as a verb, is unlikely to collide with other function names, and covers both the case where the lender lends assets it holds and the case of flash minting, where tokens are issued on the spot.
This is a successor to ERC-3156 and is still under review
This standard is a successor to the existing flash loan standard, ERC-3156. There is no direct backwards compatibility, but the source explains that it is easy to build a wrapper contract that pulls liquidity from an ERC-3156 lender and exposes this standard's external interface. So an existing ERC-3156 based lender can accept borrowers that follow this standard without significant cost. The source's status is Review, meaning it is still under review and its content may change. It is safer to check the latest source at the time of actual implementation.
Things to watch out for
- Not trusting callback arguments blindly: Callback arguments can never be unconditionally trusted. If the callback caller is dishonest, initiator, asset, and amount may point to a transaction that never actually happened, fee may be wrong or miscalculated, and data may be tampered with. To trust initiator, asset, amount, and fee, you must verify that the callback caller is on a vetted whitelist of lenders, and to also trust data you must additionally verify that initiator belongs to a group of addresses the receiver trusts.
- The risk of automatic repayment: A receiver that repays the amount and fee it received as arguments as is must include a mechanism in the callback to verify that initiator and the lender are trustworthy. As an alternative, you can have a privileged function that records in a state variable that a flash loan has started and what amount and fee are expected, or have loanReceiver verify that it actually received amount and judge for itself whether the fee is appropriate, reverting otherwise.
- Supply based oracle manipulation: An asset capable of flash minting can easily have its supply manipulated. An oracle that references that asset's supply must exclude the flash minted quantity, produce time averaged data, or find some other countermeasure against fluctuating supply.
- Arithmetic overflow and underflow: If a lender does not cap how much can be flash minted in a single transaction, anyone could mint up to 2^256-1 of the asset. A protocol on the receiving end of such an amount must use a compiler that embeds overflow protection in the bytecode, or add explicit checks, so the contract can handle it.
- Vault drain attacks: Combining flash minting with business functions on the same platform can easily produce unintended results. In the source's example, when a contract that flash mints its own asset borrows from a third party upon a user's burn request, an attacker can flash mint a large amount, trigger repayment to push the contract to its borrowing limit, drive up the third party's interest rate to put it into an undercollateralized state, and then liquidate it for profit.