单位和全局可用变量
以太币单位
一个字面量数字可以带 wei
,gwei
或 ether
后缀来指定以太币的子单位,其中没有后缀的以太币数字被认为是 Wei。
assert(1 wei == 1);
assert(1 gwei == 1e9);
assert(1 ether == 1e18);
子单位后缀的唯一影响是乘以 10 的幂。
注意
在 0.7.0 版本中,finney
和 szabo
单位已移除。
时间单位
像 seconds
,minutes
,hours
,days
和 weeks
这样的后缀可以放在字面量数字后面,用于指定时间单位,其中秒是基本单位,单位是以以下方式简单考虑的
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.blobbasefee
(uint
): 当前区块的 blob 基本费用 (EIP-7516 和 EIP-4844)block.chainid
(uint
): 当前链 IDblock.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)
: 剩余 gasmsg.data
(bytes calldata
): 完整的调用数据msg.sender
(address
): 消息(当前调用)的发送者msg.sig
(bytes4
): 调用数据的头四个字节(即函数标识符)msg.value
(uint
): 消息中发送的 Wei 数量tx.gasprice
(uint
): 交易的 gas 价格tx.origin
(address
): 交易(完整调用链)的发送者
注意
所有 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
(block.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))
是一种计算结构化数据哈希的方法(尽管要注意,可以使用不同的函数参数类型来创建“哈希冲突”)。
bytes 的成员
bytes.concat(...) returns (bytes memory)
: 将可变数量的 bytes 和 bytes1、…、bytes32 参数连接到一个字节数组
string 的成员
string.concat(...) returns (string memory)
: 将可变数量的 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
的包装器,而不会出现此问题。
注意
在私有区块链上运行 sha256
、ripemd160
或 ecrecover
时,您可能会遇到超出 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
包含给定接口
I
的 EIP-165 接口标识符的bytes4
值。此标识符定义为接口本身中定义的所有函数选择器的XOR
,不包括所有继承的函数。
可以使用以下属性来表示整数类型 T
type(T).min
类型
T
可表示的最小值。type(T).max
类型
T
可表示的最大值。
保留关键字
这些关键字在 Solidity 中是保留的。它们将来可能会成为语法的一部分
after
、alias
、apply
、auto
、byte
、case
、copyof
、default
、define
、final
、implements
、in
、inline
、let
、macro
、match
、mutable
、null
、of
、partial
、promise
、reference
、relocatable
、sealed
、sizeof
、static
、supports
、switch
、typedef
、typeof
、var
。