Vai al contenuto principale

Trasferimento e approvazione di token ERC-20 da uno Smart Contract Solidity

smart contracttokensSolidityerc-20
Intermedio
jdourlens
EthereumDev(opens in a new tab)
7 aprile 2020
6 minuti letti minute read
comp-tutorial-metadata-tip-author 0x19dE91Af973F404EDF5B4c093983a7c6E3EC8ccE

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

Per questo contratto intelligente, creeremo una semplicissima piattaforma di scambio decentralizzata, in cui un utente può scambiare ether per il nostro token ERC-20 appena distribuito.

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

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

Inizieremo a scrivere il codice del nostro scambio decentralizzato aggiungendo una 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
Copia

Il nostro nuovo Smart Contract DEX distribuirà ERC-20 e riceverà tutta la disponibilità:

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
Copia

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

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

La funzione buy

Scriviamo la funzione buy. Prima di tutto dovremo controllare l'ammontare di ether che il messaggio contiene e verificare che i contratti abbiano abbastanza token e che il messaggio abbia alcuni ether al suo interno. Se il contratto ha abbastanza token, invierà il numero dei token all'utente ed emetterà l'evento Bought.

Nota: se chiamiamo la funzione require, in caso di errore l'ether inviato sarà ripristinato direttamente e restituito all'utente.

Per semplificare le cose, 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}
Copia

Se l'acquisto va a buon fine, dovremmo vedere due eventi nella transazione: l'evento Transfer per il token e Bought.

Due eventi nella transazione: Transfer e Bought

La funzione sell

La funzione responsabile della vendita implica che l'utente abbia prima approvato l'importo chiamando la funzione approve. Per approvare il trasferimento occorre che il token ERC20Basic istanziato dal DEX sia chiamato dall'utente. È possibile farlo ottenere chiamando prima la funzione token() del contratto DEX per recuperare l'indirizzo in cui DEX ha distribuito il contratto ERC20Basic chiamato token. Creiamo quindi un'istanza di quel contratto nella nostra sessione e chiamiamo la sua funzione approve. Siamo quindi in grado di chiamare la funzione sell della DEX e scambiare nuovamente i nostri token con ether. Ad esempio, ecco come appare in una sessione interattiva di Brownie:

1#### Python in interactive brownie console...
2
3# deploy the DEX
4dex = DEX.deploy({'from':account1})
5
6# call the buy function to swap ether for token
7# 1e18 is 1 ether denominated in wei
8dex.buy({'from': account2, 1e18})
9
10# get the deployment address for the ERC20 token
11# that was deployed during DEX contract creation
12# dex.token() returns the deployed address for token
13token = ERC20Basic.at(dex.token())
14
15# call the token's approve function
16# approve the dex address as spender
17# and how many of your tokens it is allowed to spend
18token.approve(dex.address, 3e18, {'from':account2})
19
Mostra tutto
Copia

Poi, quando viene chiamata la funzione sell, controlliamo se il trasferimento dall'indirizzo del chiamante a quello del contratto è riuscito e restituiamo gli ether 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}
Copia

Se tutto funziona, si dovrebbero vedere 2 eventi (un Trasferimento e Venduto) nella transazione e il saldo di token e di ether aggiornato.

Due eventi nella transazione: Transfer e Sold

In questo tutorial abbiamo visto come controllare il saldo e la disponibilità di un token ERC-20 e come chiamare Transfer e TransferFrom di uno Smart Contract ERC20 usando l'interfaccia.

Una volta creata una transazione, abbiamo un tutorial JavaScript per attendere e ottenere dettagli sulle transazioni(opens in a new tab) eseguite sul contratto e un tutorial per decodificare gli eventi generati dai trasferimenti di token o da altri tipi di eventi(opens in a new tab) se si è in possesso dell'ABI.

Ecco il codice completo del 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
Copia

Ultima modifica: @nhsz(opens in a new tab), 15 agosto 2023

Questo tutorial è stato utile?