Solidity语言详解——view和pure函数的使用区别

Solidity 语言有两类和状态读写有关的函数类型,一类是 view 函数(也称为视图函数),另一类是 pure 函数(也称为纯函数)。他们的区别是 view 函数不修改状态,pure 函数即不修改状态也不读取状态。

view 函数

可以将函数声明为 view 函数类型,这种情况下函数保证不修改状态。

如果编译器的 EVM 目标是拜占庭或更新的(默认),则在调用 view 函数时使用操作码 STATICCALL,这将强制状态在执行EVM时保持不变。对于库 view 函数使用了 DELEGATECALL,因为没有组合 DELEGATECALLSTATICCALL。这意味着库 view 函数没有防止状态修改的运行时检查。这应该不会对安全性产生负面影响,因为库代码通常在编译时已知,而静态检查器执行编译时检查。

下面的语句被认为是在修改状态:

  1. 修改状态变量;
  2. 触发事件;
  3. 创建其他合约;
  4. 使用 selfdestruct
  5. 通过调用发送以太币;
  6. 调用任何未标记 viewpure 的函数;
  7. 使用低级调用;
  8. 使用包含某些操作码的内联汇编。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;

contract C {
    function f(uint a, uint b) public view returns (uint) {
        return a * (b + 42) + block.timestamp;
    }
}

constant 曾经是 view 的别名,但在0.5.0版本中删除了这一点。

Getter 方法被自动标记为 view

在 0.5.0 版本之前,编译器没有为 view 函数使用 STATICCALL 操作码。这样可以通过使用无效的显式类型转换来修改 view 函数中的状态。通过对 view 函数使用 STATICCALL,在EVM级别上可以防止对状态的修改。

pure 函数

在保证不读取或修改状态的情况下,函数可以被声明为 pure 函数。特别是,在编译时只给出函数输入和msg.data ,但又不知道当前区块链状态的情况下,建议使用 pure 函数。这意味着对 immutable 变量的读取可以是非纯操作。

如果编译器的 EVM 目标是拜占庭或更新的(默认),则使用操作码 STATICCALL,这不能保证状态没有被读取,但至少它没有被修改。

除了上面解释的状态修改语句列表,下面的语句被认为是读取状态的:

  1. 从状态变量中读取;
  2. 访问 address(this).balanceaddress.balance
  3. 访问 block, tx, msg 的任何成员(除了 msg.sigmsg.data);
  4. 调用任何未标记为 pure 的函数;
  5. 使用包含某些操作码的内联汇编。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;

contract C {
    function f(uint a, uint b) public pure returns (uint) {
        return a * (b + 42);
    }
}

纯函数可以使用 revert()require() 函数在发生错误时恢复潜在的状态变化。

恢复状态变化不被认为是一种“状态修改”,因为只有在以前没有 viewpure 限制的代码中对状态所做的更改才会被恢复,并且该代码可以选择捕获 revert 而不传递它。

这种行为也符合 STATICCALL 操作码。

在 EVM 级别不可能阻止函数读取状态,只能阻止它们写入状态(即只有 view 可以在 EVM 级别强制执行,而pure 不能)。

在 0.5.0 版本之前,编译器没有为 pure 函数使用 STATICCALL 操作码。这允许通过使用无效的显式类型转换在 pure 函数中修改状态。通过对 pure 函数使用 STATICCALL,在 EVM 级别上可以防止对状态的修改。

在 0.4.17 版本之前,编译器没有强制 pure 函数不读取状态。这是一个编译时类型检查,可以规避做无效的合约类型之间的显式转换,因为编译器可以验证合约的类型不做状态改变操作,但不能在运行时检查调用合约的实际类型。

猜你喜欢

转载自blog.csdn.net/zyq55917/article/details/124428142