Přeskočit na hlavní obsah

Převody a schválení tokenů ERC-20 z chytrého kontraktu v Solidity

smart kontrakt účty
tokeny
solidity
erc-20
Středně pokročilý
jdourlens
7. dubna 2020
6 minuta čtení

V předchozím tutoriálu jsme prostudovali anatomii tokenu ERC-20 v Solidity na blockchainu Etherea. V tomto článku si ukážeme, jak můžeme použít chytrý kontrakt k interakci s tokenem pomocí jazyka Solidity.

Pro tento chytrý kontrakt vytvoříme skutečnou zkušební decentralizovanou burzu, kde může uživatel směňovat ether za náš nově nasazený token ERC-20.

Pro tento tutoriál použijeme kód, který jsme napsali v předchozím tutoriálu, jako základ. Naše DEX vytvoří instanci kontraktu ve svém konstruktoru a provede následující operace:

  • směna tokenů za ether
  • směna etheru za tokeny

Začneme s kódem naší decentralizované burzy přidáním naší jednoduché kódové základny 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
Zobrazit vše

Náš nový chytrý kontrakt DEX nasadí ERC-20 a získá všechny dodané:

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}
Zobrazit vše

Takže nyní máme naši DEX a ta má k dispozici celou rezervu tokenů. Kontrakt má dvě funkce:

  • buy: Uživatel může poslat ether a na oplátku dostat tokeny
  • sell: Uživatel se může rozhodnout poslat tokeny, aby dostal zpět ether

Funkce buy

Naprogramujme funkci buy. Nejprve budeme muset zkontrolovat množství etheru, které zpráva obsahuje, a ověřit, že kontrakt vlastní dostatek tokenů a že zpráva obsahuje nějaký ether. Pokud kontrakt vlastní dostatek tokenů, pošle počet tokenů uživateli a vyšle událost Bought.

Všimněte si, že pokud v případě chyby zavoláme funkci require, zaslaný ether bude přímo vrácen a navrácen uživateli.

Pro zjednodušení směníme 1 token za 1 wei.

1function buy() payable public {
2 uint256 amountTobuy = msg.value;
3 uint256 dexBalance = token.balanceOf(address(this));
4 require(amountTobuy > 0, "Musíte poslat nějaký ether");
5 require(amountTobuy <= dexBalance, "Nedostatek tokenů v rezervě");
6 token.transfer(msg.sender, amountTobuy);
7 emit Bought(amountTobuy);
8}

V případě, že je nákup úspěšný, měli bychom v transakci vidět dvě události: událost Transfer tokenu a událost Bought.

Dvě události v transakci: Transfer a Bought

Funkce sell

Funkce zodpovědná za prodej bude nejprve vyžadovat, aby uživatel schválil částku tím, že předem zavolá funkci approve. Schválení převodu vyžaduje, aby uživatel zavolal token ERC20Basic, který byl vytvořen DEXem. Toho lze dosáhnout tím, že nejprve zavoláte funkci token() kontraktu DEX, abyste získali adresu, kam DEX nasadila kontrakt ERC20Basic nazvaný token. Poté v naší relaci vytvoříme instanci tohoto kontraktu a zavoláme jeho funkci approve. Poté jsme schopni zavolat funkci sell DEXu a směnit naše tokeny zpět za ether. Například takhle to vypadá v interaktivní relaci brownie:

1#### Python v interaktivní konzoli brownie...
2
3# nasadit DEX
4dex = DEX.deploy({'from':account1})
5
6# zavolat funkci buy pro směnu etheru za token
7# 1e18 je 1 ether vyjádřený ve wei
8dex.buy({'from': account2, 1e18})
9
10# získat adresu nasazení pro token ERC20
11# který byl nasazen při vytváření kontraktu DEX
12# dex.token() vrátí adresu nasazeného tokenu
13token = ERC20Basic.at(dex.token())
14
15# zavolat funkci approve tokenu
16# schválit adresu dexu jako utrácejícího
17# a kolik vašich tokenů smí utratit
18token.approve(dex.address, 3e18, {'from':account2})
19
Zobrazit vše

Poté, když je zavolána funkce sell, zkontrolujeme, zda byl převod z adresy volajícího na adresu kontraktu úspěšný, a poté pošleme ether zpět na adresu volajícího.

1function sell(uint256 amount) public {
2 require(amount > 0, "Musíte prodat alespoň nějaké tokeny");
3 uint256 allowance = token.allowance(msg.sender, address(this));
4 require(allowance >= amount, "Zkontrolujte povolenou částku tokenu");
5 token.transferFrom(msg.sender, address(this), amount);
6 payable(msg.sender).transfer(amount);
7 emit Sold(amount);
8}

Pokud vše funguje, měli byste v transakci vidět 2 události (Transfer a Sold) a váš zůstatek tokenů a etheru by měl být aktualizován.

Dvě události v transakci: Transfer a Sold

V tomto tutoriálu jsme viděli, jak zkontrolovat zůstatek a povolenou částku tokenu ERC-20 a také jak volat Transfer a TransferFrom chytrého kontraktu ERC20 pomocí rozhraní.

Jakmile provedete transakci, máme JavaScriptový tutoriál, jak počkat a získat podrobnosti o transakcích (opens in a new tab), které byly provedeny na vašem kontraktu, a tutoriál pro dekódování událostí generovaných převody tokenů nebo jinými událostmi (opens in a new tab), pokud máte ABI.

Zde je kompletní kód k tutoriálu:

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, "Musíte poslat nějaký ether");
92 require(amountTobuy <= dexBalance, "Nedostatek tokenů v rezervě");
93 token.transfer(msg.sender, amountTobuy);
94 emit Bought(amountTobuy);
95 }
96
97 function sell(uint256 amount) public {
98 require(amount > 0, "Musíte prodat alespoň nějaké tokeny");
99 uint256 allowance = token.allowance(msg.sender, address(this));
100 require(allowance >= amount, "Zkontrolujte povolenou částku tokenu");
101 token.transferFrom(msg.sender, address(this), amount);
102 payable(msg.sender).transfer(amount);
103 emit Sold(amount);
104 }
105
106}
Zobrazit vše

Stránka naposledy aktualizována: 21. srpna 2025

Byl tento tutoriál užitečný?