隨着上週五到來的好消息,政府認可區塊鏈技術,國內開始大力推廣宣傳區塊鏈技術,“區塊鏈是什麼”甚至上了百度熱搜。區塊鏈行業在國內算是迎來了春天,不少大廠也開始紛紛打着自己有區塊鏈業務的旗號開始宣傳着自己的企業。但其實真的做的也沒幾個,咱就當時看戲看着樂吧。因爲也不幹咱啥事。
我進入區塊鏈行業快兩年了,一直以寫以太坊智能合約爲主,慢慢着去了解區塊鏈更底層的技術。現在也在努力學習go語言,以後能夠寫一些關於公鏈開發和一些優秀的公鏈源碼解析的文章,來幫助大家更快學習和了解區塊鏈和區塊鏈技術。
我長期“潛伏”在一些以太坊智能合約的qq羣,微信羣等地方,發現羣裏任然有很多人在問ERC-20是什麼?所以感覺任然有必要寫一篇關於ERC-20標準的Token合約開發的文章。所以我大半夜的不睡覺開始寫起了這篇博文。不說廢話了,說正事。
ERC-20是什麼?
ERC(Etherum Request for Comments)表示以太坊開發者提交的協議提案,而20表示的是議案的編號。
ERC-20是一個基於以太坊(Ethereum)代幣(Token)的接口標準(協議)。任何基於ERC20標準的Token都能立即兼容以太坊錢包,同時支持共享和交換。
我們可以在https://eips.ethereum.org/EIPS/eip-20查看ERC20代幣的標準API。
看我博文的相信大家基本都是技術,那咱就按技術的路子來走。
首先咱說以太坊智能合約是用solidity語言開發的,solidity是一門高級編程語言,它也支持接口(Interface)類型,那我們就先來碼出 IERC20 。
openzeppelin庫是一個對合約開發小白入門很好的一個開源庫,裏面寫好了很多了現成的庫和一些常用標準的接口,例如咱們今天說的ERC-20,還有另一個常用的ERC-721標準庫都能在這裏面找到。
pragma solidity ^0.5.0;
interface IERC20 {
//返回代幣的名稱-例如"MyToken"。
function name() public view returns (string);
//返回代幣的符號,也就是代幣的簡稱,例如:MY。
function symbol() public view returns (string);
//返回令牌使用的小數位數-常見代幣都是18。也就是支持最小以0.000000000000000001爲單位交易。
function decimals() public view returns (uint8);
//發行代幣的總量,可以通過這個函數來獲取。所有智能合約發行的代幣總量是並不是一定的,有的可以通過後期增發和銷燬來調整Token總量。
function totalSupply() external view returns (uint256);
//輸入有效的賬戶地址,可以獲取該賬戶中此代幣的餘額。
function balanceOf(address _owner) public view returns (uint256 balance);
//調用transfer函數可以將自己的代幣轉賬給_to地址,_value爲轉賬數量。
function transfer(address _to, uint256 _value) public returns (bool success);
//配合approve使用,approve執行成功之後,調用transferFrom函數來轉移token,_to可以爲任意有效的地址。
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
//批准_spender賬戶從自己的賬戶轉移_value個代幣。可以分多次轉移。
function approve(address _spender, uint256 _value) public returns (bool success);
//返回_spender能從_owner的賬戶中轉移走的代幣數量。
function allowance(address _owner, address _spender) public view returns (uint256 remaining);
//Transfer事件,當成功轉移token時,一定要觸發該事件。
event Transfer(address indexed _from, address indexed _to, uint256 _value);
//Approval事件,執行批准方法時,一定要觸發該事件。
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}
pragma solidity ^0.5.0;
是當前合約所基於的solidity語法版本,需要在合約首部聲明。
其實solidity的語法並不複雜,接觸過javascript的老鐵們一看就會覺得很眼熟。Interface申明瞭一個IERC20接口,接口裏包含了很多的方法,方法是幹啥的我也都註釋了,這我們可以理解爲就是一個ERC-20標準。我們的Token合約只需要繼承自這個接口,並實現其裏面的方法就算是完成了一個簡單的ERC-20代幣的開發。
下面我們來具體實現這些接口。我們來新建一個contract 並繼承自 IERC20,solidity中繼承使用 is。
contract ERC20 is IERC20 {
using SafeMath for uint256;
string private _name;//代幣名稱
string private _symbol;//代幣符號,簡稱
uint8 private _decimals;//代幣小數位數量
uint256 private _totalSupply;//代幣發行總量
mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) private _allowances;
//totalSupply方法的具體實現,這裏返回了一個私有變量_totalSupply,這個值可以直接就是我們的代幣總量,這也寫的原因是把代幣總量的修改隱藏,不能對外隨意修改。
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
//balanceOf方法的具體實現,mapping是一個鍵值對數據結構。
function balanceOf(address account) public view returns (uint256) {
return _balances[account];
}
//這裏是transfer方法的具體實現,也是對_transfer方法的公開,因爲我們把transfer的功能實現在了_transfer方法體內。
function transfer(address recipient, uint256 amount) public returns (bool) {
_transfer(_msgSender(), recipient, amount);
return true;
}
function allowance(address owner, address spender) public view returns (uint256) {
return _allowances[owner][spender];
}
//這裏是approve方法的具體實現,也是對approve方法的公開,因爲我們把approve的功能實現在了_approve方法體內。
function approve(address spender, uint256 amount) public returns (bool) {
_approve(_msgSender(), spender, amount);
return true;
}
//這裏是transferFrom方法的具體實現,也是對_transferFrom方法的公開,因爲我們把transferFrom的功能實現在了_transferFrom方法體內。
function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
_transfer(sender, recipient, amount);
_approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
return true;
}
//增加批准數量,我們能看到,這裏是直接對批准的_allowances所對應的值進行了一個修改。
function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
return true;
}
//減少批准數量,批准數量最小爲0,不能爲負數。solidity本身是沒有add()sub()方法的,這是SafeMath庫裏的方法。
function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
return true;
}
//transfer方法的具體實現
function _transfer(address sender, address recipient, uint256 amount) internal {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
_balances[recipient] = _balances[recipient].add(amount);
emit Transfer(sender, recipient, amount);//這裏是觸發Transfer事件
}
//(挖礦)增發代幣,給指定有效地址添加一定數量的代幣,因爲增發了,所以要修改_totalSupply代幣總量值。使用internal 修飾符表示內部函數,根據項目需求選擇是否實現公開方法。
function _mint(address account, uint256 amount) internal {
require(account != address(0), "ERC20: mint to the zero address");
_totalSupply = _totalSupply.add(amount);
_balances[account] = _balances[account].add(amount);
emit Transfer(address(0), account, amount);
}
//(銷燬)減少代幣,銷燬該地址添加一定數量的代幣,同樣需要修改_totalSupply代幣總量值。
function _burn(address account, uint256 amount) internal {
require(account != address(0), "ERC20: burn from the zero address");
_balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
_totalSupply = _totalSupply.sub(amount);
emit Transfer(account, address(0), amount);
}
//approve方法的具體實現
function _approve(address owner, address spender, uint256 amount) internal {
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);//這裏是觸發Approval事件
}
//銷燬的另一種形式,需要批准,A 批准 B 10個代幣的轉移權,B 可銷燬 A 賬戶下10個代幣,同時也要減少 A 對B 的批准轉移數量。
function _burnFrom(address account, uint256 amount) internal {
_burn(account, amount);
_approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance"));
}
}
隨便附上safemath的具體實現,方便大家學習時使用。使用safemath是爲了避免我們在開發中對數值有錯誤的操作。
pragma solidity ^0.5.0;
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
到此,基本上一個簡單的代幣合約就開發完成了。有問題可以留言,有建議歡迎指出。
Searching...