Ir al contenido principal

Transferencias y aprobación de tokens ERC-20 desde un contrato inteligente de Solidity

contratos Inteligentes
tókenes
Solidity
erc-20
Intermedio
jdourlens
7 de abril de 2020
7 minuto leído

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;
2
3interface IERC20 {
4
5 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);
8
9 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);
12
13
14 event Transfer(address indexed from, address indexed to, uint256 value);
15 event Approval(address indexed owner, address indexed spender, uint256 value);
16}
17
18
19contract ERC20Basic is IERC20 {
20
21 string public constant name = "ERC20Basic";
22 string public constant symbol = "ERC";
23 uint8 public constant decimals = 18;
24
25
26 mapping(address => uint256) balances;
27
28 mapping(address => mapping (address => uint256)) allowed;
29
30 uint256 totalSupply_ = 10 ether;
31
32
33 constructor() {
34 balances[msg.sender] = totalSupply_;
35 }
36
37 function totalSupply() public override view returns (uint256) {
38 return totalSupply_;
39 }
40
41 function balanceOf(address tokenOwner) public override view returns (uint256) {
42 return balances[tokenOwner];
43 }
44
45 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 }
52
53 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 }
58
59 function allowance(address owner, address delegate) public override view returns (uint) {
60 return allowed[owner][delegate];
61 }
62
63 function transferFrom(address owner, address buyer, uint256 numTokens) public override returns (bool) {
64 require(numTokens <= balances[owner]);
65 require(numTokens <= allowed[owner][msg.sender]);
66
67 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}
74
75
Mostrar todo

Nuestro nuevo contrato inteligente DEX implementará el ERC-20 y obtendrá todo el suministro:

1contract DEX {
2
3 IERC20 public token;
4
5 event Bought(uint256 amount);
6 event Sold(uint256 amount);
7
8 constructor() {
9 token = new ERC20Basic();
10 }
11
12 function buy() payable public {
13 // TODO
14 }
15
16 function sell(uint256 amount) public {
17 // TODO
18 }
19
20}
Mostrar todo

Así 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.

Dos eventos en la transacción: Transfer y 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...
2
3# implementar el DEX
4dex = DEX.deploy({'from':account1})
5
6# llamar a la función de compra para intercambiar ether por token
7# 1e18 es 1 ether denominado en wei
8dex.buy({'from': account2, 1e18})
9
10# obtener la dirección de implementación para el token ERC20
11# que se implementó durante la creación del contrato DEX
12# dex.token() devuelve la dirección implementada para el token
13token = ERC20Basic.at(dex.token())
14
15# llamar a la función de aprobación del token
16# aprobar la dirección dex como gastador
17# y cuántos de sus tokens se le permite gastar
18token.approve(dex.address, 3e18, {'from':account2})
19
Mostrar todo

Luego, 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.

Dos eventos en la transacción: Transfer y Sold

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;
2
3interface IERC20 {
4
5 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);
8
9 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);
12
13
14 event Transfer(address indexed from, address indexed to, uint256 value);
15 event Approval(address indexed owner, address indexed spender, uint256 value);
16}
17
18
19contract ERC20Basic is IERC20 {
20
21 string public constant name = "ERC20Basic";
22 string public constant symbol = "ERC";
23 uint8 public constant decimals = 18;
24
25
26 mapping(address => uint256) balances;
27
28 mapping(address => mapping (address => uint256)) allowed;
29
30 uint256 totalSupply_ = 10 ether;
31
32
33 constructor() {
34 balances[msg.sender] = totalSupply_;
35 }
36
37 function totalSupply() public override view returns (uint256) {
38 return totalSupply_;
39 }
40
41 function balanceOf(address tokenOwner) public override view returns (uint256) {
42 return balances[tokenOwner];
43 }
44
45 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 }
52
53 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 }
58
59 function allowance(address owner, address delegate) public override view returns (uint) {
60 return allowed[owner][delegate];
61 }
62
63 function transferFrom(address owner, address buyer, uint256 numTokens) public override returns (bool) {
64 require(numTokens <= balances[owner]);
65 require(numTokens <= allowed[owner][msg.sender]);
66
67 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}
74
75
76contract DEX {
77
78 event Bought(uint256 amount);
79 event Sold(uint256 amount);
80
81
82 IERC20 public token;
83
84 constructor() {
85 token = new ERC20Basic();
86 }
87
88 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 }
96
97 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 }
105
106}
Mostrar todo

Última actualización de la página: 21 de agosto de 2025

¿Le ha resultado útil este tutorial?