Переводы и подтверждение токенов ERC-20 из умного контракта Solidity
В предыдущем руководстве мы изучили анатомию токена ERC-20 в Solidity в блокчейне Ethereum. В этой статье мы рассмотрим, как можно использовать умный контракт для взаимодействия с токеном с помощью языка Solidity.
Для этого умного контракта мы создадим настоящую учебную децентрализованную биржу, где пользователь сможет обменять эфир на наш недавно развернутый токен ERC-20.
Для этого руководства мы будем использовать в качестве основы код, который мы написали в предыдущем руководстве. Наш DEX будет создавать экземпляр контракта в своем конструкторе и выполнять следующие операции:
- обмен токенов на эфир
- обмен эфира на токены
Мы начнем писать код нашей децентрализованной биржи, добавив нашу простую кодовую базу ERC20:
1pragma solidity ^0.8.0;23interface IERC20 {45 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);89 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);121314 event Transfer(address indexed from, address indexed to, uint256 value);15 event Approval(address indexed owner, address indexed spender, uint256 value);16}171819contract ERC20Basic is IERC20 {2021 string public constant name = "ERC20Basic";22 string public constant symbol = "ERC";23 uint8 public constant decimals = 18;242526 mapping(address => uint256) balances;2728 mapping(address => mapping (address => uint256)) allowed;2930 uint256 totalSupply_ = 10 ether;313233 constructor() {34 balances[msg.sender] = totalSupply_;35 }3637 function totalSupply() public override view returns (uint256) {38 return totalSupply_;39 }4041 function balanceOf(address tokenOwner) public override view returns (uint256) {42 return balances[tokenOwner];43 }4445 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 }5253 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 }5859 function allowance(address owner, address delegate) public override view returns (uint) {60 return allowed[owner][delegate];61 }6263 function transferFrom(address owner, address buyer, uint256 numTokens) public override returns (bool) {64 require(numTokens <= balances[owner]);65 require(numTokens <= allowed[owner][msg.sender]);6667 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}7475Показать всеНаш новый умный контракт DEX развернет ERC-20 и получит все созданные токены:
1contract DEX {23 IERC20 public token;45 event Bought(uint256 amount);6 event Sold(uint256 amount);78 constructor() {9 token = new ERC20Basic();10 }1112 function buy() payable public {13 // TODO14 }1516 function sell(uint256 amount) public {17 // TODO18 }1920}Показать всеИтак, теперь у нас есть наш 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...23# развертываем DEX4dex = DEX.deploy({'from':account1})56# вызываем функцию buy для обмена эфира на токен7# 1e18 — это 1 эфир, выраженный в wei8dex.buy({'from': account2, 1e18})910# получаем адрес развертывания токена ERC2011# который был развернут во время создания контракта DEX12# dex.token() возвращает адрес развернутого токена13token = ERC20Basic.at(dex.token())1415# вызываем функцию 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;23interface IERC20 {45 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);89 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);121314 event Transfer(address indexed from, address indexed to, uint256 value);15 event Approval(address indexed owner, address indexed spender, uint256 value);16}171819contract ERC20Basic is IERC20 {2021 string public constant name = "ERC20Basic";22 string public constant symbol = "ERC";23 uint8 public constant decimals = 18;242526 mapping(address => uint256) balances;2728 mapping(address => mapping (address => uint256)) allowed;2930 uint256 totalSupply_ = 10 ether;313233 constructor() {34 balances[msg.sender] = totalSupply_;35 }3637 function totalSupply() public override view returns (uint256) {38 return totalSupply_;39 }4041 function balanceOf(address tokenOwner) public override view returns (uint256) {42 return balances[tokenOwner];43 }4445 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 }5253 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 }5859 function allowance(address owner, address delegate) public override view returns (uint) {60 return allowed[owner][delegate];61 }6263 function transferFrom(address owner, address buyer, uint256 numTokens) public override returns (bool) {64 require(numTokens <= balances[owner]);65 require(numTokens <= allowed[owner][msg.sender]);6667 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}747576contract DEX {7778 event Bought(uint256 amount);79 event Sold(uint256 amount);808182 IERC20 public token;8384 constructor() {85 token = new ERC20Basic();86 }8788 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 }9697 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 }105106}Показать всеПоследнее обновление страницы: 21 августа 2025 г.

