Trasferimento e approvazione di token ERC-20 da uno Smart Contract Solidity
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;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 tuttoCopia
Il nostro nuovo Smart Contract DEX distribuirà ERC-20 e riceverà tutta la disponibilità:
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 tuttoCopia
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 cambiosell
: 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
.
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...23# deploy the DEX4dex = DEX.deploy({'from':account1})56# call the buy function to swap ether for token7# 1e18 is 1 ether denominated in wei8dex.buy({'from': account2, 1e18})910# get the deployment address for the ERC20 token11# that was deployed during DEX contract creation12# dex.token() returns the deployed address for token13token = ERC20Basic.at(dex.token())1415# call the token's approve function16# approve the dex address as spender17# and how many of your tokens it is allowed to spend18token.approve(dex.address, 3e18, {'from':account2})19Mostra tuttoCopia
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.
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;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 tuttoCopia
Ultima modifica: @nhsz(opens in a new tab), 15 agosto 2023