Transferencias y aprobación de tokens ERC-20 desde un contrato inteligente de Solidity
En el tutorial anterior estudiamos la anatomía de un token ERC-20 en Solidity en la cadena de bloques de Ethereum. En este artículo veremos cómo podemos usar un contrato inteligente para interactuar con un token usando el lenguaje Solidity.
Para este contrato inteligente, crearemos un intercambio descentralizado de prueba en el que un usuario puede intercambiar ether por nuestro token ERC-20 recién implementado.
Para este tutorial, usaremos como base el código que escribimos en el tutorial anterior. Nuestro DEX instanciará una instancia del contrato en su constructor y realizará las siguientes operaciones:
- intercambio de tokens por ether
- intercambio de ether por tokens
Comenzaremos el código de nuestro intercambio descentralizado añadiendo nuestro código base simple de ERC20:
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 todoNuestro nuevo contrato inteligente DEX implementará 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 todoAsí que ahora tenemos nuestro DEX y tiene toda la reserva de tokens disponible. El contrato tiene dos funciones:
buy: el usuario puede enviar ether y obtener tokens a cambio.sell: el usuario puede decidir enviar tokens para recuperar ether.
La función de compra
Vamos a programar la función buy. Primero, tendremos que comprobar la cantidad de ether que contiene el mensaje y verificar que el contrato posea suficientes tokens y que el mensaje tenga algo de ether. Si el contrato posee suficientes tokens, enviará la cantidad de tokens al usuario y emitirá el evento Bought.
Tenga en cuenta que si llamamos a la función require en caso de error, el ether enviado se revertirá directamente y se devolverá al usuario.
Para simplificar, solo intercambiamos 1 token por 1 Wei.
1function buy() payable public {2 uint256 amountTobuy = msg.value;3 uint256 dexBalance = token.balanceOf(address(this));4 require(amountTobuy > 0, "Necesita enviar algo de ether");5 require(amountTobuy <= dexBalance, "No hay suficientes tokens en la reserva");6 token.transfer(msg.sender, amountTobuy);7 emit Bought(amountTobuy);8}Si la compra es exitosa, deberíamos ver dos eventos en la transacción: el evento Transfer del token y el evento Bought.
La función de venta
La función responsable de la venta requerirá primero que el usuario haya aprobado la cantidad llamando previamente a la función approve. La aprobación de la transferencia requiere que el usuario llame al token ERC20Basic instanciado por el DEX. Esto se puede lograr llamando primero a la función token() del contrato DEX para recuperar la dirección donde el DEX implementó el contrato ERC20Basic llamado token. A continuación, creamos una instancia de ese contrato en nuestra sesión y llamamos a su función approve. Entonces podremos llamar a la función sell del DEX e intercambiar nuestros tokens de vuelta por ether. Por ejemplo, así es como se ve en una sesión interactiva de Brownie:
1#### Python en la consola interactiva de Brownie...23# implementar el DEX4dex = DEX.deploy({'from':account1})56# llamar a la función de compra para intercambiar ether por token7# 1e18 es 1 ether denominado en wei8dex.buy({'from': account2, 1e18})910# obtener la dirección de implementación para el token ERC2011# que se implementó durante la creación del contrato DEX12# dex.token() devuelve la dirección implementada para el token13token = ERC20Basic.at(dex.token())1415# llamar a la función de aprobación del token16# aprobar la dirección dex como gastador17# y cuántos de sus tokens se le permite gastar18token.approve(dex.address, 3e18, {'from':account2})19Mostrar todoLuego, cuando se llama a la función sell, comprobaremos si la transferencia desde la dirección de la persona que llama a la dirección del contrato fue exitosa y después enviaremos los ether de vuelta a la dirección de la persona que llama.
1function sell(uint256 amount) public {2 require(amount > 0, "Necesita vender al menos algunos tokens");3 uint256 allowance = token.allowance(msg.sender, address(this));4 require(allowance >= amount, "Compruebe la asignación de tokens");5 token.transferFrom(msg.sender, address(this), amount);6 payable(msg.sender).transfer(amount);7 emit Sold(amount);8}Si todo funciona, debería ver 2 eventos (un Transfer y un Sold) en la transacción, y el saldo de sus tokens y de ether actualizados.
En este tutorial vimos cómo comprobar el saldo y la asignación de un token ERC-20 y también cómo llamar a Transfer y TransferFrom de un contrato inteligente ERC20 utilizando la interfaz.
Una vez que realice una transacción, tenemos un tutorial de JavaScript para esperar y obtener detalles sobre las transaccionesopens in a new tab que se hicieron a su contrato y un tutorial para decodificar los eventos generados por las transferencias de tokens o cualquier otro eventoopens in a new tab siempre que tenga la ABI.
Aquí está 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, "Necesita enviar algo de ether");92 require(amountTobuy <= dexBalance, "No hay suficientes tokens en la reserva");93 token.transfer(msg.sender, amountTobuy);94 emit Bought(amountTobuy);95 }9697 function sell(uint256 amount) public {98 require(amount > 0, "Necesita vender al menos algunos tokens");99 uint256 allowance = token.allowance(msg.sender, address(this));100 require(allowance >= amount, "Compruebe la asignación de tokens");101 token.transferFrom(msg.sender, address(this), amount);102 payable(msg.sender).transfer(amount);103 emit Sold(amount);104 }105106}Mostrar todoÚltima actualización de la página: 21 de agosto de 2025

