序言
本文是 Solidity 文檔(以太坊官方 Solidity 開(kāi)發(fā)手冊(cè))中文版連載的第六部分。這個(gè)連載的前五部分是智能合約概述、安裝 Solidity 編譯器、結(jié)合實(shí)例學(xué)習(xí) Solidity、源文件結(jié)構(gòu)和類(lèi)型扣猫。
這份文檔的英文原文可以在以太坊官網(wǎng)的最下方 Solidity 鏈接中找到。官方的英文版本文檔中有中譯版鏈接睁蕾,即是本連載內(nèi)容的出處。這個(gè)連載將按照英文文檔的先后順序進(jìn)行债朵。
Solidity 是以太坊官方的智能合約開(kāi)發(fā)高級(jí)語(yǔ)言子眶。這份中文譯本是由 Hiblock 社區(qū)組織貢獻(xiàn)的,官方 Github:https://github.com/etherchina/solidity-doc-cn序芦。
我本人于 3 月初加入本項(xiàng)目臭杰,目前作為管理員、貢獻(xiàn)者和校訂人利用業(yè)余時(shí)間參與日常工作谚中;截至到 5 月底渴杆,翻譯工作已接近完成。有興趣的朋友請(qǐng)直接在以太坊官網(wǎng)的鏈接中查看最新中文版本狀態(tài)宪塔,或者關(guān)注上述中文譯本的 Github repository磁奖。
出于單獨(dú)閱讀的需要,我在連載中會(huì)刪除原文里的 rst 控制標(biāo)簽某筐、部分外部鏈接和文內(nèi)鏈接比搭。
本文是對(duì) Solidity 中的單位和全局變量的完整介紹。
單位和全局變量
以太幣單位
以太幣單位之間的換算就是在數(shù)字后邊加上 wei
南誊、 finney
身诺、 szabo
或 ether
來(lái)實(shí)現(xiàn)的,如果后面沒(méi)有單位抄囚,缺省為 Wei霉赡。例如 2 ether == 2000 finney
的邏輯判斷值為 true
。
時(shí)間單位
秒是缺省時(shí)間單位幔托,在時(shí)間單位之間穴亏,數(shù)字后面帶有 seconds
、 minutes
重挑、 hours
迫肖、 days
、 weeks
和 years
的可以進(jìn)行換算攒驰,基本換算關(guān)系如下:
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 小時(shí),所以如果你要使用這些單位計(jì)算日期和時(shí)間玻粪,請(qǐng)注意這個(gè)問(wèn)題隅津。因?yàn)殚c秒是無(wú)法預(yù)測(cè)的诬垂,所以需要借助外部的預(yù)言機(jī)(oracle,是一種鏈外數(shù)據(jù)服務(wù)伦仍,譯者注)來(lái)對(duì)一個(gè)精確的日期庫(kù)進(jìn)行更新结窘。
注意:
基于上述原因years
前綴已經(jīng)不推薦使用了。
這些后綴不能直接用在變量后邊充蓝。如果想用時(shí)間單位(例如 days)來(lái)將輸入變量換算為時(shí)間隧枫,你可以用如下方式來(lái)完成:
function f(uint start, uint daysAfter) public {
if (now >= start + daysAfter * 1 days) {
// ...
}
}
特殊變量和函數(shù)
在全局命名空間中已經(jīng)存在了(預(yù)設(shè)了)一些特殊的變量和函數(shù),他們主要用來(lái)提供關(guān)于區(qū)塊鏈的信息谓苟。
區(qū)塊和交易屬性
-
block.blockhash(uint blockNumber) returns (bytes32)
: 給定區(qū)塊的哈瞎倥В—僅對(duì)最近的 256 個(gè)區(qū)塊有效而不包括當(dāng)前區(qū)塊 -
block.coinbase
(address
): 挖出當(dāng)前區(qū)塊的礦工地址 -
block.difficulty
(uint
): 當(dāng)前區(qū)塊難度 -
block.gaslimit
(uint
): 當(dāng)前區(qū)塊 gas 限額 -
block.number
(uint
): 當(dāng)前區(qū)塊號(hào) -
block.timestamp
(uint
): 自 unix epoch 起始當(dāng)前區(qū)塊以秒計(jì)的時(shí)間戳 -
gasleft() returns (uint256)
:剩余的 gas -
msg.data
(bytes
): 完整的 calldata -
msg.gas
(uint
): 剩余 gas -
msg.sender
(address
): 消息發(fā)送者(當(dāng)前調(diào)用) -
msg.sig
(bytes4
): calldata 的前 4 字節(jié)(也就是函數(shù)標(biāo)識(shí)符) -
msg.value
(uint
): 隨消息發(fā)送的 wei 的數(shù)量 -
now
(uint
): 目前區(qū)塊時(shí)間戳(block.timestamp
) -
tx.gasprice
(uint
): 交易的 gas 價(jià)格 -
tx.origin
(address
): 交易發(fā)起者(完全的調(diào)用鏈)
注意:
對(duì)于每一個(gè)外部函數(shù)調(diào)用,包括msg.sender
和msg.value
在內(nèi)所有msg
成員的值都會(huì)變化涝焙。這里包括對(duì)庫(kù)函數(shù)的調(diào)用卑笨。
注意:
不要依賴(lài)block.timestamp
、now
和block.blockhash
產(chǎn)生隨機(jī)數(shù)仑撞,除非你知道自己在做什么赤兴。時(shí)間戳和區(qū)塊哈希在一定程度上都可能受到挖礦礦工影響。例如隧哮,挖礦社區(qū)中的惡意礦工可以用某個(gè)給定的哈希來(lái)運(yùn)行賭場(chǎng)合約的 payout 函數(shù)桶良,而如果他們沒(méi)收到錢(qián),還可以用一個(gè)不同的哈希重新嘗試沮翔。
當(dāng)前區(qū)塊的時(shí)間戳必須嚴(yán)格大于最后一個(gè)區(qū)塊的時(shí)間戳艺普,但這里唯一能確保的只是它會(huì)是在權(quán)威鏈上的兩個(gè)連續(xù)區(qū)塊的時(shí)間戳之間的數(shù)值。
注意:
基于可擴(kuò)展因素鉴竭,區(qū)塊哈希不是對(duì)所有區(qū)塊都有效歧譬。你僅僅可以訪問(wèn)最近 256 個(gè)區(qū)塊的哈希,其余的哈希均為零搏存。
ABI 編碼函數(shù)
-
abi.encode(...) returns (bytes)
:返回給定參數(shù)的 ABI 編碼 -
abi.encodePacked(...) returns (bytes)
:對(duì)給定參數(shù)進(jìn)行打包的編碼 -
abi.encodeWithSelector(bytes4 selector, ...) returns (bytes)
:對(duì)從第二個(gè)參數(shù)開(kāi)始的給定參數(shù)進(jìn)行 ABI 編碼瑰步,并以第一個(gè)參數(shù)作為返回結(jié)果的前 4 字節(jié)——即用第一個(gè)參數(shù)作為函數(shù)選擇器(function selector),僅對(duì)其余參數(shù)進(jìn)行 ABI 編碼 -
abi.encodeWithSignature(string signature, ...) returns (bytes)
:等價(jià)于 ``abi.encodeWithSelector(bytes4(keccak256(signature), ...)```
注意:
這些編碼函數(shù)可以用來(lái)基于函數(shù)調(diào)用的數(shù)據(jù)來(lái)產(chǎn)生 ABI 編碼璧眠,而不會(huì)實(shí)際調(diào)用一個(gè)函數(shù)缩焦。此外,keccak256(abi.encodePacked(a, b))
是計(jì)算keccak256(a, b)
的更明確的方式责静,后者在未來(lái)的版本中不再推薦使用袁滥。
關(guān)于 ABI 編碼的詳情請(qǐng)參考后續(xù)的“應(yīng)用二進(jìn)制編碼(ABI)說(shuō)明”一章。
錯(cuò)誤處理
-
assert(bool condition)
:如果條件不滿(mǎn)足則是交易不產(chǎn)生實(shí)際效果——用于內(nèi)部錯(cuò)誤灾螃。 -
require(bool condition)
:如果條件不滿(mǎn)足則恢復(fù)(revert)——用于輸入或者外部組件引起的錯(cuò)誤题翻。 -
require(bool condition, string message)
:如果條件不滿(mǎn)足則恢復(fù)(revert)——用于輸入或者外部組件引起的錯(cuò)誤,同時(shí)提供一個(gè)錯(cuò)誤消息腰鬼。 -
revert()
:終止運(yùn)行并恢復(fù)(revert)狀態(tài)(state)變動(dòng)嵌赠。 -
revert(string reason)
:終止運(yùn)行并恢復(fù)(revert)狀態(tài)(state)變動(dòng)塑荒,并提供一個(gè)字符串信息來(lái)解釋原因。
數(shù)學(xué)和密碼學(xué)函數(shù)
-
addmod(uint x, uint y, uint k) returns (uint)
:計(jì)算(x + y) % k
姜挺,加法會(huì)在任意精度下執(zhí)行齿税,并且加法的結(jié)果即使超過(guò)2**256
也不會(huì)被截取。從 0.5.0 版本的編譯器開(kāi)始會(huì)加入對(duì)k != 0
的校驗(yàn)(assert)炊豪。 -
mulmod(uint x, uint y, uint k) returns (uint)
:計(jì)算(x * y) % k
凌箕,乘法會(huì)在任意精度下執(zhí)行,并且乘法的結(jié)果即使超過(guò)2**256
也不會(huì)被截取词渤。從 0.5.0 版本的編譯器開(kāi)始會(huì)加入對(duì)k != 0
的校驗(yàn)(assert)牵舱。 -
keccak256(...) returns (bytes32)
:計(jì)算“緊打包”參數(shù)(需要參考后續(xù)的“應(yīng)用二進(jìn)制編碼說(shuō)明”一章)的 Ethereum-SHA-3 (Keccak-256)哈希。 -
sha256(...) returns (bytes32)
:計(jì)算“緊打包”參數(shù)(需要參考后續(xù)的“應(yīng)用二進(jìn)制編碼說(shuō)明”一章)的 SHA-256 哈希掖肋。 -
sha3(...) returns (bytes32)
:等價(jià)于 keccak256仆葡。 -
ripemd160(...) returns (bytes20)
:計(jì)算“緊打包”參數(shù)(需要參考后續(xù)的“應(yīng)用二進(jìn)制編碼說(shuō)明”一章)的 RIPEMD-160 哈希赏参。 -
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)
:利用橢圓曲線(xiàn)簽名恢復(fù)與公鑰相關(guān)的地址志笼,錯(cuò)誤返回零值。參考示例把篓。
上文中的“緊打包(tightly packed)”是指不會(huì)對(duì)參數(shù)值進(jìn)行 padding 處理(就是說(shuō)所有參數(shù)值的字節(jié)碼是連續(xù)存放的纫溃,中間不保留為把長(zhǎng)度補(bǔ)充為一個(gè)“字”,即 32 字節(jié)韧掩,而追加的若干 0 值字節(jié)數(shù)據(jù)紊浩,譯者注),這意味著下邊這些調(diào)用都是等價(jià)的:
keccak256("ab", "c")
keccak256("abc")
keccak256(0x616263)
keccak256(6382179)
keccak256(97, 98, 99)
如果需要 padding疗锐,可以使用顯式類(lèi)型轉(zhuǎn)換:keccak256("\x00\x12")
和 keccak256(uint16(0x12))
是一樣的坊谁。
請(qǐng)注意,常量值會(huì)使用存儲(chǔ)它們所需要的最少字節(jié)數(shù)進(jìn)行打包滑臊。例如:keccak256(0) == keccak256(uint8(0))
口芍,keccak256(0x12345678) == keccak256(uint32(0x12345678))
。
在一個(gè)私鏈上雇卷,你很有可能碰到由于 sha256
鬓椭、ripemd160
或者 ecrecover
引起的 gas 用盡(Out-of-Gas)問(wèn)題。這個(gè)原因就是他們被當(dāng)做所謂的預(yù)編譯合約而執(zhí)行关划,并且在第一次收到消息后這些合約才真正存在(盡管合約代碼是硬編碼)小染。發(fā)送到不存在的合約的消息非常昂貴,所以實(shí)際的執(zhí)行會(huì)導(dǎo)致 Out-of-Gas 錯(cuò)誤贮折。在你的合約中實(shí)際使用它們之前裤翩,給每個(gè)合約發(fā)送一點(diǎn)兒以太幣,比如 1 Wei调榄。這在官方網(wǎng)絡(luò)或測(cè)試網(wǎng)絡(luò)上都不是問(wèn)題岛都。
地址相關(guān)
-
<address>.balance
(uint256
):以 Wei 為單位的某個(gè)地址(address
)的余額律姨。 -
<address>.transfer(uint256 amount)
:向某個(gè)地址(address
)發(fā)送數(shù)量為 amount 的 Wei,失敗時(shí)拋出異常臼疫,且將額外發(fā)送 2300 gas 的礦工費(fèi)择份,不可調(diào)整。 -
<address>.send(uint256 amount) returns (bool)
:向某個(gè)地址(address
)發(fā)送數(shù)量為 amount 的 Wei烫堤,失敗時(shí)返回false
荣赶,且將額外發(fā)送 2300 gas 的礦工費(fèi)用,不可調(diào)整鸽斟。 -
<address>.call(...) returns (bool)
:執(zhí)行低級(jí)函數(shù)CALL
拔创,失敗時(shí)返回false
,會(huì)發(fā)送所有可用 gas富蓄,不可調(diào)整剩燥。 -
<address>.callcode(...) returns (bool)
:執(zhí)行低級(jí)函數(shù)CALLCODE
,失敗時(shí)返回false
立倍,會(huì)發(fā)送所有可用 gas灭红,不可調(diào)整。 -
<address>.delegatecall(...) returns (bool)
:執(zhí)行低級(jí)函數(shù)DELEGATECALL
口注,失敗時(shí)返回false
变擒,會(huì)發(fā)送所有可用 gas,不可調(diào)整寝志。
更多信息娇斑,請(qǐng)參考上一章中介紹的“地址”部分。
警告:
使用 send 有很多危險(xiǎn):如果調(diào)用棧深度已經(jīng)達(dá)到 1024(這總是可以由調(diào)用者所強(qiáng)制指定)材部,轉(zhuǎn)賬會(huì)失敽晾隆;并且如果接收者用光了 gas乐导,轉(zhuǎn)賬同樣會(huì)失敗苦丁。為了保證以太幣轉(zhuǎn)賬安全,總是檢查send
的返回值兽叮,利用transfer
或者后文中的更好的方式:
使用一種由接收者取回資金的模式芬骄。
注意:
如果通過(guò)一個(gè)低級(jí)函數(shù) delegatecall 來(lái)訪問(wèn)一個(gè)存儲(chǔ)(storage)變量,兩個(gè)合約存儲(chǔ)中的數(shù)據(jù)布局(位置)必須一致鹦聪,以保證可以在被調(diào)用的合約中通過(guò)變量名正確地訪問(wèn)到調(diào)用合約中的存儲(chǔ)變量账阻。這當(dāng)然不是在高級(jí)的庫(kù)中通過(guò)函數(shù)參數(shù)傳遞存儲(chǔ)指針的那種情況。
注意:不鼓勵(lì)使用
callcode
泽本,并且將來(lái)它會(huì)被移除淘太。
合約相關(guān)
-
this
(current contract's type):當(dāng)前合約,可以明確轉(zhuǎn)換為某個(gè)地址(address
)。 -
selfdestruct(address recipient)
:銷(xiāo)毀合約蒲牧,并把余額發(fā)送到指定的地址(address
)撇贺。 -
suicide(address recipient)
:等價(jià)于 selfdestruct。
此外冰抢,當(dāng)前合約內(nèi)的所有函數(shù)都可以被直接調(diào)用松嘶,包括當(dāng)前函數(shù)。