Defi - 以Flash Loan進行套利


目錄

目標

平台及技術介紹

   ### Uniswap
   ### Aave
   ### Flashloan
   ### ERC20及如何自己發行

實作

   ### 前置作業
   ### 實際測試閃電貸
   ### 實作套利和結果


那就開始吧!

目標

以Aave V3的flash loan去Uniswap三個池子對尋找是否有套利空間
有的話則讓交易成立,虧爛的話則將交易回滾
而這一切都由智能合約功能自動完成,可以實現無風險的套利方式
聽起來很複雜對吧 沒關係我們接下來將一步步拆解及分析


平台及技術介紹

Uniswap

Uniswap是一個去中心化的交易平台,自動造市且由智能合約管理
(DEX - Decentralized Exchange)

運作方式

每個資金池裡有成對的(A,B)token可供兌換
供給者:提供市場流動性,放入成對等價的A、B幣,獲得LP tokens之後以此來領取分潤
交易者:在交易池當中兌換幣別,需要支付手續費

自動造市 以自動計算的方式決定池中兩種幣的匯率
公式: Qx X Qy = k
Qx,Qy為此池中幣的數量, k為定值在第一筆供給者提供初始Qx,Qy後固定

例子:
假設目前某池1000DAI和10ETH相等
      --> 匯率 10/1000 = 0.01 ETH/DAI , k=1000 x 10
甲想用100DAI去換取ETH,可以換Qe個
則, k = 1000 x 10 = (1000+100) x (10-Qe) , Qe = 0.91
--> 新匯率 (10-0.91)/(1000+100) = 0.00826 ETH/DAI

就算是同樣對的幣別在不同池子裡匯率也會因為自動計算而有所差異, 也就形成了套利的空間

Aave

Aave是一個去中心化的借貸平台

存款者
領取被動的利息,會拿到aToken(存款收據):
-和存入的資產為1:1
-可轉移
-之後要取出存款要以aToken為依據
單利計算,為池子的利用率(total borrows/total liquidity)

(在Aave測試網路上用faucet拿到DAI)


選擇交易市場和選擇要貸款的幣別後,拿到相對應的aToken
需要領錢拿aToken就可以取款

借款者
為了槓桿及投資, 存入利率高的資產, 借利率低的資產出來應用
超額抵押
但是通常貸出來的總價值會較抵押總資產低, 為Aave確保存款者利益的確保措施,借貸利息可以選擇用固定或是變動
借貸後會拿到dToken:
1.和抵押的資產是1:1計算
2.不可以轉移
3.之後以dToken去贖回抵押物

LTV: 借出的抵押品價值趴數 、 LT: 借出的抵押品價值趴數/抵押品價值 Penalty:處罰
<例子>
LTV:75% LT:80% Penalty:5%
假設我今天抵押了100 USDC, 可以借貸出來價值75 USDC的dToken
但是USDC急速貶值(可理解為數量上升), 此時那些貸出來的dToken價值/100 USDC 價值可能來到0.8
那這樣對有存入USDC的存款者來說利益後到損害,清算者必須賣出USDC成其他的幣別,降低數量使USDC價值回升,而借貸者除了可能抵押USDC被賣掉以外,還要繳交5 USDC的懲罰費用

清算者
當發現某些借貸者的Health Factor< 1,站在保護存款者的角度,清算者便要開始拍賣抵押品來保障貨幣價值, 其中借貸者繳交的罰金就為清算者的獎勵
Health Factor: (抵押品價值 x LT) / (抵押品價值 x LTV)
<延續上例 假設一次清算為50%>
此時借款者抵押的50 USDC會被清算者拿去拍賣換成別種幣別,並且有5 USDC會以罰金形式繳交給清算者作為獎勵, 此時借款者拿著當初貸到的dToken只能贖回(100-50-5)=45 USDC

--> 這樣本來要賺一波的借貸者不是虧爛了嗎? Aave V3新玩法 flash loan因此出現!

Flash Loan

抵押品價值下跌而未能及時補倉時,借款人在被強制平倉的時候一定會有損失
而閃電貸的基本想法為:
假如借款人沒有在期限內還款,那連借款連帶後續交易都一筆勾銷
區塊鏈使此變成可能,因為在交易還沒上鏈的狀況下,一切都是可以撤銷的
這也是閃電貸可以無本套利的基本原理
有賺就承認交易,賠爛就回滾使交易消失

<閃電貸目前的應用>

  1. 套利
    當發現兩個市場(通常是Uniswap不同交易池)有匯差,就可以用閃電貸貸出很 多資金用以買低賣高的方式快速賺取價差,之後在還回貸款
  2. 抵押品轉換
    假設某投資人現在在Aave用BAT做抵押品借出ETH,如果他看衰BAT未來的價格,他該如何將抵押品從BAT換成USDC來降低價格風險呢?他可以進行以下操作:
    利用閃電貸借出所欠的ETH並償還原先借款
    ->將取回的BAT抵押品在DeFi換匯所(如Curve)換成USDC
    ->用USDC作為抵押品重新借出ETH
    ->將借出的ETH拿去還閃電貸的ETH欠款
    如果以上交易成立則可以在貸出ETH不動到的情況下把抵押品從BAT換成USDC
  3. 主動平倉
    假設投資人有一個用BAT作為抵押品借出ETH的部位。若今天BAT價值下跌,使得投資人的抵押品價值不足,如果他不想補倉,也沒有足夠的ETH還款,其操作方式如下
    利用閃電貸借出ETH來還款
    ->將取回的BAT抵押品在DeFi換匯所換成ETH
    ->將部分ETH拿去償還閃電貸,所有部位結算完成

    ## ERC
    ERC20是以太坊上的一種標準協定, 與之相對的是ERC721後面會在談到
    ### 標準
    ERC-20

    1. 同質化 : 一樣種類的不同token, 兩者之間沒有價值的區別 (兩人的100元是一樣的)
    2. 可以互換 : 由於同質化的關係, 交換同種類的不同token是沒任何影響的
    3. 可分割性
    4. 需要規定有六個Function和兩個event :

      contract ERC20Interface {
      # 以下註解說明若有錯誤,以官方說明為主。
      # https://github.com/ethereum/EIPs/issues/20
      
      # 查詢代幣發行量
      function totalSupply() public constant returns (uint);
      
      # 查詢某地址的擁有代幣數量
      function balanceOf(address tokenOwner) public constant returns (uint balance);
      
      # 查詢某智能合約或地址可操作的代幣數量
      function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
      
      # 將代幣轉出給某定址
      function transfer(address to, uint tokens) public returns (bool success);
      
      # 授權某智能合約或地址可操作的代幣數量
      function approve(address spender, uint tokens) public returns (bool success);
      
      # 將某帳戶的代幣轉到另一個帳戶
      function transferFrom(address from, address to, uint tokens) public returns (bool success);
      
      # 當發生轉移時觸發的事件紀錄
      event Transfer(address indexed from, address indexed to, uint tokens);
      
      # 當發生授權時觸發的事件紀錄
      event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
      }
      

--> token符合ERC20就可以和乙太錢包兼容,用錢包儲存或是發送給別人,且發行容易
ERC-721

  1. 非同質化: 同樣種類的token有不同的ID編號,因此視為兩樣不同的東西
  2. 不可互換性
  3. 不可分割性

如何發行ERC-20

由於ERC20的規範目前發行token並非難事, 我們可以用以下網頁建立發行自己的token
https://docs.openzeppelin.com/contracts/4.x/wizard

首先選擇ERC-20及填寫token基本資料

填寫完畢之後按右上的Open in Remix,compile並選擇好錢包deploy之後
這些新發行的token就會存在你的錢包部屬的合約A當中(remix裡面可以看得到),至此新發行token就成功上鏈
(此處我是使用Goerli網路中的錢包地址來部屬)

部屬成功後要再MetaMask中新增token
MetaMask Asset最底部有import Token在裡面輸入剛剛發布新token的合約A地址



實作

前置作業

1.TestNet : Goerli (ganache錢包沒辦法import token QQ)

2.Wallet : MetaMask

3.IDE : Remix (也有考慮自己寫compile、deploy file在Vscode運行)

4.發行兩種ERC20代幣到到錢包當中 :
(1) LTNcoin x 100000 (2) JimCoin x 100000
運用Aave testnet faucet拿到 10000 DAI
至此要運用的加密貨幣就準備好了

5.使用Uniswap建立三個pool:
uniswap --> lauch APP --> pool --> more --> V2 liquidity --> create a pair

並且分成三個初始的池
(1) LTN:DAI = 10000 : 100
(2) LTN:Jim = 1000 : 1000
(3) Jim:DAI = 4000 : 250
由於是測試網路的發行幣,我們要用地址(去Etherscan找)來import至pool中
LTN : 0x397cbd684a36722903eb5f1f5c77568a8d7be9c0
Jim : 0x659fd423278a5baff605e0e8a622de0afa534854
DAI : 0xDF1742fE5b0bFc12331D8EAec6b478DfDbD31464


建立完之後可以用import功能尋找pool

5.運行的Flash Loan原始程式碼 - 在Remix執行

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
import {FlashLoanSimpleReceiverBase} from "https://github.com/aave/aave-v3-core/blob/master/contracts/flashloan/base/FlashLoanSimpleReceiverBase.sol";
import {IPoolAddressesProvider} from "https://github.com/aave/aave-v3-core/blob/master/contracts/interfaces/IPoolAddressesProvider.sol";
import {IERC20} from "https://github.com/aave/aave-v3-core/blob/master/contracts/dependencies/openzeppelin/contracts/IERC20.sol";
import { SafeMath } from "https://github.com/aave/aave-v3-core/contracts/dependencies/openzeppelin/contracts/SafeMath.sol";

// ----------------------INTERFACE------------------------------
// Uniswap
// Some helper function, it is totally fine if you can finish the lab without using these functions
interface IUniswapV2Router {

    function getAmountsOut(uint amountIn, address[] memory path) external view returns (uint[] memory amounts);

    function getAmountsIn(uint amountOut, address[] memory path) external view returns (uint[] memory amounts);

    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function swapTokensForExactTokens(
        uint amountOut,
        uint amountInMax,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function swapExactTokensForETH(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function swapExactETHForTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable returns (uint[] memory amounts);

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external ;

    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;

    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external payable returns
     (uint amountToken, uint amountETH, uint liquidity);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (
      uint amountA, uint amountB, uint liquidity);

    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB);

}

interface IUniswapV2Pair {
  function token0() external view returns (address);

  function token1() external view returns (address);

  function getReserves()
    external
    view
    returns (
      uint112 reserve0,
      uint112 reserve1,
      uint32 blockTimestampLast
    );

  function swap(
    uint amount0Out,
    uint amount1Out,
    address to,
    bytes calldata data
  ) external;
}

interface IUniswapV2Factory {
  function getPair(address token0, address token1) external view returns (address);
}

// ----------------------IMPLEMENTATION------------------------------
contract FlashloanV3 is FlashLoanSimpleReceiverBase {
    // TODO: define constants used in the contract including ERC-20 tokens, Uniswap router, Aave address provider, etc.
    //  Aave V3 DAI address (Goerli testnet): 0xDF1742fE5b0bFc12331D8EAec6b478DfDbD31464
    //  Uniswap V2 router address (Goerli testnet): 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
    //    *** Your code here ***
    // END TODO

    using SafeMath for uint256;

    constructor(){
        // TODO: (optional) initialize your contract
        //   *** Your code here ***
        // END TODO
    }

    /**
     * Allows users to access liquidity of one reserve or one transaction as long as the amount taken plus fee is returned.
     * @param _asset The address of the asset you want to borrow
     * @param _amount The borrow amount
     **/
    // Doc: https://docs.aave.com/developers/core-contracts/pool#flashloansimple
    function RequestFlashLoan(address _asset, uint256 _amount) public {
        address receiverAddress = address(this);
        address asset = _asset;
        uint256 amount = _amount;
        bytes memory params = "";
        uint16 referralCode = 0;

        // POOL comes from FlashLoanSimpleReceiverBase
        POOL.flashLoanSimple(
            receiverAddress,
            asset,
            amount,
            params,
            referralCode
        );
    }

    /**
     * This function is called after your contract has received the flash loaned amount
     * @param asset The address of the asset you want to borrow
     * @param amount The borrow amount
     * @param premium The borrow fee
     * @param initiator The address initiates this function
     * @param params Arbitrary bytes-encoded params passed from flash loan
     * @return  true or false
     **/
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    )
        external
        override
        returns (bool)
    {
        // TODO: implement your logic
        // Don't forget to payback the amount of the borrowed asset + flash loan fee 
        // END TODO
        return true;
    }

    function getBalance(address _tokenAddress) external view returns (uint256) {
        return IERC20(_tokenAddress).balanceOf(address(this));
    }

    function withdraw(address _tokenAddress) external onlyOwner {
        IERC20 token = IERC20(_tokenAddress);
        token.transfer(msg.sender, token.balanceOf(address(this)));
    }

    modifier onlyOwner() {
        require(
            msg.sender == owner,
            "Only the contract owner can call this function"
        );
        _;
    }

    receive() external payable {}

}

實際測試閃電貸

一般來說flash loan提供兩種借貸方案:
(1) flashLoan - 一筆交易中就可以借貸多種token
(2) flashLoanSimple - 一筆交易中只能借貸一種token (我們用的)

其中contract裡最重要的兩個function
RequestFlashLoan(address _asset, uint256 _amount) - 幫你借錢
executeOperation() - 拿到錢之後的處理流程

1.我們首先要申請借貸 為以下程式觸發
為用借用1 DAI 再還回DAI
會用到下面function:

function RequestFlashLoan(address _asset, uint256 _amount) public {
        address receiverAddress = address(this);
        address asset = _asset;
        uint256 amount = _amount;
        bytes memory params = "";
        uint16 referralCode = 0;

        // POOL comes from FlashLoanSimpleReceiverBase
        POOL.flashLoanSimple(
            receiverAddress,
            asset,
            amount,
            params,
            referralCode
        );
    }

在function參數裡我們要放入Aave V3 DAI address和借貸數量
Aave V3 DAI address : 0xDF1742fE5b0bFc12331D8EAec6b478DfDbD31464
https://docs.aave.com/developers/deployed-contracts/v3-testnet-addresses

2.接下來會到executeOperation()程式處理借來的token
這裡先簡單試驗借 1 DAI而沒有進行交易操作盡速還(1 DAI + fee)

function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    )
        external
        override
        returns (bool)
    {
        // TODO: implement your logic
        // Don't forget to payback the amount of the borrowed asset + flash loan fee
        uint256 amountOwed = amount + premium; 
        IERC20(asset).approve(address(POOL), amountOwed);
        // END TODO
        return true;
    }

(1) 先閃電貸合約deploy ,並且要在deploy 參數ADDRESSPROVIDER
裡加入addressProvider-Aave: 0xc4dCB5126a3AfEd129BC3668Ea19285A9f56D15D

!!注意有時候address會不符合規範需要用toChecksumAddress()去改
https://web3-tools.netlify.app/ 到這邊去轉換

(2) 先將10 DAI從錢包發給合約地址
使用合約getBalance 輸入DAI Aave V3 token address:
0xDF1742fE5b0bFc12331D8EAec6b478DfDbD31464
去查看合約是否連接到Aave goerli網路返回DAI balance

(3) 接下來開始借貸使用RequestFlashLoan()
第一個參數為Aave V3 DAI的 address 0xDF1742fE5b0bFc12331D8EAec6b478DfDbD31464
第二個參數為借貸數量 1

transact後接著去Etherscan查看交易細節,成功便表示閃電貸可以執行
借1 DAI 且沒做任何操作,還回 (1 DAI+fee) --> 閃電貸成功執行!

將合約對接到Uniswap Pool套利

Uniswap interface

https://docs.uniswap.org/contracts/v2/guides/smart-contract-integration/trading-from-a-smart-contract#safety-considerations
Uniswap中有提供很多介面可供使用,連接router參數之後可以操作uniswap pool
-router參數: IUniswapV2Router(UNISWAP_V2_ROUTER)
-UNISWAP_V2_ROUTER:
Uniswap V2 router address (Goerli testnet)
:0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
IUniswapV2Router(UNISWAP_V2_ROUTER).interface(arg...)
這樣就可以使用了會有返回值

我們會用這些介面完成閃電貸的套利細節
Uniswap當中有許多介面可供使用
比起一般了解到的介面,使用起來的感覺比較像是一個縮小版的function

其中要提使用到的兩個介面:

  1. getAmountsOut

    //為可以計算 token1 投到 uniswap pool當中可以領出多少 token2
    //input (token1的數量 ,[token1,token2])   token1/token2資料型態為address
    //output 通常會取amounts[len(amounts)-1] 為uint 換出的token2數量
    function getAmountsOut(uint amountIn, address[] memory path) external view returns (uint[] memory amounts);
    
  2. swapExactTokensForTokens

    //為可以實現真實的pool swap token功能
    //input  (token1放入數, 希望至少拿到的token2數, [token1,token2],拿到的token2要放到哪裡,block.timestamp)
    //output 通常用不到
    function swapExactTokensForTokens(
         uint amountIn,
         uint amountOutMin,
         address[] calldata path,
         address to,
         uint deadline
     ) external returns (uint[] memory amounts);
    

    值得注意這函式怎麼知道我們輸入希望至少拿到的token2數會不會超過pool可以給的?
    因為其實swapExactTokensForTokens()裡面有getAmountsOut()去計算
    如果超過了就會結束pool swap的動作
    (Uniswap doc 中提及interface實作的細節)

所以uniswap裡面的眾多介面不只連接上了router參數可以直接用操作pool
介面也會使用彼此的介面

改寫executeFlashLoanSimple()實現套利細節

function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    )
        external
        override
        returns (bool)
    {
        // TODO: implement your logic
        // Don't forget to payback the amount of the borrowed asset + flash loan fee
        address[] memory Arr = new address[](2);
        uint[] memory outputArr = new uint[](2);

            //DAI -> LTN
        Arr[0]=DAI;
        Arr[1]=LTN;
        outputArr = IUniswapV2Router(UNISWAP_V2_ROUTER).getAmountsOut(amount,Arr);
        uint outputLTN = outputArr[1];

        this.swap(DAI,LTN,amount);

            //LTN -> JIM
        Arr[0]=LTN;
        Arr[1]=JIM;
        outputArr = IUniswapV2Router(UNISWAP_V2_ROUTER).getAmountsOut(outputLTN,Arr);
        uint outputJIM = outputArr[1];

        this.swap(LTN,JIM,outputLTN);
            //JIM -> DAI
        Arr[0]=JIM;
        Arr[1]=DAI;
        outputArr = IUniswapV2Router(UNISWAP_V2_ROUTER).getAmountsOut(outputJIM,Arr);
        uint outputDAI = outputArr[1];

        this.swap(JIM,DAI,outputJIM);

        uint256 amountOwed = amount + premium; 
        IERC20(asset).approve(address(POOL), amountOwed);
        // END TODO
        return true;
    }

這邊先以LTN -> JIM swap過程來講解:

/LTN -> JIM
        Arr[0]=LTN;
        Arr[1]=JIM;
        outputArr = IUniswapV2Router(UNISWAP_V2_ROUTER).getAmountsOut(outputLTN,Arr);
        uint outputJIM = outputArr[1];

        this.swap(LTN,JIM,outputLTN);
            //JIM -> DAI

outputArr[1] 為uint,代表了如果我們輸入 outputLTN 數量的LTN
從pool中換出的JIM 數量,再用這JIM數量就是下次交換DAI的pool輸入數量

swap()執行了兌幣功能

function swap(address token1, address token2,uint inAmount) public {

        //借貸的DAI數量
        //approve pool can use these token1
        IERC20(token1).approve(address(UNISWAP_V2_ROUTER),inAmount);

        address[] memory path = new address[](2);
        path[0] = token1;
        path[1] = token2;

        //calculate output of token2
        uint[] memory outputArr = IUniswapV2Router(UNISWAP_V2_ROUTER).getAmountsOut(inAmount,path);
        uint output = outputArr[1];
        //swap
        IUniswapV2Router(UNISWAP_V2_ROUTER).swapExactTokensForTokens(inAmount, output, path, msg.sender, block.timestamp);

    }

先把LTN打到pool裡 ,因此用approve表示允許這些數量的LTN pool是可以使用的
一樣是先以getAmountsOut()計算output數量
然後根據此數量在swapExactTokensForTokens換到output數量的tokenB
之後將tokenB回傳到合約當中

實作套利和結果

1.deploy建立合約 建立時deploy參數要輸入 addressProvider-Aave : 0xc4dCB5126a3AfEd129BC3668Ea19285A9f56D15D
2.先從錢包給合約DAI,要給多一些不然可能會付不出手續費
3.在RequestFlashLoan(0xDF1742fE5b0bFc12331D8EAec6b478DfDbD31464,借貸數量) amount即借貸數量
AaveV3 token DAI address:0xDF1742fE5b0bFc12331D8EAec6b478DfDbD31464
4.去Etherscan查看transcation

黃色: 借款 、還款(有包含fee)
紅色: DAI -> LTN
橘色: LTN -> JIM
綠色: JIM -> DAI
藍色為DAI還款後剩下的淨利 --> 閃電貸成功!!

完整程式碼

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
import {FlashLoanSimpleReceiverBase} from "https://github.com/aave/aave-v3-core/blob/master/contracts/flashloan/base/FlashLoanSimpleReceiverBase.sol";
import {IPoolAddressesProvider} from "https://github.com/aave/aave-v3-core/blob/master/contracts/interfaces/IPoolAddressesProvider.sol";
import {IERC20} from "https://github.com/aave/aave-v3-core/blob/master/contracts/dependencies/openzeppelin/contracts/IERC20.sol";
import { SafeMath } from "https://github.com/aave/aave-v3-core/contracts/dependencies/openzeppelin/contracts/SafeMath.sol";
// ----------------------INTERFACE------------------------------
// Uniswap
// Some helper function, it is totally fine if you can finish the lab without using these functions
interface IUniswapV2Router {

    function getAmountsOut(uint amountIn, address[] memory path) external view returns (uint[] memory amounts);

    function getAmountsIn(uint amountOut, address[] memory path) external view returns (uint[] memory amounts);

    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function swapTokensForExactTokens(
        uint amountOut,
        uint amountInMax,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function swapExactTokensForETH(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function swapExactETHForTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable returns (uint[] memory amounts);

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external ;

    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;

    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external payable returns
     (uint amountToken, uint amountETH, uint liquidity);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (
      uint amountA, uint amountB, uint liquidity);

    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB);

}

interface IUniswapV2Pair {
  function token0() external view returns (address);

  function token1() external view returns (address);

  function getReserves()
    external
    view
    returns (
      uint112 reserve0,
      uint112 reserve1,
      uint32 blockTimestampLast
    );

  function swap(
    uint amount0Out,
    uint amount1Out,
    address to,
    bytes calldata data
  ) external;
}

interface IUniswapV2Factory {
  function getPair(address token0, address token1) external view returns (address);
}

// ----------------------IMPLEMENTATION------------------------------
contract FlashloanV3 is FlashLoanSimpleReceiverBase {
    // TODO: define constants used in the contract including ERC-20 tokens, Uniswap router, Aave address provider, etc.
    //  Aave V3 DAI address (Goerli testnet): 0xDF1742fE5b0bFc12331D8EAec6b478DfDbD31464
    //  Uniswap V2 router address (Goerli testnet): 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
    //  addressProvider-Aave : 0xc4dCB5126a3AfEd129BC3668Ea19285A9f56D15D

                //address of the uniswap v2 router
    address private constant UNISWAP_V2_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;

                //address of the token in goerli testnet
    address private constant LTN = 0x397cbd684a36722903eb5f1f5C77568a8d7be9C0;
    address private constant JIM = 0x659fd423278A5BAff605E0e8A622DE0afa534854;
    address private constant DAI = 0xDF1742fE5b0bFc12331D8EAec6b478DfDbD31464;

    // END TODO

    using SafeMath for uint256;
    address payable owner;

    constructor(address _addressProvider)
        // TODO: (optional) initialize your contract
        //   *** Your code here ***
      FlashLoanSimpleReceiverBase(IPoolAddressesProvider(_addressProvider))
    {
      owner = payable(msg.sender);
    }
        // END TODO


    //
     //Allows users to access liquidity of one reserve or one transaction as long as the amount taken plus fee is returned.
     //@param _asset The address of the asset you want to borrow
     //@param _amount The borrow amount
    // Doc: https://docs.aave.com/developers/core-contracts/pool#flashloansimple

    function RequestFlashLoan(address _asset, uint256 _amount) public {
        address receiverAddress = address(this);
        address asset = _asset;
        uint256 amount = _amount;
        bytes memory params = "";
        uint16 referralCode = 0;

        // POOL comes from FlashLoanSimpleReceiverBase
        POOL.flashLoanSimple(
            receiverAddress,
            asset,
            amount,
            params,
            referralCode
        );
    }

    /**
     * This function is called after your contract has received the flash loaned amount
     * @param asset The address of the asset you want to borrow
     * @param amount The borrow amount
     * @param premium The borrow fee
     * @param initiator The address initiates this function
     * @param params Arbitrary bytes-encoded params passed from flash loan
     * @return  true or false
     **/
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    )
        external
        override
        returns (bool)
    {
        // TODO: implement your logic
        // Don't forget to payback the amount of the borrowed asset + flash loan fee
        address[] memory Arr = new address[](2);
        uint[] memory outputArr = new uint[](2);

            //DAI -> LTN
        Arr[0]=DAI;
        Arr[1]=LTN;
        outputArr = IUniswapV2Router(UNISWAP_V2_ROUTER).getAmountsOut(amount,Arr);
        uint outputLTN = outputArr[1];

        this.swap(DAI,LTN,amount);

            //LTN -> JIM
        Arr[0]=LTN;
        Arr[1]=JIM;
        outputArr = IUniswapV2Router(UNISWAP_V2_ROUTER).getAmountsOut(outputLTN,Arr);
        uint outputJIM = outputArr[1];

        this.swap(LTN,JIM,outputLTN);
            //JIM -> DAI
        Arr[0]=JIM;
        Arr[1]=DAI;
        outputArr = IUniswapV2Router(UNISWAP_V2_ROUTER).getAmountsOut(outputJIM,Arr);
        uint outputDAI = outputArr[1];

        this.swap(JIM,DAI,outputJIM);

        uint256 amountOwed = amount + premium; 
        IERC20(asset).approve(address(POOL), amountOwed);
        // END TODO
        return true;
    }

    function getBalance(address _tokenAddress) external view returns (uint256) {
        return IERC20(_tokenAddress).balanceOf(address(this));
    }

    function withdraw(address _tokenAddress) external onlyOwner {
        IERC20 token = IERC20(_tokenAddress);
        token.transfer(msg.sender, token.balanceOf(address(this)));
    }

    modifier onlyOwner() {
        require(
            msg.sender == owner,
            "Only the contract owner can call this function"
        );
        _;
    }

    function swap(address token1, address token2,uint inAmount) public {

        //借貸的DAI數量
        //approve pool can use these token1
        IERC20(token1).approve(address(UNISWAP_V2_ROUTER),inAmount);

        address[] memory path = new address[](2);
        path[0] = token1;
        path[1] = token2;

        //calculate output of token2
        uint[] memory outputArr = IUniswapV2Router(UNISWAP_V2_ROUTER).getAmountsOut(inAmount,path);
        uint output = outputArr[1];
        //swap
        IUniswapV2Router(UNISWAP_V2_ROUTER).swapExactTokensForTokens(inAmount, output, path, msg.sender, block.timestamp);

    }

    receive() external payable {}
}






你可能感興趣的文章

CS50 IP (Internet Protocol)

CS50 IP (Internet Protocol)

[Py 百日馬 Day 4-1] random module 常見方法 - 產生隨機數與亂數取樣/排序

[Py 百日馬 Day 4-1] random module 常見方法 - 產生隨機數與亂數取樣/排序

Lidemy HTTP Challenge

Lidemy HTTP Challenge






留言討論