透過 Solidity 智能合約轉帳和核准 ERC-20 代幣
在先前的教學中,我們研究了以太坊區塊鏈上以 Solidity 撰寫的 ERC-20 代幣架構。 在本文中,我們將探討如何使用智能合約,透過 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 函式來核准數量。 核准轉帳需要使用者呼叫由 DEX 實例化的 ERC20Basic 代幣。 要做到這一點,可以先呼叫 DEX 合約的 token() 函式,以擷取 DEX 部署名為 token 的 ERC20Basic 合約的位址。 然後,我們在工作階段中建立該合約的實例,並呼叫其 approve 函式。 接著我們就可以呼叫 DEX 的 sell 函式,將我們的代幣換回以太幣。 例如,在互動式 brownie 工作階段中,它會是這個樣子:
1#### 在互動式 brownie 主控台中的 Python...23# 部署 DEX4dex = DEX.deploy({'from':account1})56# 呼叫 buy 函式,用以太幣兌換代幣7# 1e18 是 1 以太幣,以 wei 為單位8dex.buy({'from': account2, 1e18})910# 取得 ERC20 代幣的部署位址11# 該代幣在 DEX 合約創建期間部署12# dex.token() 會傳回代幣的部署位址13token = ERC20Basic.at(dex.token())1415# 呼叫代幣的 approve 函式16# 核准 dex 位址作為 spender17# 以及它被允許花費多少您的代幣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 智能合約的 Transfer 和 TransferFrom。
一旦您進行交易,我們有一個 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}顯示全部頁面最後更新時間: 2025年8月21日

