静态调用 staticcall
本章讲解在 Solidity
中,如何使用 staticcall
来调用其它合约的函数。
在 Solidity 中,staticcall
是一个用于在智能合约中调用外部合约函数的一种方式。
staticcall
是一个低级别的操作,它允许一个合约在调用外部合约函数时,仅限于读取外部合约的数据而不修改它的状态。
也就是说,staticcall
的只能调用外部合约的视图函数和纯函数,即函数的状态可变性为 view
或 pure
函数。
staticcall
的使用方法,如下所示:
contract StaticCaller { function foo(address externalAddress, bytes memory data) external view returns (bool, bytes memory) { (bool success, bytes memory returnData) = externalAddress.staticcall(data); return (success, returnData); } }
在上面的例子中,StaticCaller
合约中的 foo
函数中,使用了 staticcall
。
它接受一个外部合约的地址 externalAddress
和一个字节数组 data
,这个数组包含了要调用的外部合约函数的数据。
staticcall
返回了两个值:一个是布尔值,表示调用是否成功;另一个是 bytes
类型的数据,包含调用的返回值。
需要注意的是,staticcall
的限制是它不能修改合约的状态。如果尝试在 staticcall
中调用会改变状态的函数,会导致异常并且返回失败。
1. staticcall 实现原理
staticcall
是 EVM
中的一条指令,指令代码是 0xfa。 当执行 staticcall
调用一个外部合约的函数时,它会将 EVM
解释器的状态 readonly
置为 true
。
func (evm *EVM) StaticCall(....) (ret []byte, leftOverGas uint64, err error) { ..... ret, err = evm.interpreter.Run(contract, input, true) /*readonly=true*/ .... }
当 EVM
执行外部合约的函数时,如果解释器的状态 readonly
为 true
,那么该函数就不能执行状态变量存储指令 opSstore
。也就是说,该外部合约的函数不能改变合约状态。
func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { .... if interpreter.readOnly { return nil, ErrWriteProtection } .... }
2. 示例合约
下面是一个使用静态调用 staticcall
的示例合约:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // 被调用合约 contract Callee { // 被调用函数 function bar() external pure returns(uint256) { return 1; } } // 静态调用函数 contract StaticCaller { function foo(address externalAddress) external view returns (bool, bytes memory) { // 构造bar() 的函数选择器 bytes memory data = abi.encodeWithSignature("bar()"); // 静态调用合约 externalAddress 的函数 bar (bool success, bytes memory returnData) = externalAddress.staticcall(data); return (success, returnData); } }
我们将上面的合约复制到 Remix
,先进行编译。
编译后会有两个合约 Callee
和 StaticCaller
,我们要首先部署被调用合约 Callee
,然后再部署静态调用合约 StaticCaller
。
我们将 Callee
合约的地址填写到 StaticCaller
的 foo
参数位置,然后点击 foo
按钮进行调用。
我们可以看到调用结果为 true
,表示调用成功。而被调用合约 Callee
的函数 bar
,返回了结果值 1。