Crear e implementar una aplicación DeFi
En este tutorial crearemos una aplicación DeFi con Solidity donde los usuarios pueden depositar un token ERC20 en el contrato inteligente y este minteará y transferirá Farm Tokens. Los usuarios pueden retirar más tarde sus tokens ERC20 quemando su Farm Token en un contrato inteligente, y se les devolverán los tokens ERC20.
Instalar Truffle y Ganache
Si es la primera vez que escribe un contrato inteligente, deberá configurar su entorno. Vamos a utilizar dos herramientas: Truffle(opens in a new tab) y Ganache(opens in a new tab).
Truffle es un entorno de desarrollo y marco de pruebas para desarrollar contratos inteligentes para Ethereum. Con Truffle es fácil crear e implementar contratos inteligentes en la cadena de bloques. Ganache nos permite crear una cadena de bloques de Ethereum local para probar contratos inteligentes. Simula las características de la red real y las primeras 10 cuentas cuentan con 100 ether de prueba, lo que hace que la implementación y las pruebas de contratos inteligentes sean gratuitas y fáciles. Ganache está disponible como aplicación de escritorio y herramienta de línea de comandos. Para este artículo usaremos la aplicación de escritorio de UI.
(opens in a new tab)Aplicación de escritorio de UI de Ganache
Para crear el proyecto, ejecute los siguientes comandos:
mkdir your-project-namecd your-project-nametruffle init
Esto creará un proyecto en blanco para el desarrollo e implementación de nuestros contratos inteligentes. La estructura de proyecto creada es la siguiente:
contracts
: Carpeta para los contratos inteligentes de Soliditymigrations
: Carpeta para los scripts de implementacióntest
: Carpeta para probar nuestros contratos inteligentestruffle-config.js
: Archivo de configuración Truffle
Crear el token ERC20
Primero necesitamos crear nuestro token ERC20 que utilizaremos para apostar, o hacer staking, en el contrato inteligente. Para crear nuestro token fungible, primero necesitamos instalar la biblioteca OpenZeppelin. Esta biblioteca contiene las implementaciones de estándares como ERC20 y ERC721. Para instalarla, ejecute el comando:
npm install @openzeppelin/contracts
Utilizando la biblioteca OpenZeppelin podemos crear nuestro token ERC20 escribiendo a contracts/MyToken.sol
con el siguiente código de Solidity:
1pragma solidity ^0.8.0;23import "@openzeppelin/contracts/token/ERC20/ERC20.sol";45contract MyToken is ERC20 {6 constructor() public ERC20("MyToken", "MTKN"){7 _mint(msg.sender, 1000000000000000000000000);8 }9}Mostrar todoCopiar
En el código de arriba en:
Línea 3: Importamos el contrato ERC-20.sol desde openzeppelin que contiene la implementación para este estándar de tokens.
Línea 5: Heredamos desde el contrato ERC-20.sol.
Línea 6: Estamos llamando al contructor ERC20.sol y pasando los parámetros de nombre y símbolo como
"MyToken"
y"MTKN"
, respectivamente.Línea 7: Estamos minteando y transfiriendo 1 millon de tokens para la cuenta que está implementando el contrato inteligente (estamos usando los 18 decimales por defecto del token ERC20; eso significa que, si queremos mintear 1 token, lo representaremos como 1000000000000000000, 1 con 18 ceros).
Debajo podemos ver la implementacion del constructor ERC20.sol, donde el campo _decimals
está establecido en 18:
1string private _name;2string private _symbol;3uint8 private _decimals;45constructor (string memory name_, string memory symbol_) public {6 _name = name_;7 _symbol = symbol_;8 _decimals = 18;9}Mostrar todoCopiar
Compilar el token ERC20
Para compilar nuestro contrato inteligente, primer debemos verificar la versión de nuestro compilador de Solidity. Puede verificarla ejecutando el comando:
truffle version
La versión por defecto es Solidity v0.5.16
. Como nuestro token está escrito usando la versión de Solidity 0.6.2
, si corremos el comando para compilar nuestros contratos, obtendremos un error de compilador. Para especificar qué versión del compilador de Solidity usar, vaya al archivo truffle-config.js
y establezca la versión deseada del compilador como se ve a continuación:
1// Configure your compilers2compilers: {3 solc: {4 version: "^0.8.0", // Fetch exact version from solc-bin (default: truffle's version)5 // docker: true, // Use "0.5.1" you've installed locally with docker (default: false)6 // settings: { // See the solidity docs for advice about optimization and evmVersion7 // optimizer: {8 // enabled: false,9 // runs: 20010 // },11 // evmVersion: "byzantium"12 // }13 }14}Mostrar todo
Ahora podemos compilar nuestro contrato inteligente ejecutando el siguiente comando:
truffle compile
Implementar el token ERC20
Despues de compilar, ahora podemos implementar nuestro token.
En la carpeta migrations
, cree un archivo llamado 2_deploy_Tokens.js
. Este archivo es donde vamos a implementar tanto nuestro token ERC20 como nuestro contrato inteligente FarmToken. El siguiente código se utiliza para implementar nuestro contrato MyToken.sol:
1const MyToken = artifacts.require("MyToken")23module.exports = async function (deployer, network, accounts) {4 // Deploy MyToken5 await deployer.deploy(MyToken)6 const myToken = await MyToken.deployed()7}
Abra Ganache y seleccione la opción "Quickstart" para comenzar una cadena de bloques local de Ethereum. Para implementar nuestro contrato, ejecute:
truffle migrate
La dirección utilizada para implementar nuestros contratos es la primera de la lista de direcciones que Ganache nos muestra. Para verificarlo, podemos abrir la aplicacion de escritorio de Ganache y verificar que el saldo de ether de nuestra primera cuenta haya sido reducido debido al costo de ether para implementar nuestros contratos inteligentes:
(opens in a new tab)Aplicación de escritorio de Ganache
Para verificar que se hayan enviado 1 millón de tokens de MyToken a la dirección del implementador, podemos usar la consola de Truffle para interactuar con nuestro contrato inteligente implementado.
Para poder interactuar con nuestro contrato inteligente, ejecute el siguiente comando:
truffle console
Ahora podemos escribir los siguientes comandos en el terminal:
Obtener el contrato inteligente:
myToken = await MyToken.deployed()
Obtener el array de cuentas de Ganache:
accounts = await web3.eth.getAccounts()
Obtener el saldo de la primera cuenta:
balance = await myToken.balanceOf(accounts[0])
Formatear el saldo a partir de 18 decimales:
web3.utils.fromWei(balance.toString())
Al ejecutar los comandos de arriba, veremos que la primera dirección tiene de hecho 1 millón de MyTokens:
La primera dirección tiene 1000000 MyTokens
Crear un contrato inteligente FarmToken
El contrato inteligente FarmToken tendrá 3 funciones:
balance()
: Obtener el saldo de MyToken en el contrato inteligente FarmToken.deposit(uint256 _amount)
: Transferir MyToken en nombre del usuario al contrato inteligente FarmToken y luego mintear y transferir FarmToken al usuario.withdraw(uint256 _amount)
: Quemar FarmTokens del usuario y transferir MyTokens a la dirección del usuario.
Veamos el constructor de FarmToken:
1pragma solidity ^0.6.2;23import "@openzeppelin/contracts/token/ERC20/IERC20.sol";4import "@openzeppelin/contracts/utils/Address.sol";5import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";6import "@openzeppelin/contracts/token/ERC20/ERC20.sol";78contract FarmToken is ERC20 {9 using Address for address;10 using SafeMath for uint256; // As of Solidity v0.8.0, mathematical operations can be done safely without the need for SafeMath11 using SafeERC20 for IERC20;1213 IERC20 public token;1415 constructor(address _token)16 public17 ERC20("FarmToken", "FRM")18 {19 token = IERC20(_token);20 }Mostrar todoCopiar
Líneas 3-6: Importamos los siguientes contratos de openzeppelin: IERC20.sol, Address.sol, SafeERC20.sol y ERC20.sol.
Línea 8: El FarmToken heredará del contrato ERC20.
Líneas 14-19: El constructor de FarmToken recibirá como parámetro la dirección del contrato MyToken y asignará su contrato a nuestra variable pública llamada
token
.
Vamos a implementar la función balance()
. No recibirá ningún parámetro y devolverá el saldo de MyToken en este contrato inteligente. Se implementa así:
1function balance() public view returns (uint256) {2 return token.balanceOf(address(this));3}Copiar
Para la función deposite(uint256 _amount)
, recibirá como parámetro la cantidad que el usuario quiere depositar y minteará y transferirá FarmTokens al usuario:
1function deposit(uint256 _amount) public {2 // Amount must be greater than zero3 require(_amount > 0, "amount cannot be 0");45 // Transfer MyToken to smart contract6 token.safeTransferFrom(msg.sender, address(this), _amount);78 // Mint FarmToken to msg sender9 _mint(msg.sender, _amount);10}Mostrar todoCopiar
Para la función withdraw(uint256 _amount)
, recibiremos como parámetro la cantidad de FarmTokens que el usuario desea quemar y luego transferir la misma cantidad de MyTokens de vuelta al usuario:
1function withdraw(uint256 _amount) public {2 // Burn FarmTokens from msg sender3 _burn(msg.sender, _amount);45 // Transfer MyTokens from this smart contract to msg sender6 token.safeTransfer(msg.sender, _amount);7}Copiar
Ahora implementaremos nuestro contrato inteligente. Para hacerlo, regresaremos al archivo 2_deploy_Tokens.js
y agregaremos el nuevo contrato que se va a implementar:
1const MyToken = artifacts.require("MyToken")2const FarmToken = artifacts.require("FarmToken")34module.exports = async function (deployer, network, accounts) {5 // Deploy MyToken6 await deployer.deploy(MyToken)7 const myToken = await MyToken.deployed()89 // Deploy Farm Token10 await deployer.deploy(FarmToken, myToken.address)11 const farmToken = await FarmToken.deployed()12}Mostrar todo
Tenga en cuenta que, al implementar FarmToken, pasamos como parámetro la dirección del contrato implementado MyToken.
Ahora, ejecute truffle compile
y truffle migrate
para implementar nuestros contratos.
Probemos nuestro contrato inteligente. En lugar de usar la consola de truffle
para interactuar con nuestro contrato inteligente, crearemos un script para automatizar este proceso. Cree una carpeta llamada scripts
y añada el siguiente archivo getMyTokenBalance.js
. Comprobará el saldo de MyTokens en el contrato inteligente FarmToken:
1const MyToken = artifacts.require("MyToken")2const FarmToken = artifacts.require("FarmToken")34module.exports = async function (callback) {5 myToken = await MyToken.deployed()6 farmToken = await FarmToken.deployed()7 balance = await myToken.balanceOf(farmToken.address)8 console.log(web3.utils.fromWei(balance.toString()))9 callback()10}Mostrar todo
Para ejecutar este script, ejecute el siguiente comando de CLI:
truffle exec .\scripts\getMyTokenBalance.js
Obtendremos el resultado esperado, que es 0. Si recibe un error que indica que el FarmToken aún no se implementó, se debe a que la red de Truffle no ha recibido la última versión del código de su contrato. Cierre Ganache, vuelva a iniciarlo y asegúrese de ejecutar truffle migrate
.
Ahora, vamos a apostar MyToken en el contrato inteligente. Puesto que la función deposit(uint256 _amount)
llama a la función safeTransferFrom
desde el ERC20, el usuario debe aprobar primero el contrato inteligente para transferir MyToken en nombre del usuario. Así que, en el siguiente script, primero aprobaremos este paso y luego llamaremos a la función:
1const MyToken = artifacts.require("MyToken")2const FarmToken = artifacts.require("FarmToken")34module.exports = async function (callback) {5 const accounts = await new web3.eth.getAccounts()6 const myToken = await MyToken.deployed()7 const farmToken = await FarmToken.deployed()89 // Returns the remaining number of tokens that spender will be allowed to spend on behalf of owner through transferFrom.10 // This is zero by default.11 const allowanceBefore = await myToken.allowance(12 accounts[0],13 farmToken.address14 )15 console.log(16 "Amount of MyToken FarmToken is allowed to transfer on our behalf Before: " +17 allowanceBefore.toString()18 )1920 // In order to allow the Smart Contract to transfer to MyToken (ERC-20) on the accounts[0] behalf,21 // we must explicitly allow it.22 // We allow farmToken to transfer x amount of MyToken on our behalf23 await myToken.approve(farmToken.address, web3.utils.toWei("100", "ether"))2425 // Validate that the farmToken can now move x amount of MyToken on our behalf26 const allowanceAfter = await myToken.allowance(accounts[0], farmToken.address)27 console.log(28 "Amount of MyToken FarmToken is allowed to transfer on our behalf After: " +29 allowanceAfter.toString()30 )3132 // Verify accounts[0] and farmToken balance of MyToken before and after the transfer33 balanceMyTokenBeforeAccounts0 = await myToken.balanceOf(accounts[0])34 balanceMyTokenBeforeFarmToken = await myToken.balanceOf(farmToken.address)35 console.log("*** My Token ***")36 console.log(37 "Balance MyToken Before accounts[0] " +38 web3.utils.fromWei(balanceMyTokenBeforeAccounts0.toString())39 )40 console.log(41 "Balance MyToken Before TokenFarm " +42 web3.utils.fromWei(balanceMyTokenBeforeFarmToken.toString())43 )4445 console.log("*** Farm Token ***")46 balanceFarmTokenBeforeAccounts0 = await farmToken.balanceOf(accounts[0])47 balanceFarmTokenBeforeFarmToken = await farmToken.balanceOf(farmToken.address)48 console.log(49 "Balance FarmToken Before accounts[0] " +50 web3.utils.fromWei(balanceFarmTokenBeforeAccounts0.toString())51 )52 console.log(53 "Balance FarmToken Before TokenFarm " +54 web3.utils.fromWei(balanceFarmTokenBeforeFarmToken.toString())55 )56 // Call Deposit function from FarmToken57 console.log("Call Deposit Function")58 await farmToken.deposit(web3.utils.toWei("100", "ether"))59 console.log("*** My Token ***")60 balanceMyTokenAfterAccounts0 = await myToken.balanceOf(accounts[0])61 balanceMyTokenAfterFarmToken = await myToken.balanceOf(farmToken.address)62 console.log(63 "Balance MyToken After accounts[0] " +64 web3.utils.fromWei(balanceMyTokenAfterAccounts0.toString())65 )66 console.log(67 "Balance MyToken After TokenFarm " +68 web3.utils.fromWei(balanceMyTokenAfterFarmToken.toString())69 )7071 console.log("*** Farm Token ***")72 balanceFarmTokenAfterAccounts0 = await farmToken.balanceOf(accounts[0])73 balanceFarmTokenAfterFarmToken = await farmToken.balanceOf(farmToken.address)74 console.log(75 "Balance FarmToken After accounts[0] " +76 web3.utils.fromWei(balanceFarmTokenAfterAccounts0.toString())77 )78 console.log(79 "Balance FarmToken After TokenFarm " +80 web3.utils.fromWei(balanceFarmTokenAfterFarmToken.toString())81 )8283 // End function84 callback()85}Mostrar todo
Para ejecutar este script: truffle exec .\scripts\transferMyTokenToFarmToken.js
. Debería ver en la consola:
salida de transferMyTokenToFarmToken.js
Como podemos ver, hemos depositado exitosamente MyTokens en el contrato inteligente, ya que la primera cuenta ahora tiene FarmTokens.
Para poder retirar:
1const MyToken = artifacts.require("MyToken")2const FarmToken = artifacts.require("FarmToken")34module.exports = async function (callback) {5 const accounts = await new web3.eth.getAccounts()6 const myToken = await MyToken.deployed()7 const farmToken = await FarmToken.deployed()89 // Verify accounts[0] and farmToken balance of MyToken before and after the transfer10 balanceMyTokenBeforeAccounts0 = await myToken.balanceOf(accounts[0])11 balanceMyTokenBeforeFarmToken = await myToken.balanceOf(farmToken.address)12 console.log("*** My Token ***")13 console.log(14 "Balance MyToken Before accounts[0] " +15 web3.utils.fromWei(balanceMyTokenBeforeAccounts0.toString())16 )17 console.log(18 "Balance MyToken Before TokenFarm " +19 web3.utils.fromWei(balanceMyTokenBeforeFarmToken.toString())20 )2122 console.log("*** Farm Token ***")23 balanceFarmTokenBeforeAccounts0 = await farmToken.balanceOf(accounts[0])24 balanceFarmTokenBeforeFarmToken = await farmToken.balanceOf(farmToken.address)25 console.log(26 "Balance FarmToken Before accounts[0] " +27 web3.utils.fromWei(balanceFarmTokenBeforeAccounts0.toString())28 )29 console.log(30 "Balance FarmToken Before TokenFarm " +31 web3.utils.fromWei(balanceFarmTokenBeforeFarmToken.toString())32 )3334 // Call Deposit function from FarmToken35 console.log("Call Withdraw Function")36 await farmToken.withdraw(web3.utils.toWei("100", "ether"))3738 console.log("*** My Token ***")39 balanceMyTokenAfterAccounts0 = await myToken.balanceOf(accounts[0])40 balanceMyTokenAfterFarmToken = await myToken.balanceOf(farmToken.address)41 console.log(42 "Balance MyToken After accounts[0] " +43 web3.utils.fromWei(balanceMyTokenAfterAccounts0.toString())44 )45 console.log(46 "Balance MyToken After TokenFarm " +47 web3.utils.fromWei(balanceMyTokenAfterFarmToken.toString())48 )4950 console.log("*** Farm Token ***")51 balanceFarmTokenAfterAccounts0 = await farmToken.balanceOf(accounts[0])52 balanceFarmTokenAfterFarmToken = await farmToken.balanceOf(farmToken.address)53 console.log(54 "Balance FarmToken After accounts[0] " +55 web3.utils.fromWei(balanceFarmTokenAfterAccounts0.toString())56 )57 console.log(58 "Balance FarmToken After TokenFarm " +59 web3.utils.fromWei(balanceFarmTokenAfterFarmToken.toString())60 )6162 // End function63 callback()64}Mostrar todo
Para ejecutar este script: truffle exec .\scripts\withdrawMyTokenFromTokenFarm.js
. Como podemos ver a continuación, hemos recuperado con éxito los MyTokens y quemado los FarmTokens:
salida de DropMyTokenFromTokenFarm.js
Referencias
Contratos: documentos OpenZeppelin(opens in a new tab)
Sweet Tools for Smart Contracts | Truffle Suite(opens in a new tab)
Ganache | Truffle Suite(opens in a new tab)
¿Qué es DeFi? Guía para principiantes (actualizado 2021) (99bitcoins.com)(opens in a new tab)
DeFi - La tabla de clasificación de finanzas descentralizadas en DeFi Llama(opens in a new tab)
Última edición: @nhsz(opens in a new tab), 15 de agosto de 2023