序言
本文是 Solidity 文檔(以太坊官方 Solidity 開發(fā)手冊)中文版連載的第六部分。這個連載的前五部分是智能合約概述、安裝 Solidity 編譯器、結合實例學習 Solidity、源文件結構和類型。
這份文檔的英文原文可以在以太坊官網(wǎng)的最下方 Solidity 鏈接中找到。官方的英文版本文檔中有中譯版鏈接,即是本連載內(nèi)容的出處。這個連載將按照英文文檔的先后順序進行。
Solidity 是以太坊官方的智能合約開發(fā)高級語言。這份中文譯本是由 Hiblock 社區(qū)組織貢獻的,官方 Github:https://github.com/etherchina/solidity-doc-cn。
我本人于 3 月初加入本項目,目前作為管理員、貢獻者和校訂人利用業(yè)余時間參與日常工作;截至到 5 月底,翻譯工作已接近完成。有興趣的朋友請直接在以太坊官網(wǎng)的鏈接中查看最新中文版本狀態(tài),或者關注上述中文譯本的 Github repository。
出于單獨閱讀的需要,我在連載中會刪除原文里的 rst 控制標簽、部分外部鏈接和文內(nèi)鏈接。
本文是對 Solidity 中的單位和全局變量的完整介紹。
單位和全局變量
以太幣單位
以太幣單位之間的換算就是在數(shù)字后邊加上 wei
、 finney
、 szabo
或 ether
來實現(xiàn)的,如果后面沒有單位,缺省為 Wei。例如 2 ether == 2000 finney
的邏輯判斷值為 true
。
時間單位
秒是缺省時間單位,在時間單位之間,數(shù)字后面帶有 seconds
、 minutes
、 hours
、 days
、 weeks
和 years
的可以進行換算,基本換算關系如下:
1 == 1 seconds
1 minutes == 60 seconds
1 hours == 60 minutes
1 days == 24 hours
1 weeks == 7 days
1 years == 365 days
由于閏秒造成的每年不都是 365 天、每天不都是 24 小時,所以如果你要使用這些單位計算日期和時間,請注意這個問題。因為閏秒是無法預測的,所以需要借助外部的預言機(oracle,是一種鏈外數(shù)據(jù)服務,譯者注)來對一個精確的日期庫進行更新。
注意:
基于上述原因years
前綴已經(jīng)不推薦使用了。
這些后綴不能直接用在變量后邊。如果想用時間單位(例如 days)來將輸入變量換算為時間,你可以用如下方式來完成:
function f(uint start, uint daysAfter) public {
if (now >= start + daysAfter * 1 days) {
// ...
}
}
特殊變量和函數(shù)
在全局命名空間中已經(jīng)存在了(預設了)一些特殊的變量和函數(shù),他們主要用來提供關于區(qū)塊鏈的信息。
區(qū)塊和交易屬性
-
block.blockhash(uint blockNumber) returns (bytes32)
: 給定區(qū)塊的哈希—僅對最近的 256 個區(qū)塊有效而不包括當前區(qū)塊 -
block.coinbase
(address
): 挖出當前區(qū)塊的礦工地址 -
block.difficulty
(uint
): 當前區(qū)塊難度 -
block.gaslimit
(uint
): 當前區(qū)塊 gas 限額 -
block.number
(uint
): 當前區(qū)塊號 -
block.timestamp
(uint
): 自 unix epoch 起始當前區(qū)塊以秒計的時間戳 -
gasleft() returns (uint256)
:剩余的 gas -
msg.data
(bytes
): 完整的 calldata -
msg.gas
(uint
): 剩余 gas -
msg.sender
(address
): 消息發(fā)送者(當前調(diào)用) -
msg.sig
(bytes4
): calldata 的前 4 字節(jié)(也就是函數(shù)標識符) -
msg.value
(uint
): 隨消息發(fā)送的 wei 的數(shù)量 -
now
(uint
): 目前區(qū)塊時間戳(block.timestamp
) -
tx.gasprice
(uint
): 交易的 gas 價格 -
tx.origin
(address
): 交易發(fā)起者(完全的調(diào)用鏈)
注意:
對于每一個外部函數(shù)調(diào)用,包括msg.sender
和msg.value
在內(nèi)所有msg
成員的值都會變化。這里包括對庫函數(shù)的調(diào)用。
注意:
不要依賴block.timestamp
、now
和block.blockhash
產(chǎn)生隨機數(shù),除非你知道自己在做什么。時間戳和區(qū)塊哈希在一定程度上都可能受到挖礦礦工影響。例如,挖礦社區(qū)中的惡意礦工可以用某個給定的哈希來運行賭場合約的 payout 函數(shù),而如果他們沒收到錢,還可以用一個不同的哈希重新嘗試。
當前區(qū)塊的時間戳必須嚴格大于最后一個區(qū)塊的時間戳,但這里唯一能確保的只是它會是在權威鏈上的兩個連續(xù)區(qū)塊的時間戳之間的數(shù)值。
注意:
基于可擴展因素,區(qū)塊哈希不是對所有區(qū)塊都有效。你僅僅可以訪問最近 256 個區(qū)塊的哈希,其余的哈希均為零。
ABI 編碼函數(shù)
-
abi.encode(...) returns (bytes)
:返回給定參數(shù)的 ABI 編碼 -
abi.encodePacked(...) returns (bytes)
:對給定參數(shù)進行打包的編碼 -
abi.encodeWithSelector(bytes4 selector, ...) returns (bytes)
:對從第二個參數(shù)開始的給定參數(shù)進行 ABI 編碼,并以第一個參數(shù)作為返回結果的前 4 字節(jié)——即用第一個參數(shù)作為函數(shù)選擇器(function selector),僅對其余參數(shù)進行 ABI 編碼 -
abi.encodeWithSignature(string signature, ...) returns (bytes)
:等價于 ``abi.encodeWithSelector(bytes4(keccak256(signature), ...)```
注意:
這些編碼函數(shù)可以用來基于函數(shù)調(diào)用的數(shù)據(jù)來產(chǎn)生 ABI 編碼,而不會實際調(diào)用一個函數(shù)。此外,keccak256(abi.encodePacked(a, b))
是計算keccak256(a, b)
的更明確的方式,后者在未來的版本中不再推薦使用。
關于 ABI 編碼的詳情請參考后續(xù)的“應用二進制編碼(ABI)說明”一章。
錯誤處理
-
assert(bool condition)
:如果條件不滿足則是交易不產(chǎn)生實際效果——用于內(nèi)部錯誤。 -
require(bool condition)
:如果條件不滿足則恢復(revert)——用于輸入或者外部組件引起的錯誤。 -
require(bool condition, string message)
:如果條件不滿足則恢復(revert)——用于輸入或者外部組件引起的錯誤,同時提供一個錯誤消息。 -
revert()
:終止運行并恢復(revert)狀態(tài)(state)變動。 -
revert(string reason)
:終止運行并恢復(revert)狀態(tài)(state)變動,并提供一個字符串信息來解釋原因。
數(shù)學和密碼學函數(shù)
-
addmod(uint x, uint y, uint k) returns (uint)
:計算(x + y) % k
,加法會在任意精度下執(zhí)行,并且加法的結果即使超過2**256
也不會被截取。從 0.5.0 版本的編譯器開始會加入對k != 0
的校驗(assert)。 -
mulmod(uint x, uint y, uint k) returns (uint)
:計算(x * y) % k
,乘法會在任意精度下執(zhí)行,并且乘法的結果即使超過2**256
也不會被截取。從 0.5.0 版本的編譯器開始會加入對k != 0
的校驗(assert)。 -
keccak256(...) returns (bytes32)
:計算“緊打包”參數(shù)(需要參考后續(xù)的“應用二進制編碼說明”一章)的 Ethereum-SHA-3 (Keccak-256)哈希。 -
sha256(...) returns (bytes32)
:計算“緊打包”參數(shù)(需要參考后續(xù)的“應用二進制編碼說明”一章)的 SHA-256 哈希。 -
sha3(...) returns (bytes32)
:等價于 keccak256。 -
ripemd160(...) returns (bytes20)
:計算“緊打包”參數(shù)(需要參考后續(xù)的“應用二進制編碼說明”一章)的 RIPEMD-160 哈希。 -
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)
:利用橢圓曲線簽名恢復與公鑰相關的地址,錯誤返回零值。參考示例。
上文中的“緊打包(tightly packed)”是指不會對參數(shù)值進行 padding 處理(就是說所有參數(shù)值的字節(jié)碼是連續(xù)存放的,中間不保留為把長度補充為一個“字”,即 32 字節(jié),而追加的若干 0 值字節(jié)數(shù)據(jù),譯者注),這意味著下邊這些調(diào)用都是等價的:
keccak256("ab", "c")
keccak256("abc")
keccak256(0x616263)
keccak256(6382179)
keccak256(97, 98, 99)
如果需要 padding,可以使用顯式類型轉(zhuǎn)換:keccak256("\x00\x12")
和 keccak256(uint16(0x12))
是一樣的。
請注意,常量值會使用存儲它們所需要的最少字節(jié)數(shù)進行打包。例如:keccak256(0) == keccak256(uint8(0))
,keccak256(0x12345678) == keccak256(uint32(0x12345678))
。
在一個私鏈上,你很有可能碰到由于 sha256
、ripemd160
或者 ecrecover
引起的 gas 用盡(Out-of-Gas)問題。這個原因就是他們被當做所謂的預編譯合約而執(zhí)行,并且在第一次收到消息后這些合約才真正存在(盡管合約代碼是硬編碼)。發(fā)送到不存在的合約的消息非常昂貴,所以實際的執(zhí)行會導致 Out-of-Gas 錯誤。在你的合約中實際使用它們之前,給每個合約發(fā)送一點兒以太幣,比如 1 Wei。這在官方網(wǎng)絡或測試網(wǎng)絡上都不是問題。
地址相關
-
<address>.balance
(uint256
):以 Wei 為單位的某個地址(address
)的余額。 -
<address>.transfer(uint256 amount)
:向某個地址(address
)發(fā)送數(shù)量為 amount 的 Wei,失敗時拋出異常,且將額外發(fā)送 2300 gas 的礦工費,不可調(diào)整。 -
<address>.send(uint256 amount) returns (bool)
:向某個地址(address
)發(fā)送數(shù)量為 amount 的 Wei,失敗時返回false
,且將額外發(fā)送 2300 gas 的礦工費用,不可調(diào)整。 -
<address>.call(...) returns (bool)
:執(zhí)行低級函數(shù)CALL
,失敗時返回false
,會發(fā)送所有可用 gas,不可調(diào)整。 -
<address>.callcode(...) returns (bool)
:執(zhí)行低級函數(shù)CALLCODE
,失敗時返回false
,會發(fā)送所有可用 gas,不可調(diào)整。 -
<address>.delegatecall(...) returns (bool)
:執(zhí)行低級函數(shù)DELEGATECALL
,失敗時返回false
,會發(fā)送所有可用 gas,不可調(diào)整。
更多信息,請參考上一章中介紹的“地址”部分。
警告:
使用 send 有很多危險:如果調(diào)用棧深度已經(jīng)達到 1024(這總是可以由調(diào)用者所強制指定),轉(zhuǎn)賬會失敗;并且如果接收者用光了 gas,轉(zhuǎn)賬同樣會失敗。為了保證以太幣轉(zhuǎn)賬安全,總是檢查send
的返回值,利用transfer
或者后文中的更好的方式:
使用一種由接收者取回資金的模式。
注意:
如果通過一個低級函數(shù) delegatecall 來訪問一個存儲(storage)變量,兩個合約存儲中的數(shù)據(jù)布局(位置)必須一致,以保證可以在被調(diào)用的合約中通過變量名正確地訪問到調(diào)用合約中的存儲變量。這當然不是在高級的庫中通過函數(shù)參數(shù)傳遞存儲指針的那種情況。
注意:不鼓勵使用
callcode
,并且將來它會被移除。
合約相關
-
this
(current contract's type):當前合約,可以明確轉(zhuǎn)換為某個地址(address
)。 -
selfdestruct(address recipient)
:銷毀合約,并把余額發(fā)送到指定的地址(address
)。 -
suicide(address recipient)
:等價于 selfdestruct。
此外,當前合約內(nèi)的所有函數(shù)都可以被直接調(diào)用,包括當前函數(shù)。