单位和全局可用变量

以太币单位

一个字面量数字可以带 weigweiether 后缀来指定以太币的子单位,其中没有后缀的以太币数字被认为是 Wei。

assert(1 wei == 1);
assert(1 gwei == 1e9);
assert(1 ether == 1e18);

子单位后缀的唯一影响是乘以 10 的幂。

注意

在 0.7.0 版本中,finneyszabo 单位已移除。

时间单位

secondsminuteshoursdaysweeks 这样的后缀可以放在字面量数字后面,用于指定时间单位,其中秒是基本单位,单位是以以下方式简单考虑的

  • 1 == 1 seconds

  • 1 minutes == 60 seconds

  • 1 hours == 60 minutes

  • 1 days == 24 hours

  • 1 weeks == 7 days

使用这些单位进行日历计算时要小心,因为并非每年都等于 365 天,而且由于 闰秒,甚至并非每一天都有 24 小时。由于闰秒无法预测,因此一个精确的日历库需要由外部预言机更新。

注意

由于上述原因,在 0.5.0 版本中,years 后缀已移除。

这些后缀不能应用于变量。例如,如果您想以天为单位解释函数参数,则可以使用以下方式

function f(uint start, uint daysAfter) public {
    if (block.timestamp >= start + daysAfter * 1 days) {
        // ...
    }
}

特殊变量和函数

有一些特殊变量和函数始终存在于全局命名空间中,主要用于提供有关区块链的信息,或者是一些通用实用函数。

区块和交易属性

  • blockhash(uint blockNumber) returns (bytes32): 当 blocknumber 是最近 256 个区块之一时,返回给定区块的哈希值;否则返回零

  • blobhash(uint index) returns (bytes32): 与当前交易关联的第 index 个 blob 的版本化哈希值。一个版本化哈希值包含一个表示版本的字节(目前为 0x01),以及 KZG 承诺 (EIP-4844) 的 SHA256 哈希值的最后 31 个字节。

  • block.basefee (uint): 当前区块的基本费用 (EIP-3198EIP-1559)

  • block.blobbasefee (uint): 当前区块的 blob 基本费用 (EIP-7516EIP-4844)

  • block.chainid (uint): 当前链 ID

  • block.coinbase (address payable): 当前区块矿工的地址

  • block.difficulty (uint): 当前区块难度 (EVM < Paris)。对于其他 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): 完整的调用数据

  • msg.sender (address): 消息(当前调用)的发送者

  • msg.sig (bytes4): 调用数据的头四个字节(即函数标识符)

  • msg.value (uint): 消息中发送的 Wei 数量

  • tx.gasprice (uint): 交易的 gas 价格

  • tx.origin (address): 交易(完整调用链)的发送者

注意

所有 msg 成员的值,包括 msg.sendermsg.value,对于每个外部函数调用都会发生变化。这包括对库函数的调用。

注意

当合约是在链外而不是在包含在区块中的交易的上下文中评估时,您不应该假设 block.*tx.* 引用任何特定区块或交易的值。这些值由执行合约的 EVM 实现提供,可以是任意的。

注意

不要依赖 block.timestampblockhash 作为随机数的来源,除非您知道自己在做什么。

时间戳和区块哈希都可以被矿工在一定程度上影响。例如,矿工社区中的恶意行为者可以在他们没有收到任何补偿(例如以太币)的情况下,在一个选择的哈希值上运行一个赌场支付函数,然后只在没有收到任何补偿的情况下重试一个不同的哈希值。

当前区块时间戳必须严格大于最后一个区块的时间戳,但唯一的保证是它将在规范链上两个连续区块的时间戳之间。

注意

出于可扩展性的原因,并非所有区块的区块哈希都可用。您只能访问最近 256 个区块的哈希值,所有其他值都将为零。

注意

blockhash 函数以前称为 block.blockhash,它在 0.4.22 版本中被弃用,并在 0.5.0 版本中被移除。

注意

gasleft 函数以前称为 msg.gas,它在 0.4.21 版本中被弃用,并在 0.5.0 版本中被移除。

注意

在 0.7.0 版本中,nowblock.timestamp 的别名)被移除。

ABI 编码和解码函数

  • abi.decode(bytes memory encodedData, (...)) returns (...): 使用给定的类型(作为第二个参数给出)对给定的数据进行 ABI 解码。例如:(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): 对给定的参数执行 打包编码。请注意,打包编码可能存在歧义!

  • abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory): 对从第二个参数开始的给定参数进行 ABI 编码,并在前面添加给定的四字节选择器。

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

  • abi.encodeCall(function functionPointer, (...)) returns (bytes memory): 对调用 functionPointer 的操作进行 ABI 编码,其中参数位于元组中。执行完整的类型检查,确保类型与函数签名匹配。结果等于 abi.encodeWithSelector(functionPointer.selector, (...))

注意

这些编码函数可用于创建外部函数调用的数据,而无需实际调用外部函数。此外,keccak256(abi.encodePacked(a, b)) 是一种计算结构化数据哈希的方法(尽管要注意,可以使用不同的函数参数类型来创建“哈希冲突”)。

有关编码的详细信息,请参阅有关 ABI紧凑打包编码 的文档。

bytes 的成员

string 的成员

错误处理

有关错误处理以及何时使用哪个函数的更多详细信息,请参阅有关 assert 和 require 的专用部分。

assert(bool condition)

如果条件不满足,则会导致恐慌错误,从而导致状态更改回滚 - 用于内部错误。

require(bool condition)

如果条件不满足,则会回滚 - 用于输入或外部组件中的错误。

require(bool condition, string memory message)

如果条件不满足,则会回滚 - 用于输入或外部组件中的错误。还会提供错误消息。

revert()

中止执行并回滚状态更改。

revert(string memory reason)

中止执行并回滚状态更改,提供说明性字符串。

数学和加密函数

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,而不是一个 address payable。如果需要将资金转移到恢复的地址,请参阅 address payable 以进行转换。

有关更多详细信息,请阅读 示例用法

警告

如果使用 ecrecover,请注意,有效的签名可以转换为不同的有效签名,而无需了解相应的私钥。在 Homestead 硬分叉中,此问题已针对_交易_签名得到修复(请参阅 EIP-2),但 ecrecover 函数保持不变。

除非需要签名是唯一的或使用它们来标识项目,否则这通常不是问题。OpenZeppelin 拥有一个 ECDSA 帮助程序库,您可以将其用作 ecrecover 的包装器,而不会出现此问题。

注意

在私有区块链上运行 sha256ripemd160ecrecover 时,您可能会遇到超出 Gas 的情况。这是因为这些函数作为“预编译合约”实现,并且只有在收到第一条消息后才会真正存在(尽管它们的合约代码是硬编码的)。对不存在的合约的消息更昂贵,因此执行可能会遇到超出 Gas 的错误。解决此问题的一种方法是在您在实际合约中使用这些合约之前,先向每个合约发送一些 Wei(例如 1)。这在主网或测试网上不是问题。

地址类型的成员

<address>.balance (uint256)

以 Wei 为单位的 地址 的余额。

<address>.code (bytes memory)

位于 地址 的代码(可以为空)。

<address>.codehash (bytes32)

地址 的代码哈希值。

<address payable>.transfer(uint256 amount)

将给定的 Wei 数量发送到 地址,在失败时会回滚,转发 2300 个 Gas 津贴,不可调整。

<address payable>.send(uint256 amount) returns (bool)

将给定的 Wei 数量发送到 地址,在失败时返回 false,转发 2300 个 Gas 津贴,不可调整。

<address>.call(bytes memory) returns (bool, bytes memory)

使用给定的有效负载发出低级 CALL,返回成功条件和返回数据,转发所有可用 Gas,可调整。

<address>.delegatecall(bytes memory) returns (bool, bytes memory)

使用给定的有效负载发出低级 DELEGATECALL,返回成功条件和返回数据,转发所有可用 Gas,可调整。

<address>.staticcall(bytes memory) returns (bool, bytes memory)

使用给定的有效负载发出低级 STATICCALL,返回成功条件和返回数据,转发所有可用 Gas,可调整。

有关更多信息,请参阅有关 地址 的部分。

警告

在执行另一个合约函数时,应尽可能避免使用 .call(),因为它会绕过类型检查、函数存在检查和参数打包。

警告

使用 send 有几个风险:如果调用栈深度达到 1024,传输将失败(这可以始终由调用者强制执行),如果接收者耗尽 gas,传输也会失败。因此,为了安全地进行以太币转账,请始终检查 send 的返回值,使用 transfer,或者更好的方法是使用接收者提取以太币的模式。

警告

由于 EVM 认为对不存在的合约的调用始终会成功,Solidity 在执行外部调用时使用 extcodesize 操作码进行额外的检查。这确保了要调用的合约要么实际存在(包含代码),要么会引发异常。

在地址而不是合约实例上运行的低级调用(例如 .call().delegatecall().staticcall().send().transfer())**不** 包含此检查,这使得它们在 gas 方面更便宜,但也更不安全。

注意

在 0.5.0 版本之前,Solidity 允许通过合约实例访问地址成员,例如 this.balance。现在禁止这种方式,必须进行显式转换为地址:address(this).balance

注意

如果通过低级 delegatecall 访问状态变量,则两个合约的存储布局必须一致,以便被调用合约通过名称正确访问调用合约的存储变量。当然,如果像高级库一样将存储指针作为函数参数传递,则情况并非如此。

注意

在 0.5.0 版本之前,.call.delegatecall.staticcall 仅返回成功条件,而不返回返回值数据。

注意

在 0.5.0 版本之前,有一个名为 callcode 的成员,其语义与 delegatecall 相似,但略有不同。

类型信息

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

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

type(C).name

合约的名称。

type(C).creationCode

包含合约创建字节码的内存字节数组。这可以在内联汇编中用于构建自定义创建例程,特别是在使用 create2 操作码时。不能在合约本身或任何派生合约中访问此属性。它会导致字节码包含在调用点的字节码中,因此,像这样的循环引用是不可能的。

type(C).runtimeCode

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

除了上述属性外,还可以使用以下属性来表示接口类型 I

type(I).interfaceId

包含给定接口 IEIP-165 接口标识符的 bytes4 值。此标识符定义为接口本身中定义的所有函数选择器的 XOR,不包括所有继承的函数。

可以使用以下属性来表示整数类型 T

type(T).min

类型 T 可表示的最小值。

type(T).max

类型 T 可表示的最大值。

保留关键字

这些关键字在 Solidity 中是保留的。它们将来可能会成为语法的一部分

afteraliasapplyautobytecasecopyofdefaultdefinefinalimplementsininlineletmacromatchmutablenullofpartialpromisereferencerelocatablesealedsizeofstaticsupportsswitchtypedeftypeofvar