ERC20是以太坊上爲token提供的一種協議,也能夠理解成一種token的共同標準。遵循ERC20協議的token均可以兼容以太坊錢包,讓用戶在錢包中能夠查看token餘額以及操做token轉帳,而不須要本身再手動與token合約交互。git
ERC20規定了如下基本方法:github
contract ERC20 { // 方法 function name() view returns (string name); function symbol() view returns (string symbol); function decimals() view returns (uint8 decimals); function totalSupply() view returns (uint256 totalSupply); function balanceOf(address _owner) view returns (uint256 balance); function transfer(address _to, uint256 _value) returns (bool success); function transferFrom(address _from, address _to, uint256 _value) returns (bool success); function approve(address _spender, uint256 _value) returns (bool success); function allowance(address _owner, address _spender) view returns (uint256 remaining); // 事件 event Transfer(address indexed _from, address indexed _to, uint256 _value); event Approval(address indexed _owner, address indexed _spender, uint256 _value); }
能夠看到,經過上面的幾種方法,規定了一種token的基本信息、轉帳以及受權操做。這些操做基本能夠覆蓋貨幣使用的絕大部分場景,該協議一經提出後,立獲得了開發者的接納。安全
ERC20雖然廣受開發者喜好,可是依然有本身侷限的一面。
讓咱們先從一個你們十分熟悉的場景開始談起。假設某一天,星巴克忽然宣佈爲了擁抱區塊鏈技術,再也不接受法幣買咖啡了,你們之後能夠用以太幣或者星巴克本身發行的星星幣來買咖啡。app
首先,咱們來看用以太幣來買咖啡的流程。框架
簡單寫一個買咖啡的合約(注:僞代碼,僅表示邏輯)區塊鏈
contract BuyCoffee { function buy() public payable { starbucks.transfer(msg.value); COFFEE.transfer(msg.sender); } }
(熟悉ERC721的小夥伴確定看出來了,這裏的COFFEE是遵照ERC721的NFT token,本文重點講解的是ERC20,所以就不在贅述ERC721的實現了)。測試
整個調用過程以下圖:ui
客戶直接調用buy()
方法,輸入買咖啡須要的以太幣數量,BuyCoffee
合約就把本身有的COFFEE
轉給客戶。整個過程只須要一步。this
星巴克本身發行了token,取名StarCoin
,遵循ERC20協議。編碼
那麼BuyCoffee
合約就要作一些小修改:(注:僞代碼,僅表示邏輯)
contract BuyCoffee { // 一杯咖啡的StarCoin價格 uint constant COFFEE_PRICE; //@param _fee - 用戶買咖啡須要支付的StarCoin數量 function buy(uint _fee) public payable { require(_fee >= COFFEE_PRICE); StarCoin.transferFrom(msg.sender, address(this), _fee); COFFEE.transfer(msg.sender); } }
整個買咖啡的過程以下圖:
圖中能夠看到,由於StarCoin
和BuyCoffee
是兩個合約,分別有本身獨立的地址,因此客戶買咖啡就要通過兩次操做:
BuyCoffee
;BuyCoffee
中的buy(uint)
方法買咖啡;經過上面的分析能夠看到,若是要使用星巴克發行的StarCoin
進行付款的話,買一杯咖啡要操做兩次,無疑這增長了操做成本,而且很反常識。一個很好的辦法就是把StarCoin
和BuyCoffee
合二爲一,若是token邏輯和業務邏輯都在同一個合約裏的話,就不存在上述問題了。
這看上去是一個不錯的辦法,然而治標不治本。萬一之後星巴克還宣佈可使用星星幣買積分、參加優惠活動甚至直接參與星巴克公司分成,鑑於智能合約不可更改的特色,這麼多業務邏輯不可能一開始就所有規劃好,之後的新業務依然面臨屢次操做的問題。
approveAndCall
方法能夠完美地解決上述問題,把兩次操做合併爲一次,讓用戶在付款時感受不到這些複雜的操做。
使用approveAndCall
方法以後,整個操做的流程以下:
StarCoin
) 中受權一筆token給業務合約 (BuyCoffee
), 經過token合約中的approveAndCall
方法;receiveApproval
方法;整個過程就以下圖:
這就須要在token合約裏建立approveAndCall
方法,以下:
function approveAndCall(address _to, uint256 _value, bytes _extraData) { approve(_to, _value); ApproveAndCallFallBack(_to).receiveApproval( msg.sender, _value, extraData) }
(參數的個數能夠根據須要自行選擇,例如能夠加上address(tokenContract))
而後在service合約中建立receiveApproval
方法,以下:
function receiveApproval(address _sender, uint256 _value, bytes _extraData) { require(msg.sender == tokenContract); // do something by breaking down _extraData ... }
爲何要使用approveAndCall
以及怎樣使用它,上文已經解釋清楚了。有些可能以爲再多寫一個ApproveAndCallFallBack
接口有些畫蛇添足,不如直接使用address(_to).call(...)
來的簡單直接。
ConsenSys公司的思路也是這樣的,如下代碼就是Consensys的approveAndCall
方法:
/* Approves and then calls the receiving contract */ function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) { allowed[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); //call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this. //receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData) //it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead. if(!_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)) { throw; } return true; } }
想看所有源碼的能夠訪問: https://github.com/ConsenSys/...
可是你們若是稍加嘗試就會發現,若是這裏的_extraData
超過32個字節,就會報錯。
address(_to).call(...)
這樣的調用,並不會對所傳數據作ABI.encode
編碼,而bytes做爲動態數據類型,它的ABI編碼方式和基礎的、固定長度類型的變量是不同的。舉個例子:
下面是長度爲64字節的bytes (換行只是爲了讓你們看着不費力) :
0x0000000000000000000000000000000100000000000000000000000000000001 000000000000000000000000964633feef5a290be634c2e718353b98def350be
它的ABI編碼以下 (換行只是爲了讓你們看着不費力) :
0x0000000000000000000000000000000100000000000000000000000000000060 0000000000000000000000000000000100000000000000000000000000000040 0000000000000000000000000000000100000000000000000000000000000001 000000000000000000000000964633feef5a290be634c2e718353b98def350be
因此上面的bytes參數若是超過32byte長度,第二個32byte就會被當成bytes參數的長度,最後由於out of gas
而致使調用失敗。
針對上面的ConsenSys公司的代碼,正確寫法應該是:
/* Approves and then calls the receiving contract */ function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) { approve(_spender, _value); //若是該token遵循ERC20的話 if(!_spender.call(bytes4(keccak256("receiveApproval(address,uint256,address,bytes)")), abi.encode(msg.sender, _value, this, _extraData)) { throw; } return true; } }
在address(_spender).call(...)
方法中,使用abi.encode()
方法對參數進行ABI編碼,能夠防止出現上述錯誤。
接着上面的代碼繼續說,除了上面的abi.encode
對參數進行ABI編碼的例子,還可使用abi.encodeWithSelector(...)
方法:
/* Approves and then calls the receiving contract */ function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) { approve(_spender, _value); //若是該token遵循ERC20的話 if(!_spender.call(abi.encodeWithSelector(bytes4(keccak256("receiveApproval(address,uint256,address,bytes)")),msg.sender, _value, this, _extraData)) { throw; } return true; } }
abi.encodeWithSelector
會自動忽略前四個字節,對後面的內容進行ABI編碼。
還有一個使代碼看上去更加簡潔的代碼方式就是上面提到的,增長ApproveAndCallFallBack
接口:
interface ApproveAndCallFallBack { function receiveApproval(address from, uint256 _amount, address _token, bytes _data) public; }
以後approveAndCall
方法內的實現變爲:
function approveAndCall(address _spender, uint256 _amount, bytes _extraData ) returns (bool success) { if (!approve(_spender, _amount)) throw; ApproveAndCallFallBack(_spender).receiveApproval( msg.sender, _amount, this, _extraData ); return true; }
以上代碼貢獻自:https://github.com/evolutionl...
注:這是一個以太坊上的沙盤遊戲。其中RING token的設計目的之一就是爲了在遊戲中買賣地塊,感興趣的同窗能夠詳細研究其中的erc20和erc721token之間的交互方式。
這一篇解釋了爲何使用approveAndCall
以及怎樣更好地使用它。區塊鏈是一個更新迭代迅速同時又極其強調安全的領域,對於權威組織給出的代碼,咱們也不能簡單地copy-and-paste,審計和測試是必須的。
至於ERC20爲何沒有把approveAndCall
添加進協議中,可能早期在以太坊上流通的大部分多爲token合約,尚未可以創建去較爲複雜的應用強的程序,所以更增強調的是token做爲貨幣具備的流通手段的職能;隨着以太坊生態的發展出現了愈來愈多的應用,這時ERC20 token的支付手段的職能才被你們重視起來。
也可能由於approveAndCall
和業務的聯繫過於緊密,ERC20做爲一個框架性的協議,這些細節並不在考慮範圍以內。
鑑於智能合約的不可更改性,但願從此的發行token的組織機構或者我的,在實現ERC20的基礎上,能夠儘量安全地實現approveAndCall
方法,使得基於token的應用生態更加魯棒。
最後提醒,ERC223的tokenFallback
方法也有相似的效果,若是你們感興趣也能夠本身作進一步的研究。友情提醒:ERC223的
tokenFallback
方法在以前提到的https://github.com/evolutionl...,感興趣的朋友能夠自行參考。