1. 程式人生 > >Solidity學習::(17)fallback函式

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