Перейти до основного контенту

Перекази та схвалення токенів ERC-20 зі смарт-контракту Solidity

Смарт-контракти
токени, лексеми
мова програмування
erc-20
Середнячок
jdourlens
7 квітня 2020 р.
6 читається за хвилину

У попередньому посібнику ми вивчили анатомію токена ERC-20 на Solidity на блокчейні Ethereum. У цій статті ми розглянемо, як можна використовувати смарт-контракт для взаємодії з токеном за допомогою мови Solidity.

Для цього смарт-контракту ми створимо просту демонстраційну децентралізовану біржу, де користувач зможе обмінювати ефір на наш щойно розгорнутий токен ERC-20.

Для цього посібника ми використаємо код, який ми написали в попередньому посібнику, як основу. Наша DEX створить екземпляр контракту у своєму конструкторі та виконуватиме такі операції:

  • обмін токенів на ефір
  • обмін ефіру на токени

Розпочнемо код нашої децентралізованої біржі, додавши нашу просту кодову базу 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
Показати все

Наш новий смарт-контракт DEX розгорне ERC-20 і отримає всю пропозицію токенів:

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}
Показати все

Отже, тепер у нас є наша DEX, і вона має в наявності весь резерв токенів. Контракт має дві функції:

  • buy: користувач може надсилати ефір і отримувати токени в обмін
  • sell: користувач може надіслати токени, щоб отримати ефір назад

Функція buy

Напишемо код для функції buy. Спочатку нам потрібно буде перевірити кількість ефіру, яку містить повідомлення, і переконатися, що контракт володіє достатньою кількістю токенів, а також що повідомлення містить певну кількість ефіру. Якщо контракт володіє достатньою кількістю токенів, він надішле відповідну кількість токенів користувачеві та ініціює подію Bought.

Зверніть увагу, що якщо ми викликаємо функцію require, у разі помилки надісланий ефір буде негайно скасовано, а кошти повернуто користувачеві.

Для простоти ми просто обмінюємо 1 токен на 1 Wei.

1function buy() payable public {
2 uint256 amountTobuy = msg.value;
3 uint256 dexBalance = token.balanceOf(address(this));
4 require(amountTobuy > 0, "Ви повинні надіслати трохи ефіру");
5 require(amountTobuy <= dexBalance, "Недостатньо токенів у резерві");
6 token.transfer(msg.sender, amountTobuy);
7 emit Bought(amountTobuy);
8}

У разі успішної купівлі ми повинні побачити дві події в транзакції: Transfer токена та подію Bought.

Дві події в транзакції: Transfer і Bought

Функція sell

Функція, що відповідає за продаж, спершу вимагатиме, щоб користувач схвалив суму, викликавши заздалегідь функцію approve. Схвалення переказу вимагає, щоб користувач викликав функцію на токені ERC20Basic, екземпляр якого було створено за допомогою DEX. Цього можна досягти, спершу викликавши функцію token() контракту DEX, щоб отримати адресу, за якою DEX розгорнув контракт ERC20Basic під назвою token. Потім ми створюємо екземпляр цього контракту в нашій сесії та викликаємо його функцію approve. Після цього ми зможемо викликати функцію sell контракту DEX і обміняти наші токени назад на ефір. Наприклад, ось як це виглядає в інтерактивній сесії brownie:

1#### Python в інтерактивній консолі brownie...
2
3# розгортаємо DEX
4dex = DEX.deploy({'from':account1})
5
6# викликаємо функцію buy, щоб обміняти ефір на токен
7# 1e18 — це 1 ефір, виражений у wei
8dex.buy({'from': account2, 1e18})
9
10# отримуємо адресу розгортання токена ERC20
11# який було розгорнуто під час створення контракту DEX
12# dex.token() повертає адресу розгорнутого токена
13token = ERC20Basic.at(dex.token())
14
15# викликаємо функцію approve токена
16# схвалюємо адресу dex як витратника
17# і вказуємо, скільки ваших токенів дозволено витратити
18token.approve(dex.address, 3e18, {'from':account2})
19
Показати все

Потім, коли викликається функція sell, ми перевіримо, чи успішним був переказ з адреси викликаючого на адресу контракту, і після цього надішлемо ефір назад на адресу викликаючого.

1function sell(uint256 amount) public {
2 require(amount > 0, "Ви повинні продати хоча б кілька токенів");
3 uint256 allowance = token.allowance(msg.sender, address(this));
4 require(allowance >= amount, "Перевірте дозвіл на використання токенів");
5 token.transferFrom(msg.sender, address(this), amount);
6 payable(msg.sender).transfer(amount);
7 emit Sold(amount);
8}

Якщо все спрацює, ви повинні побачити 2 події (Transfer і Sold) у транзакції, а також оновлення вашого балансу токенів та ефіру.

Дві події в транзакції: Transfer і Sold

З цього посібника ми дізналися, як перевіряти баланс і дозвіл токена ERC-20, а також як викликати Transfer і TransferFrom смарт-контракту ERC20 за допомогою інтерфейсу.

Після того, як ви здійсните транзакцію, у нас є посібник з JavaScript про те, як очікувати та отримувати відомості про транзакції (opens in a new tab), які були зроблені з вашим контрактом, а також посібник із декодування подій, згенерованих передачами токенів або будь-якими іншими подіями (opens in a new tab), за умови, що у вас є ABI.

Ось повний код для цього посібника:

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, "Ви повинні надіслати трохи ефіру");
92 require(amountTobuy <= dexBalance, "Недостатньо токенів у резерві");
93 token.transfer(msg.sender, amountTobuy);
94 emit Bought(amountTobuy);
95 }
96
97 function sell(uint256 amount) public {
98 require(amount > 0, "Ви повинні продати хоча б кілька токенів");
99 uint256 allowance = token.allowance(msg.sender, address(this));
100 require(allowance >= amount, "Перевірте дозвіл на використання токенів");
101 token.transferFrom(msg.sender, address(this), amount);
102 payable(msg.sender).transfer(amount);
103 emit Sold(amount);
104 }
105
106}
Показати все

Останні оновлення сторінки: 21 серпня 2025 р.

Чи була ця інструкція корисною?