Solidity 学习笔记
作者:李康
1. 可见性
函数可以被定义为 external
,public
,internal
或者 private
,默认是 public
;对于状态变量来说,没有 external
类型,默认是 internal
。
external
:External 函数是合约接口的一部分,意味着它们可以被其它合约调用,也可以通过交易来调用。一个 external 函数 f 不能在内部调用(例如,f() 是不可行的,但是 this.f() 是可行的)。当接收大型数据数组的时候,external 函数有时是更高效的。public
:对于函数来说,可以在内部调用,或者通过消息调用,也可以被其它合约调用;对于状态变量来说,会自动生成一个getter
函数internal
:这些类型的函数和状态变量只能够从内部访问(例如,从当前合约内部,或者是子合约的内部),无需使用 thisprivate
:这些类型的函数和状态变量只能够从定义它们合约的内部访问
对于 public 和 external 类型的函数来说,区别在于一个可以在内部调用,一个不可以,而且 external 在传递大型数组时效率更高,具体原因请查看 https://ethereum.stackexchange.com/questions/19380/external-vs-public-best-practices
2. call/callcode/delegatecall
delegatecall 的意思是:我是一个合约,我允许(委托)你对我的存储做任何事情,所以必须信任被调用合约不会随意修改调用合约的存储
delegatecall 是 callcode 的一个 bug fix,delegatecall 保留了 callcode 没有保留的 msg.sender 与 msg.value,下面来看一个具体的例子:
contract D {
uint public n;
address public sender;
function callSetN(address _e, uint _n) {
_e.call(bytes4(sha3("setN(uint256)")), _n); // E's storage is set, D is not modified
}
function callcodeSetN(address _e, uint _n) {
_e.callcode(bytes4(sha3("setN(uint256)")), _n); // D's storage is set, E is not modified
}
function delegatecallSetN(address _e, uint _n) {
_e.delegatecall(bytes4(sha3("setN(uint256)")), _n); // D's storage is set, E is not modified
}
}
contract E {
uint public n;
address public sender;
function setN(uint _n) {
n = _n;
sender = msg.sender;
// msg.sender is D if invoked by D's callcodeSetN. None of E's storage is updated
// msg.sender is C if invoked by C.foo(). None of E's storage is updated
}
}
contract C {
function foo(D _d, E _e, uint _n) {
_d.delegatecallSetN(_e, _n);
}
}
当合约 D CALL 合约 E 的时候,代码运行在 E 的上下文环境中:合约 E 的存储被使用。
当合约 D CALLCODE 合约 E 的时候,代码运行在 D 的上下文环境中,即合约 E 的代码存在于合约 D 中。当该代码修改存储的时候,发生变化的是合约 D 的存储,而不是 E 的。
当合约 D CALLCODE 合约 E 的时候,在 E 中调用 msg.sender
,得到的结果是 D。
当一个合约 C 调用 D,然后 D 再 DELEGATECALL 合约 E,此时在 E 中调用 msg.sender
,得到的结果是 C,也就是说 E 中有着与 D 中相同的 msg.sender
与 msg.value
。
3. 异常处理
在智能合约中,我们常常会去调用某一个合约的函数,这时候通常有两种方法:
使用 call/callcode/delegatecall/send/transfer 底层函数;
使用被调用合约的名称以及它的函数名,算是高层次的调用
当被调用的合约函数出现异常时,这两种方法在 solidity 编译器处的处理逻辑是不一样的,底层函数的使用,会返回 true 或 false 交由调用合约来处理;高层次的调用会 bubbling up,意思是会导致调用合约抛出异常,进而回滚整个交易的操作,具体见 https://solidity.readthedocs.io/en/latest/control-structures.html?highlight=send#exceptions