{"content":{"title":"Solidity Staticcall EIP 214","body":"Staticcall 类似于常规的以太坊调用，不同的是，如果发生状态更改，它将回滚。它不能用于转账以太币。无论是 EVM 操作码、Yul 汇编函数还是 Solidity 内置函数，都称为 staticcall。\r\n\r\n## EIP 214\r\n\r\nStaticcall 是在 [EIP 214](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-214.md) 中引入的，该提案于 2017 年作为 [Byzantium 硬分叉](https://ethereum.org/en/history/#byzantium)的一部分被添加到以太坊中。对于那些只需要返回值而不希望有状态更改的函数，它增加了一层安全性。如果被调用的合约导致状态更改，Staticcall 将回滚，这些状态更改包括：记录事件、发送以太币、创建合约、销毁合约或设置存储变量。读取存储变量是可以的。只要不导致状态更改，不转账以太币的调用也是允许的。\r\n\r\n## View 函数\r\n\r\nSolidity 将调用外部合约的 view 函数编译为在底层使用 staticcall。\r\n\r\n以下是一个简单的示例：\r\n\r\n```solidity\r\ncontract ERC20User {\r\n    IERC20 public token;\r\n\r\n    // 其余代码\r\n\r\n    function myBalance() public view returns (uint256 balance) {\r\n        balance = token.balanceOf(address(this));\r\n    }\r\n\r\n    function myBalanceLowLevelEquivalent() public view returns (uint256 balance) {\r\n        (bool ok, bytes memory result) = token.staticcall(abi.encodeWithSignature(\"balanceOf(address)\", address(this)));\r\n        require(ok);\r\n        \r\n        balance = abi.decode(result, (uint256));\r\n        \r\n    } \r\n}\r\n```\r\n\r\n如果 `balanceOf` 更改了状态，代码将回滚。\r\n\r\n## 元参数\r\n\r\n可以以下方式指定转发的 gas 数量。转发的 gas 数量受 [EIP 150](https://learnblockchain.cn/article/11230) 中描述的 63/64 规则限制。\r\n\r\n```solidity\r\nstaticcall{gas: gasAmount}(abiEncodedArguments);\r\n```\r\n\r\n## 攻击向量\r\n\r\n尽管 staticcall 在某种程度上比常规调用“更安全”，但这并不意味着在使用时可以忽视安全性。\r\n\r\n### 拒绝服务\r\n\r\nStaticcall 仍然会受到 gas 消耗型拒绝服务攻击的影响。考虑上述 ERC20 代币在 balanceOf 函数中放入一个无限循环的情况。根据 [EIP 150](https://learnblockchain.cn/article/11230)，调用合约仍然会有剩余的 gas，但只有原始数量的 1/64。\r\n\r\n### 只读重入\r\n\r\nStaticcall 的另一个攻击向量是获得了错误的值。在 [只读重入攻击](https://learnblockchain.cn/article/10525) 中，代币值通过闪电贷被临时操纵，因此任何查看值（通过 staticcall）的智能合约都可能因预言机操纵而被攻击。\r\n\r\n## Yul 汇编中的 Staticcall\r\n\r\n在 Yul 汇编中，staticcall 的参数如下：\r\n\r\n```solidity\r\nlet ok := staticcall(gas, addressToCall, in, insize, out, outsize)\r\n```\r\n\r\n参数 out 和 outsize 是内存中返回值将被复制到的区域。然而，这两个值通常设置为零，并使用“returndatacopy”和“returndatasize”来灵活处理可变长度的返回值。\r\n\r\n这些操作码在 [EIP 211](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-211.md) 中引入，消除了预测其他智能合约返回值长度的需求。\r\n\r\n参见以下示例：\r\n\r\n```solidity\r\nreturndatacopy(0, 0, returndatasize())\r\n```\r\n\r\n变量 in 和 insize 指向内存中调用外部合约的（通常是 abi 编码的）数据部分。\r\n\r\n如果 staticcall 成功，变量 ok 返回 true；如果回滚，则返回 false。回滚不会向上传递。\r\n\r\n## 与预编译智能合约的使用\r\n\r\nStaticcall 是与 [以太坊预编译合约](https://www.rareskills.io/post/solidity-precompiles)（地址 0x01 到 0x09）交互的适当方式，因为它们都不会更改状态。\r\n\r\n以下示例将计算一个 uint 的 SHA256。请注意，此预编译直接哈希数据，而不应进行 [abi 编码](https://learnblockchain.cn/article/8296)。\r\n\r\n```solidity\r\n function getSha256(uint256 x) public view returns (bytes32 hashOut) {\r\n    (bool ok, bytes memory result) = address(0x02).staticcall(abi.encode(x));\r\n    require(ok);\r\n    \r\n    hashOut = abi.decode(result, (bytes32));\r\n} \r\n```\r\n\r\n## 了解更多\r\n\r\n参见我们的专家 [Solidity 开发者训练营](https://learnblockchain.cn/openspace/1) 以了解更多高级以太坊开发概念。我们还提供 [免费的 Solidity 课程](https://learnblockchain.cn/column/1)。\r\n\r\n**最初发布于 2023 年 4 月 10 日**\r\n\r\n>- 原文链接： [rareskills.io/post/solid...](https://www.rareskills.io/post/solidity-staticcall)\r\n>- 登链社区 AI 助手，为大家转译优秀英文文章，如有翻译不通的地方，还请包涵～"},"author":{"user":"https://learnblockchain.cn/people/20722","address":null},"history":null,"timestamp":1740545862,"version":1}