Transferencias y aprobación de tókenes ERC-20 desde un contrato inteligente en Solidity
En el tutorial anterior, se estudió la anatomía de un token ERC-20 en Solidity sobre la cadena de bloques de Ethereum. En este artículo veremos cómo usar un contrato inteligente para la interacción con un token, usando el lenguaje de programación Solidity.
Para este contrato inteligente, crearemos un exchange descentralizado realmente falso donde un usuario pueda intercambiar ether por nuestro recientemente implementado token ERC-20.
Para este tutorial usaremos el código que escribimos en el tutorial anterior como punto de partida. El DEX representará una instancia del contrato en su constructor y perfeccionará las operaciones de:
- intercambio de tókenes por ether
- intercambio de ether por tókenes
Iniciaremos nuestro código de intercambio descentralizado añadiendo un código ERC20 sencillo:
1pragma solidity ^0.8.0;23interface IERC20 {45 function totalSupply() external view returns (uint256);6 function balanceOf(address account) external view returns (uint256);7 function allowance(address owner, address spender) external view returns (uint256);89 function transfer(address recipient, uint256 amount) external returns (bool);10 function approve(address spender, uint256 amount) external returns (bool);11 function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);121314 event Transfer(address indexed from, address indexed to, uint256 value);15 event Approval(address indexed owner, address indexed spender, uint256 value);16}171819contract ERC20Basic is IERC20 {2021 string public constant name = "ERC20Basic";22 string public constant symbol = "ERC";23 uint8 public constant decimals = 18;242526 mapping(address => uint256) balances;2728 mapping(address => mapping (address => uint256)) allowed;2930 uint256 totalSupply_ = 10 ether;313233 constructor() {34 balances[msg.sender] = totalSupply_;35 }3637 function totalSupply() public override view returns (uint256) {38 return totalSupply_;39 }4041 function balanceOf(address tokenOwner) public override view returns (uint256) {42 return balances[tokenOwner];43 }4445 function transfer(address receiver, uint256 numTokens) public override returns (bool) {46 require(numTokens <= balances[msg.sender]);47 balances[msg.sender] = balances[msg.sender]-numTokens;48 balances[receiver] = balances[receiver]+numTokens;49 emit Transfer(msg.sender, receiver, numTokens);50 return true;51 }5253 function approve(address delegate, uint256 numTokens) public override returns (bool) {54 allowed[msg.sender][delegate] = numTokens;55 emit Approval(msg.sender, delegate, numTokens);56 return true;57 }5859 function allowance(address owner, address delegate) public override view returns (uint) {60 return allowed[owner][delegate];61 }6263 function transferFrom(address owner, address buyer, uint256 numTokens) public override returns (bool) {64 require(numTokens <= balances[owner]);65 require(numTokens <= allowed[owner][msg.sender]);6667 balances[owner] = balances[owner]-numTokens;68 allowed[owner][msg.sender] = allowed[owner][msg.sender]-numTokens;69 balances[buyer] = balances[buyer]+numTokens;70 emit Transfer(owner, buyer, numTokens);71 return true;72 }73}7475Mostrar todoCopiar
El nuevo contrato inteligente de DEX desplegará el ERC-20 y obtendrá todo el suministro:
1contract DEX {23 IERC20 public token;45 event Bought(uint256 amount);6 event Sold(uint256 amount);78 constructor() {9 token = new ERC20Basic();10 }1112 function buy() payable public {13 // TODO14 }1516 function sell(uint256 amount) public {17 // TODO18 }1920}Mostrar todoCopiar
Entonces, ahora tenemos nuestro (intercambio descentralizado) y tiene toda la reserva de tókenes disponible. El contrato tiene dos funciones:
buy
(comprar): el usuario puede enviar ether y obtener tókenes a cambio.sell
(vender): el usuario puede optar por enviar tókenes para recuperar el ether.
La función comprar
Codifiquemos la función comprar. Primero se tiene que comprobar la cantidad de ether que contiene el mensaje y verificar que los contratos poseen suficientes tókenes y que el mensaje contiene algún ether. Si el contrato posee suficientes tókenes, enviará el número de tókenes al usuario y emitirá el evento Bought
(comprado).
Hay que tener en cuenta que si se ejecuta la función requerida en caso de error, el ether enviado rebotará y será devuelto directamente al usuario.
Para simplificar las cosas, intercambiamos un token por un Wei.
1function buy() payable public {2 uint256 amountTobuy = msg.value;3 uint256 dexBalance = token.balanceOf(address(this));4 require(amountTobuy > 0, "You need to send some ether");5 require(amountTobuy <= dexBalance, "Not enough tokens in the reserve");6 token.transfer(msg.sender, amountTobuy);7 emit Bought(amountTobuy);8}Copiar
En el caso de que la compra sea exitosa, se deberían ver dos eventos en la transacción: el token Transfer
(transferencia) y el evento Bought
(comprado).
La función vender
La función responsable de la venta requerirá, primero, que el usuario haya aprobado la cantidad llamando a la función de aprobación de antemano. La aprobación de la transferencia requiere que el usuario ejecute el token básico ERC20 instanciado por el DEX. Esto se puede lograr ejecutando primero la función token()
del contrato DEX para recuperar la dirección donde DEX desplegó el contrato ERC20Basic llamado token
. Luego creamos una instancia de este contrato en nuestra sesión y ejecutamos su función de approve
. Una vez realizado esto, podemos lanzar la función sell
de DEX e intercambiar nuestros tókenes por ether. Por ejemplo, este es el aspecto que tiene una sesión de brownie interactiva:
1#### Python in interactive brownie console...23# deploy the DEX4dex = DEX.deploy({'from':account1})56# call the buy function to swap ether for token7# 1e18 is 1 ether denominated in wei8dex.buy({'from': account2, 1e18})910# get the deployment address for the ERC20 token11# that was deployed during DEX contract creation12# dex.token() returns the deployed address for token13token = ERC20Basic.at(dex.token())1415# call the token's approve function16# approve the dex address as spender17# and how many of your tokens it is allowed to spend18token.approve(dex.address, 3e18, {'from':account2})19Mostrar todoCopiar
Al ejecutar la sesión de venta (sell), comprobamos que la transferencia desde la dirección del solicitante a la dirección del contrato se ha realizado con éxito y devolvemos los ethers a la dirección del solicitante.
1function sell(uint256 amount) public {2 require(amount > 0, "You need to sell at least some tokens");3 uint256 allowance = token.allowance(msg.sender, address(this));4 require(allowance >= amount, "Check the token allowance");5 token.transferFrom(msg.sender, address(this), amount);6 payable(msg.sender).transfer(amount);7 emit Sold(amount);8}Copiar
Si todo funciona, debería ver 2 eventos (un Transfer
y Sold
) en la transacción, además de su saldo de tokens y saldo de ether actualizados.
Desde este tutorial se ha visto cómo comprobar el saldo y la autorización de un token ERC, y también cómo ejecutar Transfer
y TransferFrom
de un contrato inteligente ERC20 usando la interfaz.
Una vez realizada esta transacción, tenemos un tutorial de JavaScript para esperar y obtener los detalles de las transacciones(opens in a new tab) que se han hecho a su contrato y un tutorial para descodificar los eventos generados por las transferencias de tókenes o cualquier otro evento(opens in a new tab) siempre y cuando tenga la ABI.
He aquí el código completo para el tutorial:
1pragma solidity ^0.8.0;23interface IERC20 {45 function totalSupply() external view returns (uint256);6 function balanceOf(address account) external view returns (uint256);7 function allowance(address owner, address spender) external view returns (uint256);89 function transfer(address recipient, uint256 amount) external returns (bool);10 function approve(address spender, uint256 amount) external returns (bool);11 function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);121314 event Transfer(address indexed from, address indexed to, uint256 value);15 event Approval(address indexed owner, address indexed spender, uint256 value);16}171819contract ERC20Basic is IERC20 {2021 string public constant name = "ERC20Basic";22 string public constant symbol = "ERC";23 uint8 public constant decimals = 18;242526 mapping(address => uint256) balances;2728 mapping(address => mapping (address => uint256)) allowed;2930 uint256 totalSupply_ = 10 ether;313233 constructor() {34 balances[msg.sender] = totalSupply_;35 }3637 function totalSupply() public override view returns (uint256) {38 return totalSupply_;39 }4041 function balanceOf(address tokenOwner) public override view returns (uint256) {42 return balances[tokenOwner];43 }4445 function transfer(address receiver, uint256 numTokens) public override returns (bool) {46 require(numTokens <= balances[msg.sender]);47 balances[msg.sender] = balances[msg.sender]-numTokens;48 balances[receiver] = balances[receiver]+numTokens;49 emit Transfer(msg.sender, receiver, numTokens);50 return true;51 }5253 function approve(address delegate, uint256 numTokens) public override returns (bool) {54 allowed[msg.sender][delegate] = numTokens;55 emit Approval(msg.sender, delegate, numTokens);56 return true;57 }5859 function allowance(address owner, address delegate) public override view returns (uint) {60 return allowed[owner][delegate];61 }6263 function transferFrom(address owner, address buyer, uint256 numTokens) public override returns (bool) {64 require(numTokens <= balances[owner]);65 require(numTokens <= allowed[owner][msg.sender]);6667 balances[owner] = balances[owner]-numTokens;68 allowed[owner][msg.sender] = allowed[owner][msg.sender]-numTokens;69 balances[buyer] = balances[buyer]+numTokens;70 emit Transfer(owner, buyer, numTokens);71 return true;72 }73}747576contract DEX {7778 event Bought(uint256 amount);79 event Sold(uint256 amount);808182 IERC20 public token;8384 constructor() {85 token = new ERC20Basic();86 }8788 function buy() payable public {89 uint256 amountTobuy = msg.value;90 uint256 dexBalance = token.balanceOf(address(this));91 require(amountTobuy > 0, "You need to send some ether");92 require(amountTobuy <= dexBalance, "Not enough tokens in the reserve");93 token.transfer(msg.sender, amountTobuy);94 emit Bought(amountTobuy);95 }9697 function sell(uint256 amount) public {98 require(amount > 0, "You need to sell at least some tokens");99 uint256 allowance = token.allowance(msg.sender, address(this));100 require(allowance >= amount, "Check the token allowance");101 token.transferFrom(msg.sender, address(this), amount);102 payable(msg.sender).transfer(amount);103 emit Sold(amount);104 }105106}Mostrar todoCopiar
Última edición: @nhsz(opens in a new tab), 15 de agosto de 2023