四、单位和全局可用变量
4.1 Ether 单位
字面数值可以带后缀wei
、gwei
或ether
来指定Ether
的子名称,其中没有后缀的ether数被假定为Wei
。
assert(1 wei == 1);
assert(1 gwei == 1e9);
assert(1 ether == 1e18);
单位后缀的唯一作用是乘以10的幂次方。
0.7.0 版本中删除了 finney 和 szabo 这两个单位。
4.2 时间单位
像seconds
, minutes
, hours
, days
和weeks
这样的后缀可以用于指定时间单位,其中秒是基本单位,单位被简单地考虑为以下方式:
- 1 == 1
seconds
- 1
minutes
== 60seconds
- 1
hours
== 60minutes
- 1
days
== 24hours
- 1
weeks
== 7days
使用这些单位进行日历计算时要小心,因为不是每年都等于365天,甚至不是每天都有24小时,因为有闰秒(leap seconds
)。由于闰秒无法预测,因此必须由外部oracle更新精确的日历库。
由于上述原因,后缀
years
在0.5.0版本中被删除。
这些后缀不能应用于变量。例如,如果要以天为单位解释函数参数,可以采用以下方法:
function f(uint start, uint daysAfter) public {
if (block.timestamp >= start + daysAfter * 1 days) {
// ...
}
}
4.3 特殊变量及函数
有一些特殊的变量和函数总是存在于全局命名空间中,主要用于提供关于区块链的信息,或者是通用的实用函数。
4.3.1 区块和交易属性
blockhash(uint blockNumber) returns (bytes32)
当 blocknumber
是最近的256个区块之一时,给定区块的哈希值;否则返回0。
block.basefee
(uint
)
当前区块的基本费用(EIP-3198 and EIP-1559)
block.chainid
(uint
)
当前链id
block.coinbase
(address payable
)
当前区块矿工的地址
block.difficulty
(uint
)
当前区块难度值
block.gaslimit
(uint
)
当前区块 gas 限额
block.number
(uint
)
当前区块号
block.timestamp
(uint
)
自 unix epoch 起始到当前区块以秒计的时间戳
gasleft() returns
(uint256
)
剩余的 gas
msg.data
(bytes calldata
)
完整的 calldata
msg.sender
(address
)
消息发送者(当前调用)
msg.sig
(bytes4
)
calldata的前四个字节(即函数标识符)
msg.value
(uint
)
随消息发送的 wei 的数量
tx.gasprice
(uint
)
交易的 gas 价格
tx.origin
(address
)
交易的发送方(完整的调用链)
msg
的所有成员的值,包括msg.sender
和msg.value
。可以被每个外部函数调用更改。这包括对库函数的调用。
当合约是在链下而不是在区块中包含的交易上下文中评估时,您不应该假设该
block.*
和tx.*
指的是来自任何特定区块或事务的值。这些值是由执行合约的EVM实现提供的,可以是任意的。
不要依赖于
block.timestamp
或blockhash
是随机性的来源,除非你知道自己在做什么。
时间戳和区块哈希都可以在一定程度上受到矿工的影响。例如,采矿社区中的不良行为者可以在选定的哈希上运行赌场支付函数,如果他们没有收到任何钱,就重试不同的哈希。
当前块的时间戳必须严格大于上一个块的时间戳,但唯一的保证是它将位于规范链中两个连续块的时间戳之间。
由于可伸缩性的原因,并不是所有的块都可用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
)被移除。
4.3.2 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。encodepack (a, b))
是一种计算结构化数据哈希的方法(尽管要注意,可以使用不同的函数参数类型来制作“哈希碰撞”)。
4.3.3 bytes
的成员
bytes.concat(...) returns (bytes memory)
将可变数量的字bytes
和bytes1
,…,bytes32
参数连接到一个字节数组
4.3.4 string
的成员
string.concat(...) returns (string memory)
将可变数量的字string
参数连接到一个 string 数组
4.3.5 错误处理
有关错误处理以及何时使用哪个函数的详细信息,请参阅assert和require的专用部分。
assert(bool condition)
导致Panic错误,因此,如果条件不满足,状态更改返回-用于内部错误。
require(bool condition)
如果不满足条件则返回-用于输入或外部组件中的错误。
require(bool condition, string memory message)
同上,提供错误消息。
revert()
中止执行并恢复状态更改
revert(string memory reason)
同上,提供解释性字符串
4.3.6 数学和加密函数
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哈希值
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硬分叉中,针对_transaction_
签名修复了这个问题(参见EIP-2),但ecrecover
函数保持不变。
这通常不是问题,除非您要求签名是唯一的或使用它们来识别项目。OpenZeppelin有一个ECDSA帮助库,您可以使用它作为ecrecover
的包装器,而不会出现这个问题。
当在私有区块链上运行
sha256
、ripemd160
或ecrecover
时,您可能会遇到Out-of-Gas。这是因为这些函数被实现为“预编译的合约”,并且只有在它们收到第一条消息后才真正存在(尽管它们的合约代码是硬编码的)。发送到不存在的合约的消息代价更高,因此执行可能会遇到Out-of-Gas错误。解决这个问题的一个方法是,在实际的合约中使用它们之前,首先向每个合约发送Wei(例如1)。这不是主网或测试网的问题。
4.3.7 地址类型的成员
<address>.balance (uint256)
以 Wei 为单位的 地址类型 的余额。
<address>.code (bytes memory)
地址处的代码(可以为空)
<address>.codehash (bytes32)
地址类型 的代码哈希值
<address payable>.transfer(uint256 amount)
向 地址类型 发送数量为 amount 的 Wei,失败时抛出异常,发送 2300 gas 的矿工费,不可调节。
<address payable>.send(uint256 amount) returns (bool)
send given amount of Wei to Address, returns false
on failure, forwards 2300 gas stipend, not adjustable
<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:address(this).balance
。
如果通过低级委托调用访问状态变量,两个合约的存储布局必须对齐,以便被调用的合约按名称正确访问调用合约的存储变量。当然,如果存储指针像高级库那样作为函数参数传递,则不会出现这种情况。
在0.5.0版本之前,
.call
,.delegatecall
和.staticcall
只返回成功条件而不返回数据。
在0.5.0版本之前,有一个名为
callcode
的成员,其语义与delegatecall
类似,但略有不同。
4.3.8 合约相关
this
(当前合约类型)
当前合约,可以明确转换为 地址类型
selfdestruct(address payable recipient)
销毁当前合约,将其资金发送到给定的地址并结束执行。注意selfdestruct
有一些继承自EVM的特性:
- 接收合约的接收函数不会被执行。
- 合约只有在交易结束时才真正被销毁, 任何一个
revert
可能会 “恢复” 销毁。
此外,当前合约的所有函数都是可直接调用的,包括当前函数。
在0.5.0版本之前,有一个名为
suicide
的函数,其语义与selfdestruct
相同。
4.3.9 类型信息
表达式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
可表示的最大值。
4.4 保留关键字
这些关键字保留在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
.