staticcall 类似于常规的 Ethereum 调用,不同之处在于,如果发生状态更改,它将会回滚(revert)。它不能用于转移 Ether。EVM 操作码、Yul 汇编函数以及内置的 Solidity 函数都名为 staticcall。
EIP 214
staticcall 在 EIP 214 中被引入,并在 2017 年作为 Byzantium hard fork 的一部分被添加到 Ethereum 中。对于只希望获得返回值且不希望发生状态更改的函数,它增加了一层安全性。如果被调用的合约导致状态更改,staticcall 将会回滚,这些更改包括:记录事件(logging an event)、发送 Ether、创建合约、销毁合约或设置存储变量。读取存储变量是允许的。只要不导致状态更改,不转移 Ether 的调用也是被允许的。
View 函数
Solidity 在编译调用外部合约的 view 函数时,会在底层使用 staticcall。
这里有一个最小示例:
contract ERC20User {
IERC20 public token;
// rest of the code
function myBalance() public view returns (uint256 balance) {
balance = token.balanceOf(address(this));
}
function myBalanceLowLevelEquivalent() public view returns (uint256 balance) {
(bool ok, bytes memory result) = token.staticcall(abi.encodeWithSignature("balanceOf(address)", address(this)));
require(ok);
balance = abi.decode(result, (uint256));
}
}
如果 balanceOf 更改了状态,代码将会回滚。
元参数
可以按以下方式指定转发的 gas 数量。转发的 gas 数量受 EIP 150 中描述的 63/64 规则限制。
staticcall{gas: gasAmount}(abiEncodedArguments);
攻击向量
尽管在某种意义上 staticcall 比常规调用“更安全”,但这并不意味着在使用它时可以忽视安全性。
拒绝服务
staticcall 仍然容易受到伴随 gas 恶意消耗(gas griefing)的拒绝服务攻击。考虑上述 ERC20 代币在 balanceOf 函数内部放入无限循环的情况。根据 EIP 150 的规定,调用合约仍会有剩余的 gas,但只有原始数量的 1/64。
只读重入
staticcall 的另一个攻击向量是获取到错误的返回值。在只读重入攻击中,代币价值被闪电贷暂时操纵,因此任何(通过 staticcall)查看这些价值的智能合约都可能遭到预言机操纵攻击。
Yul 汇编中的 Staticcall
在 Yul 汇编中,staticcall 的参数如下:
let ok := staticcall(gas, addressToCall, in, insize, out, outsize)
参数 out 和 outsize 是返回值将被复制到的内存区域。然而,这两个值通常被设置为零,并使用 “returndatacopy” 和 “returndatasize” 来灵活处理可变大小的返回值。
这些操作码在 EIP 211 中被引入,消除了预测来自其他智能合约返回值长度的需要。
请看此示例:
returndatacopy(0, 0, returndatasize())
变量 in 和 insize 指向调用外部合约的数据部分(通常经过 ABI 编码)所在的内存区域。
如果 staticcall 成功,变量 ok 返回 true;如果回滚,则返回 false。回滚(Reverts)不会向上冒泡(bubbled up)。
与预编译智能合约的结合使用
staticcall 是与 Ethereum 预编译合约(地址 0x01 到 0x09)交互的恰当方式,因为它们都不会更改状态。
下面的示例将计算一个 uint 的 SHA256 哈希值。请注意,此预编译直接对数据进行哈希处理,不应将其进行 ABI 编码。
function getSha256(uint256 x) public view returns (bytes32 hashOut) {
(bool ok, bytes memory result) = address(0x02).staticcall(abi.encode(x));
require(ok);
hashOut = abi.decode(result, (bytes32));
}
了解更多
请参阅我们的专家级 Solidity 开发者训练营 以了解更多高级 Ethereum 开发概念。我们也提供免费的 Solidity 课程。
最初发布于 2023 年 4 月 10 日