Перекази та схвалення токенів 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 р.

