WEB3DEV

Cover image for Um Guia Completo para Arbitragem de Empréstimo Relâmpago Aave usando o Hardhat
Adriano P. Araujo
Adriano P. Araujo

Posted on

Um Guia Completo para Arbitragem de Empréstimo Relâmpago Aave usando o Hardhat

Você está pronto para mergulhar no mundo da arbitragem de empréstimos relâmpago usando Aave e Hardhat? Se você está procurando alavancar o poder das finanças descentralizadas (DeFi) para lucrar com discrepâncias de preços, você está no lugar certo. Neste tutorial passo a passo, iremos orientá-lo no processo de configuração e execução da arbitragem de empréstimos relâmpago usando o protocolo Aave e o ambiente de desenvolvimento Hardhat.

Pré-requisitos

Antes de embarcarmos nesta emocionante jornada, certifique-se de que possui os seguintes pré-requisitos:

  1. Sólido entendimento de Blockchain e Contratos Inteligentes: Você deve ter um conhecimento sólido da tecnologia blockchain e de como os contratos inteligentes funcionam.

  2. Conhecimento em Ethereum e Hardhat: É essencial estar familiarizado com o Ethereum e o ambiente de desenvolvimento Hardhat. Se você é novo no Hardhat, considere primeiro ler a documentação oficial.

  3. Node.js e npm: Certifique-se de ter o Node.js e o npm (Node Package Manager) instalados em sua máquina.

Agora que temos nossos pré-requisitos em ordem, vamos começar a configurar nosso projeto e mergulhar no fascinante mundo da arbitragem de empréstimos relâmpago!

Configurando o Projeto

Passo 1: Inicialize um Novo Projeto Hardhat

Abra o seu terminal e navegue até o diretório do projeto desejado. Execute os seguintes comandos:


npm install --save-dev hardhat

npx hardhat

Enter fullscreen mode Exit fullscreen mode

Siga as instruções para criar um novo projeto Hardhat. Escolha as configurações padrão para simplificar.

Passo 2: Instalar Dependências

Será necessário instalar algumas dependências adicionais para o nosso projeto. Abra o seu terminal e execute os seguintes comandos:


yarn add --dev @nomiclabs/hardhat-ethers@npm:hardhat-deploy-ethers ethers @nomiclabs/hardhat-etherscan @nomiclabs/hardhat-waffle chai ethereum-waffle hardhat hardhat-contract-sizer hardhat-deploy hardhat-gas-reporter prettier prettier-plugin-solidity solhint solidity-coverage dotenv

yarn add @aave/core-v3

Enter fullscreen mode Exit fullscreen mode

Passo 3: Estrutura do Projeto

O diretório do seu projeto deve agora ter a seguinte estrutura:


- contracts/

  - FlashLoanArbitrage.sol

  - Dex.sol

- deploy/

  - 00-deployDex.js

  - 01-deployFlashLoanArbitrage.js  

- scripts/

- test/

- hardhat.config.js

- package.json

- README.md

Enter fullscreen mode Exit fullscreen mode

Crie um arquivo .env, adicione tanto SEPOLIA_RPC_URL quanto PRIVATE_KEY com suas credenciais apropriadas da seguinte maneira:


SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/....

PRIVATE_KEY=....


Enter fullscreen mode Exit fullscreen mode

Abra o arquivo hardhat.config.js e atualize-o com os detalhes a seguir:


require("@nomiclabs/hardhat-waffle")

require("hardhat-deploy")

require("dotenv").config()



/**

 * @type import('hardhat/config').HardhatUserConfig

 */



const SEPOLIA_RPC_URL =

    process.env.SEPOLIA_RPC_URL || "https://eth-sepolia.g.alchemy.com/v2/YOUR-API-KEY"

const PRIVATE_KEY = process.env.PRIVATE_KEY || "0x"



module.exports = {

    defaultNetwork: "hardhat",

    networks: {

        hardhat: {

            // Se você quiser fazer um fork, descomente isso

            // forking: {

            //   url: MAINNET_RPC_URL

            // }

            chainId: 31337,

        },

        localhost: {

            chainId: 31337,

        },

        sepolia: {

            url: SEPOLIA_RPC_URL,

            accounts: PRIVATE_KEY !== undefined ? [PRIVATE_KEY] : [],

            //   accounts: {

            //     mnemonic: MNEMONIC,

            //   },

            saveDeployments: true,

            chainId: 11155111,

        },

    },

    namedAccounts: {

        deployer: {

            default: 0, // por padrão, este será o primeiro endereço como deployer

            1: 0, // da mesma forma, na mainnet, será o primeiro endereço como deployer. No entanto, observe que, dependendo de como as redes Hardhat estão configuradas, o endereço 0 em uma rede pode ser diferente em relação a outra.

        },

        player: {

            default: 1,

        },

    },

    solidity: {

        compilers: [

            {

                version: "0.8.7",

            },

            {

                version: "0.8.10",

            },

        ],

    },

    mocha: {

        timeout: 500000, // tempo máximo de 500 segundos para execução de testes

    },

}

Enter fullscreen mode Exit fullscreen mode

Além disso, crie um novo arquivo chamado helper-hardhat-config.js no diretório raiz e adicione os seguintes detalhes necessários, lembrando que estamos usando a rede de teste Sepolia para todos os endereços salvos:

  1. PoolAddressesProvider,

  2. daiAddress,

  3. usdcAddress

Aqui está como esse arquivo deve parecer:


const { ethers } = require('hardhat');



const networkConfig = {

  default: {

    name: 'hardhat',

  },

  11155111: {

    name: 'sepolia',

    PoolAddressesProvider: '0x0496275d34753A48320CA58103d5220d394FF77F',

    daiAddress:'0x68194a729C2450ad26072b3D33ADaCbcef39D574',

    usdcAddress:'0xda9d4f9b69ac6C22e444eD9aF0CfC043b7a7f53f',

  },

};



module.exports = {

  networkConfig

}

Enter fullscreen mode Exit fullscreen mode

Após todas as adaptações feitas acima, aqui está como a estrutura do nosso projeto deve parecer:


- contracts/

  - FlashLoanArbitrage.sol

  - Dex.sol

- deploy/

  - 00-deployDex.js

  - 01-deployFlashLoanArbitrage.js  

- scripts/

- test/

-.env

- hardhat.config.js

-helper-hardhat-config

- package.json

- README.md

Enter fullscreen mode Exit fullscreen mode

Passo 4: Contratos

Neste tutorial, estaremos trabalhando com dois contratos inteligentes:

  1. Dex.sol: Este contrato simula uma exchange descentralizada onde ocorrem oportunidades de arbitragem.

  2. FlashLoanArbitrage.sol: Este contrato lida com empréstimos relâmpago e operações de arbitragem.

Vamos analisar esses contratos e entender cada linha de código. Primeiro, vamos explorar o FlashLoanArbitrage.sol.

Dex.sol


// SPDX-License-Identifier: MIT

pragma solidity 0.8.10;



import {IERC20} from "@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol";



contract Dex {

    address payable public owner;



    IERC20 private dai;

    IERC20 private usdc;



    // índices de taxa de câmbio

    uint256 dexARate = 90;

    uint256 dexBRate = 100;



    // mantém o controle dos saldos de DAI dos indivíduos

    mapping(address => uint256) public daiBalances;



    // mantém o controle dos saldos de USDC dos indivíduos

    mapping(address => uint256) public usdcBalances;



    constructor(address  _daiAddress, address _usdcAddress) {

        owner = payable(msg.sender);

        dai = IERC20(_daiAddress);

        usdc = IERC20(_usdcAddress);

    }



    function depositUSDC(uint256 _amount) external {

        usdcBalances[msg.sender] += _amount;

        uint256 allowance = usdc.allowance(msg.sender, address(this));

        require(allowance >= _amount, "Verifique a autorização do token");

        usdc.transferFrom(msg.sender, address(this), _amount);

    }



    function depositDAI(uint256 _amount) external {

        daiBalances[msg.sender] += _amount;

        uint256 allowance = dai.allowance(msg.sender, address(this));

        require(allowance >= _amount, "Verifique a autorização do token");

        dai.transferFrom(msg.sender, address(this), _amount);

    }



    function buyDAI() external {

        uint256 daiToReceive = ((usdcBalances[msg.sender] / dexARate) * 100) *

            (10**12);

        dai.transfer(msg.sender, daiToReceive);

    }



    function sellDAI() external {

        uint256 usdcToReceive = ((daiBalances[msg.sender] * dexBRate) / 100) /

            (10**12);

        usdc.transfer(msg.sender, usdcToReceive);

    }



    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,

            "Somente o proprietário do contrato pode chamar esta função"

        );

        _;

    }



    receive() external payable {}

}

Enter fullscreen mode Exit fullscreen mode

Leia a Explicação do Contrato Dex:

O contrato Dex.sol simula uma bolsa descentralizada. Vamos analisar suas principais características:

  • Contrato Dex: O contrato principal define variáveis de armazenamento para o proprietário, os endereços de DAI e USDC e instâncias de IERC20 para DAI e USDC.

  • Depósitos de Tokens: As funções depositUSDCdepositDAI permitem que os usuários depositem tokens USDC e DAI, atualizando seus saldos.

  • Troca de Tokens: As funções  buyDAIsellDAI simulam trocas de tokens, comprando DAI com USDC e vendendo DAI por USDC com base nas taxas de câmbio.

  • Acompanhamento de Saldo: O contrato acompanha os saldos individuais dos usuários para DAI e USDC com mapeamentos.

  • Retirada de Tokens: A função withdraw permite que o proprietário do contrato retire tokens do contrato.

FlashLoanArbitrage.sol


// SPDX-License-Identifier: MIT

pragma solidity 0.8.10;



import {FlashLoanSimpleReceiverBase} from "@aave/core-v3/contracts/flashloan/base/FlashLoanSimpleReceiverBase.sol";

import {IPoolAddressesProvider} from "@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol";

import {IERC20} from "@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol";



interface IDex {

    function depositUSDC(uint256 _amount) external;



    function depositDAI(uint256 _amount) external;



    function buyDAI() external;



    function sellDAI() external;

}



contract FlashLoanArbitrage is FlashLoanSimpleReceiverBase {

    address payable owner;

    // Endereço do contrato Dex

    address private dexContractAddress =

        0x81EA031a86EaD3AfbD1F50CF18b0B16394b1c076;



    IERC20 private dai;

    IERC20 private usdc;

    IDex private dexContract;



    constructor(address _addressProvider, address _daiAddress, address _usdcAddress)

        FlashLoanSimpleReceiverBase(IPoolAddressesProvider(_addressProvider))

    {

        owner = payable(msg.sender);



        dai = IERC20(_daiAddress);

        usdc = IERC20(_usdcAddress);

        dexContract = IDex(dexContractAddress);

    }



    /**

        Esta função é chamada após o seu contrato receber a quantia emprestada pelo flash loan

     */

    function executeOperation(

        address asset,

        uint256 amount,

        uint256 premium,

        address initiator,

        bytes calldata params

    ) external override returns (bool) {

        //

        // Este contrato agora possui os fundos solicitados.

        // Sua lógica vai aqui.

        //



        // Operação de arbitragem

        dexContract.depositUSDC(1000000000); // 1000 USDC

        dexContract.buyDAI();

        dexContract.depositDAI(dai.balanceOf(address(this)));

        dexContract.sellDAI();



        // No final da sua lógica acima, este contrato deve

        // a quantia emprestada + prêmios.

        // Portanto, garanta que seu contrato tenha o suficiente para pagar

        // essas quantias.



        // Aprova a permissão do contrato da Pool para *retirar* a quantia devida

        uint256 amountOwed = amount + premium;

        IERC20(asset).approve(address(POOL), amountOwed);



        return true;

    }



    function requestFlashLoan(address _token, uint256 _amount) public {

        address receiverAddress = address(this);

        address asset = _token;

        uint256 amount = _amount;

        bytes memory params = "";

        uint16 referralCode = 0;



        POOL.flashLoanSimple(

            receiverAddress,

            asset,

            amount,

            params,

            referralCode

        );

    }



    function approveUSDC(uint256 _amount) external returns (bool) {

        return usdc.approve(dexContractAddress, _amount);

    }



    function allowanceUSDC() external view returns (uint256) {

        return usdc.allowance(address(this), dexContractAddress);

    }



    function approveDAI(uint256 _amount) external returns (bool) {

        return dai.approve(dexContractAddress, _amount);

    }



    function allowanceDAI() external view returns (uint256) {

        return dai.allowance(address(this), dexContractAddress);

    }



    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,

            "Somente o proprietário do contrato pode chamar esta função"

        );

        _;

    }



    receive() external payable {}

}

Enter fullscreen mode Exit fullscreen mode

Leia a Explicação do Contrato FlashLoanArbitrage:

O contrato FlashLoanArbitrage.sol é o núcleo de nossa estratégia de arbitragem. Ele utiliza empréstimos relâmpago da Aave para executar negociações lucrativas. Vamos analisar os principais aspectos do contrato:

  • Imports e Interfaces: Importe os contratos e interfaces necessários da Aave e OpenZeppelin. Isso inclui FlashLoanSimpleReceiverBase, IPoolAddressesProvider e IERC20.

  • Interface IDex: Define a interface para a exchange descentralizada (DEX) onde ocorre a arbitragem. Métodos como depositUSDC, depositDAI, buyDAI e sellDAI são definidos.

  • Contrato FlashLoanArbitrage: O contrato principal, herdando de, inicializa endereços e contratos para DAI, USDC e o DEX. Ele implementa a função executeOperation que executa a operação de arbitragem, comprando DAI a uma taxa mais baixa e vendendo-o a uma taxa mais alta.

  • Execução de Empréstimo Relâmpago: A operação de arbitragem é executada dentro da função executeOperation. Fundos são depositados, DAI é comprado, depositado e depois vendido.

  • Pagamento do Empréstimo: O contrato paga o valor do empréstimo relâmpago mais o prêmio à Aave, aprovando o contrato Pool a retirar a quantia devida.

  • Solicitação de Empréstimo Relâmpago: A função requestFlashLoan inicia o empréstimo relâmpago chamando flashLoanSimple do contrato POOL.

  • Aprovação de Token e Permissão: Funções como approveUSDC, approveDAI, allowanceUSDC e allowanceDAI são incluídas para aprovar tokens e verificar permissões para a DEX.

  • Consulta de Saldo e Retirada: A função getBalance verifica o saldo de um token. A função withdraw permite que o proprietário do contrato retire tokens.

Passo 5: Implantação de Contratos Inteligentes

Implantar seus contratos inteligentes é o próximo passo crucial. Vamos dar uma olhada nos scripts de implantação.

00-deployDex.js

O script de implantação para o contrato Dex.sol:


const { network } = require("hardhat")

const { networkConfig } = require("../helper-hardhat-config")



module.exports = async ({ getNamedAccounts, deployments }) => {

  const { deploy, log } = deployments

  const { deployer } = await getNamedAccounts()

  const chainId = network.config.chainId

  const arguments = [networkConfig[chainId]["daiAddress"],networkConfig[chainId]["usdcAddress"]]



  const dex = await deploy("Dex", {

    from: deployer,

    args: arguments,

    log: true,

  })



  log("Dex contract deployed at : ", dex.address)

}



module.exports.tags = ["all", "dex"]

Enter fullscreen mode Exit fullscreen mode

01-deployFlashLoanArbitrage.js

O script de implantação para o contrato FlashLoanArbitrage.sol:


const { network } = require("hardhat")

const { networkConfig } = require("../helper-hardhat-config")



module.exports = async ({ getNamedAccounts, deployments }) => {

  const { deploy, log } = deployments

  const { deployer } = await getNamedAccounts()

  const chainId = network.config.chainId

  const arguments = [networkConfig[chainId]["PoolAddressesProvider"],networkConfig[chainId]["daiAddress"],networkConfig[chainId]["usdcAddress"]]



  const dex = await deploy("FlashLoanArbitrage", {

    from: deployer,

    args: arguments,

    log: true,

  })



  log("FlashLoanArbitrage contract deployed at : ", dex.address)

}



module.exports.tags = ["all", "FlashLoanArbitrage"]

Enter fullscreen mode Exit fullscreen mode

Vamos começar implantando o contrato Dex.sol:


yarn hardhat deploy --tags dex --network sepolia



Enter fullscreen mode Exit fullscreen mode

O endereço do contrato Dex é "0x81EA031a86EaD3AfbD1F50CF18b0B16394b1c076", que é adicionado ao contrato FlashLoanArbitrage.

Em seguida, implantamos o contrato FlashLoanArbitrage.sol executando o comando abaixo:


yarn hardhat deploy --tags FlashLoanArbitrage --network sepolia

Enter fullscreen mode Exit fullscreen mode

Aqui está o endereço do contrato FlashLoanArbitrage:

"0xc30b671E6d94C62Ee37669b229c7Cd9Eab2f7098"

Passo 5: Testando Contratos Inteligentes

Agora vamos testar os contratos usando o ambiente de desenvolvimento Remix IDE, mas para ser mais específico, aqui está onde a Arbitragem de Empréstimo Relâmpago acontece:




// Índices de Taxa de Câmbio

uint256 dexARate = 90;

uint256 dexBRate = 100;



Enter fullscreen mode Exit fullscreen mode

Aqui, compramos **1 DAI** a **0.90** e o vendemos a 100,00. Quando copiar o código para o Remix IDE, considere alterar as importações abaixo em ambos os contratos de acordo:


import {IERC20} from "@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol";

import {FlashLoanSimpleReceiverBase} from "https://github.com/aave/aave-v3-core/blob/master/contracts/flashloan/base/FlashLoanSimpleReceiver.sol";

import {IPoolAddressesProvider} from "https://github.com/aave/aave-v3-core/blob/master/contracts/interfaces/IPoolAddressesProvider.sol";

Enter fullscreen mode Exit fullscreen mode

Adicionando liquidez ao contrato Dex.sol:

  1. **USDC 1500**

  2. **DAI 1500**

Aprovação

  1. **USDC 1000000000**

  2. **DAI 1200000000000000000000**


Solicitação de Empréstimo — USDC (6 casas decimais):

  1. **0xda9d4f9b69ac6C22e444eD9aF0CfC043b7a7f53f, 1000000000 // 1.000 USDC**

Vamos visualizar nossa transação no Etherscan.

Aqui está a explicação das transações:

  1. Transferência de **1000 USDC** do contrato Aave LendingPool para o contrato FlashLoanArbitrage.

  2. Depósito de **1000 USDC** do contrato FlashLoanArbitrage no contrato Dex.

  3. Compra de DAI do contrato Dex para o contrato FlashLoanArbitrage.

  4. Depósito da quantia de DAI no contrato Dex.

  5. Transferência de **1.111,1111 USDC** do contrato Dex para o contrato FlashLoanArbitrage.

  6. Pagamento de **1000 USDC** + **0,05%** da taxa de empréstimo relâmpago (**1000,5 USDC**).

 Após uma transação bem-sucedida, verificaremos nosso saldo, que aumenta para **110,611100 USDC**.

Repositório do GitHub: https://github.com/Muhindo-Galien/Aave-Flash-Loan-Arbitrage

Conclusão

Parabéns! Você embarcou em uma jornada emocionante para explorar a arbitragem de empréstimos relâmpago usando Aave e Hardhat. Neste tutorial, você aprendeu a configurar seu projeto, entender contratos inteligentes, implantá-los e testá-los usando o REMIX IDE. Com seu conhecimento recém-adquirido, agora você está preparado para explorar e experimentar mais no mundo das finanças descentralizadas. Feliz codificação e descoberta de oportunidades de arbitragem!


Este artigo foi escrito por Galien Dev e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.

Top comments (0)