跳至主要内容

透過 Solidity 智能合約轉帳和核准 ERC-20 代幣

smart contracts
tokens
solidity
erc-20
中等
jdourlens
2020年4月7日
9 分鐘閱讀

在先前的教學中,我們研究了以太坊區塊鏈上以 Solidity 撰寫的 ERC-20 代幣架構。 在本文中,我們將探討如何使用智能合約,透過 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}

如果購買成功,我們應該會在交易中看到兩個事件:代幣 TransferBought 事件。

交易中的兩個事件:轉帳和購買

sell 函式

負責出售的函式會先要求使用者預先呼叫 approve 函式來核准數量。 核准轉帳需要使用者呼叫由 DEX 實例化的 ERC20Basic 代幣。 要做到這一點,可以先呼叫 DEX 合約的 token() 函式,以擷取 DEX 部署名為 token 的 ERC20Basic 合約的位址。 然後,我們在工作階段中建立該合約的實例,並呼叫其 approve 函式。 接著我們就可以呼叫 DEX 的 sell 函式,將我們的代幣換回以太幣。 例如,在互動式 brownie 工作階段中,它會是這個樣子:

1#### 在互動式 brownie 主控台中的 Python...
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 位址作為 spender
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 代幣的餘額和授權額度,以及如何透過介面呼叫 ERC20 智能合約的 TransferTransferFrom

一旦您進行交易,我們有一個 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}
顯示全部

頁面最後更新時間: 2025年8月21日

這個使用教學對你有幫助嗎?