Арбитраж Aave Flash Loan с использованием Hardhat

Готовы ли вы погрузиться в мир арбитража мгновенных кредитов с помощью Aave и Hardhat? Если вы хотите использовать возможности децентрализованных финансов (DeFi) для получения прибыли от расхождений цен, вы попали в нужное место. В этом пошаговом руководстве мы проведем вас через процесс настройки и выполнения арбитража флэш-кредитов с использованием протокола Aave и среды разработки Hardhat.

Необходимые условия

Прежде чем мы отправимся в это увлекательное путешествие, убедитесь, что у вас есть следующие предварительные условия:

  1. Четкое понимание блокчейна и смарт-контрактов: Вы должны хорошо разбираться в технологии блокчейн и в том, как работают смарт-контракты.
  2. Ethereum и Hardhat Knowledge: Знакомство с Ethereum и средой разработки Hardhat имеет важное значение. Если вы новичок в Hardhat, подумайте о том, чтобы сначала ознакомиться с их официальной документацией.
  3. Node.js и npm: убедитесь, что на вашем компьютере установлены Node.js и npm (диспетчер пакетов узлов).

Теперь, когда мы разобрались с нашими предварительными требованиями, давайте приступим к настройке нашего проекта и погрузимся в увлекательный мир арбитража мгновенных кредитов!

Настройка проекта

Шаг 1: Инициализируйте новый проект Hardhat

Откройте терминал и перейдите в нужный каталог проекта. Выполните следующие команды:npm install —save-dev hardhat
npx hardhat

Следуйте инструкциям, чтобы создать новый проект Hardhat. Для простоты выберите настройки по умолчанию.

Шаг 2: Установка зависимостей

Нам нужно будет установить некоторые дополнительные зависимости для нашего проекта. Откройте терминал и выполните следующие команды: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

Шаг 3: Структура проекта

Теперь каталог проекта должен иметь следующую структуру:- contracts/
— FlashLoanArbitrage.sol
— Dex.sol
— deploy/
— 00-deployDex.js
— 01-deployFlashLoanArbitrage.js
— scripts/
— test/
— hardhat.config.js
— package.json
— README.md

Создайте файл .env, добавьте SEPOLIA_RPC_URL и PRIVATE_KEY с помощью соответствующих учетных данных следующим образом:SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/….
PRIVATE_KEY=….

Откройте hardhat.config.js и обновите его, указав следующие сведения: 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: {
// // If you want to do some forking, uncomment this
// 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, // here this will by default take the first account as deployer
1: 0, // similarly on mainnet it will take the first account as deployer. Note though that depending on how hardhat network are configured, the account 0 on one network can be different than on another
},
player: {
default: 1,
},
},
solidity: {
compilers: [
{
version: «0.8.7»,
},
{
version: «0.8.10»,
},
],
},
mocha: {
timeout: 500000, // 500 seconds max for running tests
},
}

Кроме того, создайте новый файл с именем helper-hardhat-config.js в корневом каталоге и добавьте следующие необходимые сведения, учитывая, что мы используем тестовую сеть Sepolia для всех адресов сохранения:
1. PoolAddressesProvider,
2. daiAddress,
3. Адрес usdcAddress

Вот как должен выглядеть этот файл:const { ethers } = require(‘hardhat’);

const networkConfig = {
default: {
name: ‘hardhat’,
},
11155111: {
name: ‘sepolia’,
PoolAddressesProvider: ‘0x0496275d34753A48320CA58103d5220d394FF77F’,
daiAddress:’0x68194a729C2450ad26072b3D33ADaCbcef39D574′,
usdcAddress:’0xda9d4f9b69ac6C22e444eD9aF0CfC043b7a7f53f’,
},
};

module.exports = {
networkConfig
}

После всех корректировок, сделанных выше, вот как должна выглядеть структура нашего проекта:- 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

Шаг 4: Контракты

В этом уроке мы будем работать с двумя смарт-контрактами:

  1. Dex.sol контракт имитирует децентрализованную биржу, где возникают возможности арбитража.
  2. FlashLoanArbitrage.sol: Этот контракт обрабатывает флэш-кредиты и арбитражные операции.

Давайте углубимся в эти контракты и разберемся в каждой строчке кода. Во-первых, мы рассмотрим 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;

// exchange rate indexes
uint256 dexARate = 90;
uint256 dexBRate = 100;

// keeps track of individuals’ dai balances
mapping(address => uint256) public daiBalances;

// keeps track of individuals’ USDC balances
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, «Check the token allowance»);
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, «Check the token allowance»);
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,
«Only the contract owner can call this function»
);
_;
}

receive() external payable {}
}

Прочтите объяснение контракта Dex:

Контракт Dex.sol имитирует децентрализованную биржу. Давайте углубимся в его ключевые особенности:

  • Контракт Dex: Основной контракт определяет переменные хранения для владельца, адреса DAI и USDC, а также экземпляры IERC20 для DAI и USDC.
  • Депозиты токенов: depositUSDC и depositDAI позволяют пользователям вносить токены USDC и DAI, обновляя свои балансы.
  • Обмен токенами: функции buyDAI имитируют обмен токенами, покупку DAI за USDC и продажу sellDAI за USDC на основе обменных курсов.
  • Отслеживание баланса: контракт отслеживает балансы отдельных пользователей для DAI и USDC с помощью сопоставлений.
  • Вывод токенов: Функция withdraw позволяет владельцу контракта выводить токены из контракта.

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;
// Dex contract address
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);
}

/**
This function is called after your contract has received the flash loaned amount
*/
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address initiator,
bytes calldata params
) external override returns (bool) {
//
// This contract now has the funds requested.
// Your logic goes here.
//

// Arbirtage operation
dexContract.depositUSDC(1000000000); // 1000 USDC
dexContract.buyDAI();
dexContract.depositDAI(dai.balanceOf(address(this)));
dexContract.sellDAI();

// At the end of your logic above, this contract owes
// the flashloaned amount + premiums.
// Therefore ensure your contract has enough to repay
// these amounts.

// Approve the Pool contract allowance to *pull* the owed amount
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,
«Only the contract owner can call this function»
);
_;
}

receive() external payable {}
}

Прочтите объяснение арбитражного контракта FlashLoanArbitrage:

Контракт FlashLoanArbitrage.sol является ядром нашей арбитражной стратегии. Он использует флэш-кредиты Aave для совершения прибыльных сделок. Давайте разберем ключевые аспекты контракта:

  • Импорт и интерфейсы: импорт необходимых контрактов и интерфейсов из Aave и OpenZeppelin. К ним относятся FlashLoanSimpleReceiverBaseIPoolAddressesProvider и IERC20.
  • Интерфейс IDex: Определите интерфейс для децентрализованной биржи (DEX), где происходит арбитраж. Определены такие методы, как depositUSDCdepositDAI buyDAI и sellDAI.
  • Арбитражный контракт FlashLoanArbitrage: основной контракт, наследуемый от, инициализирует адреса и контракты для DAI, USDC и DEX. Он реализует функцию executeOperation, которая выполняет арбитражную операцию, покупая DAI по более низкой ставке и продавая его по более высокой ставке.
  • Flash Loan Execution: Арбитражная операция выполняется в функции executeOperation. Средства вносятся, DAI покупается, депонируется, а затем продается.
  • Погашение кредита: Контракт погашает сумму срочного кредита плюс премию Aave, утверждая контракт пула на получение причитающейся суммы.
  • Flash Loan Request: Функция requestFlashLoan инициирует мгновенный кредит путем вызова flashLoanSimple из контракта POOL.
  • Одобрение токенов и разрешение: Такие функции, как одобрение USDC, approveDAI allowanceUSDC и allowanceDAI, включены для утверждения токенов и проверки разрешений для DEX.approveUSDC
  • Запрос баланса и вывод средств: getBalance проверяет баланс токена. Функция withdraw позволяет владельцу контракта выводить токены.

Шаг 5: Развертывание смарт-контрактов

Развертывание ваших смарт-контрактов является следующим важным шагом. Давайте посмотрим на сценарии развертывания.

00-deployDex.js

Сценарий развертывания для контракта 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»]

01-deployFlashLoanArbitrage.js

Сценарий развертывания контракта 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»]

Начнем с развертывания контракта Dex.sol :yarn hardhat deploy —tags dex —network sepolia

Адрес контракта Dex — «0x81EA031a86EaD3AfbD1F50CF18b0B16394b1c076», который добавляется к контракту FlashLoanArbitrage

Затем мы развертываем FlashLoanArbitrage.sol выполнив следующую команду:yarn hardhat deploy —tags FlashLoanArbitrage —network sepolia

который выводит адрес контракта FlashLoanArbitrage ниже:
«0xc30b671E6d94C62Ee37669b229c7Cd9Eab2f7098«

Шаг 5: Тестирование смарт-контрактов

Давайте теперь напишем контракты с помощью Remix IDE, но для более конкретного примера вот где арбитраж Flash Loan:
// exchange rate indexes
uint256 dexARate = 90;
uint256 dexBRate = 100;

Здесь мы покупаем 1DAI по 0.90 и продаем по 100.00 .
При копировании кода в Remix IDE рассмотрите возможность изменения импорта ниже в обоих контрактах соответствующим образом: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»;

Добавьте ликвидность в Dex.sol:

  1. USDC 1500
  2. DAI 1500

Одобрение:

  1. USDC 1000000000
  2. DAI 1200000000000000000000

Запросить кредит — USDC (6 знаков после запятой):

  1. 0xda9d4f9b69ac6C22e444eD9aF0CfC043b7a7f53f,1000000000 // 1,000 USDC

Давайте посмотрим нашу транзакцию на ehterscan

Вот объяснение трансакций:

  1. Перевести 1000 USDC из контракта aave LendingPool в арбитражный контрактFlashLoanArbitrage,
  2. Внесите 1000 USDC с контракта FlashLoanArbitrage на контракт Dex,
  3. Покупка контракта DAI From Dex на арбитражный контракт FlashLoanArbitrage,
  4. Внесение суммы DAI в контракт Dex,
  5. Перевести 1,111.1111 USDC с контракта Dex на FlashLoanArbitrage
  6. Rapay 1000 USDC 0.05% комиссии за флэш-кредит (1000.5 USDC 1000 USDC)

После успешного переноса мы вернем наш баланс, который увеличится до 110.611100 USDC

Репозиторий GitHub: https://github.com/Muhindo-Galien/Aave-Flash-Loan-Arbitrage

Источник