¿Estás listo para profundizar en el mundo del arbitraje de préstamo rápido usando Aave y Hardhat? Si estás buscando aprovechar el poder de las finanzas descentralizadas (DeFi) para sacar provecho de las discrepancias de precios, estás en el lugar correcto. En este tutorial paso a paso, te guiaremos por el proceso de configurar y ejecutar el arbitraje de préstamo rápido usando el protocolo de Aave y el entorno de desarrollo de Hardhat.
Prerrequisitos
Antes de embarcarnos en esta vía emocionante, asegúrate de tener los siguientes prerrequisitos:
Entendimiento Sólido de la Blockchain y los Contratos Inteligentes: deberías tener un conocimiento sólido de la tecnología blockchain y cómo los contratos inteligentes funcionan.
Conocimiento Ethereum y de Hardhat: es vital estar familiarizado con el entorno de desarrollo de Hardhat. Si eres nuevo con Hardhat, considera leer primero los documentos oficiales.
Node.js y npm: asegúrate que tienes Node.js y npm (Administrador de Paquetes de Nodo, Node Packcage Manager) instalado en tu máquina.
Ahora que ya tenemos nuestros prerrequisitos arreglados, ¡vamos a comenzar configurando nuestro proyecto y profundizar en el fascinante mundo del arbitraje de préstamo rápido!
Configurando el Proyecto
Paso 1: Inicia un Nuevo Proyecto Hardhat
Abre tu terminal y navega al directorio de tu proyecto. Ejecuta los siguientes comandos:
npm install --save-dev hardhat
npx hardhat
Sigue los prompts para crear un nuevo proyecto Hardhat. Elige la configuración por defecto para simplificar las cosas.
Paso 2: Instala las Dependencias
Necesitamos instalar algunas dependencias adicionales para nuestro proyecto. Abre tu terminal y ejecuta los siguientes 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
Paso 3: Estructura del Proyecto
El directorio de tu proyecto debería tener la siguiente estructura:
- contracts/
- FlashLoanArbitrage.sol
- Dex.sol
- deploy/
- 00-deployDex.js
- 01-deployFlashLoanArbitrage.js
- scripts/
- test/
- hardhat.config.js
- package.json
- README.md
Crea un archivo .env
, añade ambos SEPOLIA_RPC_URL
y PRIVATE_KEY
por sus credenciales como a continuación:
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/....
PRIVATE_KEY=....
Abre hardhat.config.js
y actualízalo con los siguientes detalles:
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: {
// // Si quieres hacer un forking, descomenta este
// 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, // aquí estará por defecto toma la primera cuenta como desplegador
1: 0, // similarmente en la mainnet tomará la primera cuenta como desplegador. Nota que, dependiendo de cómo la red de harhat está configurada, la cuenta 0 en la red puede ser diferente que otra
},
player: {
default: 1,
},
},
solidity: {
compilers: [
{
version: "0.8.7",
},
{
version: "0.8.10",
},
],
},
mocha: {
timeout: 500000, // 500 máximo de segundos para ejecutar las pruebas
},
}
Adicionalmente, crea un nuevo archivo llamado helper-hardhat-config.js
en el directorio raíz y añade los siguientes detalles necesarios, tomando en cuenta que estamos usando la red de pruebas de Sepolia para todas las direcciones guardadas:
-
PoolAddressesProvider
, -
daiAddress
, usdcAddress
Aquí está cómo el archivo debería verse:
const { ethers } = require('hardhat');
const networkConfig = {
default: {
name: 'hardhat',
},
11155111: {
name: 'sepolia',
PoolAddressesProvider: '0x0496275d34753A48320CA58103d5220d394FF77F',
daiAddress:'0x68194a729C2450ad26072b3D33ADaCbcef39D574',
usdcAddress:'0xda9d4f9b69ac6C22e444eD9aF0CfC043b7a7f53f',
},
};
module.exports = {
networkConfig
}
Luego de todos los ajustes que hicimos arriba, aquí está cómo la estructura de nuestro proyecto debería verse:
- 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
Paso 4: Contratos
En este tutorial, estaremos trabajando con dos contratos inteligentes:
-
Dex.sol
: este contrato simula un intercambio descentralizado donde las oportunidades de arbitraje suceden. -
FlashLoanArbitrage.sol
: este contrato maneja los préstamos rápidos y las operaciones de arbitraje.
Vamos a profundizar en estos contratos y entender cada línea de código. Primero, exploraremos 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 cambio
uint256 dexARate = 90;
uint256 dexBRate = 100;
// mantiene el rastreo del saldo dai de los individuos
mapping(address => uint256) public daiBalances;
// mantiene el rastreo del saldo USDC de los individuos
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 {}
}
Lee la Explicación del Contrato Dex:
El contrato Dex.sol
simula un intercambio descentralizado. Profundicemos en las características claves>
- Contrato Dex: el contrato principal define el las variables del almacenamiento por el dueño, las direcciones DAI y USDC y las instancias de IERC20 para DAI y USDC.
- Depósitos Token: las funciones
depositUSDC
ydepositDAI
permite a los usuarios depositar tokens DAI y USDC, actualizando sus saldo. - Intercambio de Token: las funciones
buyDAI
ysellDAI
simulan el intercambio de token, comprando DAI con USDC y vendiendo DAI por USDC basado en las tasas de intercambio. - Rastreo del Saldo: el contrato rastrea los saldos individuales del usuario para los mapeos del DAI y USDC.
- Retiro del Token: la función
withdraw
permite al propietario del contrato retirar tokens del 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;
// dirección del 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 función se llama luego que tu contrato ha recibido la cantidad del préstamo rápido
*/
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address initiator,
bytes calldata params
) external override returns (bool) {
//
// Este contrato ahora tiene los fondos pedidos.
// Tu lógica va aquí.
//
// Operación de arbitraje
dexContract.depositUSDC(1000000000); // 1000 USDC
dexContract.buyDAI();
dexContract.depositDAI(dai.balanceOf(address(this)));
dexContract.sellDAI();
// Al final de la lógica de arriba, este contrato debe
// la cantidad prestada rápidamente y los premiums.
// Por lo tanto, asegúrate que tu contrato tiene suficiente para pagar
// estas cantidades.
// Aprueba la asignación por el contrato Pool para *pujar* la cantidad que se debe
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 {}
}
Lee la Explicación del Contrato FlashLoanArbitrage:
El contrato FlashLoanArbitrage.sol
es el centro de nuestra estrategia de arbitraje. Utiliza los préstamos rápidos de Aave para ejecutar los intercambios rentables. Analicemos los aspectos claves del contrato:
- Importar e Interfaces: importa los contratos requeridos y las interfaces desde Aave y OpenZeppelin. Esto incluye:
FlashLoanSimpleReceiverBase
,IPoolAddressesProvider
yIERC20
. - Interfaz IDex: define la interfaz para el intercambio descentralizado (DEX) donde el arbitraje toma lugar. Métodos como
depositUSDC
,depositDAI
,buyDAI
ysellDAI
son definidos. - Contrato FlashLoanArbitrage: el contrato principal, heredado desde, inicializa las direcciones y contratos para DAI, USDC y el DEX. Implementa la función
executeOperation
que ejecuta la operación de arbitraje, comprando el DAI en una tasa baja y vendiéndola en una mayor tasa. - Ejecución de Préstamo Rápido: la operación de arbitraje se ejecuta dentro de la función
executeOperation
. Los fondos son depositados, el DAI es comprado, depositado y luego vendido. - Pago del Préstamo: el contrato paga la cantidad del préstamo rápido además del premium a Aave, aprobando el contrato Pool para empujar la cantidad que se debe.
- Pedido de Préstamo Rápido: la función
requestFlashLoan
inicia el préstamo rápido llamandoflashLoanSimple
desde el contrato POOL. - Aprobación y Asignación del Token: las funciones como
approveUSDC
,approveDAI
,allowanceUSDC
yallowanceDAI
están incluídos para aprobar los tokens y revisar las asignaciones para el DEX. - Consulta y Retiro del Saldo: la función
getBalance
revisa el saldo del token. La funciónwithdraw
permite al propietario del contrato retirar los tokens.
Paso 4: Despliega los Contratos Inteligentes
Desplegar tus contratos inteligentes es el siguiente paso crucial. Ahora, vamos a ver los script del despliegue.
00-deployDex.js
El script del despliegue para el 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"]
01-deployFlashLoanArbitrage.js
El script de despliegue para el 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"]
Comencemos desplegando el contrato Dex.sol
:
yarn hardhat deploy --tags dex --network sepolia
La dirección del contrato Dex
es “0x81EA031a86EaD3AfbD1F50CF18b0B16394b1c076
”, el cual es añadido al contrato FlashLoanArbitrage
.
Luego, desplegamos FlashLoanArbitrage.sol
, ejecutando el comando de abajo:
yarn hardhat deploy --tags FlashLoanArbitrage --network sepolia
Los outputs FlashLoanArbitrage
de las direcciones del contrato de abajo: “0xc30b671E6d94C62Ee37669b229c7Cd9Eab2f7098
”
Paso 5: Probando los Contratos Inteligentes
Ahora, vamos a enviar un mensaje a los contratos usando Remix IDE, pero para ser más específicos aquí es donde el Arbitraje de Préstamo Rápido:
// índices de intercambo
uint256 dexARate = 90;
uint256 dexBRate = 100;
Aquí compramos 1DAI
en 0.90
y lo vendemos a 100.00
.
Cuando copias el código a Remix IDE, considera cambiar los importes de abajo en ambos contratos:
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";
Añade liquidez a Dex.sol:
USDC 1500
DAI 1500
Aprueba:
USDC 1000000000
DAI 1200000000000000000000
Pedido de Préstamo: USDC (6 decimales):
0xda9d4f9b69ac6C22e444eD9aF0CfC043b7a7f53f,1000000000 // 1,000 USDC
Veamos nuestra transacción en etherscan:
https://miro.medium.com/v2/resize:fit:720/format:webp/1*CjiefK_EUQrrWP6p3EwkOQ.png
Aquí está la explicación de las transiciones:
- Transfiere
1000 USDC
desde el contrato aaveLendingPool
para el contratoFlashLoanArbitrage
, - Deposita
1000 USDC
desde el contratoFlashLoanArbitrage
al contratoDex
, - Compra DAI desde el contrato
Dex
al contratoFlashLoanArbitrage
, - Deposita la cantidad de DAI en el contrato
Dex
, - Transfiere
1,111.1111 USDC
desde el contratoDex
aFlashLoanArbitrage
- Paga
1000 USDC
+0.05%
de la tasa de préstamo rápido (1000.5 USDC
)
Luego de una transacción exitosa, volveremos a revisar nuestro saldo el cual se incrementa a 110.611100 USDC
Repositorio de GitHub: https://github.com/Muhindo-Galien/Aave-Flash-Loan-Arbitrage
Conclusión
¡Felicitaciones! Te has embarcado en un recorrido emocionante para explorar el arbitraje de préstamos rápidos usando Aave y Hardhat. En este tutorial, aprendiste cómo configurar tu proyecto, entender contratos inteligentes, desplegarlos y probarlos usando REMIX IDE. Con tu conocimiento recién ganado, ahora estás equipado para explorar y experimentar más en el mundo de las finanzas descentralizadas. ¡Feliz codeo y descubre las oportunidades de arbitraje!
Este artículo es una traducción de Galien Dev, hecha por Gabriella Martínez. Puedes encontrar el artículo original aquí
Sería genial escucharte en nuestro Discord, puedes contarnos tus ideas, comentarios, sugerencias y dejarnos saber lo que necesitas.
Si prefieres puedes escribirnos a @web3dev_es en Twitter.
Discussion (1)
Me gustaría contactar contigo para desarrollar este proyecto en un servidor de alta frecuencia nuestro. [email protected]