Перейти к основному содержанию

Переводы и подтверждение токенов ERC-20 из умного контракта Solidity

Умные контракты
токенов
Solidity
erc-20
Intermediate
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 г.

Было ли это руководство полезным?