zoukankan      html  css  js  c++  java
  • 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的限制果然是起作用的

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

  • 相关阅读:
    android 11中报new Handler()已过时
    My WelcomeApplet
    设计模式(五):抽象工厂方法(多个工厂方法的组合)
    [Redis知识体系] 一文全面总结Redis知识体系
    RocketMq的事务消息发送方法,消息零丢失的实现方式,代码流程讲解,干货分享
    充血枚举的用法
    Rocketmq的启动jvm配置解析-runbroker.sh
    springboot+mybatis整合,基本框架
    rabbitmq单机入门安装教程(ubuntu环境 亲测有效)
    常用表单验证插件
  • 原文地址:https://www.cnblogs.com/wanghui-garcia/p/9580464.html
Copyright © 2011-2022 走看看