solidity fallback函數


什么是fallback函數:

出處:http://me.tryblockchain.org/blockchain-solidity-fallback.html

回退函數是合約里的特殊函數,沒有名字,不能有參數,沒有返回值。當調用的函數找不到時,就會調用默認的fallback函數

⚠️Even though the fallback function cannot have arguments, one can still use msg.data to retrieve any payload supplied with the call.

由於Solidity中,Solidity提供了編譯期檢查,所以我們不能直接通過Solidity調用一個不存在的函數。但我們可以使用Solidity的提供的底層函數address.call來模擬這一行為,進行函數調用:

pragma solidity ^0.4.24; contract ExecuteFallback{ //回退事件,會把調用的數據打印出來 event FallbackCalled(bytes data); //fallback函數,注意是沒有名字的,沒有參數,沒有返回值的 function() public{ emit FallbackCalled(msg.data);//在這里返回的是functionNotExist()函數簽名0x69774a91 } //調用已存在函數的事件,會把調用的原始數據,請求參數打印出來 event ExistFuncCalled(bytes data, uint256 para); //一個存在的函數 function existFunc(uint256 para) public { emit ExistFuncCalled(msg.data, para); } // 模擬從外部對一個存在的函數發起一個調用,將直接調用函數 function callExistFunc() public returns(bool){ bytes4 funcIdentifier = bytes4(keccak256("existFunc(uint256)")); return address(this).call(funcIdentifier, uint256(1)); } //模擬從外部對一個不存在的函數發起一個調用,由於匹配不到函數,將調用回退函數 function callNonExistFunc() public returns(bool){ bytes4 funcIdentifier = bytes4(keccak256("functionNotExist()")); return address(this).call(funcIdentifier); } }

調用callExistFunc,返回:

調用callNonExistFunc,有調用fallback函數返回,而且要注意,這里call的返回值也為true:

 ⚠️一個沒有定義一個回退函數的合約。如果接收ether,會觸發異常,並返還ether(solidity v0.4.0開始)。所以合約要接收ether,必須實現回退函數。

  •    balance 和 transfer

   可以通過地址的balance屬性來查看一個地址的余額,發送以太幣(單位為:wei)到一個地址可以使用 transfer方法

 

address x = 0x123; address myAddress = this; if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);//將合約this中的10wei轉到賬戶地址x

  注意:如果x是一個合約地址,它的代碼(如果存在的話,更明確是為 fallback 函數)將會和 transfer 調用一起被執行,send也一樣(這是EVM的限制,是不可阻止的)。如果執行過程中gas不夠或是失敗,當前合約會終止拋出異常

1)send(),有返回值bool

舉例說明:

pragma solidity ^0.4.24; contract SendFallback{ //fallback函數及其事件 event FallbackTrigged(bytes data); function() public payable{//一定要聲明為payable,否則send()執行結果將會始終為false emit FallbackTrigged(msg.data); } //存入一些ether用於后面的測試 function deposit() public payable{ } //查詢當前的余額 function getBalance() public view returns(uint){ return address(this).balance; } event SendEvent(address to, uint value, bool result); //使用send()發送ether,觀察會觸發fallback函數 function sendEther() public{ bool result = address(this).send(1);//從合約地址的余額中發送1wei給它自己,所以其balance不會變,只是會消耗msg.sender賬戶gas emit SendEvent(this, 1, result); } }

一開始,調用getBalance函數得到合約地址中余額為0,這時候如果調用sendEther函數,result將為false,也沒有調用fallback函數:

然后通過調用deposit函數傳入value = 5,使得合約地址賬戶余額為5,這時候再調用sendEther函數,result將為true,並且調用了fallback函數,附帶的數據是0x(bytes類型的默認空值),空數據:

因為是address(this).send(1),所以調用getBalance函數發現並不會有改變,還是5
如果改為msg.sender.send(1),就不是自己給自己了,而是從合約賬戶中傳1wei給msg.sender賬戶,調用getBalance函數值變為了4

2)transfer(),沒有返回值
但是如果改成使用transfer的話,發現並沒有調用fallback函數,是不是后面設置為transfer不調用fallback函數了???????:
pragma solidity ^0.4.24; contract SendFallback{ ...event SendEvent(address to, uint value); //使用send()發送ether,觀察會觸發fallback函數 function sendEther() public{ msg.sender.transfer(1); emit SendEvent(msg.sender, 1); } }

返回:

后面發現了原因,這里有一個概念沒有搞明白,就是調用的fallback函數與你所指定的地址有關。比如上面的例子中,使用的是msg.sender.transfer(1),那么將意味着如果msg.sender為一個合約地址,就調用它里面寫的fallback函數,如果不是合約地址,那么自然就沒有fallback函數調用,所以這里的結果才會沒有調用fallback函數,所以如果我們將其改成address(this).transfer(1),就會發現果然調用了:

 

3)call.value(),有返回值bool

改成msg.sender.call.value(1)():

pragma solidity ^0.4.24; contract SendFallback{ ... event SendEvent(address to, uint value,bool result); //使用send()發送ether,觀察會觸發fallback函數 function sendEther() public{ bool result = msg.sender.call.value(1)(); emit SendEvent(msg.sender, 1,result); } }

返回也沒有觸發fallback函數,這是因為這里調用的是msg.sender.call.value(1)(),而不是address(this).call.value(1)():

 將msg.sender.call.value(1)改為address(this).call.value(1)()后,就會發現還是會觸發fallback函數:

 從上面我們可以看出這三個調用都會調用訪問其的地址的fallback函數,這會有危險。

fallback中的限制

上面三個函數總是會調用fallback,這個行為非常危險,著名的DAO被黑也與這有關。比如當我們對一系列帳戶進行send()操作,其中某個做惡意帳戶中的fallback函數實現了一個無限循環,將因為gas耗盡,導致所有send()失敗。為解決這個問題,send()函數當前即便gas充足,也只會附帶限定的2300gas,故而fallback函數內除了可以進行日志操作外,你幾乎不能做任何操作。

下述行為消耗的gas都將超過fallback函數限定的gas值:

  • 向區塊鏈中寫數據( x =1;)
  • 創建一個合約
  • 調用一個external的函數
  • 發送ether

所以一般,我們只能在fallback函數中進行一些日志操作

舉例說明:

pragma solidity ^0.4.24; contract Test { event FallbackTrigged1(bytes data); function() external { emit FallbackTrigged1(msg.data); } function getBalance() public view returns(uint){ return address(this).balance; } } contract Sink { event FallbackTrigged2(bytes data); function() external payable {emit FallbackTrigged2(msg.data); } function getBalance() public view returns(uint){ return address(this).balance; } } contract Caller { function deposit() payable public{} function callTest(Test test) public returns (bool) { require(address(test).call(abi.encodeWithSignature("nonExistingFunction()")));//一般call進行調用都返回true,不管里面的函數是否存在 return address(test).send(2 ether); } }

在這個例子中callTest(Sink合約地址)會成功:

callTest(Test合約地址)失敗,因為沒有payable:

如果改為:

pragma solidity ^0.4.24; contract Test { function() external { x = 1;} uint x; function getBalance() public view returns(uint){ return address(this).balance; } } contract Sink { function() external payable { x = 1;} uint x; function getBalance() public view returns(uint){ return address(this).balance; } }

則兩個都會失敗,返回形如下面的結果:

說明gas的限制果然是起作用的

但是好像要是想要寫復雜的操作也是可以的,但是沒有查到呢,查到再補充?????????

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM