我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。
所有代码和教程开源在github: github.com/WTFAcademy/WTF-Opcodes
这一讲,我们介绍EVM中的STATICCALL
指令,它和CALL
指令类似,允许合约执行其他合约的代码,但是不能改变合约状态。它是Solidity中pure
和view
关键字的基础。
STATICCALL 指令
STATICCALL
指令会创建一个子环境来执行其他合约的部分代码,并返回数据。返回数据可以使用RETURNDATASIZE
和RETURNDATACOPY
获取。若执行成功,会将1
压入堆栈;否则,则压入0
。如果目标合约没有代码,仍将1
压入堆栈(视为成功)。
和CALL
指令的不同,STATICCALL
不能发送ETH
,也不能改变合约的状态。它不允许子环境执行的代码中包含以下指令:
CREATE
,CREATE2
,SELFDESTRUCT
LOG0
-LOG4
SSTORE
value
不为0的CALL
它从堆栈中弹出6个参数,依次为:
gas
:为这次调用分配的gas量。to
:被调用合约的地址。mem_in_start
:输入数据(calldata)在内存的起始位置。mem_in_size
:输入数据的长度。mem_out_start
:返回数据(returnData)在内存的起始位置。mem_out_size
:返回数据的长度。
它的操作码为0xFA
,gas消耗为:内存扩展成本+地址操作成本。
下面,我们在极简evm中实现STATICCALL
指令。首先,我们需要检查子环境的代码是否包含STATICCALL
不支持的指令:
然后在init()
函数中初始化一个is_static
状态,当它为true
时,意味着执行的是STATICCALL
,需要检查不支持的指令:
此外,对于不为0的value的CALL,我们需要稍作修改:
最后,我们可以加入staticcall
函数:
测试
在测试中,我们会使用第一个地址(0x9bbf
起始)调用第二个地址(0x1000
起始),运行上面的代码(PUSH1 0x42 PUSH1 0 MSTORE PUSH1 1 PUSH1 31 RETURN
),成功的话会返回0x42
。
测试字节码为6001601f5f5f731000000000000000000000000000000000000c425ffA5f51
(PUSH1 1 PUSH1 31 PUSH0 PUSH0 PUSH20 1000000000000000000000000000000000000c42 PUSH0 STATICCALL PUSH0 MLOAD),它会调用第二个地址上的代码,然后将内存中的返回值0x42
压入堆栈。
总结
这一讲,我们探讨了STATICCALL
指令,它提供了一种安全的方法来执行其他合约的代码,而不修改合约状态,是Solidity中pure
和view
关键字的基础。目前,我们已经学习了144个操作码中的140个(97%)!