Passer au contenu principal

Transferts et approbation des jetons ERC-20 à partir d'un contrat intelligent de solidity

contrats intelligentsjetonssolidityerc-20
Intermédiaire
jdourlens
EthereumDev(opens in a new tab)
7 avril 2020
7 minutes de lecture minute read
comp-tutorial-metadata-tip-author 0x19dE91Af973F404EDF5B4c093983a7c6E3EC8ccE

Dans le tutoriel précédent, nous avons étudié l'anatomie d'un jeton ERC-20 dans Solidity sur la blockchain Ethereum. Dans cet article, nous allons voir comment nous pouvons utiliser un contrat intelligent pour interagir avec un jeton en utilisant le langage Solidity.

Pour ce contrat intelligent, nous allons créer un échange décentralisé très rudimentaire où un utilisateur pourra échanger de l'Ether contre notre nouveau jeton ERC-20 récemment déployé.

Pour ce tutoriel, nous utiliserons le code que nous avons écrit dans le tutoriel précédent comme base. Notre DEX instanciera une instance du contrat dans son constructeur et effectuera les opérations de :

  • échange de jetons en ethers
  • échange d'ethers contre des jetons

Nous allons commencer notre code d'échange décentralisé en ajoutant notre simple base de code 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
Afficher tout
Copier

Notre nouveau contrat intelligent DEX déploiera l'ERC-20 et fera tout le nécessaire :

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}
Afficher tout
Copier

Nous avons à présent notre DEX et il possède toute la réserve de jetons disponibles. Le contrat a deux fonctions :

  • Acheter : L'utilisateur peut envoyer des ethers et obtenir des jetons en échange
  • sell : L'utilisateur peut décider d'envoyer des jetons pour récupérer des ethers

La fonction d'achat

Codons la fonction achat. Nous devrons d'abord vérifier la quantité d'ethers que le message contient et vérifier que les contrats possèdent suffisamment de jetons et que le message contient un peu d'ethers dedans. Si le contrat possède suffisamment de jetons, il enverra le nombre de jetons à l'utilisateur et émettra l'événement Acheté.

Notez que si nous appelons la fonction require dans le cas d'une erreur, l'ether envoyé sera directement restauré et restitué à l'utilisateur.

Pour garder les choses simples, il suffit d'échanger 1 jeton contre 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}
Copier

Dans le cas où l'achat est réussi, nous devrions voir deux événements dans la transaction : le jeton Transfert et l'événement Achat.

Deux événements dans la transaction : Transfert et Achat

La fonction de vente

La fonction responsable de la vente demandera d'abord à l'utilisateur d'avoir approuvé le montant en appelant la fonction d'approbation au préalable. L'approbation du transfert nécessite que le jeton ERC20Basic, instancié par le DEX, soit appelé par l'utilisateur. Ceci peut être réalisé en appelant d'abord la fonction token() du contrat DEX pour récupérer l'adresse où DEX a déployé le contrat ERC20Basic appelé token. Ensuite, nous créons une instance de ce contrat dans notre session et appelons sa fonction approve. Ensuite, nous pouvons appeler la fonction DEX sell et échanger nos jetons contre des ethers. Par exemple, voici à quoi cela ressemble dans une session de navigation interactive :

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
Afficher tout
Copier

Ensuite, lorsque la fonction de vente est appelée, nous vérifierons si le transfert de l’adresse de l’appelant à l’adresse du contrat a été réussi et nous retournerons ensuite les ethers à l’adresse de l’appelant.

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}
Copier

Si tout fonctionne correctement, vous devriez voir 2 événements (un Transfer et Sold) dans la transaction, ainsi que la mise à jour de votre solde de jetons et de votre solde d'Ether.

Deux événements dans la transaction : le transfert et la vente

À partir de ce tutoriel, nous avons vu comment vérifier le solde et la provision d'un jeton ERC-20 et comment appeler Transfer et TransferFrom d'un contrat intelligent ERC20 à l'aide de l'interface.

Une fois que vous avez effectué une transaction, nous avons un tutoriel JavaScript pour attendre et obtenir des détails sur les transactions(opens in a new tab) qui ont été réalisées dans votre contrat et un tutoriel pour décoder les événements générés par des transferts de jetons ou tout autre événement(opens in a new tab) tant que vous disposez de l'ABI.

Voici le code complet du tutoriel :

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}
Afficher tout
Copier

Dernière modification: @nhsz(opens in a new tab), 15 août 2023

Ce tutoriel vous a été utile ?