TR kizaki Tech Memo

Sum up coding for Arbitrage with Flash loan by Aave v2

What’s Flash Loan?

Flashloan is an arbitrage driver in non-custodial and no centralized authorities financial world in blockchain.

Arbitrage is the simultaneous purchase and sale of the same asset in different markets in order to profit from tiny differences in the asset’s listed price.

Flash Loans are special transactions that allow the borrowing of an asset, as long as the borrowed amount (and a fee) is returned before the end of the transaction (also called One Block Borrows).

These transactions do not require a user to supply collateral prior to engaging in the transaction. There is no real world analogy to Flash Loans, so it requires some basic understanding of how state is managed within blocks in blockchains.

Understanding DeFi, Dex and AMM

DeFi is a set of autonomous finance features built on blockchain in the underlying internet communications. It does not rely on any people, and the authorities but only code. Financial feature such as lending, borrowing can be embedded into blockchain by means of smart contracts.

DEXs are non-custodial and leverage the functionality of self-executing smart contracts for peer-to-peer trading, while users retain control of their private keys and funds. The first thing you must understand is AMM(Automated Market Makers) that the most of DEX protocols adopted for their exchange systems.

AMM DEXs pay users to form liquidity pools in exchange for a percentage of the fees that traders earn by swapping tokens in and out of the liquidity pools.

Why choose Aave? Comparison between Flashloan providers dYdX and Uniswap

Why choose Aave on this time ? Because it’s quite easy to integrate, as they have good documentation and even a Truffle box (i.e., a template you can use to create your own flash-loan quickly). Also we can borrow ETH directly.

But sounds like cons which they charge a 0.09 pct fee for each Flashloan.

dYdX is the only Flashloan provider that does not charge a fee.

But they propose us to limited choice of tokens. Also you cannot borrow ETH directly. Instead, you get WETH(Wrapped Ether) which is less convenient to manipulate.

Uniswap is one of the most popular decentralized exchange in DeFi and the most wide choice of tokens. they can borrow ETH directly, instead of WETH.

But there is a 0.3pct fee for each flashloan with Uniswap.

Okay, It’s time to look at how to code a smart contract for Arbitrage with Flashloan by Aave v2.


FlashLoanReceiverBase.sol

// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.10;

import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import {IFlashLoanReceiver} from '../interfaces/IFlashLoanReceiver.sol';
import {IPoolAddressesProvider} from '../../interfaces/IPoolAddressesProvider.sol';
import {IPool} from '../../interfaces/IPool.sol';

/**
 * @title FlashLoanReceiverBase
 * @author Aave
 * @notice Base contract to develop a flashloan-receiver contract.
 */
abstract contract FlashLoanReceiverBase is IFlashLoanReceiver {
  IPoolAddressesProvider public immutable override ADDRESSES_PROVIDER;
  IPool public immutable override POOL;

  constructor(IPoolAddressesProvider provider) {
    ADDRESSES_PROVIDER = provider;
    POOL = IPool(provider.getPool());
  }
}

TestAaveFlashLoan.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "./interfaces/aave/FlashLoanReceiverBase.sol";

contract TestAaveFlashLoan is FlashLoanReceiverBase {
  using SafeMath for uint;

  event Log(string message, uint val);

  constructor(ILendingPoolAddressesProvider _addressProvider)
    public
    FlashLoanReceiverBase(_addressProvider)
  {}

  function testFlashLoan(address asset, uint amount) external {
    uint bal = IERC20(asset).balanceOf(address(this));
    require(bal > amount, "bal <= amount");

    address receiver = address(this);

    address[] memory assets = new address[](1);
    assets[0] = asset;

    uint[] memory amounts = new uint[](1);
    amounts[0] = amount;

    // 0 = no debt, 1 = stable, 2 = variable
    // 0 = pay all loaned
    uint[] memory modes = new uint[](1);
    modes[0] = 0;

    address onBehalfOf = address(this);

    bytes memory params = ""; //abi.encode(...)
    uint16 referralCode = 0;

    LENDING_POOL.flashLoan(
      receiver,
      assets,
      amounts,
      modes, // 0 = no debt, 1 = stable, 2 = variable // 0 = pay all loaned
      onBehalfOf, //receive the debt in case mode is equal to one or two 
      params,
      referralCode
    );
  }

  function executeOperation(
    address[] calldata assets,
    uint[] calldata amounts,
    uint[] calldata premiums,//these are the fees that we need to pay back for borowing 
    address initiator,//the address that executed flash loan 
    bytes calldata params
  ) external override returns (bool) {
    // do stuff here (arbitrage, liquidation, etc...)
    // abi.decode(params) to decode params
    for (uint i = 0; i < assets.length; i++) {
      emit Log("borrowed", amounts[i]);
      emit Log("fee", premiums[i]);

      uint amountOwing = amounts[i].add(premiums[i]);
      IERC20(assets[i]).approve(address(LENDING_POOL), amountOwing);
    }
    // repay Aave
    return true;
  }
}

test-aave-flash-loan.js

const BN = require("bn.js")
const { sendEther, pow } = require("./util")
const { DAI, DAI_WHALE, USDC, USDC_WHALE, USDT, USDT_WHALE } = require("./config")

const IERC20 = artifacts.require("IERC20")
const TestAaveFlashLoan = artifacts.require("TestAaveFlashLoan")

contract("TestAaveFlashLoan", (accounts) => {
  const WHALE = USDC_WHALE
  const TOKEN_BORROW = USDC
  const DECIMALS = 6
  const FUND_AMOUNT = pow(10, DECIMALS).mul(new BN(2000))
  const BORROW_AMOUNT = pow(10, DECIMALS).mul(new BN(1000))

  const ADDRESS_PROVIDER = "0xB53C1a33016B2DC2fF3653530bfF1848a515c8c5"

  let testAaveFlashLoan
  let token
  beforeEach(async () => {
    token = await IERC20.at(TOKEN_BORROW)
    testAaveFlashLoan = await TestAaveFlashLoan.new(ADDRESS_PROVIDER)

    await sendEther(web3, accounts[0], WHALE, 1)

    // send enough token to cover fee
    const bal = await token.balanceOf(WHALE)
    assert(bal.gte(FUND_AMOUNT), "balance < FUND")
    await token.transfer(testAaveFlashLoan.address, FUND_AMOUNT, {
      from: WHALE,
    })
  })

  it("flash loan", async () => {
    const tx = await testAaveFlashLoan.testFlashLoan(token.address, BORROW_AMOUNT, {
      from: WHALE,
    })
    for (const log of tx.logs) {
      console.log(log.args.message, log.args.val.toString())
    }
  })
})

IFlashLoanReceiver.sol

// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;

import {IPoolAddressesProvider} from '../../interfaces/IPoolAddressesProvider.sol';
import {IPool} from '../../interfaces/IPool.sol';

/**
 * @title IFlashLoanReceiver
 * @author Aave
 * @notice Defines the basic interface of a flashloan-receiver contract.
 * @dev Implement this interface to develop a flashloan-compatible flashLoanReceiver contract
 **/
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);
}

.env.sample

WEB3_INFURA_PROJECT_ID=
ARCHIVE_NODE_API_KEY=
WETH_WHALE=0xee2826453A4Fd5AfeB7ceffeEF3fFA2320081268
DAI_WHALE=0xF977814e90dA44bFA03b6295A0616a897441aceC
USDC_WHALE=0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE
USDT_WHALE=0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE
WBTC_WHALE=0xF977814e90dA44bFA03b6295A0616a897441aceC

Terminal

source .env

# using infura.io
npx ganache-cli \
--fork https://mainnet.infura.io/v3/$WEB3_INFURA_PROJECT_ID \
--unlock $WETH_WHALE \
--unlock $DAI_WHALE \
--unlock $USDC_WHALE \
--unlock $USDT_WHALE \
--unlock $WBTC_WHALE \
--networkId 999

references:

https://docs.aave.com/developers/guides/flash-loans

https://github.com/t4sk/defi-by-example

https://www.investopedia.com/terms/a/arbitrage.asp

https://defiprime.com/flahloans-comparison