Vai al contenuto principale

Trasferimenti e approvazione di token ERC-20 da un contratto intelligente in Solidity

contratti intelligenti
token
Solidity
erc-20
Intermedio
jdourlens
7 aprile 2020
7 minuti di lettura

Nel tutorial precedente abbiamo studiato l'anatomia di un token ERC-20 in Solidity sulla blockchain di Ethereum. In questo articolo vedremo come possiamo usare un contratto intelligente per interagire con un token usando il linguaggio Solidity.

Per questo contratto intelligente, creeremo un vero e proprio exchange decentralizzato fittizio in cui un utente può scambiare ether per il nostro token ERC-20 appena distribuito.

Per questo tutorial useremo come base il codice che abbiamo scritto nel tutorial precedente. Il nostro DEX istanzierà un'istanza del contratto nel suo costruttore ed eseguirà le operazioni di:

  • scambio di token in ether
  • scambio di ether in token

Inizieremo il codice del nostro exchange decentralizzato aggiungendo la nostra semplice base di codice 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
Mostra tutto

Il nostro nuovo contratto intelligente del DEX distribuirà l'ERC-20 e otterrà tutta la fornitura:

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}
Mostra tutto

Quindi ora abbiamo il nostro DEX e ha tutta la riserva di token disponibile. Il contratto ha due funzioni:

  • buy: L'utente può inviare ether e ottenere token in cambio
  • sell: L'utente può decidere di inviare token per riavere ether

La funzione buy

Codifichiamo la funzione buy. Per prima cosa dovremo controllare la quantità di ether che il messaggio contiene e verificare che il contratto possieda abbastanza token e che il messaggio contenga degli ether. Se il contratto possiede abbastanza token, invierà il numero di token all'utente ed emetterà l'evento Bought.

Nota che se chiamiamo la funzione require in caso di errore, gli ether inviati verranno direttamente annullati e restituiti all'utente.

Per mantenere le cose semplici, scambiamo semplicemente 1 token per 1 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}

Nel caso in cui l'acquisto vada a buon fine, dovremmo vedere due eventi nella transazione: l'evento Transfer del token e l'evento Bought.

Due eventi nella transazione: Transfer e Bought

La funzione sell

La funzione responsabile della vendita richiederà prima all'utente di aver approvato l'importo chiamando in anticipo la funzione approve. L'approvazione del trasferimento richiede che il token ERC20Basic istanziato dal DEX venga chiamato dall'utente. Questo può essere ottenuto chiamando prima la funzione token() del contratto del DEX per recuperare l'indirizzo in cui il DEX ha distribuito il contratto ERC20Basic chiamato token. Quindi creiamo un'istanza di quel contratto nella nostra sessione e chiamiamo la sua funzione approve. A questo punto siamo in grado di chiamare la funzione sell del DEX e scambiare nuovamente i nostri token per ether. Ad esempio, ecco come appare in una sessione interattiva di brownie:

1# ### Python nella console interattiva di brownie...
2
3# distribuisci il DEX
4dex = DEX.deploy({'from':account1})
5
6# chiama la funzione buy per scambiare ether con token
7# 1e18 è 1 ether denominato in wei
8dex.buy({'from': account2, 1e18})
9
10# ottieni l'indirizzo di distribuzione per il token ERC20
11# che è stato distribuito durante la creazione del contratto DEX
12# dex.token() restituisce l'indirizzo distribuito per il token
13token = ERC20Basic.at(dex.token())
14
15# chiama la funzione approve del token
16# approva l'indirizzo del dex come spender
17# e quanti dei tuoi token è autorizzato a spendere
18token.approve(dex.address, 3e18, {'from':account2})
19
Mostra tutto

Quindi, quando viene chiamata la funzione sell, controlleremo se il trasferimento dall'indirizzo del chiamante all'indirizzo del contratto è andato a buon fine e poi invieremo gli Ether indietro all'indirizzo del chiamante.

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}

Se tutto funziona dovresti vedere 2 eventi (un Transfer e un Sold) nella transazione e il tuo saldo dei token e il saldo degli ether aggiornati.

Due eventi nella transazione: Transfer e Sold

Da questo tutorial abbiamo visto come controllare il saldo e l'allowance di un token ERC-20 e anche come chiamare Transfer e TransferFrom di un contratto intelligente ERC20 usando l'interfaccia.

Una volta effettuata una transazione, abbiamo un tutorial in JavaScript per attendere e ottenere i dettagli sulle transazioni (opens in a new tab) che sono state fatte al tuo contratto e un tutorial per decodificare gli eventi generati dai trasferimenti di token o da qualsiasi altro evento (opens in a new tab) a patto di avere l'ABI.

Ecco il codice completo per il 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, "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 }
96
97 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 }
105
106}
Mostra tutto

Ultimo aggiornamento della pagina: 21 agosto 2025

Questo tutorial è stato utile?