Pular para o conteúdo principal

Transferências e aprovação de tokens ERC-20 de um contrato inteligente Solidity

smart contracts
tokens
Solidity
erc-20
Intermediário
jdourlens
7 de abril de 2020
7 minutos de leitura

No tutorial anterior, estudamos a anatomia de um token ERC-20 em Solidity na blockchain da Ethereum. Neste artigo, veremos como podemos usar um contrato inteligente para interagir com um token usando a linguagem Solidity.

Para este contrato inteligente, criaremos uma exchange descentralizada de exemplo, onde um usuário pode trocar ether por nosso token ERC-20 recém-implantado.

Para este tutorial, usaremos o código que escrevemos no tutorial anterior como base. Nossa DEX instanciará uma instância do contrato em seu construtor e realizará as operações de:

  • troca de tokens por ether
  • troca de ether por tokens

Começaremos o código da nossa exchange descentralizada adicionando nossa base de código simples do 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
Exibir tudo

Nosso novo contrato inteligente de DEX implantará o ERC-20 e obterá todo o suprimento:

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 // A FAZER
14 }
15
16 function sell(uint256 amount) public {
17 // A FAZER
18 }
19
20}
Exibir tudo

Então, agora temos nossa DEX e ela tem toda a reserva de tokens disponível. O contrato tem duas funções:

  • buy: O usuário pode enviar ether e obter tokens em troca
  • sell: o usuário pode decidir enviar tokens para recuperar o ether

A função buy

Vamos programar a função de compra. Primeiro, precisaremos verificar a quantidade de ether que a mensagem contém e verificar se o contrato possui tokens suficientes e se a mensagem tem algum ether. Se o contrato possuir tokens suficientes, ele enviará o número de tokens ao usuário e emitirá o evento Bought.

Observe que, se chamarmos a função require em caso de erro, o ether enviado será diretamente revertido e devolvido ao usuário.

Para simplificar, apenas trocamos 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, "Você precisa enviar algum ether");
5 require(amountTobuy <= dexBalance, "Não há tokens suficientes na reserva");
6 token.transfer(msg.sender, amountTobuy);
7 emit Bought(amountTobuy);
8}

Caso a compra seja bem-sucedida, devemos ver dois eventos na transação: o evento Transfer do token e o evento Bought.

Dois eventos na transação: Transferência e Compra

A função sell

A função responsável pela venda exigirá primeiro que o usuário tenha aprovado o valor, chamando a função approve antecipadamente. Aprovar a transferência requer que o token ERC20Basic instanciado pela DEX seja chamado pelo usuário. Isso pode ser alcançado chamando primeiro a função token() do contrato da DEX para recuperar o endereço onde a DEX implantou o contrato ERC20Basic chamado token. Em seguida, criamos uma instância desse contrato em nossa sessão e chamamos sua função approve. Então, podemos chamar a função sell da DEX e trocar nossos tokens de volta por ether. Por exemplo, é assim que isso aparece em uma sessão interativa do brownie:

1#### Python no console interativo do brownie...
2
3# implante a DEX
4dex = DEX.deploy({'from':account1})
5
6# chame a função de compra para trocar ether por token
7# 1e18 é 1 ether, em wei
8dex.buy({'from': account2, 1e18})
9
10# obtenha o endereço de implantação do token ERC20
11# que foi implantado durante a criação do contrato DEX
12# dex.token() retorna o endereço implantado para o token
13token = ERC20Basic.at(dex.token())
14
15# chame a função approve do token
16# aprove o endereço da DEX como gastador
17# e quantos dos seus tokens ele pode gastar
18token.approve(dex.address, 3e18, {'from':account2})
19
Exibir tudo

Então, quando a função sell é chamada, verificamos se a transferência do endereço do chamador para o endereço do contrato foi bem-sucedida e, em seguida, enviamos os Ethers de volta para o endereço do chamador.

1function sell(uint256 amount) public {
2 require(amount > 0, "Você precisa vender pelo menos alguns tokens");
3 uint256 allowance = token.allowance(msg.sender, address(this));
4 require(allowance >= amount, "Verifique a permissão do token");
5 token.transferFrom(msg.sender, address(this), amount);
6 payable(msg.sender).transfer(amount);
7 emit Sold(amount);
8}

Se tudo funcionar, você deverá ver 2 eventos (um Transfer e um Sold) na transação e seus saldos de token e ether atualizados.

Dois eventos na transação: Transferência e Venda

Neste tutorial, vimos como verificar o saldo e a permissão (allowance) de um token ERC-20, e também como chamar Transfer e TransferFrom de um contrato inteligente ERC20 usando a interface.

Assim que você faz uma transação, temos um tutorial de JavaScript para aguardar e obter detalhes sobre as transações (opens in a new tab) que foram feitas para o seu contrato e um tutorial para decodificar eventos gerados por transferências de tokens ou quaisquer outros eventos (opens in a new tab), contanto que você tenha a IAB.

Aqui está o código completo para o 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, "Você precisa enviar algum ether");
92 require(amountTobuy <= dexBalance, "Não há tokens suficientes na reserva");
93 token.transfer(msg.sender, amountTobuy);
94 emit Bought(amountTobuy);
95 }
96
97 function sell(uint256 amount) public {
98 require(amount > 0, "Você precisa vender pelo menos alguns tokens");
99 uint256 allowance = token.allowance(msg.sender, address(this));
100 require(allowance >= amount, "Verifique a permissão do token");
101 token.transferFrom(msg.sender, address(this), amount);
102 payable(msg.sender).transfer(amount);
103 emit Sold(amount);
104 }
105
106}
Exibir tudo

Última atualização da página: 21 de agosto de 2025

Este tutorial foi útil?