Solidity的函数修改器(十九)|入门系列

2017/5/20 posted in  Solidity入门系列

函数修改器(Function Modifiers)1可以方便的控制函数的逻辑,比如可以在某个行为执行前检查某个前置条件,函数修改器还支持继承和重写。可以大家会想,一些检查行为提升为一个语言级特性到底是必要的么?由于整个区块链运行环境是透明的,分布式的,且是图灵完备的。为保证其上运行的代码安全,势必存在大量的检查行为,升级为语言特性可以让检查代码复用,看起来也更简洁。

定义

在实际情况中,我们经常需要对调用者进行一些限制。比如,只能是合约的所有者才能改变归属。我们一起来看看如何用函数修改器实现这一限制:

pragma solidity ^0.4.0;

contract Ownable {
  address public owner = msg.sender;

  /// 限制只有创建者才能访问
  modifier onlyOwner {
    if (msg.sender != owner) throw;
    _;
  }

  /// 改变合约的所有者
  function changeOwner(address _newOwner)
  onlyOwner
  {
    if(_newOwner == 0x0) throw;
    owner = _newOwner;
  }
}

上述例子中,首先我们通过关键字modifier后接函数修改器名onlyOwner,定义了一个函数修改器。在函数修改器代码块内,判断如果不是合约所有者调用就抛出异常,否则执行占位符_处代码,_代指的是使用函数修改器的函数体。真正调用changeOwner()函数时,由于使用了函数修改器onlyOwner,会先判断调用者是否为所有者,如果是,才会执行changeOwner()的函数体,允许修改为新所有者。

注意,0.4.0以后,_必须要写为_;2

函数修改器支持参数

函数修改器,支持函数内可以访问的任意符号(包括函数的入参)。直接在函数修改器的参数中传入即可。下面来看一个例子:

pragma solidity ^0.4.0;

contract Parameter{
  uint balance = 10;

  modifier lowerLimit(uint _balance, uint _withdraw){
    if( _withdraw < 0 || _withdraw > _balance) throw;
    _;
  }

  //含参数的函数修改器
  function f(uint withdraw) lowerLimit(balance, withdraw) returns (uint){
    return balance;
  }
}

在上面的例子中,f()函数,有一个函数修改器lowerLimit(),传入了状态变量参数balance,和入参withdraw,以lowerLimit(balance, withdraw)的方式进行调用。最后函数能否正确执行取决于输入的withdraw值大小。

函数修改器参数支持表达式

函数修改器的参数支持任意的表达式,只要在上下文中,可见的符号均可在函数修改器中使用。而在函数修改器中引入的符号,在函数中不可见(因为它们可能因为重载而发生改变)。

pragma solidity ^0.4.0;

contract ParameterExpression{
  modifier m(uint a){
    if(a > 0)
      _;
  }

  function add(uint a, uint b) private returns(uint){
    return a + b;
  }

  function f() m(add(1, 1)) returns(uint){
    return 1;
  }
}

上例中,我们在函数修改器m中,使用了add()函数。

return

如果函数有返回值,但由于函数修改器判断不成功,那么将返回对应类型的默认值,下面是一个简单的例子:

pragma solidity ^0.4.0;

contract Return{
  //函数修改器永远不成功
  modifier A() {
      if(false)
        _;
  }

  //返回默认值
  function uintReturn() A returns(uint){
      uint a = 0;
      return uint(1);
  }
  
  //返回默认值
  function stringReturn() A returns(string){
      return "Hello world";
  }
}

上例中,我们写了一个特殊的函数修改器A,永远判断不成功。故uintReturn()stringReturn()中的代码都将不会执行。上述函数将分别返回uintstring的默认值0和空串。

函数修改器允许return;(中断当前流程)。但不允许明确的return值。社区正在建议能支持返回值3

执行流程

函数体内,或函数修改器内显式return语句,只会退出当前代码块。返回值将被赋值,但会继续执行_后续的逻辑。下面是一个展示执行流程的例子:

pragma solidity ^0.4.0;

contract ProcessFlow{
  mapping(bool => uint) public mapp;

  modifier A(mapping(bool => uint) mapp){
    if(mapp[true] == 0){
      mapp[true]= 1;
      _;
      mapp[true]= 3;
    }
  }

  function f() A(mapp) returns(uint){
    mapp[true] = 2;
    return mapp[true];
  }
}

上面的例子中,执行函数f()时,先执行函数修改器A的判断mapp[true]当前值为0。既而会将mapp[true]置为1。然后执行f()函数体,将mapp[true]设置为2,并返回当前值,中断当前函数执行,回到函数修改器。函数修改器A_;后面的代码mapp[true]= 3;接着执行。所以函数执行完成后,mapp[true]的值将为3,你可以通过访问器看到最后的值。

备注:如果这里的mapp按引用传递,那么,我们是不是可以在函数修改器中还能修改函数已经指定的返回结果呢(这里不能直接验证,因为以太坊不支持直接返回mapping。而只能返回mapp[true],返回值是其存储的对应uint值,已经不是引用本身)。另外,试着将上述数据结构由mapping,改为array[],发送并不是寻常的引用传递,因为在函数修改器中的更改数据,完全没有产生应有效果。

多函数修改器

如果一个函数有多个函数修改器,定义时依次填写,并用空格隔开。函数修改器执行时,将按定义顺序依次执行。下面是一个简单的例子:

pragma solidity ^0.4.0;

contract MultiModifier{
  address owner = msg.sender;

  /// 限制只有创建者才能访问
  modifier onlyOwner {
    if (msg.sender != owner) throw;
    _;
  }

  modifier inState(bool state){
    if(!state) throw;
    _;
  }

  //多个函数修改器
  function f(bool state) onlyOwner inState(state) returns(uint){
    return 1;
  }

}

上面的例子中,我们定义了两个函数修改器,onlyOwnerinState。函数f()同时使用了这两个函数修改器。

重写

我们可以重写父类的函数修改器。来改变父类的修改器行为。下面来看一个例子:

pragma solidity ^0.4.0;

contract bank{
  modifier transferLimit(uint _withdraw){
    if(_withdraw > 100) throw;
    _;
  }
}

contract ModifierOverride is bank{
  //覆盖的父类的
  modifier transferLimit(uint _withdraw){
    if(_withdraw > 10) throw;
    _;
  }

  function f(uint withdraw) transferLimit(withdraw) returns(uint){
    return withdraw;
  }
}

上例中,我们在子类ModifierOverride覆写了父类bank的函数修改器transferLimit。加强了对_withdraw的数量检查。

函数修改器区域

函数修改器区域4,是一个全新的特性,目前尚不支持,实现方式也正在讨论中。它允许你同时对多个函数应用函数修改器。

contract c {
   // ...
  modifier inState(state) { if (currentState != state) throw; _ }
  using modifier inState(State.transfer) {
    function f() { ... }
    function g() { ... }
  }
  using modifier inState(State.cleanup) {
    function h() { ... }
    function i() { ... }
  }
}

上述代码是将一个表示状态的函数修改器同时应用到多个函数的例子。使用函数修改器区域的好处是将能比较直观知道哪些函数应用了某个函数修改器。但也引入了复杂性,以及由于使用者不规范,超大代码带来的不方便阅读代码的问题。

备注:会出现这样的特性本身说明了传统程序与智能合约程序的不同。由于智能合约强检查性的特点,开始慢慢发展出自己的独特特性。

关于作者

专注基于以太坊(Ethereum)的相关区块链(Blockchain)技术,了解以太坊,Solidity,Truffle,web3.js。

个人博客: http://tryblockchain.org
版权所有,转载注明出处

参考资料


  1. 主要参考文档 http://solidity.readthedocs.io/en/develop/contracts.html#function-modifiers 

  2. Solidity 0.4.0之后,"_"必须要写为"_;" https://github.com/ethereum/solidity/releases/tag/v0.4.0 

  3. 函数修改器支持返回结果github讨论。https://github.com/ethereum/solidity/issues/49 

  4. 函数修改器区域的github讨论。https://github.com/ethereum/solidity/issues/623 

处于某些特定的环境下,可以看到评论框,欢迎留言交流^_^。

友情链接: 区块链技术中文社区    深入浅出区块链