Nhảy đến nội dung chính

Chuyển khoản và phê duyệt token ERC-20 từ một hợp đồng thông minh Solidity

hợp đồng thông minh
tokens
Solidity
erc-20
Trung gian
jdourlens
7 tháng 4, 2020
8 số phút đọc

Trong hướng dẫn trước, chúng ta đã nghiên cứu cấu trúc của một token ERC-20 trong Solidity trên chuỗi khối Ethereum. Trong bài viết này, chúng ta sẽ xem cách có thể sử dụng hợp đồng thông minh để tương tác với token bằng ngôn ngữ Solidity.

Đối với hợp đồng thông minh này, chúng tôi sẽ tạo một sàn giao dịch phi tập trung mẫu, nơi người dùng có thể giao dịch ether để lấy token ERC-20 mới được triển khai của chúng tôi.

Đối với hướng dẫn này, chúng tôi sẽ sử dụng mã mà chúng tôi đã viết trong hướng dẫn trước làm cơ sở. Sàn giao dịch phi tập trung (DEX) của chúng tôi sẽ khởi tạo một bản sao của hợp đồng trong hàm khởi tạo và thực hiện các hoạt động sau:

  • đổi token lấy ether
  • đổi ether lấy token

Chúng tôi sẽ bắt đầu mã sàn giao dịch phi tập trung của mình bằng cách thêm cơ sở mã ERC20 đơn giản của chúng tôi:

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
Hiện tất cả

Hợp đồng thông minh của sàn giao dịch phi tập trung (DEX) mới của chúng tôi sẽ triển khai ERC-20 và nhận tất cả nguồn cung:

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}
Hiện tất cả

Vì vậy, bây giờ chúng tôi đã có sàn giao dịch phi tập trung (DEX) của mình và nó có sẵn tất cả lượng token dự trữ. Hợp đồng có hai chức năng:

  • buy: Người dùng có thể gửi ether và nhận lại token
  • sell: Người dùng có thể quyết định gửi token để nhận lại ether

Hàm mua

Hãy lập trình hàm mua. Trước tiên, chúng tôi sẽ cần kiểm tra lượng ether chứa trong thông điệp và xác minh rằng hợp đồng sở hữu đủ token và thông điệp có chứa một lượng ether. Nếu hợp đồng sở hữu đủ token, nó sẽ gửi số lượng token cho người dùng và phát ra sự kiện Bought.

Lưu ý rằng nếu chúng tôi gọi hàm require trong trường hợp có lỗi, ether đã gửi sẽ được hoàn lại ngay lập tức và trả lại cho người dùng.

Để đơn giản hóa, chúng tôi chỉ đổi 1 token lấy 1 Wei.

1function buy() payable public {
2 uint256 amountTobuy = msg.value;
3 uint256 dexBalance = token.balanceOf(address(this));
4 require(amountTobuy > 0, "Bạn cần gửi một ít ether");
5 require(amountTobuy <= dexBalance, "Không đủ token trong kho dự trữ");
6 token.transfer(msg.sender, amountTobuy);
7 emit Bought(amountTobuy);
8}

Trong trường hợp mua thành công, chúng ta sẽ thấy hai sự kiện trong giao dịch: Sự kiện Transfer token và sự kiện Bought.

Hai sự kiện trong giao dịch: Transfer và Bought

Hàm bán

Hàm chịu trách nhiệm bán trước tiên sẽ yêu cầu người dùng phải phê duyệt số tiền bằng cách gọi hàm phê duyệt trước đó. Việc phê duyệt chuyển khoản yêu cầu người dùng phải gọi token ERC20Basic được khởi tạo bởi sàn giao dịch phi tập trung (DEX). Điều này có thể được thực hiện bằng cách trước tiên gọi hàm token() của hợp đồng DEX để truy xuất địa chỉ nơi DEX đã triển khai hợp đồng ERC20Basic có tên là token. Sau đó, chúng tôi tạo một bản sao của hợp đồng đó trong phiên của mình và gọi hàm approve của nó. Sau đó, chúng ta có thể gọi hàm sell của DEX và hoán đổi token của mình để lấy lại ether. Ví dụ, đây là giao diện của nó trong một phiên brownie tương tác:

1#### Python trong bảng điều khiển brownie tương tác...
2
3# triển khai DEX
4dex = DEX.deploy({'from':account1})
5
6# gọi hàm mua để hoán đổi ether lấy token
7# 1e18 là 1 ether được tính bằng wei
8dex.buy({'from': account2, 1e18})
9
10# lấy địa chỉ triển khai cho token ERC20
11# đã được triển khai trong quá trình tạo hợp đồng DEX
12# dex.token() trả về địa chỉ đã triển khai cho token
13token = ERC20Basic.at(dex.token())
14
15# gọi hàm phê duyệt của token
16# phê duyệt địa chỉ dex là người chi tiêu
17# và số lượng token của bạn được phép chi tiêu
18token.approve(dex.address, 3e18, {'from':account2})
19
Hiện tất cả

Sau đó, khi hàm bán được gọi, chúng tôi sẽ kiểm tra xem việc chuyển từ địa chỉ của người gọi đến địa chỉ hợp đồng có thành công không và sau đó gửi Ethers trở lại địa chỉ của người gọi.

1function sell(uint256 amount) public {
2 require(amount > 0, "Bạn cần bán ít nhất một số token");
3 uint256 allowance = token.allowance(msg.sender, address(this));
4 require(allowance >= amount, "Kiểm tra khoản phụ cấp token");
5 token.transferFrom(msg.sender, address(this), amount);
6 payable(msg.sender).transfer(amount);
7 emit Sold(amount);
8}

Nếu mọi thứ hoạt động, bạn sẽ thấy 2 sự kiện (một Transfer và một Sold) trong giao dịch và số dư token cũng như số dư ether của bạn được cập nhật.

Hai sự kiện trong giao dịch: Transfer và Sold

Từ hướng dẫn này, chúng ta đã thấy cách kiểm tra số dư và khoản phụ cấp của một token ERC-20, cũng như cách gọi TransferTransferFrom của một hợp đồng thông minh ERC20 bằng giao diện.

Khi bạn thực hiện một giao dịch, chúng tôi có một hướng dẫn JavaScript để chờ và lấy chi tiết về các giao dịch (opens in a new tab) đã được thực hiện đối với hợp đồng của bạn và một hướng dẫn để giải mã các sự kiện được tạo ra bởi việc chuyển khoản token hoặc bất kỳ sự kiện nào khác (opens in a new tab) miễn là bạn có Giao diện nhị phân ứng dụng (ABI).

Đây là mã hoàn chỉnh cho hướng dẫn:

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, "Bạn cần gửi một ít ether");
92 require(amountTobuy <= dexBalance, "Không đủ token trong kho dự trữ");
93 token.transfer(msg.sender, amountTobuy);
94 emit Bought(amountTobuy);
95 }
96
97 function sell(uint256 amount) public {
98 require(amount > 0, "Bạn cần bán ít nhất một số token");
99 uint256 allowance = token.allowance(msg.sender, address(this));
100 require(allowance >= amount, "Kiểm tra khoản phụ cấp token");
101 token.transferFrom(msg.sender, address(this), amount);
102 payable(msg.sender).transfer(amount);
103 emit Sold(amount);
104 }
105
106}
Hiện tất cả

Lần cập nhật trang lần cuối: 21 tháng 8, 2025

Hướng dẫn này có hữu ích không?