文章目录
- 单位和全局可用变量(Units and Globally Available Variables)
- 特殊变量和函数
- 1. 区块和交易属性
- 2. ABI 编码和解码函数
- 3. bytes 成员函数
- 4. string 成员函数
- 5. 错误处理
- 6. 数学和加密函数
- 7. 地址类型成员函数
- 8. 与合约相关
- 9. 类型信息
单位和全局可用变量(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):将多个 bytes
和 bytes1
到 bytes32
类型的参数连接成一个字节数组。
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 可表示的最大值。