文章目錄
- 單位和全局可用變量(Units and Globally Available Variables)
- 特殊變量和函數
- 1. 區塊和交易屬性
- 2. ABI 編碼和解碼函數
- 3. bytes 成員函數
- 4. string 成員函數
- 5. 錯誤處理
- 6. 數學和加密函數
- 7. 地址類型成員函數
- 8. 與合約相關
- 9. 類型信息
單位和全局可用變量(Units and Globally Available Variables)
特殊變量和函數
1. 區塊和交易屬性
在全局命名空間中始終存在一些特殊變量和函數,主要用于提供區塊鏈相關信息或作為通用工具函數。
- blockhash(uint blockNumber) returns (bytes32):返回指定區塊的哈希值,僅適用于最近 256 個區塊;否則返回 0。
- blobhash(uint index) returns (bytes32):返回當前交易中第 index 個 blob 的版本化哈希值。版本化哈希由 1 個字節的版本號(目前為 0x01)和 KZG 承諾的 SHA256 哈希的后 31 個字節組成(EIP-4844)。如果不存在該索引的 blob,則返回 0。
- block.basefee (uint):當前區塊的基礎費用(EIP-3198 和 EIP-1559)。
- block.blobbasefee (uint):當前區塊的 blob 基礎費用(EIP-7516 和 EIP-4844)。
- block.chainid (uint):當前鏈的 Chain ID。
- block.coinbase (address payable):當前區塊礦工的地址。
- block.difficulty (uint):當前區塊的難度(僅適用于 Paris 之前的 EVM 版本)。在其他 EVM 版本中,它是 block.prevrandao 的廢棄別名(EIP-4399)。
- block.gaslimit (uint):當前區塊的 gas 限制。
- block.number (uint):當前區塊的編號。
- block.prevrandao (uint):由信標鏈提供的隨機數(適用于 EVM >= Paris)。
- block.timestamp (uint):當前區塊的時間戳(自 Unix 紀元以來的秒數)。
- gasleft() returns (uint256):返回當前交易中剩余的 gas。
- msg.data (bytes calldata):完整的 calldata(調用數據)。
- msg.sender (address):消息(當前調用)的發送者地址。
- msg.sig (bytes4):calldata 的前 4 個字節(即函數標識符)。
- msg.value (uint):隨消息發送的 Wei 數量。
- tx.gasprice (uint):交易的 gas 價格。
- tx.origin (address):交易的原始發送者地址(完整調用鏈中的最初發起者)。
注意
1.msg 變量的動態性
msg.sender、msg.value 等 msg 成員的值會在每次外部函數調用時發生變化,包括調用庫函數時。
2.區塊和交易屬性的限制
當合約在鏈下執行(如本地測試或模擬環境)時,不要假設 block. 和 tx. 具有特定的值,這些值取決于 EVM 實現。
3.不要依賴 block.timestamp 或 blockhash 作為隨機數源
block.timestamp 和 blockhash 可能受到礦工的操縱,在某些應用中,惡意礦工可以重復計算直到獲得有利的哈希值。當前區塊的時間戳必須嚴格大于上一個區塊的時間戳,但唯一的保證是它位于兩個連續區塊的時間戳之間。
4.blockhash 的可用性
由于可擴展性原因,并非所有區塊的哈希值都可用,只有最近 256 個區塊的哈希值可訪問,超過該范圍的值將返回 0。
5.廢棄的函數和別名:
block.blockhash 在 Solidity 0.4.22 版本被棄用,并在 0.5.0 版本中移除。
msg.gas 在 Solidity 0.4.21 版本被棄用,并在 0.5.0 版本中移除(替換為 gasleft())。
now(block.timestamp 的別名)在 Solidity 0.7.0 版本中被移除。
2. ABI 編碼和解碼函數
-
abi.decode(bytes memory encodedData, (…)) returns (…):對給定的 encodedData 進行 ABI 解碼,第二個參數括號內指定解碼后的數據類型。示例:
solidity
(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):對給定的參數進行緊湊編碼(packed encoding)。注意:緊湊編碼可能會導致數據歧義!
-
abi.encodeWithSelector(bytes4 selector, …) returns (bytes memory):以 selector 作為前綴,對后續參數進行 ABI 編碼。
-
abi.encodeWithSignature(string memory signature, …) returns (bytes memory):等效于:
solidity
abi.encodeWithSelector(bytes4(keccak256(bytes(signature))), …) -
abi.encodeCall(function functionPointer, (…)) returns (bytes memory):對函數指針 functionPointer 及其參數進行 ABI 編碼,同時進行完整的類型檢查,確保參數類型與函數簽名匹配。結果等價于:
solidity
abi.encodeWithSelector(functionPointer.selector, (…))
注意
1.這些編碼函數可用于構造外部函數調用的數據,而不實際調用該函數。
2.keccak256(abi.encodePacked(a, b)) 可用于計算結構化數據的哈希值。警告:不同的參數類型可能導致哈希碰撞,應謹慎使用。
3.詳細的 ABI 編碼規則和緊湊編碼(tightly packed encoding)請參考 Solidity 文檔。
3. bytes 成員函數
bytes.concat(…) returns (bytes memory):將多個 bytes
和 bytes1
到 bytes32
類型的參數連接成一個字節數組。
4. string 成員函數
string.concat(…) returns (string memory):將多個字符串參數連接成一個字符串。
5. 錯誤處理
-
assert(bool condition):如果條件不成立,會觸發 Panic 錯誤并回滾狀態更改 - 用于內部錯誤。
-
require(bool condition):如果條件不成立,回滾并撤銷交易 - 用于輸入錯誤或外部組件錯誤。
-
require(bool condition, string memory message):如果條件不成立,回滾并撤銷交易,并提供錯誤消息 - 用于輸入錯誤或外部組件錯誤。
-
revert():中止執行并回滾狀態更改。
-
revert(string memory reason):中止執行并回滾狀態更改,并提供一個解釋字符串。
6. 數學和加密函數
-
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 payable 進行轉換。
進一步說明
在使用 ecrecover 時需要注意,一個有效的簽名可以被轉換成另一個有效的簽名,而無需了解對應的私鑰。在 Homestead 硬分叉中,針對交易簽名的問題(參見 EIP-2)已被修復,但 ecrecover 函數未做更改。通常這不會造成問題,除非你要求簽名是唯一的,或者使用簽名來識別項目。你可以使用 OpenZeppelin 的 ECDSA 輔助庫,它是 ecrecover 的封裝,避免了這個問題。
注意
在私有區塊鏈上運行 sha256、ripemd160 或 ecrecover 時,可能會遇到 “Out-of-Gas” 錯誤。這是因為這些函數作為“預編譯合約”實現,只有在接收到第一個消息后才真正存在(盡管它們的合約代碼是硬編碼的)。發送到不存在的合約的消息更昂貴,因此執行可能會出現 “Out-of-Gas” 錯誤。解決該問題的方法是,先向每個合約發送 Wei(例如 1),然后再在實際合約中使用它們。這個問題在主網或測試網上并不存在。
7. 地址類型成員函數
1.<address>
.balance (uint256) 返回地址的余額,以 Wei 為單位。
2.<address>
.code (bytes memory) 返回地址處的代碼(可能為空)。
3.<address>
.codehash (bytes32) 返回地址的代碼哈希。
4.<address payable>
.transfer(uint256 amount) 向地址發送指定的 Wei 數量,失敗時回滾,轉發 2300 gas 補助,不可調節。
5.<address payable>
.send(uint256 amount) returns (bool) 向地址發送指定的 Wei 數量,失敗時返回 false,轉發 2300 gas 補助,不可調節。
6.<address>
.call(bytes memory) returns (bool, bytes memory) 進行低級 CALL,帶有給定的有效載荷,返回成功狀態和返回數據,轉發所有可用的 gas,可以調節。
7.<address>
.delegatecall(bytes memory) returns (bool, bytes memory) 進行低級 DELEGATECALL,帶有給定的有效載荷,返回成功狀態和返回數據,轉發所有可用的 gas,可以調節。
8.<address>
.staticcall(bytes memory) returns (bool, bytes memory) 進行低級 STATICCALL,帶有給定的有效載荷,返回成功狀態和返回數據,轉發所有可用的 gas,可以調節。
警告
1.盡量避免使用 .call(),因為它繞過了類型檢查、函數存在性檢查和參數打包。
2.使用 send 時有一些危險:當調用棧深度達到 1024 時,轉賬會失敗(這可以被調用者強制),如果接收者沒有足夠的 gas,也會失敗。因此,為了確保安全的以太幣轉賬,始終檢查 send 的返回值,使用 transfer 或更好的方式:使用一種模式,讓接收者主動提取以太幣。
3.由于 EVM 將對不存在的合約的調用視為始終成功,因此 Solidity 在執行外部調用時使用 extcodesize 操作碼進行了額外檢查。這確保了即將調用的合約實際上存在(它包含代碼),否則會拋出異常。對地址而不是合約實例進行操作的低級調用不包括此檢查(即.call()、.delegatecall()、.staticcall()、.send() 和 .transfer()),這使得它們在 gas 上更便宜,但也更不安全。
注意
1.在版本 0.5.0 之前,Solidity 允許通過合約實例訪問地址成員,例如 this.balance。現在已被禁止,必須顯式轉換為地址:address(this).balance。
2.如果通過低級 delegatecall 訪問狀態變量,兩個合約的存儲布局必須一致,以便被調用合約能夠正3.確按名稱訪問調用合約的存儲變量。當然,如果傳遞存儲指針作為函數參數(如高層庫的情況),則存儲布局不會一致。
4.在版本 0.5.0 之前,.call、.delegatecall 和 .staticcall 只返回成功狀態,而不返回返回數據。
5.在版本 0.5.0 之前,有一個名為 callcode 的成員,它與 delegatecall 的語義略有不同。
8. 與合約相關
1.this (當前合約的類型):當前合約,可以顯式轉換為地址類型。
2.super:繼承層次結構中上一級的合約。
3.selfdestruct(address payable recipient): 銷毀當前合約,將其資金發送到給定的地址并結束執行。selfdestruct有一些繼承自 EVM 的特性:
- 接收合約的 receive 函數不會被執行。
- 合約僅在交易結束時被真正銷毀,并且回滾可能會“撤銷”銷毀操作。
- 此外,當前合約的所有函數都可以直接調用,包括當前函數。
警告
1.從 EVM >= Cancun 開始,selfdestruct 將只會將賬戶中的所有以太幣發送到給定的接收者,而不再銷毀合約。然而,當 selfdestruct 在同一交易中被調用,并且創建了調用它的合約時,selfdestruct 會遵循 Cancun 硬分叉之前(即 EVM <= Shanghai)的行為,仍然會銷毀當前合約,刪除所有數據,包括存儲鍵、代碼和合約本身。詳情請參見 EIP-6780。
2.新的行為是全網范圍的變化,影響所有部署在以太坊主網和測試網的合約。需要注意的是,這一變化取決于合約部署鏈的 EVM 版本,編譯合約時使用的 --evm-version 設置不會影響此行為。
3.此外,selfdestruct 操作碼在 Solidity 版本 0.8.18 中已被棄用,按照 EIP-6049 的建議,棄用仍然有效,編譯器會在使用時發出警告。在新部署的合約中強烈不建議使用,即使考慮到新的行為,未來 EVM 的更改可能會進一步減少該操作碼的功能。
注意
在0.5.0版本之前,有一個名為suicide的函數,語義與selfdestruct相同。
9. 類型信息
表達式 type(X) 可用于檢索關于類型 X 的信息。目前,支持此功能的類型有限(X 可以是合約類型或整數類型),但未來可能會擴展。
對于合約類型 C,以下屬性可用:
-
type?.name:合約的名稱
-
type?.creationCode:包含合約創建字節碼的內存字節數組。可以在內聯匯編中使用此字節碼構建自定義創建例程,特別是通過使用 create2 操作碼。該屬性無法在合約本身或任何派生合約中訪問,它會導致字節碼被包含在調用站點的字節碼中,因此像這樣的循環引用是不可行的。
-
type?.runtimeCode:包含合約運行時字節碼的內存字節數組。通常,這是合約 C 的構造函數部署的代碼。如果 C 的構造函數使用了內聯匯編,這可能與實際部署的字節碼不同。還要注意,庫在部署時會修改其運行時字節碼,以防止常規調用。與 .creationCode 相同的限制也適用于此屬性。
除了上述屬性,以下屬性適用于接口類型 I:
- type(I).interfaceId:一個 bytes4 值,包含給定接口 I 的 EIP-165 接口標識符。此標識符是接口中所有函數選擇器的 XOR,排除了所有繼承的函數。
對于整數類型 T,以下屬性可用:
-
type(T).min:類型 T 可表示的最小值。
-
type(T).max:類型 T 可表示的最大值。