Los Flash Loans son uno de los éxitos más importantes dentro de la corriente de DeFi, y por otro lado, uno de los terminos más escuchados recientemente en los principales foros de Ethereum y demás redes blockchain.
En este artículo vamos a hacer una breve introducción a que son y como funcionan dentro de la red de Ethereum estos flash loans, con un ejemplo sencillo de como sería su funcionamiento con un token ERC20 sobre el que se ejecutaría el préstamo.
Para esta parte implementaremos una serie de smart contract para seguir el flujo de funcionamiento, que se podría resumir en el siguiente gráfico:
Conviene recordar que este es el flujo más básico de como implementar un flash loan, los protocolos en funcionamiento con Aave, utilizan sistemas parecidos, pero con mayor funcionalidad que permite un mayor aprovechamiento del concepto de flash loan.
Es importante recordar que todo esto ocurre en una única transacción, por tanto, si en el momento de invocar el retorno de fondos, estos no pudiesen retornarse, toda la opertiva anterior quedaria revertida y solo se habría consumido el coste de gas de ejecución.
En primer lugar, se crea un simple token ERC20:
pragma solidity ^0.6.0;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol";
contract LendingToken is ERC20 {
constructor(string memory name, string memory symbol, uint256 initialAmount) public ERC20(name, symbol) {
_mint(msg.sender,initialAmount);
}
}
Es un token muy sencillo ERC20 que se utilizará en el contrato de lending. Este contrato tiene dos piezas fundamentales:
Una interfaz de Lend Request que establece que cualquier otro contrato que quiera poder utilizar un préstamo de este, debe tener implementada la interfaz de “tomador”, de esta forma, el contrato de préstamo puede ejecutar el codigo deseado una vez se realiza el prestamo.
Un gestor del token sobre el que se hace el prestamo para poder enviar y recibir la cantidad de tokens necesaria.
El codigo del contrato de lending sería el siguiente:
pragma solidity ^0.6.0;
import "./LendingToken.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol";
interface LendRequester {
function executeThis() external returns(bool);
}
contract Lending {
LendingToken lendingPool;
constructor(string memory name, string memory symbol, uint8 decimals, uint256 initialAmount) public{
lendingPool = new LendingToken(name, symbol, decimals, initialAmount);
}
function faucet(uint256 amount) public {
lendingPool.transfer(msg.sender, amount);
}
function requestLend(uint256 amount) external {
lendingPool.transfer(msg.sender, amount);
lendRequester _req = LendRequester(msg.sender);
_req.executeThis();
uint256 fee = getFee(amount);
lendingPool.transferFrom(msg.sender,address(this),amount + fee);
}
function getLendingTokenAddress() public view returns(address) {
return address(lendingPool);
}
function getFee(uint256 amount) internal returns(uint256) {
return 10;
}
}
Es un contrato que establece un coste sobre cualquier prestamo de 10 unidades y que se debe invocar a la función requestLend para que se ejecute el préstamo.
El contrato utilizado como ejemplo de requester sería el siguiente:
pragma solidity ^0.6.0;
import "./Lending.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol";
interface LendRequester {
function executeThis() external returns(bool);
}
contract LendingTest is LendRequester{
Lending _lending;
IERC20 _token;
uint256 public checkBalanceInLending;
constructor(address _lendingAddress, address _tokenAddress) public{
_lending = Lending(_lendingAddress);
_token = IERC20(_tokenAddress);
}
function executeWithLend(uint256 amount) public {
_lending.faucet(10);
_token.approve(address(_lending),amount+10);
_lending.requestLend(amount);
_token.approve(address(_lending),0);
}
function executeThis() public override returns(bool) {
checkBalanceInLending = _token.balanceOf(address(this));
}
function checkBalance() public view returns(uint256) {
return _token.balanceOf(address(this));
}
}
Donde se ve que existe una función executeWithLend que permite llamar al contrato de leding, solicitando un prestamo que ejecuta lo inlcudo en la función executeThis dentro de la misma transacción.
Si se compilan estos contratos en un entorno como remix, se puede comprobar que el saldo almacenado en checkBalanceInLending es el que tuviera el contrato en el momento inicial (0 en este caso) más las 10 unidades que se solicitan al faucet junto a la cantidad que se le pase para solicitar de forma dinámica.