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