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 {} }
Очень простой смарт-контракт из четырех функций.
Наш конструктор будет вызывать конструктор из смарт-контракта FlashLoanSimpleReceiverBase, который мы наследуем. Внутри того конструктора, мы должны передать аргумент, адрес провайдера пула Aave. Где его брать покажу позже.
Переменная _addressProvider внутри FlashLoanSimpleReceiverBase будет обернута в интерфейс IPoolAddressesProvider, после чего там будет вызвана функция getPool(), которая вернет нам адрес Proxy Pool (контракт, который реализует все функции флэш кредитов). И все это сохранится в переменную POOL, которую нужно обернуть в интерфейс IPOOL, как и наш адрес, чтоб получить доступ к функция Proxy Pool контракта.
На самом деле с точки зрения механики этого процесса, это самое сложное тут. Но если не париться, то просто передаем в конструктор адрес провайдера пула.
Это функция флэш кредита, которую мы будем вызывать.
Как мы помним, наша переменная 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 и передам все параметры.
Данная функция будет сама вызываться после того, как на счет нашего смарт-контракта придут средства.
Эту функцию нужно обязательно реализовать, так как она будет возвращать кредит. Эту функцию мы переопределяем, поэтому ее называем именно так. Без нее при вызове функции fn_RequestFlashLoan будет ошибка.
P.S внутри flashLoanSimple вызывается функция executeFlashLoanSimple в которой есть require, в котором идем проверка на то, что существует и правильно задана функция executeOperation со всеми параметрами. Там это делается очень хитро, поэтому забрать себе все токены не получиться, придется возвращать ((
По факту там такие же переменные как и в fn_RequestFlashLoan, помимо premium, переменная, которая отвечает за процент кредита. На сегодня это 0.05% от суммы.
Внутри функции мы можем производить логику с этими токенами, которые мы взяли в кредит. Для примера я задаю значение строке. Да, это не связано с токенами, но для простоты, я показал где нужно реализовывать логику. Дальше мы считаем токены, которые мы должны вернуть
Да с подсчетом токенов обмануть не получиться, я пробовал
После мы даем approve через интерфейс IERC20, на списание токенов и там они переводятся на адрес Pool proxy.
Хоть 2 последних аргумента мы не используем, они нужны для этой функции, иначе будет ошибка
Ура наш смарт-контракт FlashLoan готов. Давайте его развернем в сети 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.
Нажимаем 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.
Версию компилятора (Та которая у вас стоит в конфиге. У меня 0.8.17)
Выбираем вид лицензии. (Я использую MIT)
Дальше нам нужно вставить код смарт-контракта, но со всеми файлами, которые мы импортировали. Чтоб это сделать быстро и просто заходим в remix. Создаем новый файл и вставляем туда наш смарт-контракт
Тыкаем на наш смарт-контракт и нажимаем на flatten. Таким образом мы получим все смарт-контракты в одном файле Flashloans_flattened.sol в моем случае.
Заходим в него и первой строчкой добавляем лицензию. Он ее не добавляет. Это важно. После берем и все копируем.
Потом все вставляем в etherscane.
Нажимаем на verify and publish и ждем. Если все правильно, то идем обратно в наш смарт-контракт на etherscan
Чтоб затестить наш кредит мы должны передать чуть чуть токенов в наш контракт для комиссии кредита. Я буду использовать dai токены.
Важно. AAVE используют свои токены для тестовых сетей и чтоб их получить идем сюда
У названия токена нажимаем кнопку и добавляем их токен dai в метамаск
Нажимаем на подчеркнутую строчку и получим их токены DAI
ЭТО не настоящий DAI, а аля DAI от AAVE. Их создали для упрощения тестирования и, чтоб в их тестовых пулах было достаточно токенов для всех.
Переходим на смарт-контракт токена dai и переводим чуть чуть токенов (я переведу 1 токен) на наш смарт-контракт flashloan по его адресу.
amount в decimals, поэтому так много нулей.
Когда перевели токены на смарт-контракт flashloans, наконец, мы можем взять кредит!
Возвращаемся в наш смарт-контракт и вызываем функцию fn_RequestFlashLoan
Аргумент _token это адрес токена dai:
dai - 0x68194a729C2450ad26072b3D33ADaCbcef39D574
_amount - 1000000000000000000000
После выполнения транзакции идем смотреть ее в etherscan
Если все прошло успешно то мы увидим:
ERC20 Tokens Transferred: На наш смарт-контракт перевели 100 токенов dai и потом их забрали с процентами. Все работает ура!
Если вы зайдете в Read Contract и откроете строчку, то там будет наше сообщение, которое мы установили в функции executeOperation.
На этом думаю можно закончить. У нас получилось реализовать Flashloan, это может не каждый рядовой пользователь как сказали AAVE в своей документации. Так что мы чуть чуть круче. Дальше больше. Возможно потом покажу как можно свапать токены которые мы взяли в кредит. Тоже интересно. Удачи!
tg: мой телеграмчик)
github: этот проект на гит хабе