以太坊开发入门-ERC20合约
在上一章節中完成了一個非常簡單的合約,本節中將按照ERC20協議完成一個合約, 本章部分源代碼參考于網絡開源代碼,詳細了解:openzeppelin-contracts/contracts/token/ERC20 at master · OpenZeppelin/openzeppelin-contracts · GitHub。
開始之前先介紹一下什么是ERC20:
ERC-20指的是以太坊網絡的一種代幣合約標準。ERC-20是現在最出名的標準,ERC-20標準里無價值的差別,Token之間是能夠進行互換的。意思就是在ERC-20標準下,你的100塊“錢”和我的100塊“錢”相同,沒什么區別。ERC-20標準里規定了Token要有它的名字、符號、總供應量以及包含轉賬、匯款等其他功能。這個標準的優勢就是:只要Token符合ERC-20標準,這樣的話它就會兼容以太坊錢包。也就是說,就可以太坊錢包里加入這個Token,還能通過錢包把它發給別人。由于ERC-20標準的存在,發行Token就會更加簡單。現在以太坊上ERC-20 Token的數量超過了180000種。
ERC2.0是一套接口定義,定義了合約的基本功能,其定義如下:
// SPDX-License-Identifier: MIT //file IERC20.sol pragma solidity ^0.8.0;interface IERC20 {// 總發行量function totalSupply() external view returns (uint256);// 查看地址余額function balanceOf(address account) external view returns (uint256);/// 從自己帳戶給指定地址轉賬function transfer(address account, uint256 amount) external returns (bool);// 查看被授權人還可以使用的代幣余額function allowance(address owner, address spender) external view returns (uint256);// 授權指定帳戶使用你擁有的代幣function approve(address spender, uint256 amount) external returns (bool);// 從一個地址轉賬至另一個地址,該函數只能是通過approver授權的用戶可以調用function transferFrom(address from,address to,uint256 amount) external returns (bool);/// 定義事件,發生代幣轉移時觸發event Transfer(address indexed from, address indexed to, uint256 value);/// 定義事件 授權時觸發event Approval(address indexed owner, address indexed spender, uint256 value); }詳細說明參考代碼注釋。
// SPDX-License-Identifier: MIT //file IERC20Metadata.sol pragma solidity ^0.8.0; import "./IERC20.sol"; interface IERC20Metadata is IERC20 {// 代幣名稱, 如:BitCoinfunction name() external view returns (string memory);// 代幣符號或簡稱, 如:BTCfunction symbol() external view returns (string memory);// 代幣支持的小數點后位數,若無特別需求,我們一般默認采用18位。function decimals() external view returns (uint8); }MetaData數據定義,該部分比較簡單,定義三個函數,分別對應代幣名稱,代幣簡稱和代幣小數點位數。
具體代碼實現:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0;import "./IERC20.sol"; import "./IERC20Metadata.sol"; contract ERC20 is IERC20, IERC20Metadata {// 地址余額mapping(address => uint256) private _balances;// 授權地址余額mapping(address => mapping(address => uint256)) private _allowances;uint256 private _totalSupply;string private _name;string private _symbol;// 設定代幣名稱符號,并初始化鑄造了10000000000代幣在發布者帳號下。constructor() {_name = "HarryToken";_symbol = "HYT";_mint(msg.sender, 10000000000);}function name() public view virtual override returns (string memory) {return _name;}function symbol() public view virtual override returns (string memory) {return _symbol;}/// 小數點位數一般為 18function decimals() public view virtual override returns (uint8) {return 18;}// 返回當前流通代幣的總量function totalSupply() public view virtual override returns (uint256) {return _totalSupply;}// 查詢指定帳號地址余額function balanceOf(address account) public view virtual override returns (uint256) {return _balances[account];}// 轉帳功能function transfer(address to, uint256 amount) public virtual override returns (bool) {address owner = msg.sender;_transfer(owner, to, amount);return true;}// 獲取被授權者可使用授權帳號的可使用余額function allowance(address owner, address spender) public view virtual override returns (uint256) {return _allowances[owner][spender];}// 授權指定帳事情可使用自己一定額度的帳戶余額。// 授權spender, 可將自己余額。使用可使用的余額的總量為amountfunction approve(address spender, uint256 amount) public virtual override returns (bool) {address owner = msg.sender;_approve(owner, spender, amount);return true;}//approve函數中的spender調用,將授權人 from 帳戶中的代幣轉入to 帳戶中function transferFrom(address from,address to,uint256 amount) public virtual override returns (bool) {address spender = msg.sender;_spendAllowance(from, spender, amount);_transfer(from, to, amount);return true;}function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {address owner = msg.sender;_approve(owner, spender, _allowances[owner][spender] + addedValue);return true;}function decreaseAllowance(address spender, uint256 substractedValue) public virtual returns (bool) {address owner = msg.sender;uint256 currentAllowance = _allowances[owner][spender];require(currentAllowance >= substractedValue, "ERC20: decreased allowance below zero");unchecked {_approve(owner, spender, currentAllowance - substractedValue);}return true;}function _transfer(address from,address to,uint256 amount) internal virtual {require(from != address(0), "ERC20: transfer from the zero address");require(to != address(0), "ERC20: transfer to the zero address");_beforeTokenTransfer(from, to, amount);uint256 fromBalance = _balances[from];require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");unchecked {_balances[from] = fromBalance - amount;}_balances[to] += amount;emit Transfer(from, to, amount);_afterTokenTransfer(from, to, amount);}function _mint(address account, uint256 amount) internal virtual {require(account != address(0), "ERC20: mint to the zero address");_beforeTokenTransfer(address(0), account, amount);_totalSupply += amount;_balances[account] += amount;emit Transfer(address(0), account, amount);_afterTokenTransfer(address(0), account, amount);}function _burn(address account, uint256 amount) internal virtual {require(account != address(0), "ERC20: burn from the zero address");_beforeTokenTransfer(account, address(0), amount);uint256 accountBalance = _balances[account];require(accountBalance >= amount, "ERC20: burn amount exceeds balance");unchecked {_balances[account] = accountBalance - amount;}_totalSupply -= amount;emit Transfer(account, address(0), amount);_afterTokenTransfer(account, address(0), amount);}function _approve(address owner,address spender,uint256 amount) internal virtual {require(owner != address(0), "ERC20: approve from the zero address");require(spender != address(0), "ERC20: approve to the zero address");_allowances[owner][spender] = amount;emit Approval(owner, spender, amount);}function _spendAllowance(address owner,address spender,uint256 amount) internal virtual {uint256 currentAllowance = allowance(owner, spender);if (currentAllowance != type(uint256).max) {require(currentAllowance >= amount, "ERC20: insufficient allowance");unchecked {_approve(owner, spender, currentAllowance - amount);}}}function _beforeTokenTransfer(address from,address to,uint256 amount) internal virtual {}function _afterTokenTransfer(address from,address to,uint256 amount) internal virtual {} } // 地址余額mapping(address => uint256) private _balances;// 授權地址余額mapping(address => mapping(address => uint256)) private _allowances;uint256 private _totalSupply;string private _name;string private _symbol;ERC20合約中定義了5個變量:
_balances變量以keyv=>value方式存儲帳號和其對應的余額。
_allowances變量是一個兩層mapping,數據值以下結構存儲:0x123456=>[0x123457=>1000, 0x123458=>2000],代表的意思是0x123456帳號授權0x123457和0x123458兩個帳號,分別可以使用0x123456帳號1000和2000余額額度。使用余額的函數為transferFrom。
_totalSupply變量是存儲當成代幣合約發行的代幣總量,一般我們每鑄造一個新代幣,就在其值上+1。
_name變量是代幣的名稱,如比特幣名稱:BitCoin
_symbol變量是代幣的簡稱, 如比特幣簡稱:BTC
// 設定代幣名稱符號,并初始化鑄造了10000000000代幣在發布者帳號下。constructor() {_name = "HarryToken";_symbol = "HYT";_mint(msg.sender, 10000000000);}構造函數,指令name和symbol。這里我們調用了一個private的函數_mint,給合約創建者新鑄造了10000000000個代幣。因為本合約實現的時候并沒有public的mint函數可以鑄造代幣,所以直接初始化入創建者帳戶,該合約所有的代幣都只能用創建都帳戶轉出。當前也可以將實現一個public的mint函數,關加上權限控制,讓有權限的帳戶可以隨時調mint鑄造新代幣。
function name() public view virtual override returns (string memory) {return _name;}function symbol() public view virtual override returns (string memory) {return _symbol;}/// 小數點位數一般為 18function decimals() public view virtual override returns (uint8) {return 18;}IERC20Metadata 接口的實現方法,主要用于獲取代幣名稱,簡稱及支持的小數點位數。
function totalSupply() public view virtual override returns (uint256) {return _totalSupply; }totalSupply查詢當前代幣的發行總量。
function balanceOf(address account) public view virtual override returns (uint256) {return _balances[account]; }balanceOf查詢指令帳戶的代幣余額。
// 轉帳功能function transfer(address to, uint256 amount) public virtual override returns (bool) {address owner = msg.sender;_transfer(owner, to, amount);return true;}function _transfer(address from,address to,uint256 amount) internal virtual {require(from != address(0), "ERC20: transfer from the zero address");require(to != address(0), "ERC20: transfer to the zero address");_beforeTokenTransfer(from, to, amount);uint256 fromBalance = _balances[from];require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");unchecked {_balances[from] = fromBalance - amount;}_balances[to] += amount;emit Transfer(from, to, amount);_afterTokenTransfer(from, to, amount);}transfer轉帳函數,一個比較重要的功能,調用者可以將自己的余額轉給其它帳戶。
_transfer為private的具體實現函數。主要是作了一些必要的檢查,然后從發起帳戶扣減余額,再將余額加到接收帳戶。最后發送了一個轉帳事件,方便開發者監聽轉帳功能。這里的_beforeTokenTransfer和_afterTokenTransfer并沒有實現具體功能,開發中可根據實現需要做一些功能實現。
// 獲取被授權者可使用授權帳號的可使用余額function allowance(address owner, address spender) public view virtual override returns (uint256) {return _allowances[owner][spender];}// 授權指定帳事情可使用自己一定額度的帳戶余額。// 授權spender, 可將自己余額。使用可使用的余額的總量為amountfunction approve(address spender, uint256 amount) public virtual override returns (bool) {address owner = msg.sender;_approve(owner, spender, amount);return true;}function _approve(address owner,address spender,uint256 amount) internal virtual {require(owner != address(0), "ERC20: approve from the zero address");require(spender != address(0), "ERC20: approve to the zero address");_allowances[owner][spender] = amount;emit Approval(owner, spender, amount);}allowance、approve主要是實現授權其它帳戶可以使用自己的余額,并設定使用上限。相關的授權者存儲在_allowances變量中。
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {address owner = msg.sender;_approve(owner, spender, _allowances[owner][spender] + addedValue);return true;}function decreaseAllowance(address spender, uint256 substractedValue) public virtual returns (bool) {address owner = msg.sender;uint256 currentAllowance = _allowances[owner][spender];require(currentAllowance >= substractedValue, "ERC20: decreased allowance below zero");unchecked {_approve(owner, spender, currentAllowance - substractedValue);}return true;}increaseAllowance、decreaseAllowance兩個函數是對approve函數功能的加強,對授權額度進行增減,這兩個函數關不是ERC20協議中的內容。只是作者在參考源碼時覺得有用,就加入了這兩個函數。
function transferFrom(address from,address to,uint256 amount) public virtual override returns (bool) {address spender = msg.sender;_spendAllowance(from, spender, amount);_transfer(from, to, amount);return true;}function _spendAllowance(address owner,address spender,uint256 amount) internal virtual {uint256 currentAllowance = allowance(owner, spender);if (currentAllowance != type(uint256).max) {require(currentAllowance >= amount, "ERC20: insufficient allowance");unchecked {_approve(owner, spender, currentAllowance - amount);}}}transferFrom,_spendAllowance是在授權額度下,進行轉帳的功能實現。
transferFrom函數的from參數是授權帳戶,to是余額接受帳戶,amount是轉帳余額,該函數的功能是將from帳戶的余額轉移amount個數據至to用戶帳戶中,調用者必須是from帳戶通過_approve對其進行過授權,并且還有剩余的授權額度。該函數與transfer的區別是,transfer只能轉移出調用者自己的帳戶余額。
_spendAllowance是在進行授權轉帳時首先扣減授權額度,保證被授權都在授權額度范圍內使用轉帳功能。
function _mint(address account, uint256 amount) internal virtual {require(account != address(0), "ERC20: mint to the zero address");_beforeTokenTransfer(address(0), account, amount);_totalSupply += amount;_balances[account] += amount;emit Transfer(address(0), account, amount);_afterTokenTransfer(address(0), account, amount);}function _burn(address account, uint256 amount) internal virtual {require(account != address(0), "ERC20: burn from the zero address");_beforeTokenTransfer(account, address(0), amount);uint256 accountBalance = _balances[account];require(accountBalance >= amount, "ERC20: burn amount exceeds balance");unchecked {_balances[account] = accountBalance - amount;}_totalSupply -= amount;emit Transfer(account, address(0), amount);_afterTokenTransfer(account, address(0), amount);}_mint和_burn是兩個相反的功能,一個是新鑄造代幣,一個是燃燒(銷毀)代幣。兩個方法都是private,關沒有對外開放。
完成上述三個源文件的代碼就可能編譯部署,選中ERC20.sol文件,完成編譯。部署時要特別注意在選中Contract中的實現合約ERC20,不要誤選了接口合約。
否則會部署后報如下錯誤,很多剛開始接觸合約的讀者都遇到該問題了,所以作者提示一下。
部署成功后,就可以查看并調用合約了。
一個比較健全的ERC20代幣合約就完成了。下一章節給大家分享在ERC721協議下實現NTF相關的功能。
總結
以上是生活随笔為你收集整理的以太坊开发入门-ERC20合约的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 今年诺贝尔奖得主居然把这事研究清楚了:学
- 下一篇: 【致远】OA强力清除已删除仍显示的表单