Переводы и подтверждение токенов ERC-20 из умного контракта Solidity
В предыдущем руководстве мы изучили анатомию токена 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 // TODO14 }15
16 function sell(uint256 amount) public {17 // TODO18 }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.
Функция sell
Функция, отвечающая за продажу, сначала потребует от пользователя предварительно подтвердить сумму, вызвав функцию approve. Подтверждение перевода требует, чтобы пользователь вызвал контракт токена ERC20Basic, экземпляр которого был создан DEX. Этого можно достичь, сначала вызвав функцию token() контракта DEX, чтобы получить адрес, по которому DEX развернул контракт ERC20Basic, названный token. Затем мы создаем экземпляр этого контракта в нашей сессии и вызываем его функцию approve. Затем мы можем вызвать функцию sell контракта DEX и обменять наши токены обратно на эфир. Например, вот как это выглядит в интерактивной сессии brownie:
1#### Python в интерактивной консоли brownie...2
3# развертываем DEX4dex = DEX.deploy({'from':account1})5
6# вызываем функцию buy для обмена эфира на токен7# 1e18 — это 1 эфир, выраженный в wei8dex.buy({'from': account2, 1e18})9
10# получаем адрес развертывания токена ERC2011# который был развернут во время создания контракта DEX12# 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), а также обновленные балансы токенов и эфира.
Из этого руководства мы узнали, как проверять баланс и разрешенное количество для токена 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}Последнее обновление страницы: 3 марта 2026 г.

