摘要:前提是擁有者必須要通過某些機制對這個請求進行確認,比如通過進行。事件,當被調用時,需要觸發該事件。允許從中轉出的數增加所有者允許花費代幣的數量。已經歸屬合約,其余歸還給所有者。計算已歸屬但尚未釋放的金額。源碼分析到這里就結束了。
ERC20:Ethereum Request for Comments 20,是一個基于以太坊代幣的接口標準(協議)。所有符合ERC-20標準的代幣都能立即兼容以太坊錢包,它能讓用戶和交易所,都能非常方便的管理多種代幣,轉賬、存儲、ICO等等。
OpenZeppelin的Token中實現了ERC20的一個安全的合約代碼,本篇主要來分析一下源碼,了解一下ERC20的實現,由于代碼之間的調用可能略復雜,直接每個文件每個文件的來看會有點繞,我直接畫了一個繼承和調用關系的思維導圖,可以幫助更容易地看源碼。
ERC20Basic.solpragma solidity ^0.4.23; contract ERC20Basic { function totalSupply() public view returns (uint256); function balanceOf(address who) public view returns (uint256); function transfer(address to, uint256 value) public returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); }
ERC20Basic合約主要定義了ERC20的基本接口,定義了必須要實現的方法:
totalSupply 返回總共發行量
balanceOf 查詢指定address的余額
transfer 發送指定數目的token到指定賬戶,同時發送后需要觸發Transfer事件
Transfer事件,任何token發送發生時,必須觸發該事件,即使是0額度。 當一個token合約創建時,應該觸發一個Transfer事件,token的發送方是0x0,也就是說憑空而來的token,簡稱空氣幣。
ERC20.solpragma solidity ^0.4.23; import "./ERC20Basic.sol"; contract ERC20 is ERC20Basic { function allowance(address owner, address spender) public view returns (uint256); function transferFrom(address from, address to, uint256 value) public returns (bool); function approve(address spender, uint256 value) public returns (bool); event Approval( address indexed owner, address indexed spender, uint256 value ); }
ERC20合約繼承了ERC20Basic,另外定義了approve相關的方法:
allowance 獲取指定用戶的批準額度,控制代幣的交易,如可交易賬號及資產, 控制Token的流通
transferFrom 從一個地址向另外一個地址轉賬指定額度的token,這個方法可以理解為一個收款流程,允許合約來代表token持有者發送代幣。比如,合約可以幫助你向另外一個人發送token或者索要token。前提是token擁有者必須要通過某些機制對這個請求進行確認,比如通過MetaMask進行confirm。否則,執行將失敗。 跟transfer一樣,即使發送0代幣,也要觸發Transfer事件。
approve 批準額度,允許一個賬戶最多能從你的賬戶你取現指定額度。重復調用時,以最后一次的額度為主。為了防止攻擊,最開始這個額度必須設置為0。
Approval事件,當approve被調用時,需要觸發該事件。
DetailedERC20.solpragma solidity ^0.4.23; import "./ERC20.sol"; contract DetailedERC20 is ERC20 { string public name; string public symbol; uint8 public decimals; constructor(string _name, string _symbol, uint8 _decimals) public { name = _name; symbol = _symbol; decimals = _decimals; } }
DetailedERC20 主要定義了token的展示信息:
name token的名稱,比如"XXXToken"
symbol token的符號,比如"XXX"
decimals token精確的小數點位數,比如18
BasicToken.solpragma solidity ^0.4.23; import "./ERC20Basic.sol"; import "../../math/SafeMath.sol"; /** * @title 實現ERC20基本合約的接口 * @dev 基本的StandardToken,不包含allowances. */ contract BasicToken is ERC20Basic { using SafeMath for uint256; mapping(address => uint256) balances; uint256 totalSupply_; /** * @dev 返回存在的token總數 */ function totalSupply() public view returns (uint256) { return totalSupply_; } /** * @dev 給特定的address轉token * @param _to 要轉賬到的address * @param _value 要轉賬的金額 */ function transfer(address _to, uint256 _value) public returns (bool) { //做相關的合法驗證 require(_to != address(0)); require(_value <= balances[msg.sender]); // msg.sender余額中減去額度,_to余額加上相應額度 balances[msg.sender] = balances[msg.sender].sub(_value); balances[_to] = balances[_to].add(_value); //觸發Transfer事件 emit Transfer(msg.sender, _to, _value); return true; } /** * @dev 獲取指定address的余額 * @param _owner 查詢余額的address. * @return An uint256 representing the amount owned by the passed address. */ function balanceOf(address _owner) public view returns (uint256) { return balances[_owner]; } }
通過SafeMath來做運算很重要,在我們自己寫合約的時候也盡量使用,可以避免一些計算過程的溢出等安全問題。
StandardToken.solpragma solidity ^0.4.23; import "./BasicToken.sol"; import "./ERC20.sol"; /** * @title 標準 ERC20 token * * @dev 實現基礎的標準token * @dev https://github.com/ethereum/EIPs/issues/20 * @dev Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol */ contract StandardToken is ERC20, BasicToken { mapping (address => mapping (address => uint256)) internal allowed; /** * @dev 從一個地址向另外一個地址轉token * @param _from 轉賬的from地址 * @param _to address 轉賬的to地址 * @param _value uint256 轉賬token數量 */ function transferFrom( address _from, address _to, uint256 _value ) public returns (bool) { // 做合法性檢查 require(_to != address(0)); require(_value <= balances[_from]); require(_value <= allowed[_from][msg.sender]); //_from余額減去相應的金額 //_to余額加上相應的金額 //msg.sender可以從賬戶_from中轉出的數量減少_value balances[_from] = balances[_from].sub(_value); balances[_to] = balances[_to].add(_value); allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); // 觸發Transfer事件 emit Transfer(_from, _to, _value); return true; } /** * @dev 批準傳遞的address以代表msg.sender花費指定數量的token * * Beware that changing an allowance with this method brings the risk that someone may use both the old * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this * race condition is to first reduce the spender"s allowance to 0 and set the desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * @param _spender 花費資金的地址 * @param _value 可以被花費的token數量 */ function approve(address _spender, uint256 _value) public returns (bool) { //記錄msg.sender允許_spender動用的token allowed[msg.sender][_spender] = _value; //觸發Approval事件 emit Approval(msg.sender, _spender, _value); return true; } /** * @dev 函數檢查所有者允許的_spender花費的token數量 * @param _owner address 資金所有者地址. * @param _spender address 花費資金的spender的地址. * @return A uint256 指定_spender仍可用token的數量。 */ function allowance( address _owner, address _spender ) public view returns (uint256) { //允許_spender從_owner中轉出的token數 return allowed[_owner][_spender]; } /** * @dev 增加所有者允許_spender花費代幣的數量。 * * allowed[_spender] == 0時approve應該被調用. 增加allowed值最好使用此函數避免2此調用(等待知道第一筆交易被挖出) * From MonolithDAO Token.sol * @param _spender 花費資金的地址 * @param _addedValue 用于增加允許動用的token牌數量 */ function increaseApproval( address _spender, uint _addedValue ) public returns (bool) { //在之前允許的數量上增加_addedValue allowed[msg.sender][_spender] = ( allowed[msg.sender][_spender].add(_addedValue)); //觸發Approval事件 emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); return true; } /** * @dev 減少所有者允許_spender花費代幣的數量 * * allowed[_spender] == 0時approve應該被調用. 減少allowed值最好使用此函數避免2此調用(等待知道第一筆交易被挖出) * From MonolithDAO Token.sol * @param _spender 花費資金的地址 * @param _subtractedValue 用于減少允許動用的token牌數量 */ function decreaseApproval( address _spender, uint _subtractedValue ) public returns (bool) { uint oldValue = allowed[msg.sender][_spender]; if (_subtractedValue > oldValue) { //減少的數量少于之前允許的數量,則清零 allowed[msg.sender][_spender] = 0; } else { //減少對應的_subtractedValue數量 allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue); } //觸發Approval事件 emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); return true; } }
上面合約定義的 mapping allowed,它用來記錄某個地址允許另外一個地址動用多少token。假設錢包地址為B,有另外一個合約其合約地址為C,合約C會通過支付XXX Token來做一些事情,根據ERC20的定義,每個地址只能操作屬于自己的Token,則合約C無法直接使用B地址所擁有的Token,這時候allowed Mapping就派上用場了,它上面可以記錄一個允許操作值,像是「B 錢包地址允許 C 合約地址動用屬于 B 錢包地址的 1000 XXX Token」,以 Mapping 的結構來說標記為「B => C => 1000」
BurnableToken.solpragma solidity ^0.4.23; import "./BasicToken.sol"; /** * @title 可銷毀 Token * @dev Token可以被不可逆轉地銷毀 */ contract BurnableToken is BasicToken { event Burn(address indexed burner, uint256 value); /** * @dev 銷毀指定數量的token. * @param _value 被銷毀的token數量. */ function burn(uint256 _value) public { _burn(msg.sender, _value); } function _burn(address _who, uint256 _value) internal { require(_value <= balances[_who]); //不需要驗證value <= totalSupply,因為這意味著發送者的余額大于總供應量,這應該是斷言失敗 balances[_who] = balances[_who].sub(_value); totalSupply_ = totalSupply_.sub(_value); emit Burn(_who, _value); emit Transfer(_who, address(0), _value); } }
該合約比較簡單,就是調用者可以銷毀一定數量的token,然后totalSupply減去對應銷毀的數量
StandardBurnableToken.solpragma solidity ^0.4.23; import "./BurnableToken.sol"; import "./StandardToken.sol"; /** * @title 標準可銷毀token * @dev 將burnFrom方法添加到ERC20實現中 */ contract StandardBurnableToken is BurnableToken, StandardToken { /** * @dev 從目標地址銷毀特定數量的token并減少允許量 * @param _from address token所有者地址 * @param _value uint256 被銷毀的token數量 */ function burnFrom(address _from, uint256 _value) public { require(_value <= allowed[_from][msg.sender]); // Should https://github.com/OpenZeppelin/zeppelin-solidity/issues/707 be accepted, // 此方法需要觸發具有更新批準的事件。 allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); _burn(_from, _value); } }MintableToken.sol
pragma solidity ^0.4.23; import "./StandardToken.sol"; import "../../ownership/Ownable.sol"; /** * @title 可增發 token * @dev 簡單的可增發的 ERC20 Token 示例 * @dev Issue: * https://github.com/OpenZeppelin/openzeppelin-solidity/issues/120 * Based on code by TokenMarketNet: https://github.com/TokenMarketNet/ico/blob/master/contracts/MintableToken.sol */ contract MintableToken is StandardToken, Ownable { event Mint(address indexed to, uint256 amount); event MintFinished(); //初始化增發狀態為false bool public mintingFinished = false; modifier canMint() { // 檢查沒有增發結束 require(!mintingFinished); _; } modifier hasMintPermission() { //owner只能為msg.sender require(msg.sender == owner); _; } /** * @dev 增發token方法 * @param _to 獲取增發token的地址_to. * @param _amount 增發的token數量. * @return A boolean that indicates if the operation was successful. */ function mint( address _to, uint256 _amount ) hasMintPermission canMint public returns (bool) { // 總發行量增加_amount數量的token totalSupply_ = totalSupply_.add(_amount); // 獲取增發的地址增加_amount數量的token balances[_to] = balances[_to].add(_amount); // 觸發增發事件 emit Mint(_to, _amount); // 觸發Transfer事件 emit Transfer(address(0), _to, _amount); return true; } /** * @dev 停止增發新token. * @return True if the operation was successful. */ function finishMinting() onlyOwner canMint public returns (bool) { // 改變增發狀態為已完成 mintingFinished = true; // 觸發增發已完成事件 emit MintFinished(); return true; } }
增發token的合約也很簡單,就是通過增發一定量的token給對應的address,并給總發行量增加對應的增發token,可以通過調用finishMinting來完成增發。
CappedToken.solpragma solidity ^0.4.23; import "./MintableToken.sol"; /** * @title 上限 token * @dev 設置一個頂的可增發token. */ contract CappedToken is MintableToken { uint256 public cap; constructor(uint256 _cap) public { require(_cap > 0); cap = _cap; } /** * @dev 增發token * @param _to 獲取增發token的地址_to. * @param _amount 增發token數量. * @return A boolean that indicates if the operation was successful. */ function mint( address _to, uint256 _amount ) public returns (bool) { // 驗證總發行量+增發量小于所設置的上限 require(totalSupply_.add(_amount) <= cap); // 調用父合約的增發方法 return super.mint(_to, _amount); } }
CappedToken 也很簡單,就是在可增發合約上加了一個"cap",來限制增發的上限
RBACMintableToken.solpragma solidity ^0.4.23; import "./MintableToken.sol"; import "../../ownership/rbac/RBAC.sol"; /** * @title RBACMintableToken * @author Vittorio Minacori (@vittominacori) * @dev Mintable Token, with RBAC minter permissions */ contract RBACMintableToken is MintableToken, RBAC { /** * 指定一個增發者的常量名. */ string public constant ROLE_MINTER = "minter"; /** * @dev 重寫Mintable token合約的 modifier,增加角色有關的邏輯 */ modifier hasMintPermission() { // 調用RBAC合約中的角色檢查 checkRole(msg.sender, ROLE_MINTER); _; } /** * @dev 將一個地址添加為可增發者角色 * @param minter address */ function addMinter(address minter) onlyOwner public { addRole(minter, ROLE_MINTER); } /** * @dev 將一個地址移除可增發者角色 * @param minter address */ function removeMinter(address minter) onlyOwner public { removeRole(minter, ROLE_MINTER); } }
RBACMintableToken 合約將增發操作中添加了RBAC邏輯,就是角色權限管理的邏輯,將一個地址這是為增發者角色,也可以移除一個地址的增發者角色,只有擁有"minter"角色的address才有權限增發token
SafeERC20.solpragma solidity ^0.4.23; import "./ERC20Basic.sol"; import "./ERC20.sol"; /** * @title SafeERC20 * @dev 圍繞ERC20操作發生故障的包裝程序. * 可以在合約中通過這樣使用這個庫 `using SafeERC20 for ERC20;` 來使用安全的操作`token.safeTransfer(...)` */ library SafeERC20 { function safeTransfer(ERC20Basic token, address to, uint256 value) internal { require(token.transfer(to, value)); } function safeTransferFrom( ERC20 token, address from, address to, uint256 value ) internal { require(token.transferFrom(from, to, value)); } function safeApprove(ERC20 token, address spender, uint256 value) internal { require(token.approve(spender, value)); } }
SafeERC20 是一個ERC20的安全操作庫,在下面的TokenTimelock鎖定期后釋放token的合約中我們可以看到用法
TokenTimelock.solpragma solidity ^0.4.23; import "./SafeERC20.sol"; /** * @title TokenTimelock 鎖定期釋放token * @dev TokenTimelock 是一個令token持有人合同,將允許一個受益人在給定的發布時間之后提取token */ contract TokenTimelock { //這里用到了上面的SafeERC20 using SafeERC20 for ERC20Basic; // ERC20 basic token contract being held ERC20Basic public token; // token被釋放后的受益人address address public beneficiary; // token可以被釋放的時間戳 uint256 public releaseTime; // 對token,受益人address和釋放時間初始化 constructor( ERC20Basic _token, address _beneficiary, uint256 _releaseTime ) public { require(_releaseTime > block.timestamp); token = _token; beneficiary = _beneficiary; releaseTime = _releaseTime; } /** * @notice 將時間限制內的token轉移給受益人. */ function release() public { require(block.timestamp >= releaseTime); uint256 amount = token.balanceOf(this); require(amount > 0); token.safeTransfer(beneficiary, amount); } }
TokenTimelock 合約通過初始化受益人以及釋放的時間和鎖定的token,通過release來將鎖定期過后釋放的token轉給受益人
TokenVesting.solpragma solidity ^0.4.23; import "./ERC20Basic.sol"; import "./SafeERC20.sol"; import "../../ownership/Ownable.sol"; import "../../math/SafeMath.sol"; /** * @title TokenVesting 定期釋放token * @dev token持有人合同可以逐漸釋放token余額典型的歸屬方案,有斷崖時間和歸屬期, 可選擇可撤銷的所有者。 */ contract TokenVesting is Ownable { using SafeMath for uint256; using SafeERC20 for ERC20Basic; event Released(uint256 amount); event Revoked(); // 釋放后的token收益人 address public beneficiary; uint256 public cliff; //斷崖表示「鎖倉4年,1年之后一次性解凍25%」中的一年 uint256 public start;//起始時間 uint256 public duration;//持續鎖倉時間 bool public revocable; mapping (address => uint256) public released; mapping (address => bool) public revoked; /** * @dev 創建一份歸屬權合同,將任何ERC20 token的余額歸屬給_beneficiary,逐漸以線性方式,直到_start + _duration 所有的余額都將歸屬。 * @param _beneficiary 授予轉讓token的受益人的地址 * @param _cliff 持續時間以秒為單位,代幣將開始歸屬 * @param _start 歸屬開始的時間(如Unix時間) * @param _duration 持續時間以token的歸屬期限為單位 * @param _revocable 歸屬是否可撤銷 */ constructor( address _beneficiary, uint256 _start, uint256 _cliff, uint256 _duration, bool _revocable ) public { require(_beneficiary != address(0)); require(_cliff <= _duration); beneficiary = _beneficiary; revocable = _revocable; duration = _duration; cliff = _start.add(_cliff); start = _start; } /** * @notice 將歸屬代幣轉讓給受益人. * @param token ERC20 token which is being vested */ function release(ERC20Basic token) public { uint256 unreleased = releasableAmount(token); require(unreleased > 0); released[token] = released[token].add(unreleased); token.safeTransfer(beneficiary, unreleased); emit Released(unreleased); } /** * @notice允許所有者撤銷歸屬。 token已經歸屬合約,其余歸還給所有者。 * @param token ERC20 token which is being vested */ function revoke(ERC20Basic token) public onlyOwner { require(revocable); require(!revoked[token]); uint256 balance = token.balanceOf(this); uint256 unreleased = releasableAmount(token); uint256 refund = balance.sub(unreleased); revoked[token] = true; token.safeTransfer(owner, refund); emit Revoked(); } /** * @dev 計算已歸屬但尚未釋放的金額。 * @param token ERC20 token which is being vested */ function releasableAmount(ERC20Basic token) public view returns (uint256) { return vestedAmount(token).sub(released[token]); } /** * @dev 計算已歸屬的金額. * @param token ERC20 token which is being vested */ function vestedAmount(ERC20Basic token) public view returns (uint256) { uint256 currentBalance = token.balanceOf(this); uint256 totalBalance = currentBalance.add(released[token]); if (block.timestamp < cliff) { return 0; } else if (block.timestamp >= start.add(duration) || revoked[token]) { return totalBalance; } else { return totalBalance.mul(block.timestamp.sub(start)).div(duration); } } }
TokenVesting也是鎖倉的一種方式,主要解決的是有斷崖時間和持續鎖倉時間的鎖倉場景
PausableToken.solpragma solidity ^0.4.23; import "./StandardToken.sol"; import "../../lifecycle/Pausable.sol"; /** * @title Pausable token * @dev StandardToken modified with pausable transfers. **/ contract PausableToken is StandardToken, Pausable { function transfer( address _to, uint256 _value ) public whenNotPaused returns (bool) { return super.transfer(_to, _value); } function transferFrom( address _from, address _to, uint256 _value ) public whenNotPaused returns (bool) { return super.transferFrom(_from, _to, _value); } function approve( address _spender, uint256 _value ) public whenNotPaused returns (bool) { return super.approve(_spender, _value); } function increaseApproval( address _spender, uint _addedValue ) public whenNotPaused returns (bool success) { return super.increaseApproval(_spender, _addedValue); } function decreaseApproval( address _spender, uint _subtractedValue ) public whenNotPaused returns (bool success) { return super.decreaseApproval(_spender, _subtractedValue); } }
PausableToken繼承了StandardToken,但是在方法中都添加了whenNotPaused函數修改器,whenNotPaused繼承自Pausable合約,Pausable有個paused來標記暫停的狀態,從而控制合約的是否暫停。
OpenZeppelin ERC20源碼分析到這里就結束了。
轉載請注明: 轉載自Ryan是菜鳥 | LNMP技術棧筆記
如果覺得本篇文章對您十分有益,何不 打賞一下
本文鏈接地址: OpenZeppelin ERC20源碼分析
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/24147.html
摘要:它和我寫的上一篇源碼分析介紹的有所不同,最小的單位為無法再分割,代表獨一無二的,針對不可置換的的智能合約標準接口。源碼分析到這里就結束了。 ERC721 官方簡介是:A standard interface for non-fungible tokens, also known as deeds.也叫非同質代幣,或者不可置換代幣(NFTs)。提到ERC721,一個好理解的例子就是Cry...
摘要:從這節開始,我們將學習代幣標準以及加密收集資產等知識。聲明一個繼承的新合約,命名為。注意目前是一個草稿,還沒有正式商定的實現。所以把這一個可能的實現當作考慮,但不要把它作為代幣的官方標準。 從這節開始,我們將學習代幣, ERC721標準, 以及加密收集資產等知識。 一、代幣 代幣 讓我們來聊聊以太坊上的代幣。 如果你對以太坊的世界有一些了解,你很可能聽過人們聊到代幣——尤其是 ERC2...
摘要:合約安全增強溢出和下溢我們將來學習你在編寫智能合約的時候需要注意的一個主要的安全特性防止溢出和下溢。實戰演練給加上一些標簽把這里變成標準的注釋把一個管理轉移僵尸所有權的合約符合對標準草案的實現 通過上一節的學習,我們完成了 ERC721 的實現。并不是很復雜,對吧?很多類似的以太坊概念,當你只聽人們談論它們的時候,會覺得很復雜。所以最簡單的理解方式就是你自己來實現它。 一、預防溢出 不...
摘要:我們目前正處于一個新興的區塊鏈開發行業中。,一種在以太坊開發人員中流行的新的簡單編程語言,因為它是用于開發以太坊智能合約的語言。它是全球至少萬開發人員使用的世界上最流行的編程語言之一。以太坊,主要是針對工程師使用進行區塊鏈以太坊開發的詳解。 我們目前正處于一個新興的區塊鏈開發行業中。區塊鏈技術處于初期階段,然而這種顛覆性技術已經成功地風靡全球,并且最近經歷了一場與眾不同的繁榮。由于許多...
摘要:假設某一天,星巴克突然宣布為了擁抱區塊鏈技術,不再接受法幣買咖啡了,大家以后可以用以太幣或者星巴克自己發行的星星幣來買咖啡。用星星幣買咖啡星巴克自己發行了,取名,遵循協議。 什么是ERC20 ERC20是以太坊上為token提供的一種協議,也可以理解成一種token的共同標準。遵循ERC20協議的token都可以兼容以太坊錢包,讓用戶在錢包中可以查看token余額以及操作token轉賬...
閱讀 3719·2021-10-18 13:34
閱讀 2413·2021-08-11 11:15
閱讀 1207·2019-08-30 15:44
閱讀 699·2019-08-26 10:32
閱讀 994·2019-08-26 10:13
閱讀 2069·2019-08-23 18:36
閱讀 1781·2019-08-23 18:35
閱讀 531·2019-08-23 17:10