Solidity
April 28, 2023

Solidity | Flash Loans Aave

Привет всем! Сегодня пойдет речь о флэш кредитах. Эта тема для меня показалось очень крутой и перспективной, потому что идея очень революционна в своем базисе.

Как работают флэш кредиты?

Flashloans - это моментальный кредит без залога, почти на любую сумму, так еще и можно сказать беспроцентный, ты отдаешь сверху всего 0.05% от той суммы, которую взял.

Это стало возможным благодаря атомарности блокчейна. То есть все транзакции неделимы. Или как написано в википедии. Либо все, либо ничего.

Именно поэтому такие протоколы как Aave могут предложить вам кредит на любую сумму в рамках одного блока.

Конечно есть и минусы. В следствие того, что мы должны взять кредит и отдать в рамках одного блока получается так, что эти деньги не на долго у нас в кармане. Поэтому пока мало идей как использовать flashloan. Чаще всего их используют для арбитража dex.

Ну ладно. Эту информацию можно найти в интернете очень просто, поэтому приступим к написанию смарт-контракта для flashloans!

Если у вас нету node.js то скачиваем тут . Нам понадобиться команда npm от туда.

Устанавливаем последнюю версию npm

npm install -g npm
npm -v

Для начала создаем проект hardhat, я думаю все уже знают как это сделать, а кто нет, то это очень просто.

В своей папки проекта через cmd пишем 4 команды:

npm init
npm install --save-dev hardhat
npx hardhat
npm install --save-dev "hardhat@^2.14.0" "@nomicfoundation/hardhat-toolbox@^2.0.0"

Устанавливаем ядро aave, чтоб подгрузить все СК, которые импортируем

npm install @aave/core-v3

В папке contracts пишем новый смарт-контракт, а тот который предоставлен удаляем. Назовем его SimpleFlashLoan.

// SPDX-License-Identifier: MITpragma solidity ^0.8.9;
import "@aave/core-v3/contracts/flashloan/base/FlashLoanSimpleReceiverBase.sol";
import "@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol";
import "@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol";
contract SimpleFlashLoan is FlashLoanSimpleReceiverBase {    
    address payable owner;    
    string public str;    
    constructor(address _addressProvider) FlashLoanSimpleReceiverBase(IPoolAddressesProvider(_addressProvider)) {        
        owner = payable(msg.sender);    
    }
    function fn_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  executeOperation(        
      address asset,        
      uint256 amount,       
      uint256 premium,        
      address initiator,        
      bytes calldata params    
    )  external override returns (bool) {                
        str = "NEW Logic in FlashLoans!";                
        uint256 totalAmount = amount + premium;        
        IERC20(asset).approve(address(POOL), totalAmount);
        return true;    
    }
    receive() external payable {}
}

Очень простой смарт-контракт из четырех функций.

Переменные смарт-контракта:

owner - владелец СК

str - просто строка

Конструктор:

Наш конструктор будет вызывать конструктор из смарт-контракта FlashLoanSimpleReceiverBase, который мы наследуем. Внутри того конструктора, мы должны передать аргумент, адрес провайдера пула Aave. Где его брать покажу позже.

Переменная _addressProvider внутри FlashLoanSimpleReceiverBase будет обернута в интерфейс IPoolAddressesProvider, после чего там будет вызвана функция getPool(), которая вернет нам адрес Proxy Pool (контракт, который реализует все функции флэш кредитов). И все это сохранится в переменную POOL, которую нужно обернуть в интерфейс IPOOL, как и наш адрес, чтоб получить доступ к функция Proxy Pool контракта.

На самом деле с точки зрения механики этого процесса, это самое сложное тут. Но если не париться, то просто передаем в конструктор адрес провайдера пула.

Функция fn_RequestFlashLoan

Это функция флэш кредита, которую мы будем вызывать.

Как мы помним, наша переменная POOL имеет возможность вызывать функции FlashLoan. Мы будем использовать только flashLoanSimple.

Если посмотреть в интерфейс, то мы увидим какие параметры нам нужно передать:

 function flashLoanSimple(    
  address receiverAddress,    
  address asset,    
  uint256 amount,    
  bytes calldata params,    
  uint16 referralCode  
 ) external;
 

receiverAddress - получатель кредита, в нашем случае наш СК
address receiverAddress = address(this)

asset - по сути адрес токена, который мы хотим взять в долг (у нас это аргумент функции _token)

amount - ну тут понятно, что это количество токенов, которое берем в долг.

params - это какая-то доп информация к кредиту, например сообщение или фраза. В нашем случае пустая строка. (Если будете что то передавать, то не забудьте перевести информацию в байты)

referralCode - я честно так и не понял смысл этого параметра. В интерфейсе написано, что это регистрация интегратора, инициирующего операцию, для потенциального вознаграждения. Я так и не понял как это реализовать, поэтому пишем 0, так как мы будем выполнять операцию сами.

Дальше через нашу любимую переменную POOL из FlashLoanSimpleReceiverBase вызываем функцию flashLoanSimple и передам все параметры.

Функция executeOperation

Данная функция будет сама вызываться после того, как на счет нашего смарт-контракта придут средства.

Эту функцию нужно обязательно реализовать, так как она будет возвращать кредит. Эту функцию мы переопределяем, поэтому ее называем именно так. Без нее при вызове функции fn_RequestFlashLoan будет ошибка.

P.S внутри flashLoanSimple вызывается функция executeFlashLoanSimple в которой есть require, в котором идем проверка на то, что существует и правильно задана функция executeOperation со всеми параметрами. Там это делается очень хитро, поэтому забрать себе все токены не получиться, придется возвращать ((

По факту там такие же переменные как и в fn_RequestFlashLoan, помимо premium, переменная, которая отвечает за процент кредита. На сегодня это 0.05% от суммы.

Внутри функции мы можем производить логику с этими токенами, которые мы взяли в кредит. Для примера я задаю значение строке. Да, это не связано с токенами, но для простоты, я показал где нужно реализовывать логику. Дальше мы считаем токены, которые мы должны вернуть

Да с подсчетом токенов обмануть не получиться, я пробовал

После мы даем approve через интерфейс IERC20, на списание токенов и там они переводятся на адрес Pool proxy.

Хоть 2 последних аргумента мы не используем, они нужны для этой функции, иначе будет ошибка

Ура наш смарт-контракт FlashLoan готов. Давайте его развернем в сети Sepolia и проверим его работоспособность.

Теперь, где же брать адреса пулов и токенов:

Для тестнета тут

Нам нужна вкладка sepolia

PoolAddressesProvider-Aave 0x0496275d34753A48320CA58103d5220d394FF77F

Для маиннета тут

Мой скрипт деплоя:

const hre = require('hardhat');const ethers = hre.ethers;
const path = require('path');
async function main() {  
  const [deployer] = await ethers.getSigners();
  console.log("account deploy:", deployer.address);
  console.log("Account balance:", (await deployer.getBalance()).toString());
  const FlashLoan = await ethers.getContractFactory("SimpleFlashLoan"); //название контракта
  const FlashLoanDeploy = await FlashLoan.deploy("0x0496275d34753A48320CA58103d5220d394FF77F");  
  await FlashLoanDeploy.deployed()  
  console.log("address:", FlashLoanDeploy.address);
}
main()  
.then(() => process.exit(0))  
.catch((error) => {   
   console.error(error)    
 process.exit(1)  
})

Самый обычный скрипт на деплой. Думаю объяснять тут нечего.

Дальше как развернуть в любой сети наш смарт-контракт.

Способов много, я пользуюсь своим:

В качестве провайдера я использую Alchemy.

Регистрируемся и заходим в раздел apps - create app.

Тут выбираем сеть sepolia

Нажимаем create App и видим Api key и все остальное. Копируем HTTPS ссылку

Дальше я покажу мой hardhat.config.js

 require("@nomicfoundation/hardhat-toolbox");
const PRIVATE_KEY = "PRIVATE KEY"
module.exports = {  
  solidity: "0.8.17",  
  networks: {    
    sepolia: {      
      url: "https://eth-sepolia.g.alchemy.com/v2/API KEY",      
      chainId: 11155111,     
      accounts: [        
        PRIVATE_KEY      
      ],    
  },    
    goerli: {      
      url: "https://eth-goerli.g.alchemy.com/v2/API KEY",      
      chainId: 5,      
      accounts: [        
        PRIVATE_KEY      
      ],    
    },    
    main: {      
      url: "https://eth-mainnet.g.alchemy.com/v2/API KEY",      
      chainId: 1,      
      accounts: [        
        PRIVATE_KEY      
      ],    
    },    
    polygon: {      
      url: "https://polygon-mainnet.g.alchemy.com/v2/API KEY",      
      chainId: 137,      
      accounts: [        
        PRIVATE_KEY      
      ],    
    },  
  },
};

Вместо url вставляете скопированную ссылку HTTPS из alchemy.

Там где accounts вы должны вставить свой приватный ключ кошелька.

Где его взять:

Заходите в Metamask и нажимаете на Export Private key.

Там его копируете и в конфиг вставляете.

Все конфиг настрен, деплой скрипт готов.

Команда для запуска скрипта deploy.js в сети sepolia:

Если вы как я меняли deploy.js скрип в проекте hardhat, то вот команда для его запуска:

 npx hardhat run scripts\deploy.js --network sepolia
P.S сеть можно использовать любую

Главное чтоб у вас на аккаунте, у которого вы взяли Private Key были тестовые токены sepolia.

Взять токены можно тут

Если у вас все прошло четко, то ваш смарт-контракт уже в сети.

Заходим во вкладку mempool alchemy

Ваша транзакция в последней строчки или первая сверху

Нажимаете на HASH и потом переходим в transaction

Там будет address TO, переходим и видим наш смарт-контракт в etherscan

Во вкладке contracts нужно верифицировать ваш СК.

Для этого нажимаем на Verify and Publish.

Выбираем single File

Версию компилятора (Та которая у вас стоит в конфиге. У меня 0.8.17)

Выбираем вид лицензии. (Я использую MIT)

Дальше нам нужно вставить код смарт-контракта, но со всеми файлами, которые мы импортировали. Чтоб это сделать быстро и просто заходим в remix. Создаем новый файл и вставляем туда наш смарт-контракт

Тыкаем на наш смарт-контракт и нажимаем на flatten. Таким образом мы получим все смарт-контракты в одном файле Flashloans_flattened.sol в моем случае.

Заходим в него и первой строчкой добавляем лицензию. Он ее не добавляет. Это важно. После берем и все копируем.

Потом все вставляем в etherscane.

Нажимаем на verify and publish и ждем. Если все правильно, то идем обратно в наш смарт-контракт на etherscan

Чтоб затестить наш кредит мы должны передать чуть чуть токенов в наш контракт для комиссии кредита. Я буду использовать dai токены.

Важно. AAVE используют свои токены для тестовых сетей и чтоб их получить идем сюда

У названия токена нажимаем кнопку и добавляем их токен dai в метамаск

Нажимаем на подчеркнутую строчку и получим их токены DAI

1000 токенов получите

ЭТО не настоящий DAI, а аля DAI от AAVE. Их создали для упрощения тестирования и, чтоб в их тестовых пулах было достаточно токенов для всех.

Переходим на смарт-контракт токена dai и переводим чуть чуть токенов (я переведу 1 токен) на наш смарт-контракт flashloan по его адресу.

amount в decimals, поэтому так много нулей.

Когда перевели токены на смарт-контракт flashloans, наконец, мы можем взять кредит!

Возвращаемся в наш смарт-контракт и вызываем функцию fn_RequestFlashLoan

Аргумент _token это адрес токена dai:

dai - 0x68194a729C2450ad26072b3D33ADaCbcef39D574

amount я взял 100 dai

_amount - 1000000000000000000000

После выполнения транзакции идем смотреть ее в etherscan

Если все прошло успешно то мы увидим:

ERC20 Tokens Transferred: На наш смарт-контракт перевели 100 токенов dai и потом их забрали с процентами. Все работает ура!

Если вы зайдете в Read Contract и откроете строчку, то там будет наше сообщение, которое мы установили в функции executeOperation.

На этом думаю можно закончить. У нас получилось реализовать Flashloan, это может не каждый рядовой пользователь как сказали AAVE в своей документации. Так что мы чуть чуть круче. Дальше больше. Возможно потом покажу как можно свапать токены которые мы взяли в кредит. Тоже интересно. Удачи!

tg: мой телеграмчик)

github: этот проект на гит хабе