Implementing ERC3156

https://eips.ethereum.org/EIPS/eip-3156

A flash lending feature integrates two smart contracts using a callback pattern. These are called the LENDER and the RECEIVER.

A flashloan lender must implement the IERC3156FlashLender interface. Hence our vault contract will inherit IERC3156FlashLender

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "lib/yield-utils-v2/contracts/mocks/ERC20Mock.sol";
import "lib/openzeppelin-contracts/contracts/access/Ownable.sol";

import "lib/yield-utils-v2/contracts/token/IERC20.sol";
import "src/IERC3156FlashLender.sol";
import "src/IERC3156FlashBorrower.sol";

/**
@title Flash Loan Server
@author Calnix
@dev Contract allows users to exchange a pre-specified ERC20 token for some other wrapped ERC20 tokens.
@notice Wrapped tokens will be burned, when user withdraws their deposited tokens.
*/

contract FlashLoanVault is ERC20Mock, IERC3156FlashLender {

    ///@dev The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan"
    bytes32 public constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");

    ///@notice Fee is a constant 0.1%
    ///@dev 1000 == 0.1%  (1 == 0.0001 %)
    uint256 public constant fee = 1000; 
    
    ///@dev mapping of supported addresses by Flash loan provider
    mapping(address => bool) public supportedTokens;

    ///@dev ERC20 interface specifying token contract functions
    IERC20 public immutable underlying;   

Lender

The Lending contract needs to implement the following functions

flashfee & _flashfee()

These two functions are used to set the fee for each token the lender is willing to lend out. In our implementation we will only be lending a single token, DAI, hence the fee calculation is static.

maxFlashLoan()

  • Returns the maximum amount of a token the lender is able to offer in a flash loan - dependent on the lender's holdings.

  • Also, it is used to tell when a token is not support (or does not have liquidity) by returning a zero.

flashloan()

flashLoan() function executes the flash loan. A borrower would call on this function to execute a flash loan (via flashBorrow).

  1. The receiver address must be a contract implementing the borrower interface. Any arbitrary data may be passed in addition to the call.

  2. The only requirement for the implementation details of the function are that you have to call the onFlashLoan callback from the receiver:

require(receiver.onFlashLoan(msg.sender, token, amount, fee, data) == keccak256("ERC3156FlashBorrower.onFlashLoan"), "IERC3156: Callback failed");

After the callback, the flashLoan function must take the amount + fee token from the receiver, or revert if this is not successful.

Function checks for the following:

  • Tokens that was being requested for flash loan are supported

  • Calculates fee to be charged for flash loan amount

  • Transfers the loan amount to the receiver

  • Callback: calls back to receiver. Receiver has to be an IERC3156FlashBorrower and contain the function onFlashLoan as described in the next section. This function ensures the legitimacy of the flash loan by

    • verifying the sender is the correct lender

    • verifying the initiator for the flash loan was actually the receiver contract

    • returns keccak256("ERC3156FlashBorrower.onFlashLoan")

  • Finally, the amount+fee is transferred from the receiver to the lender.

Receiver

Receiver has to implement IERC3156FlashBorrower interface. This will allow for callback pattern.

The borrower interface consists of only of 1 callback function: onFlashLoan(). We will overwrite this in our implementation.

Lender is a fixed IERC3156FlashLender contract defined on deployment.

  • Flash lender address is passed as IERC3156FlashLender into the constructor.

flashBorrow()

For the transaction to not revert, inside the onFlashLoan the borrower contract must approve amount + fee of the token to be taken by msg.sender.

OnFlashLoan()

This does three things:

  • verify the sender is actually the lender

  • verify the initiator for the flash loan was actually our contract

  • return the pre-defined hash to verify a successful flash loan

We could further implement additional logic here based on the passed data field if required. Essentially, what do we do with the flash loan once we have received it.

Last updated