Solidity學習::(17)fallback函式
fallback函式
在合約呼叫沒有匹配到函式簽名,或者呼叫沒有帶任何資料時被自動呼叫。
宣告方式:
沒有名字,不能有引數,沒有返回值
pragma solidity ^0.4.0;
contract SimpleFallback{
function(){
//fallback function
}
}
簡單例子:
由於Solidity編輯器remix中,提供了編譯期檢查,所以我們不能直接通過Solidity呼叫一個不存在的函式。但我們可以使用Solidity的提供的底層函式address.call來模擬這一行為
pragma solidity ^0.4.0; contract ExecuteFallback{ //回退事件,會把呼叫的資料打印出來 event FallbackCalled(bytes data); //fallback函式,注意是沒有名字的,沒有引數,沒有返回值的 function(){ FallbackCalled(msg.data); } //呼叫已存在函式的事件,會把呼叫的原始資料,請求引數打印出來 event ExistFuncCalled(bytes data, uint256 para); //一個存在的函式 function existFunc(uint256 para){ ExistFuncCalled(msg.data, para); } // 模擬從外部對一個存在的函式發起一個呼叫,將直接呼叫函式 function callExistFunc(){ bytes4 funcIdentifier = bytes4(keccak256("existFunc(uint256)")); this.call(funcIdentifier, uint256(1)); } //模擬從外部對一個不存在的函式發起一個呼叫,由於匹配不到函式,將呼叫回退函式 function callNonExistFunc(){ bytes4 funcIdentifier = bytes4(keccak256("functionNotExist()")); this.call(funcIdentifier); } }
(1)我們呼叫callExistFunc(),這個方法後,返回日誌,可以看到返回的值跟傳入的引數一致,是正常的呼叫
(2)呼叫callNonExistFunc(),這個方法,返回的日誌是如下圖,可以發現當沒有找到對應函式可呼叫時,會預設呼叫fallback函式
ether傳送send()
address.send(ether to send)向某個合約直接轉帳時,address指向的合約必須有fallback函式,且為payable的,才可以接收到傳送來的ether,因為:address.send(ether to send)這個行為沒有傳送任何資料,所以接收合約總是會呼叫fallback函式
合約例項測試:
- 兩個合約,兩個合約均有傳送ether的函式,一個合約有fallback函式且為payable,另一個合約沒有fallback函式
- 部署時,兩個合約同時存入100
- 兩個合約相互發送ether,看結果如何
pragma solidity ^0.4.0; contract SendWithFallback{ function SendWithFallback() payable { //建構函式,部署時用來存入 } //fallback函式及其事件 event fallbackTrigged(bytes data); function() payable{fallbackTrigged(msg.data);} //查詢當前的餘額 function getBalance() constant returns(uint){ return this.balance; } event SendEvent(address to, uint value, bool result); //使用send()傳送ether function sendEther(address _addto){ bool result = _addto.send(3); SendEvent(_addto, 1, result); } } contract SendWithoutFallback{ function SendWithoutFallback() payable { //建構函式,部署時用來存入 } //查詢當前的餘額 function getBalance() constant returns(uint){ return this.balance; } event SendEvent(address to, uint value, bool result); //使用send()傳送ether function sendEther(address _addto){ bool result = _addto.send(3); SendEvent(_addto, 1, result); } }
測試步驟:
(1)部署,兩個合約都存入100
(2)先用不帶Fallback的合約向帶Fallback的合約傳送ether
傳送會成功,觸發事件
再看兩合約的餘額
(3)再用帶Fallback的合約向不帶Fallback的合約傳送ether
返回結果result為false,表明傳送ether是失敗的,
再看看兩合約的餘額,並沒有變化
(4)因此,要接收ether的合約,要有fallback函式,且為payable屬性的
fallback的限制:
send()函式總是會呼叫fallback,這個行為非常危險,著名的DAO被黑也與這有關。如果我們在分紅時,對一系列帳戶進行send()操作,其中某個做惡意帳戶中的fallback函式實現了一個無限迴圈,將因為gas耗盡,導致所有send()失敗。為解決這個問題,send()函式當前即便gas充足,也只會附帶限定的2300gas,故而fallback函式內除了可以進行日誌操作外,你幾乎不能做任何操作。
下述行為消耗的gas都將超過fallback函式限定的gas值:
注意:上述僅對使用send()方式的有2300gas的限制,對使用call()方式沒有這樣的限制。
- 向區塊鏈中寫資料
- 建立一個合約
- 呼叫一個external的函式
- 傳送ether