require 和 modifier 的使用有没有最佳实践?

感觉两者的作用差不多,有没有最佳实践可以指导分别什么场景下用哪种?
比如说检验调用者是不是 owner ,下面两者表现上有什么差异吗?可能消耗 quota 不太一样?
solidity 中的 throw 会抛个啥玩意儿出来?

modifier onlyOwner(){
    if (msg.sender != owner) {
          throw;
      }
    _;
}
function updateValue(uint a) onlyOwner {
    a = a+1;
}
function update(uint a) {
    require(msg.sender == owner);
    a = a+1;
}

另外,如果为了监听链上合约执行的一些事件,那是不是还是挨个 if 判断然后 emit 一个 event 出来比较好?

function update(uint a) {
    if(msg.sender != owner) {
       emit SomeEvent("朋友,不是你的合约,别调了");
       return;
    }
    a = a+1;
}

@leeyr @kaikai 手动@大佬们

我的理解是这样:

  1. 首先 modifier 定义是函数修改器,其中的 _ 表示所修饰的函数。比如:
modifier onlyOwner(){
    if (msg.sender != owner) {
          throw;
      }
    _;
}
function updateValue(uint a) onlyOwner {
    a = a+1;
}

与下面的 code 执行结果是一致的:

function updateValue(uint a) {
    if (msg.sender != owner) {
          throw;
      }

   // 原 onlyOwner 中的 _ 替换成 所修饰的函数本身。注意,这里的 _ 不一定是放在最后。
    a = a+1;
}

理解了以上描述之后,我觉得 modifier 在做函数前置检查时,可以使合约有很强代码可读性。

  1. 你可以写成:
require(msg.sender == owner, "You are can not call the contract!");
  1. 如果你希望在 DApp 端可以捕获到这个 log 信息,你的确需要 emit .

  2. return 的行为与 throw (或者说 exception) 是不一样的。return 的语意是这个合约正常执行,并结束了,该改的数据状态是要改的;而 exception 的语意是这个合约没有被正常执行,这个交易的状态是要被回滚的,所有状态都不会被改变。

  3. 你可以参考一下 CITA 的 系统合约设计, 希望对你有帮助。

@kaikai, 你看一下我描述的内容是否有偏差?

modifier可以理解把函数执行前的检查逻辑抽出来了

require(fasle)和throw,在查receipt的时候会看到一个reverted,是交易发生了回滚,看不到event
require一般用在函数参数检查,throw一般用在函数逻辑的判断里

想要根据tx hash查event就要emit

补充一下, throw 已经废弃了。现在是 require & assert, require 用的较多,assert 发生回滚时会把全部的 gas 扣除,更多信息可以查看官网

https://solidity.readthedocs.io/en/v0.5.8/control-structures.html#error-handling-assert-require-revert-and-exceptions

modifier 会对阅读友好,但是和在原来函数中实际实现是有差别的。modifier 会占用 入参数个数,你可以构造一个 15 个参数的函数,然后看 modifier 的区别。

再补充一点, 一般会抛出错误信息在 call(汇编)的 output 里,是有的但是通过常规手段拿不到。常规手段与 evm 的交互就是 log。

1赞

and this:

https://consensys.github.io/smart-contract-best-practices/recommendations/

再补充一个供思考,就是使用 modifier 的优劣

  • Modifiers - e.g. in Solidity you can do function foo() mod1 { ... } , where mod1 can be defined elsewhere in the code to include a check that is done before execution, a check that is done after execution, some state changes, or possibly other things. Vyper does not have this, because it makes it too easy to write misleading code. mod1 just looks too innocuous for something that could add arbitrary pre-conditions, post-conditions or state changes. Also, it encourages people to write code where the execution jumps around the file, harming auditability. The usual use case for a modifier is something that performs a single check before the execution of a program; our recommendation is to simply inline these checks as asserts.

可以看到 require 是不用扣全部 gas, 而仅是扣已经发生的部分,对吗?

 Note that assert-style exceptions consume all gas available to the call, while require-style exceptions will not consume any gas starting from the Metropolis release.

对的

所以如果是链外有个业务系统监听链上情况的话,还是把这些条件写成如下形式主动 emit 事件比较好?

if(some condition) {
    emit SomeEvent('something happens');
    return;
}

对,事件是和外部通信的方式