单位和全局可用变量
以太坊(Ether) 单位
一个字面常数可以带一个后缀 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小时,而且因为闰秒是无法预测的, 所以需要借助外部的预言机(oracle,是一种链外数据服务,译者注)来对一个确定的日期代码库进行时间矫正。
备注
由于上述原因,在 0.5.0 版本中删除了后缀 years
。
这些后缀单位不能应用于变量。例如, 如果您想用时间单位(例如 days)来将输入变量换算为时间,您可以用以下方式:
function f(uint start, uint daysAfter) public {
if (block.timestamp >= start + daysAfter * 1 days) {
// ...
}
}
特殊变量和函数
有一些特殊的变量和函数总是存在于全局命名空间,主要用于提供区块链的信息,或者是通用的工具函数。
区块和交易属性
blockhash(uint blockNumber) returns (bytes32)
: 当blocknumber
是最近的256个区块之一时,给定区块的哈希值;否则返回0。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.timestamp
(uint
): 自 unix epoch 起始到当前区块以秒计的时间戳gasleft() returns (uint256)
: 剩余的 gasmsg.data
(bytes calldata
): 完整的 calldatamsg.sender
(address
): 消息发送者(当前调用)msg.sig
(bytes4
): calldata 的前 4 字节(也就是函数标识符)msg.value
(uint
): 随消息发送的 wei 的数量tx.gasprice
(uint
): 随消息发送的 wei 的数量tx.origin
(address
): 交易发起者(完全的调用链)
备注
对于每一个 外部(external) 函数调用,
包括 msg.sender
和 msg.value
在内所有 msg
成员的值都会变化。
这里包括对库函数的调用。
备注
当合约在链下而不是在区块中包含的交易的背景下计算时,
您不应该认为 block.*
和 tx.*
是指任何特定区块或交易的值。
这些值是由执行合约的EVM实现提供的,可以是任意的。
备注
不要依赖 block.timestamp
和 blockhash
产生随机数,除非您知道自己在做什么。
时间戳和区块哈希在一定程度上都可能受到挖矿矿工影响。 例如,挖矿社区中的恶意矿工可以用某个给定的哈希来运行赌场合约的 payout 函数, 而如果他们没收到钱,还可以用一个不同的哈希重新尝试。
当前区块的时间戳必须严格大于最后一个区块的时间戳, 但这里唯一能确保的只是它会是在权威链上的两个连续区块的时间戳之间的数值。
备注
基于可扩展因素,区块哈希不是对所有区块都有效。 您仅仅可以访问最近 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-对给定参数进行编码,并以给定的函数选择器作为起始的4字节数据一起返回abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)
: 相当于abi.encodeWithSelector(bytes4(keccak256(bytes(signature))), ...)
abi.encodeCall(function functionPointer, (...)) returns (bytes memory)
: 对函数指针
的调用进行ABI编码,参数在元组中找到。执行全面的类型检查,确保类型与函数签名相符。结果相当于abi.encodeWithSelector(functionPointer.selector, (...))
。
备注
这些编码函数可用于制作外部函数调用的数据,而无需实际调用外部函数。
此外, keccak256(abi.encodePacked(a, b))
是一种计算结构化数据的哈希值的方法
(但是要注意有可能使用不同的函数参数类型会制作出一个 "哈希碰撞")。
字节类型的成员
bytes.concat(...) returns (bytes memory)
: 将可变数量的字节和byte1, ..., byte32参数串联成一个字节数组
字符串的成员
string.concat(...) returns (string memory)
: 将可变数量的字符串参数串联成一个字符串数组
错误处理
关于错误处理和何时使用哪个函数的更多细节, 请参见 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
的校验(assert)。mulmod(uint x, uint y, uint k) returns (uint)
计算
(x * y) % k
,乘法会在任意精度下执行,并且乘法的结果即使超过2**256
也不会被截取。从 0.5.0 版本的编译器开始会加入对k != 0
的校验(assert)。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
。 参见 地址类型 进行转换,以备您需要向恢复的地址转移资金。更多细节,请阅读 使用示例.
警告
如果您使用 ecrecover
,请注意,一个有效的签名可以变成另一个有效的签名,而不需要知道相应的私钥。
在 Homestead 硬分叉中,这个问题对 _transaction_ 签名进行了修复
(见 EIP-2),
但 ecrecover 函数仍然没有改变。
这通常不是一个问题,除非您要求签名是唯一的,或者用它们来识别个体。
OpenZeppelin 有一个 ECDSA 辅助库,
您可以用它作为 ecrecover
的包装,那样就没有这个问题。
备注
当在 私有区块链 上运行 sha256
, ripemd160
或 ecrecover
时,您可能会遇到超出 gas(Out-of-Gas)的错误。这是因为这些功能是作为 “预编译合约” 实现的,只有在它们收到第一个消息后才真正存在(尽管它们的合约代码是硬编码的)。向不存在的合约发送消息的成本较高,因此执行时可能会遇到 Out-of-Gas 错误。这个问题的一个变通方法是,在您的实际合约中使用它们之前,先向每个合约发送 Wei(例如1)。这在主网和测试网上都没有问题。
合约相关
this
(当前合约类型)当前合约,可以明确转换为 地址类型
super
继承层次结构中更高一级的合约
selfdestruct(address payable recipient)
销毁当前合约,将其资金发送到给定的 地址类型 并结束执行。 注意,
selfdestruct
有一些从EVM继承的特殊性:接收合约的接收函数不会被执行。
合约只有在交易结束时才真正被销毁, 任何一个
revert
可能会 "恢复" 销毁。
此外,当前合约的所有函数都可以直接调用,包括当前函数。
警告
从 0.8.18 及以上版本开始,在 Solidity 和 Yul 中使用 selfdestruct
将触发一个已废弃警告,
因为 SELFDESTRUCT
操作码最终会发生如 EIP-6049 中所述的行为上的重大变化。
备注
在 0.5.0 版本之前,有一个叫做 suicide
的函数,其语义与 selfdestruct
相同。
类型信息
表达式 type(X)
可以用来检索关于 X
类型的信息。
目前,对这一功能的支持是有限的( X
可以是合约类型或整数型),但在未来可能会扩展。
以下是合约类型 C
的可用属性:
type(C).name
合约的名称。
type(C).creationCode
内存字节数组,包含合约的创建字节码。 可以在内联程序中用来建立自定义的创建程序, 特别是通过使用
create2
操作码。 这个属性 不能 在合约本身或任何派生合约中被访问。 它会导致字节码被包含在调用站点的字节码中,因此像这样的循环引用是不可能的。type(C).runtimeCode
内存字节数组,包含合约运行时的字节码。 通常是由
C
的构造函数部署的代码。 如果C
有一个使用内联汇编的构造函数,这可能与实际部署的字节码不同。 还要注意的是,库合约在部署时修改其运行时字节码,以防止常规调用。 与.creationCode
相同的限制也适用于这个属性。
除了上述属性外,以下属性对接口类型 I
可用:
type(I).interfaceId
:一个
bytes4
值,是包含给定接口I
的 EIP-165 接口标识符。 这个标识符被定义为接口本身定义的所有函数选择器的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
。