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的限制果然是起作用的

但是好像要是想要写复杂的操作也是可以的,但是没有查到呢,查到再补充?????????

 

posted @ 2018-09-03 19:47  慢行厚积  阅读(4841)  评论(0编辑  收藏  举报