Trasferimenti e approvazione di token ERC-20 da un contratto intelligente in Solidity
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;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}7475Mostra tuttoIl nostro nuovo contratto intelligente del DEX distribuirà l'ERC-20 e otterrà tutta la fornitura:
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}Mostra tuttoQuindi 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 cambiosell: 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.
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...23# distribuisci il DEX4dex = DEX.deploy({'from':account1})56# chiama la funzione buy per scambiare ether con token7# 1e18 è 1 ether denominato in wei8dex.buy({'from': account2, 1e18})910# ottieni l'indirizzo di distribuzione per il token ERC2011# che è stato distribuito durante la creazione del contratto DEX12# dex.token() restituisce l'indirizzo distribuito per il token13token = ERC20Basic.at(dex.token())1415# chiama la funzione approve del token16# approva l'indirizzo del dex come spender17# e quanti dei tuoi token è autorizzato a spendere18token.approve(dex.address, 3e18, {'from':account2})19Mostra tuttoQuindi, 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.
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;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, "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 }9697 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 }105106}Mostra tuttoUltimo aggiornamento della pagina: 21 agosto 2025

