【区块链安全 | 第二十四篇】单位和全局可用变量(二)

发布于:2025-04-01 ⋅ 阅读:(20) ⋅ 点赞:(0)

在这里插入图片描述

单位和全局可用变量(Units and Globally Available Variables)

特殊变量和函数

1. 区块和交易属性

在全局命名空间中始终存在一些特殊变量和函数,主要用于提供区块链相关信息或作为通用工具函数。

  • blockhash(uint blockNumber) returns (bytes32):返回指定区块的哈希值,仅适用于最近 256 个区块;否则返回 0。
  • blobhash(uint index) returns (bytes32):返回当前交易中第 index 个 blob 的版本化哈希值。版本化哈希由 1 个字节的版本号(目前为 0x01)和 KZG 承诺的 SHA256 哈希的后 31 个字节组成(EIP-4844)。如果不存在该索引的 blob,则返回 0。
  • block.basefee (uint):当前区块的基础费用(EIP-3198 和 EIP-1559)。
  • block.blobbasefee (uint):当前区块的 blob 基础费用(EIP-7516 和 EIP-4844)。
  • block.chainid (uint):当前链的 Chain ID。
  • block.coinbase (address payable):当前区块矿工的地址。
  • block.difficulty (uint):当前区块的难度(仅适用于 Paris 之前的 EVM 版本)。在其他 EVM 版本中,它是 block.prevrandao 的废弃别名(EIP-4399)。
  • block.gaslimit (uint):当前区块的 gas 限制。
  • block.number (uint):当前区块的编号。
  • block.prevrandao (uint):由信标链提供的随机数(适用于 EVM >= Paris)。
  • block.timestamp (uint):当前区块的时间戳(自 Unix 纪元以来的秒数)。
  • gasleft() returns (uint256):返回当前交易中剩余的 gas。
  • msg.data (bytes calldata):完整的 calldata(调用数据)。
  • msg.sender (address):消息(当前调用)的发送者地址。
  • msg.sig (bytes4):calldata 的前 4 个字节(即函数标识符)。
  • msg.value (uint):随消息发送的 Wei 数量。
  • tx.gasprice (uint):交易的 gas 价格。
  • tx.origin (address):交易的原始发送者地址(完整调用链中的最初发起者)。

注意
1.msg 变量的动态性
msg.sender、msg.value 等 msg 成员的值会在每次外部函数调用时发生变化,包括调用库函数时。

2.区块和交易属性的限制
当合约在链下执行(如本地测试或模拟环境)时,不要假设 block. 和 tx. 具有特定的值,这些值取决于 EVM 实现。

3.不要依赖 block.timestamp 或 blockhash 作为随机数源
block.timestamp 和 blockhash 可能受到矿工的操纵,在某些应用中,恶意矿工可以重复计算直到获得有利的哈希值。当前区块的时间戳必须严格大于上一个区块的时间戳,但唯一的保证是它位于两个连续区块的时间戳之间。

4.blockhash 的可用性
由于可扩展性原因,并非所有区块的哈希值都可用,只有最近 256 个区块的哈希值可访问,超过该范围的值将返回 0。

5.废弃的函数和别名:
block.blockhash 在 Solidity 0.4.22 版本被弃用,并在 0.5.0 版本中移除。
msg.gas 在 Solidity 0.4.21 版本被弃用,并在 0.5.0 版本中移除(替换为 gasleft())。
now(block.timestamp 的别名)在 Solidity 0.7.0 版本中被移除。

2. ABI 编码和解码函数

  • abi.decode(bytes memory encodedData, (…)) returns (…):对给定的 encodedData 进行 ABI 解码,第二个参数括号内指定解码后的数据类型。示例:
    solidity
    (uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes));

  • abi.encode(…) returns (bytes memory):对给定的参数进行 ABI 编码。

  • abi.encodePacked(…) returns (bytes memory):对给定的参数进行紧凑编码(packed encoding)。注意:紧凑编码可能会导致数据歧义!

  • abi.encodeWithSelector(bytes4 selector, …) returns (bytes memory):以 selector 作为前缀,对后续参数进行 ABI 编码。

  • abi.encodeWithSignature(string memory signature, …) returns (bytes memory):等效于:
    solidity
    abi.encodeWithSelector(bytes4(keccak256(bytes(signature))), …)

  • abi.encodeCall(function functionPointer, (…)) returns (bytes memory):对函数指针 functionPointer 及其参数进行 ABI 编码,同时进行完整的类型检查,确保参数类型与函数签名匹配。结果等价于:
    solidity
    abi.encodeWithSelector(functionPointer.selector, (…))

注意
1.这些编码函数可用于构造外部函数调用的数据,而不实际调用该函数。
2.keccak256(abi.encodePacked(a, b)) 可用于计算结构化数据的哈希值。警告:不同的参数类型可能导致哈希碰撞,应谨慎使用。
3.详细的 ABI 编码规则和紧凑编码(tightly packed encoding)请参考 Solidity 文档。

3. bytes 成员函数

bytes.concat(…) returns (bytes memory):将多个 bytesbytes1bytes32 类型的参数连接成一个字节数组。

4. string 成员函数

string.concat(…) returns (string memory):将多个字符串参数连接成一个字符串。

5. 错误处理

  • assert(bool condition):如果条件不成立,会触发 Panic 错误并回滚状态更改 - 用于内部错误。

  • require(bool condition):如果条件不成立,回滚并撤销交易 - 用于输入错误或外部组件错误。

  • require(bool condition, string memory message):如果条件不成立,回滚并撤销交易,并提供错误消息 - 用于输入错误或外部组件错误。

  • revert():中止执行并回滚状态更改。

  • revert(string memory reason):中止执行并回滚状态更改,并提供一个解释字符串。

6. 数学和加密函数

  • addmod(uint x, uint y, uint k) returns (uint) :计算 (x + y) % k,其中加法是以任意精度执行的,不会在 2^256 上溢出。从版本 0.5.0 起,确保 k != 0。

  • mulmod(uint x, uint y, uint k) returns (uint):计算 (x y) % k,其中乘法是以任意精度执行的,不会在 2^256 上溢出。从版本 0.5.0 起,确保 k != 0。

  • keccak256(bytes memory) returns (bytes32):计算输入的 Keccak-256 哈希值。
    之前 keccak256 有一个别名叫 sha3,在版本 0.5.0 中已被移除。

  • sha256(bytes memory) returns (bytes32):计算输入的 SHA-256 哈希值。

  • ripemd160(bytes memory) returns (bytes20):计算输入的 RIPEMD-160 哈希值。

  • ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address):从椭圆曲线签名中恢复与公钥相关联的地址,如果发生错误则返回零。该函数参数对应于 ECDSA 签名的值:

    • r = 签名的前 32 字节
    • s = 签名的第二个 32 字节
    • v = 签名的最后 1 字节

    ecrecover 返回一个地址,而不是一个可支付地址。如果需要将资金转移到恢复的地址,可以通过 address payable 进行转换。

    进一步说明
    在使用 ecrecover 时需要注意,一个有效的签名可以被转换成另一个有效的签名,而无需了解对应的私钥。在 Homestead 硬分叉中,针对交易签名的问题(参见 EIP-2)已被修复,但 ecrecover 函数未做更改。通常这不会造成问题,除非你要求签名是唯一的,或者使用签名来识别项目。你可以使用 OpenZeppelin 的 ECDSA 辅助库,它是 ecrecover 的封装,避免了这个问题。

注意
在私有区块链上运行 sha256、ripemd160 或 ecrecover 时,可能会遇到 “Out-of-Gas” 错误。这是因为这些函数作为“预编译合约”实现,只有在接收到第一个消息后才真正存在(尽管它们的合约代码是硬编码的)。发送到不存在的合约的消息更昂贵,因此执行可能会出现 “Out-of-Gas” 错误。解决该问题的方法是,先向每个合约发送 Wei(例如 1),然后再在实际合约中使用它们。这个问题在主网或测试网上并不存在。

7. 地址类型成员函数

1.<address>.balance (uint256) 返回地址的余额,以 Wei 为单位。

2.<address>.code (bytes memory) 返回地址处的代码(可能为空)。

3.<address>.codehash (bytes32) 返回地址的代码哈希。

4.<address payable>.transfer(uint256 amount) 向地址发送指定的 Wei 数量,失败时回滚,转发 2300 gas 补助,不可调节。

5.<address payable>.send(uint256 amount) returns (bool) 向地址发送指定的 Wei 数量,失败时返回 false,转发 2300 gas 补助,不可调节。

6.<address>.call(bytes memory) returns (bool, bytes memory) 进行低级 CALL,带有给定的有效载荷,返回成功状态和返回数据,转发所有可用的 gas,可以调节。

7.<address>.delegatecall(bytes memory) returns (bool, bytes memory) 进行低级 DELEGATECALL,带有给定的有效载荷,返回成功状态和返回数据,转发所有可用的 gas,可以调节。

8.<address>.staticcall(bytes memory) returns (bool, bytes memory) 进行低级 STATICCALL,带有给定的有效载荷,返回成功状态和返回数据,转发所有可用的 gas,可以调节。

警告
1.尽量避免使用 .call(),因为它绕过了类型检查、函数存在性检查和参数打包。
2.使用 send 时有一些危险:当调用栈深度达到 1024 时,转账会失败(这可以被调用者强制),如果接收者没有足够的 gas,也会失败。因此,为了确保安全的以太币转账,始终检查 send 的返回值,使用 transfer 或更好的方式:使用一种模式,让接收者主动提取以太币。
3.由于 EVM 将对不存在的合约的调用视为始终成功,因此 Solidity 在执行外部调用时使用 extcodesize 操作码进行了额外检查。这确保了即将调用的合约实际上存在(它包含代码),否则会抛出异常。对地址而不是合约实例进行操作的低级调用不包括此检查(即.call()、.delegatecall()、.staticcall()、.send() 和 .transfer()),这使得它们在 gas 上更便宜,但也更不安全。

注意
1.在版本 0.5.0 之前,Solidity 允许通过合约实例访问地址成员,例如 this.balance。现在已被禁止,必须显式转换为地址:address(this).balance。
2.如果通过低级 delegatecall 访问状态变量,两个合约的存储布局必须一致,以便被调用合约能够正3.确按名称访问调用合约的存储变量。当然,如果传递存储指针作为函数参数(如高层库的情况),则存储布局不会一致。
4.在版本 0.5.0 之前,.call、.delegatecall 和 .staticcall 只返回成功状态,而不返回返回数据。
5.在版本 0.5.0 之前,有一个名为 callcode 的成员,它与 delegatecall 的语义略有不同。

8. 与合约相关

1.this (当前合约的类型):当前合约,可以显式转换为地址类型。

2.super:继承层次结构中上一级的合约。

3.selfdestruct(address payable recipient): 销毁当前合约,将其资金发送到给定的地址并结束执行。selfdestruct有一些继承自 EVM 的特性:

  • 接收合约的 receive 函数不会被执行。
  • 合约仅在交易结束时被真正销毁,并且回滚可能会“撤销”销毁操作。
  • 此外,当前合约的所有函数都可以直接调用,包括当前函数。

警告
1.从 EVM >= Cancun 开始,selfdestruct 将只会将账户中的所有以太币发送到给定的接收者,而不再销毁合约。然而,当 selfdestruct 在同一交易中被调用,并且创建了调用它的合约时,selfdestruct 会遵循 Cancun 硬分叉之前(即 EVM <= Shanghai)的行为,仍然会销毁当前合约,删除所有数据,包括存储键、代码和合约本身。详情请参见 EIP-6780。
2.新的行为是全网范围的变化,影响所有部署在以太坊主网和测试网的合约。需要注意的是,这一变化取决于合约部署链的 EVM 版本,编译合约时使用的 --evm-version 设置不会影响此行为。
3.此外,selfdestruct 操作码在 Solidity 版本 0.8.18 中已被弃用,按照 EIP-6049 的建议,弃用仍然有效,编译器会在使用时发出警告。在新部署的合约中强烈不建议使用,即使考虑到新的行为,未来 EVM 的更改可能会进一步减少该操作码的功能。

注意
在0.5.0版本之前,有一个名为suicide的函数,语义与selfdestruct相同。

9. 类型信息

表达式 type(X) 可用于检索关于类型 X 的信息。目前,支持此功能的类型有限(X 可以是合约类型或整数类型),但未来可能会扩展。

对于合约类型 C,以下属性可用:

  • type©.name:合约的名称

  • type©.creationCode:包含合约创建字节码的内存字节数组。可以在内联汇编中使用此字节码构建自定义创建例程,特别是通过使用 create2 操作码。该属性无法在合约本身或任何派生合约中访问,它会导致字节码被包含在调用站点的字节码中,因此像这样的循环引用是不可行的。

  • type©.runtimeCode:包含合约运行时字节码的内存字节数组。通常,这是合约 C 的构造函数部署的代码。如果 C 的构造函数使用了内联汇编,这可能与实际部署的字节码不同。还要注意,库在部署时会修改其运行时字节码,以防止常规调用。与 .creationCode 相同的限制也适用于此属性。

除了上述属性,以下属性适用于接口类型 I:

  • type(I).interfaceId:一个 bytes4 值,包含给定接口 I 的 EIP-165 接口标识符。此标识符是接口中所有函数选择器的 XOR,排除了所有继承的函数。

对于整数类型 T,以下属性可用:

  • type(T).min:类型 T 可表示的最小值。

  • type(T).max:类型 T 可表示的最大值。


网站公告

今日签到

点亮在社区的每一天
去签到