Préstamos Flashloans personalizados (ERC-3156)

RAFAEL FUENTES
Blockchain Academy Mexico
7 min readAug 14, 2021

Aprende a hacer préstamos y FLASHLOANS con Aave

Los Flashloans son aquellas operaciones transaccionales para solicitar préstamos sin garantía, utilizados con algún fin y retornarlos dentro de la misma transacción.

Complejidad — Intermedia

Haremos un ejercicio para explicar y experimentar más este complejo concepto, para ello necesitaremos una interfaz estándar 20, (IERC20) de OpenZeppelin, también el ERC-3156, una red ya sea de prueba o mainnet, y algunos Wrappers.

Necesitaremos tener ether en nuestra red, en este caso se utilizará el testnet de Kovan.

Kovan Testnet

ERC-3156

Este ERC especifica interfaces para que los prestamistas acepten solicitudes de préstamos urgentes y para que los prestatarios tomen el control temporal de la transacción dentro de la ejecución del prestamista. También se especifica el proceso para la ejecución segura de préstamos flash.

En el núcleo del contrato ERC 3156, hay dos interfaces principales, llamadas “prestamista” y “receptor”. Tomándolo directamente de la especificación, así es como se ve la interfaz del prestamista.

Interfaz del prestamista

Como podemos ver, la interfaz del prestamista se compone de 3 funciones básicas.

  1. maxFlashLoan: Es la cantidad máxima de tokens que alguien puede tomar en un flashloan. También se utiliza para saber cuándo un token no es compatible o que no tiene liquidez, devolviendo un cero. Proyecta como “recomendación” la cantidad máxima segura a prestar.
  2. flashFee (opcional): Esta devuelve a la persona emisora, la cantidad del fee cobrada por la transacción, se supone que debe pagarse en la token con la que se pidió prestado, si la operación no esta permitida, por alguna u otra razón, la función revertirá.
  3. flashLoan: Esta va a ser la función a la que se llama, incluso, para prestar al contrato, y que tiene la capacidad de usarse en flashloans para apalancamiento, para aumentar o disminuir el apalancamiento. Como requisito, es que este estándar pase una función de devolución de llamada cada vez que llame al flashloan.

Interfaz del prestario

Para que el flashloan tenga éxito, debe aprobar el smart contract de flashloan para gastar el saldo ERC20. Es común en DEFI que los contratos hagan eso, porque esto permite que el código envíe y reciba tokens en su nombre mediante programación. Puesto que el estándar de flashloan es un smart contract, debe, en los tokens ERC20 que desea usar, aprobar manualmente su contrato de flashloan para usar su saldo por usted. Debe existir un saldo suficiente para que el smart contract mueva al nombre del propietario.

Aprobar un smart contract para gastar desde un ERC20

Siempre que se desee que un contrato pueda realizar transacciones utilizando un token de terceros, como un smart contract de préstamo flash que realice préstamos flash utilizando un token ERC20 de su propiedad, debe aprobarlo en el contrato ERC20. Primero echemos un vistazo a cómo funcionaría ese proceso para un contrato que ya se implemento en la red de prueba, en este caso, Uniswap, y USDC.

Esto se puede ver en DEFI, cuando se usa Uniswap. Si aún no aprobó el código, para usar los fondos de su saldo, será necesario que lo haga:

Pero eso solo le permitiría a Uniswap gastar sus tokens ERC20. Si se desea hacerlo manualmente, puede ir al explorador, y verificar la address del smart contract.

Como en el ejemplo que estamos haciendo, en Kovan testnet, estamos usando USDC, y se quiere aprobar el protocolo Compound para acuñar cUSDC de mi saldo de Kovan Testnet, así que nos dirijimos al contrato del token original, donde tenemos un saldo, y verificamos el codigo.

En esta parte podemos ver la función “aprobar”, por defecto para todas las tokens ERC20, que toma 2 parámetros como entrada, “_spender” y “monto”.

_spender es el contrato que queremos permitir para usar nuestro saldo para ese token, y la cantidad es cuánto queremos permitirles gastar en total. Cada vez que el contrato usa su saldo, este número disminuye.

Existe una técnica que le permite “aprobar ilimitadamente”, pero no es realmente ilimitada, es solo un número muy grande, a decir verdad, es el número más grande que permite la EVM (Ethereum Virtual Machine) en decimales, es justo:

115792089237316195423570985008687907853269984665640564039457584007913129639935

Así que guarde eso, y siempre que desee aprobar un contrato para usar un token ERC20 específico de forma indefinida, establezca la asignación en ese colosal número.

Complete el código ERC-3156

Ahora, con las interfaces listas, podemos implementar nuestro propio código. Los estándares ERC definen cuales son las funciones y parámetros para que cada contrato entienda cómo comunicarse entre sí, pero el programador puede cambiar el código final y definitivo, especialmente si / cuando se descubren nuevas vulnerabilidades o mejoras.

Aquí, implementamos el código para la interfaz del prestamista y préstamo que discutimos anteriormente, y luego implementaremos el código de préstamo flash de referencia, todo de una vez para que sea más fácil de entender, sim embargo, si se utiliza Remix, puede, y probablemente debe separarlos en archivos diferentes, aqui, solo las interfaces Prestamistas y Prestario están separadas en el mismo directorio.

pragma solidity ^0.8.0;

import "./IERC3156FlashBorrower.sol";
import "./IERC3156FlashLender.sol";

interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}

//The borrower implementation
contract FlashBorrower is IERC3156FlashBorrower {
enum Action {NORMAL, OTHER}
IERC3156FlashLender lender;
constructor (IERC3156FlashLender lender_) {
lender = lender_;
}

/// @dev ERC-3156 Flash loan callback
function onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes calldata data) external override returns(bool) {
require(msg.sender == address(lender), "FlashBorrower: Untrusted lender");
require(initiator == address(this), "FlashBorrower: Untrusted loan initiator");
(Action action) = abi.decode(data, (Action));
return keccak256("ERC3156FlashBorrower.onFlashLoan");
}

/// @dev Initiate a flash loan
function flashBorrow(address token, uint256 amount) public {
bytes memory data = abi.encode(Action.NORMAL);
uint256 _allowance = IERC20(token).allowance(address(this), address(lender));
uint256 _fee = lender.flashFee(token, amount);
uint256 _repayment = amount + _fee;
IERC20(token).approve(address(lender), _allowance + _repayment);
lender.flashLoan(this, token, amount, data);
}
}

//The Lender implementation
contract FlashLender is IERC3156FlashLender {
bytes32 public constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");
mapping(address => bool) public supportedTokens;
uint256 public fee; // 1 == 0.0001 %.

constructor(address[] memory supportedTokens_, uint256 fee_) {
for (uint256 i = 0; i < supportedTokens_.length; i++) {
supportedTokens[supportedTokens_[i]] = true;
}
fee = fee_;
}

function flashLoan(IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data) external override returns(bool) {
require(supportedTokens[token], "FlashLender: Unsupported currency");
uint256 fee = _flashFee(token, amount);
require(IERC20(token).transfer(address(receiver), amount),"FlashLender: Transfer failed");
require(receiver.onFlashLoan(msg.sender, token, amount, fee, data) == CALLBACK_SUCCESS,"FlashLender: Callback failed");
require(IERC20(token).transferFrom(address(receiver), address(this), amount + fee),"FlashLender: Repay failed");
return true;
}

function flashFee(address token, uint256 amount) external view override returns (uint256) {
require(supportedTokens[token],"FlashLender: Unsupported currency");
return _flashFee(token, amount);
}

function _flashFee(address token,uint256 amount) internal view returns (uint256) {
return amount * fee / 10000;
}

function maxFlashLoan(address token) external view override returns (uint256) {
return supportedTokens[token] ? IERC20(token).balanceOf(address(this)) : 0;
}
}

Usando el FlashLoan smart contract

Se debe usar el flashloan en smart contracts ya implementados, preferiblemente implementaciones con buena liquidez y versiones de testnet, para evitar la liquidación y los altos fees en Ethereum mientras aún está en desarrollo.

En especial smart contracts que adopten el estandar ERC3156 como: Uniswap, Aave y Yield.

Entonces en un smart contract implementado, donde se tomará el préstamo, se llamará a la función flashBorrow.

Este contrato específico permite elegir un contrato de prestamista (el ya implementado), por lo tanto, para los parámetros: 0xeBe2432d4b8C59F33674F6076ddeE8643B8039d1 como contrato de prestamista (aquí puede implementar el suyo). 0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa como el valor de préstamo que se desea.

La parte del flashloan es, que hasta que se quede sin gas, se puede hacer cualquier cosa cuando se recupere el contrato. Aquí se puede ver que al hacer un préstamo flash de DAI, el contrato del préstamo se devolvió y luego se llamó a otro contrato.

Todo sucedió de la función onFlashLoan en el smart contrato del prestario, y ahi es donde se puede persoanlizar qué hacer con el préstamo, cuando se llama a esa función, por ejemplo, enviar esos tokens a otro contrato, que tenga tasas más altas, e incluso podría ser utilizada maliciosamente.

Fuentes:

Para la elaboración de este artículo se utilizaron dos principales fuentes:

Blockdemy

ERC-3156

Rafael Fuentes Rangel

Blockchain Academy México

--

--

RAFAEL FUENTES
Blockchain Academy Mexico

I am blockchain / web3 developer, consultant and researcher. 🚀