(七)特殊函数
底层函数交互
特殊交互方式
call 是底层的调用(没有封装过),直接发送消息给合约。方式如下:
- 所有的参数,都会打包成一串 32 个字节,连续存放的序列。
- 若第一个参数是函数的签名(函数哈希之后的前 4 个字节),则第二、第三这些后面的参数是函数的参数。如:
nameReg.call(bytes4(keccak256("fun(uint256)")), a);
-
<address>.call(bytes memory) returns (bool, bytes memory)
用给定的合约发出低级
CALL
调用,返回成功状态及返回数据,发送所有可用 gas,也可以调节 gas。
1 | // SPDX-License-Identifier: MIT |
-
<address>.delegatecall(bytes memory) returns (bool, bytes memory)
用给定的合约发出低级
DELEGATECALL
调用 ,返回成功状态并返回数据,失败时返回false
。上下文属于发出调用的合约。
1 | // SPDX-License-Identifier: MIT |
-
<address>.staticcall(bytes memory) returns (bool, bytes memory)
用给定的有效载荷 发出低级
STATICCALL
调用 ,如果改变了被调用合约的状态,立即回滚。
注意:.call
会绕过类型检查,函数存在检查和参数打包。
注意:send
调用栈深度达到 1024 就会失败。
注意:0.5.0 以后不允许通过合约实例来访问地址成员this.balance
。0.5.0 以前,底层调用只会返回是否成功不会返回数据。
注意:因为 EVM 不会检查调用的合约是否存在,并且总是把调用不存在的合约视为成功,因此提供了 extcodesize
的操作码,确认合约存在(即合约地址内有代码),否则引起异常。注意底层调用不会触发。
注意:底层的调用略去了很多检查,使得他们更加节省 gas 但是安全性更低。
receive 函数
一个合约至多有一个 receive 函数,形如 receive() external payable { ... }
,注意:
- 没有
function
的标识 - 没有参数
- 只能是
external
和payable
标识 - 可以有函数修饰器
- 支持重载。
receive
函数在调用数据为空时(如用 call
传入空字节,或者转账)执行,如果没有设置 receive
函数,那么就会执行 fallback
函数,如果这两个函数都不存在,合约就不能通过交易的形式获取以太币。
注意 receive
函数只有 2300gas 可用,因此它进行其他操作的空间很小。 以下功能都因为超过消耗的 gas 而不能够实现。
- 写入存储
- 创建合约
- 调用消耗大量 gas 的外部函数
- 发送以太币
每一步都会消耗 2300gas.
我们建议只使用 receive
函数来接收以太币。
回退函数
一个合约至多一个回退函数,格式如:fallback () external [payable]
或者 fallback (bytes calldata _input) external [payable] returns (bytes memory _output)
,后者的函数参数会接收完整的调用数据(msg.data
),返回未经过 ABI 编码的原始数据。
- 回退函数只当没有与调用数据匹配的函数签名时执行。
- 可以重载,也可以被修饰器修饰。
- 在函数调用时,如果没有与之匹配的函数签名或者调用数据为空且无
receive
函数,就会调用fallbakc
函数。 - 如果回退函数代替了
receive
函数完成接收以太币的功能,那么仍然只有 2300gas 可用。