Passer au contenu principal

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

contrats intelligents
jetons
Solidity
erc-20
Intermédiaire
jdourlens
7 avril 2020
7 minutes de lecture

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 verrons 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 véritable échange décentralisé factice où un utilisateur pourra échanger de l'éther contre notre jeton ERC-20 nouvellement 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 suivantes :

  • échanger des jetons contre de l'éther
  • échanger de l'éther contre des jetons

Nous allons commencer le code de notre échange décentralisé en ajoutant notre base de code ERC20 simple :

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

Notre nouveau contrat intelligent DEX déploiera l'ERC-20 et obtiendra toute la réserve de jetons :

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

Nous avons donc maintenant notre DEX et il dispose de toute la réserve de jetons. Le contrat a deux fonctions :

  • buy : L'utilisateur peut envoyer de l'éther et obtenir des jetons en échange
  • sell : L'utilisateur peut envoyer des jetons pour récupérer de l'éther

La fonction buy

Codons la fonction buy. Nous devrons d'abord vérifier la quantité d'éther que le message contient et vérifier que le contrat possède suffisamment de jetons et que le message contient bien de l'éther. Si le contrat possède suffisamment de jetons, il enverra le nombre de jetons à l'utilisateur et émettra l'événement Bought.

Notez que si nous appelons la fonction require, en cas d'erreur, l'éther envoyé sera directement annulé et rendu à l'utilisateur.

Pour simplifier, nous échangeons simplement 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, "Vous devez envoyer de l'éther");
5 require(amountTobuy <= dexBalance, "Pas assez de jetons dans la réserve");
6 token.transfer(msg.sender, amountTobuy);
7 emit Bought(amountTobuy);
8}

Si l'achat réussit, nous devrions voir deux événements dans la transaction : l'événement de jeton Transfer et l'événement Bought.

Deux événements dans la transaction : Transfer et Bought

La fonction sell

La fonction responsable de la vente exigera d'abord que l'utilisateur ait approuvé le montant en appelant au préalable la fonction approve. L'approbation du transfert exige que le jeton ERC20Basic instancié par le DEX soit appelé par l'utilisateur. Cela peut être réalisé en appelant d'abord la fonction token() du contrat DEX pour récupérer l'adresse où le 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 sell du DEX et échanger nos jetons contre de l'éther. Par exemple, voici à quoi cela ressemble dans une session interactive de Brownie :

1#### Python in interactive brownie console...
2
3# déployer le DEX
4dex = DEX.deploy({'from':account1})
5
6# appeler la fonction buy pour échanger de l'éther contre un jeton
7# 1e18 correspond à 1 éther exprimé en wei
8dex.buy({'from': account2, 1e18})
9
10# obtenir l'adresse de déploiement du jeton ERC20
11# qui a été déployé lors de la création du contrat DEX
12# dex.token() renvoie l'adresse déployée pour le jeton
13token = ERC20Basic.at(dex.token())
14
15# appeler la fonction approve du jeton
16# approuver l'adresse du dex en tant que dépensier
17# et le nombre de vos jetons qu'il est autorisé à dépenser
18token.approve(dex.address, 3e18, {'from':account2})
19
Afficher tout

Ensuite, lorsque la fonction sell est appelée, nous vérifierons si le transfert depuis l'adresse de l'appelant vers l'adresse du contrat a réussi, puis nous renverrons l'éther à l'adresse de l'appelant.

1function sell(uint256 amount) public {
2 require(amount > 0, "Vous devez vendre au moins quelques jetons");
3 uint256 allowance = token.allowance(msg.sender, address(this));
4 require(allowance >= amount, "Vérifiez l'allocation de jetons");
5 token.transferFrom(msg.sender, address(this), amount);
6 payable(msg.sender).transfer(amount);
7 emit Sold(amount);
8}

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

Deux événements dans la transaction : Transfer et Sold

Dans ce tutoriel, nous avons vu comment vérifier le solde et l'allocation d'un jeton ERC-20, ainsi que la manière d'appeler Transfer et TransferFrom d'un contrat intelligent ERC20 en utilisant l'interface.

Une fois que vous avez effectué une transaction, nous avons un tutoriel JavaScript pour attendre et obtenir les détails sur les transactions (opens in a new tab) qui ont été effectuées sur votre contrat et un tutoriel pour décoder les événements générés par les transferts de jetons ou tout autre événement (opens in a new tab) tant que vous avez 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, "Vous devez envoyer de l'éther");
92 require(amountTobuy <= dexBalance, "Pas assez de jetons dans la réserve");
93 token.transfer(msg.sender, amountTobuy);
94 emit Bought(amountTobuy);
95 }
96
97 function sell(uint256 amount) public {
98 require(amount > 0, "Vous devez vendre au moins quelques jetons");
99 uint256 allowance = token.allowance(msg.sender, address(this));
100 require(allowance >= amount, "Vérifiez l'allocation de jetons");
101 token.transferFrom(msg.sender, address(this), amount);
102 payable(msg.sender).transfer(amount);
103 emit Sold(amount);
104 }
105
106}
Afficher tout

Dernière mise à jour de la page : 21 août 2025

Ce tutoriel vous a été utile ?