solidity基础(4)

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

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

      pragma solidity ^0.4.18;
      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以后,_必须要写为_;](https://github.com/ethereum/solidity/releases/tag/v0.4.0)

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

      pragma solidity ^0.4.18;
      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值大小

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

      pragma solidity ^0.4.18;
      contract Test {
        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()函数

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

      pragma solidity ^0.4.18;
      contract Return {
        //函数修改器永远不成功
        modifier A() {
          if (false)
          _;
        }
      
        //返回默认值
        function uintReturn() A returns(uint) {
          uint a = 10;
          return a;
        }
      
        //返回默认值
        function stringReturn() A returns(string) {
          return "hello,world";
        }
      }
      

    上例中,我们写了一个特殊的函数修改器A,永远判断不成功,故而uintReturn(),stringReturn()中的代码都将不会执行。上述函数将分别返回uint,string的默认“零值”
    函数修改器允许return;(类似于break,中断当前流程)。但是不允许明确的return值,即return 1之类,社区正在建议能支持返回值,但是目前(2018/10/22)还不可以

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

      pragma solidity ^0.4.18;
      contract Test {
        mapping(bool => uint) public map;
      
        modifier A(mapping(bool => uint) map) {
          if (map[true] == 0) {
            map[true] = 1;
            _;
            map[true] = 3;
            }
          }
          function f() A(map) returns(uint) {
            map[true] = 2;
            return map[ttue];
        }
      }
      

    上面的例子,函数先执行f(),然后执行函数修改判断器,接着判断map[true]的值,为0,执行map[true] = 1;,然后执行f()的函数体,执行完后回到A中执行map[true] = 3;,合约执行完毕
    个人认为,函数体执行完后就直接返回了,并不是等到整个合约执行完后再一起返回,所有返回值任然是2,使用数组之类的引用传递,依然没有效果

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

      pragma solidity ^0.4.18;
      contract Test {
        address owner = msg.sender;
        //限制只有创建者才能访问
        modifier onlyOwner {
          if (msg.sender != owner) throw;
          _;
        }
        //查看状态
        modifier inSate(bool state) {
          if (!state) throw;
          _;
        }
        //多个函数修改器
        function f(bool state) onlyOwner inSate(state) returns(uint) {
          return 1;
        }
      }
      
    2. 重写
      我们可以重写父类的函数修改器,来改变父类的修改器行为,下面来看一个示例:

      pragma solidity ^0.4.18;
      contract bank{
        modifier transferLimit(uint _withdraw) {
          if (_withdraw > 100) throw;
          _;
        }
      }
      
      contract Test is bank{
        //覆盖的父类的
        modifier transferLimit(uint _withdraw) {
          if (_withdraw > 10) throw;
          _;
        }
        function f(uint withdraw) transferLimit(withdraw) returns(uint) {
          return withdraw;
        }
      }
      


    上例中,我们在子类中覆写了父类的函数修改器,加强了对_withdraw的数量检查,但是覆写的修改器只在子类中有效,在父类中依然按照父类的修改器

    1. 函数修改器区域
      函数修改器区域,是一个全新的特性,目前尚不支持,它允许你同时对多个函数应用函数修改器

      //该段代码是不能执行的
      pragma solidity ^0.4.18;
      contract test {
        modifier inState(bool state) {
          if (currentSate != state) throe;
          _;
        }
        using modifier inSate(state.transfer) {
          function f() returns(uint) {return 2;}
          function g() returns(uint) {return 2;}
        }
        using modifier inSate(state.cleanup) {
          function h() returns(uint) {return 2;}
          function i() returns(uint) {return 2;}
        }
      }
      

    上述代码是将一个表示状态的函数修改器同时应用到多个函数的例子。使用函数修改器区域的好处是将能比较直观知道哪些函数应用了某个函数修改器,但也引入了复杂性,以及由于作者不规范,超大代码带来的不方便阅读代码的问题,
    备注:会出现这样的特性本身说明了传统程序与智能合约程序的不同。由于智能合约强检查性的特点,开始慢慢发展出自己的独特特性

  2. solidity的event事件
    在介绍事件前,我们先明确事件,日志这两个概念。事件发生后被记录到区块链上成为了日志,总的来说,事件强调功能,一种行为;日志强调储存,内容。
    事件是以太坊EVM提供的一种日志基础设施。事件可以用来做操作记录,储存为日志。也可以用来实现一些交互功能,比如通过UI,返回函数调用结果等

    1. 事件
      当定义的事件触发时,我们可以将事件储存到EVM的交易日志中,日志是区块中的一种特殊数据结构。日志与合约关联,与合约的储存合并存入区块链中,只要某个区块可以访问,其相关的日志就可以访问。但在合约中,我们不能直接访问日志和事件数据(即便是创建日志的合约),下面我们来看如何在solidity中实现一个事件:

      pragma solidity ^0.4.18;
      contract Transfer {
        event transfer(address indexed _from,address indexed _to,uint indexed value);
      
        function deposit() payable {
          address current = this;
          uint value = msg.value;
          transfer(msg.sender,current.value);
        }
      
        function getBalance() constant returns(uint) {
          return this.balance;
        }
      }
      

    从上面的例子中,我们使用event关键字定义一个事件,参数列表中为要记录的日志参数的名称及类型

    1. 监听事件
      由于监听事件需要使用web3.js,这里先跳过,在后续学习web3.js时再回头来看
    2. 捡索日志
      • Indexed属性
        可以在事件参数上增加indexed属性,最多可以对三个参数增加这样的属性。加上这个属性,可以允许你在web3.js中通过对加了这个属性的参数进行值过滤
      • 后续基本都涉及到web3.js,如果没有学过的可以先跳一下,学过的,我这里附上原文链接
      • 后续切记补上,因为事件和日志在以太坊中异常重要。因为它是合约之间,合约与终端之间的沟通桥梁。

你可能感兴趣的:(solidity基础(4))