Solidity中的external与public使用最佳实践

2018-01-07

Written by 蔡一 | TsaiYee 转载请注明出处。

原文链接https://tsaiyee.com/blog/2018/01/07/solidity-external-public-best-practices/

Solidity有两种函数调用方式,一种是内部调用,不会创建一个EVM调用(也叫做消息调用),另一种则是外部调用,会创建EVM调用(会发起消息调用)。

Solidity对函数和状态变量提供了四种可见性。分别是external,public,internal,private。其中函数默认是public。状态变量默认的可见性是internal。

  • interal - 函数只能通过内部访问(当前合约或者继承的合约),可在当前合约或继承合约中调用。类似于Java的protected
  • public - public标识的函数是合约接口的一部分。可以通过内部,或者消息来进行调用。与Java的public含义一致。
  • external - external标识的函数是合约接口的一部分。函数只能通过外部的方式调用。外部函数在接收大的数组时更有效。Java中无与此对应的关键字。
  • private - 只能在当前合约内访问,在继承合约中都不可访问。与Java中的private含义一致。

在培训和与学员交流的过程中,大家比较困惑的是external和public的使用。使用public也可以在外部调用,为啥还需要使用external呢?什么时候该使用public,什么时候该使用external呢?

我们来看一下的例子:

pragma solidity^0.4.12;

contract Test {
    function test(uint[20] a) public returns (uint){
         return a[10]*2;
    }

    function test2(uint[20] a) external returns (uint){
         return a[10]*2;
    }
}

调用例子中的两个函数,我们能看到,调用test函数(public)使用496 gas,调用test2(external)函数仅花费261 gas。这是为什么呢?

对于public函数,每次调用时Solidity会将参数copy到内存中;而调用external函数,则可以直接读取calldata。内存分配在EVM中是非常昂贵的,而读取calldata则相对廉价很多。

那为什么public需要做内存复制呢?那是因为public需要支持内部调用,而内部调用与外部调用的处理机制是完全不同的。内部调用是通过jump指令执行的,参数数据在内部是指向内存的。因此,当编译器在编译可内部调用的函数时,函数希望它的参数是载入内存的。

而对于external,编译器是不需要允许内部调用的,因此编译器可以直接从calldata读取数据,而省略了内存复制。

综上,作为最佳实践,如果你的函数仅仅需要外部调用,那么你应该用external,如果你的函数需要内部和外部同时调用,那么使用public。值得注意的是,合约内调用public,一定不要使用this.f(),因为这需要EVM执行CALL指令,这也是很昂贵的。


若需评论,请科学上网。