以太单位 Ether Units

文字数字可以采用 wei、gwei 或 ether 的后缀来指定 Ether 的子面额,其中不带后缀的 Ether 数字被假定为 Wei。

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

小面额后缀的唯一效果是乘以十的幂。

  • 笔记

0.7.0 版中删除了 finney 和 szabo 面额。

时间单位

文字数字后的秒、分钟、小时、天和周等后缀可用于指定时间单位,其中秒是基本单位,单位以下列方式被天真地考虑:

1 == 1 秒 1 分钟 == 60 秒 1 小时 == 60 分钟 1 天 == 24 小时 1 周 == 7 天

使用这些单位执行日历计算时要小心,因为并非每年都等于 365 天,而且由于闰秒,甚至每天都有 24 小时。

由于无法预测闰秒,因此必须由外部预言机更新精确的日历库。

  • NOTE

由于上述原因,后缀年份已在 0.5.0 版本中删除。

这些后缀不能应用于变量。

例如,如果你想以天为单位解释一个函数参数,你可以通过以下方式:

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

特殊变量和函数

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

区块和交易属性

blockhash(uint blockNumber) 返回 (bytes32):当 blocknumber 是 256 个最近的块之一时给定块的哈希;否则返回零
block.basefee (uint):当前区块的基本费用(EIP-3198 和 EIP-1559)
block.chainid (uint): 当前链id
block.coinbase(应付地址):当前区块矿工的地址
block.difficulty (uint): 当前区块难度
block.gaslimit (uint): 当前区块gaslimit
block.number (uint): 当前块号
block.timestamp (uint): 当前区块时间戳,自 unix 纪元以来的秒数
gasleft() 返回 (uint256): 剩余气体
msg.data (bytes calldata): 完整的calldata
msg.sender(地址):消息的发送者(当前通话)
msg.sig (bytes4):calldata 的前四个字节(即函数标识符)
msg.value (uint): 随消息发送的wei数
tx.gasprice (uint):交易的gas价格
tx.origin(地址):交易的发送者(完整的调用链)

笔记

msg 的所有成员的值,包括 msg.sender 和 msg.value 可以随着每个外部函数调用而改变。这包括对库函数的调用。

笔记

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

笔记

不要依赖 block.timestamp 或 blockhash 作为随机源,除非你知道自己在做什么。

时间戳和区块哈希都会在一定程度上受到矿工的影响。例如,采矿社区中的不良行为者可以在选定的哈希上运行赌场支付功能,如果他们没有收到任何钱,只需重试不同的哈希。

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

笔记

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

笔记

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

笔记

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

笔记

在 0.7.0 版本中,别名 now (for block.timestamp) 已被删除。

ABI 编码和解码功能

abi.decode(bytes memory encodedData, (...)) 返回 (...):ABI 解码给定数据,而类型在括号中作为第二个参数给出。示例: (uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))
abi.encode(...) 返回(字节内存):ABI 编码给定的参数
abi.encodePacked(...) 返回(字节内存):对给定参数执行打包编码。请注意,打包编码可能不明确!
abi.encodeWithSelector(bytes4 selector, ...) 返回(字节内存):ABI 从第二个开始对给定参数进行编码,并将给定的四字节选择器放在前面
abi.encodeWithSignature(string memory signature, ...) 返回 (bytes memory): 等价于 abi.encodeWithSelector(bytes4(keccak256(bytes(signature))), ...)
abi.encodeCall(function functionPointer, (...)) 返回(字节内存):ABI 使用元组中的参数对 functionPointer 的调用进行编码。执行完整的类型检查,确保类型与函数签名匹配。结果等于 abi.encodeWithSelector(functionPointer.selector, (...))

笔记

这些编码函数可用于为外部函数调用制作数据,而无需实际调用外部函数。

此外,keccak256(abi.encodePacked(a, b)) 是一种计算结构化数据哈希的方法(尽管请注意,可以使用不同的函数参数类型来制造“哈希冲突”)。

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

字节的成员

bytes.concat(…) 返回(字节内存):将可变数量的字节和 bytes1、…、bytes32 参数连接到一个字节数组

字符串成员

string.concat(…) 返回(字符串内存):将可变数量的字符串参数连接到一个字符串数组

错误处理

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

  • assert(bool condition)

如果条件不满足,则会导致 Panic 错误并因此状态更改恢复 - 用于内部错误。

  • 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 处回绕。 断言 k != 0 从版本 0.5.0 开始。

  • mulmod(uint x, uint y, uint k) returns (uint)

计算 (x * y) % k,其中以任意精度执行乘法,并且不会在 2**256 处回绕。 断言 k != 0 从版本 0.5.0 开始。

  • keccak256(bytes memory) returns (bytes32)

计算输入的 Keccak-256 哈希

  • 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 返回一个地址,而不是一个应付地址。 请参阅应付转换地址,以防您需要将资金转移到恢复的地址。

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

  • WARNING

如果您使用 ecrecover,请注意可以将有效签名转换为不同的有效签名,而无需知道相应的私钥。

在 Homestead 硬分叉中,此问题已针对 transaction 签名(参见 EIP-2)进行了修复,但 ecrecover 功能保持不变。

这通常不是问题,除非您要求签名是唯一的或使用它们来识别项目。

OpenZeppelin 有一个 ECDSA 帮助程序库,您可以将其用作 ecrecover 的包装器,而不会出现此问题。

  • NOTE

在私有区块链上运行 sha256、ripemd160 或 ecrecover 时,您可能会遇到 Out-of-Gas。

这是因为这些功能是作为“预编译合约”实现的,并且只有在它们收到第一条消息后才真正存在(尽管它们的合约代码是硬编码的)。

不存在的合约的消息更昂贵,因此执行可能会遇到 Out-of-Gas 错误。

此问题的解决方法是先将 Wei(例如 1)发送到每个合同,然后再将它们用于实际合同。

这在主网上或测试网上都不是问题。

地址类型的成员

<address>.balance (uint256)

魏地址余额

<address>.code(bytes memory)

地址处的代码(可以为空)

<address>.codehash (bytes32)

地址的代码哈希

<address payable>.transfer(uint256 amount)

将给定数量的 Wei 发送到地址,失败时恢复,转发 2300 气体津贴,不可调整

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

发送给定数量的 Wei 到地址,失败返回 false,转发 2300 gas 津贴,不可调整

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

使用给定的payload发出低级CALL,返回成功条件和返回数据,转发所有可用gas,可调

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

使用给定的payload发出低级DELEGATECALL,返回成功条件和返回数据,转发所有可用gas,可调

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

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

有关详细信息,请参阅地址部分。

合同相关

  • this (current contract’s type)

当前合约,可显式转换为地址

  • selfdestruct(address payable recipient)

销毁当前合约,将其资金发送到给定地址并结束执行。

注意 selfdestruct 有一些继承自 EVM 的特性:

接收合约的接收函数没有被执行。

合约只有在交易结束时才真正被销毁,而 revert 可能会“撤消”销毁。

此外,当前合约的所有函数都可以直接调用,包括当前函数。

笔记

在 0.5.0 版本之前,有一个名为自杀的函数,其语义与 selfdestruct 相同。

类型信息

表达式 type(X) 可用于检索有关类型 X 的信息。

目前,对该功能的支持有限(X 可以是合同或整数类型),但将来可能会扩展。

以下属性可用于合同类型 C:

  • type(C).name

合同的名称。

  • type(C).creationCode

包含合约创建字节码的内存字节数组。

这可以在内联汇编中用于构建自定义创建例程,尤其是通过使用 create2 操作码。此属性不能在合约本身或任何派生合约中访问。它导致字节码包含在调用站点的字节码中,因此不可能进行这样的循环引用。

  • type(C).runtimeCode

包含合约运行时字节码的内存字节数组。

这是通常由 C 的构造函数部署的代码。

如果 C 有使用内联汇编的构造函数,这可能与实际部署的字节码不同。

另请注意,库在部署时会修改其运行时字节码以防止常规调用。

与 .creationCode 相同的限制也适用于此属性。

除了上述属性之外,接口类型 I 还可以使用以下属性:

  • type(I).interfaceId:

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

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

  • type(T).min

类型 T 可表示的最小值。

  • type(T).max

类型 T 可表示的最大值。

参考资料

https://docs.soliditylang.org/en/latest/units-and-global-variables.html