文章目錄
- 值類型
- 布爾值
- 整數
- 運算符
- 取模運算
- 指數運算
- 定點數
- 地址(Address)
- 類型轉換
- 地址成員
- balance 和 transfer
- send
- call,delegatecall 和 staticcall
- code 和 codehash
- 合約類型(Contract Types)
- 固定大小字節數組(Fixed-size byte arrays)
- 地址字面量(Address Literals)
Solidity 是一種靜態類型語言,這意味著每個變量(無論是狀態變量還是局部變量)都需要指定其類型。Solidity 提供了幾種基本類型,這些類型可以組合形成復雜類型。
此外,不同類型可以在包含運算符的表達式中相互作用,并且具有優先級的區分。
Solidity 沒有“未定義”或“空”值的概念,但新聲明的變量總是具有默認值,該默認值取決于其類型。為了處理任何意外的值,應該使用 revert
函數回滾整個交易,或者返回一個帶有布爾值的元組,其中第二個 bool
值表示操作是否成功。
值類型
值類型的變量始終按值傳遞,即在作為函數參數或賦值時總是被復制。
與引用類型不同,值類型的聲明不指定數據位置,因為它們足夠小,可以存儲在棧中。唯一的例外是狀態變量,狀態變量默認存儲在存儲中,但也可以標記為 transient、constant 或 immutable。
布爾值
bool
:值是 true
或 false
。
整數
int
/ uint
:各種大小的有符號和無符號整數。
關鍵字 uint8
到 uint256
(以 8 為步長,表示 8 位到 256 位的無符號整數),以及 int8
到 int256
。
uint
和 int
分別是 uint256
和 int256
的別名。
運算符
比較運算符:<=
,<
,==
,!=
,>=
,>
(結果為 bool
)
位運算符:&
,|
,^
(按位異或),~
(按位取反)
移位運算符:<<
(左移),>>
(右移)
算術運算符:+
,-
,一元 -
(僅適用于有符號整數),*
,/
,%
(取模),**
(指數運算)
對于整數類型 X
,可以使用 type(X).min
和 type(X).max
來訪問該類型可表示的最小值和最大值。
運算符 ||
和 &&
遵循短路規則。這意味著在表達式 f(x) || g(y)
中,如果 f(x)
計算結果為 true
,則 g(y)
將不會被計算。
注意
Solidity 中的整數受到特定范圍的限制。例如,對于 uint32
,其范圍為 0
到 2**32 - 1
。
在 Solidity 中,整數運算有兩種模式:“溢出”模式(wrapping/unchecked mode) 和 “檢查”模式(checked mode)。
默認情況下,運算始終處于 “檢查”模式,這意味著如果運算結果超出了類型的值范圍,則調用會因失敗的斷言而回滾。
可以使用 unchecked { ... }
切換到 “溢出”模式,但應謹慎使用。
取模運算
%
的結果是操作數 a
除以操作數 n
后的余數 r
,其中 q = int(a / n)
,并且 r = a - (n * q)
。
這意味著取模運算的結果與其左操作數的符號相同(或為零),并且對于負數 a
,a % n == -(-a % n)
恒成立:
int256(5) % int256(2) == int256(1)int256(5) % int256(-2) == int256(1)int256(-5) % int256(2) == int256(-1)int256(-5) % int256(-2) == int256(-1)
注意:使用 0 作為取模運算的除數會導致 Panic
錯誤。此檢查無法通過 unchecked { ... }
關閉。
指數運算
指數運算 **
僅適用于無符號類型作為指數(冪)。指數運算的結果類型始終與底數的類型相同。請確保底數足夠大以容納結果,并預防潛在的斷言失敗或溢出行為。
注意:在 檢查模式(checked mode)下,對于較小的底數,指數運算僅使用相對廉價的 EXP
操作碼。
例如,在計算 x**3
時,使用 x*x*x
可能更便宜。因此,建議進行 Gas 成本測試 并使用優化器。此外,EVM 規定 0**0
的結果為 1
。
定點數
警告:定點數在 Solidity 中尚不完全支持。它們可以被聲明,但不能進行賦值操作。
fixed
/ ufixed
:帶符號和無符號定點數,具有不同的大小。關鍵字 ufixedMxN
和 fixedMxN
,其中 M 代表類型所占用的位數,N 代表可用的小數位數。M 必須是 8 的倍數,范圍從 8 到 256 位。N 必須在 0 到 80 之間(包含 0 和 80)。ufixed
和 fixed
是 ufixed128x18
和 fixed128x18
的別名。
操作符
-
比較操作符:
<=
,<
,==
,!=
,>=
,>
(結果為布爾值) -
算術操作符:
+
,-
,一元-
(僅對帶符號數),*
,/
,%
(取模)
注意:浮動點數(許多語言中的 float
和 double
,更精確地說是 IEEE 754 數字)和定點數的主要區別在于,浮動點數用于表示整數和小數部分的位數是靈活的,而定點數則嚴格定義了每部分所占的位數。通常,在浮動點數中,幾乎整個空間用于表示數字,而只有少數位數用于定義小數點的位置。
地址(Address)
地址類型有兩種主要相似的變體:
address
:持有一個 20 字節的值(以太坊地址的大小)。address payable
:與address
相同,但具有額外的transfer
和send
成員。
這種區分的想法是,address payable
是一個可以接收以太幣的地址,而普通的 address
不能接收以太幣,例如,它可能是一個不支持接收以太幣的智能合約。
類型轉換
1.允許從 address payable
到 address
的隱式轉換,而從 address
到 address payable
必須通過顯式轉換 payable(<address>)
。
2.允許顯式轉換到 address
類型并返回 uint160
、整數字面量、bytes20
和合約類型。
3.只有 address
類型和合約類型的表達式可以通過顯式轉換 payable(...)
轉換為 address payable
。對于合約類型,只有在合約可以接收以太幣的情況下(即合約有 receive
或 payable
回退函數)才能進行這種轉換。注意,payable(0)
是有效的,且是這一規則的例外。
注意
如果我們需要一個 address
類型的變量,并計劃向其發送以太幣,那么應將其聲明為 address payable
以使該要求更加明顯。此外,盡量在最早階段做出這種區分或轉換。
從 0.5.0 版本開始,address
和 address payable
之間的區分被引入。自那時起,合約不能隱式轉換為 address
類型,但如果它們有 receive
或 payable
回退函數,仍然可以顯式地轉換為 address
或 address payable
。
地址成員
balance 和 transfer
可以使用 balance
屬性查詢地址的余額,并使用 transfer
函數將以太幣(以 wei 為單位)發送到可支付的地址:
address payable x = payable(0x123);
address myAddress = address(this);
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
transfer
函數在當前合約的余額不足或接收方帳戶拒絕接收以太幣時失敗,并會回滾操作。
注意
如果 x
是一個合約地址,它的代碼(更具體地說:其 Receive Ether
函數如果存在,或其他回退函數如果存在)將在 transfer
調用時一起執行,這是 EVM 的特性,無法阻止。如果該執行耗盡了 gas 或以任何方式失敗,Ether 轉賬將回滾,當前合約將停止并拋出異常。
send
send
是 transfer
的低級對等函數。如果執行失敗,當前合約不會停止并拋出異常,而是返回 false
。
使用 send
時存在一些危險:如果調用堆棧深度達到 1024(調用者可以強制此情況),或者接收方耗盡 gas,則轉賬將失敗。因此,為了安全地轉賬以太幣,始終檢查 send
的返回值,使用 transfer
或更好的方式是使用一個模式,其中接收方自己提取以太幣。
call,delegatecall 和 staticcall
為了與不符合 ABI 的合約交互,或為了更直接地控制編碼,可以使用 call
、delegatecall
和 staticcall
函數。它們都接受一個字節內存參數,并返回成功條件(布爾值)和返回的數據(字節內存)。abi.encode
、abi.encodePacked
、abi.encodeWithSelector
和 abi.encodeWithSignature
可以用來編碼結構化數據。
示例:
bytes memory payload = abi.encodeWithSignature("register(string)", "MyName");
(bool success, bytes memory returnData) = address(nameReg).call(payload);
require(success);
注意,這些都是低級函數,應該小心使用。特別是,任何未知的合約可能是惡意的,如果調用它,你將把控制權交給該合約,而該合約可能會再次調用你的合約,因此當調用返回時,需要做好準備處理可能會更改的狀態變量。與其他合約交互的常規方式是調用合約對象上的函數(例如 x.f()
)。
可以使用 gas
修飾符調整提供的 gas:
address(nameReg).call{gas: 1000000}(abi.encodeWithSignature("register(string)", "MyName"));
類似地,也可以控制提供的 Ether 數量:
address(nameReg).call{value: 1 ether}(abi.encodeWithSignature("register(string)", "MyName"));
最后,這些修飾符可以結合使用,它們的順序無關緊要:
address(nameReg).call{gas: 1000000, value: 1 ether}(abi.encodeWithSignature("register(string)", "MyName"));
類似地,可以使用 delegatecall
函數,不同之處在于,只有給定地址的代碼會被使用,所有其他方面(存儲、余額等)都來自當前合約。delegatecall
的目的是使用存儲在另一個合約中的庫代碼。用戶必須確保兩個合約的存儲布局適合使用 delegatecall
。
code 和 codehash
你可以查詢任何智能合約的已部署代碼。使用 .code
獲取 EVM 字節碼作為字節內存(可能為空)。使用 .codehash
獲取該代碼的 Keccak-256 哈希(作為 bytes32
)。注意,addr.codehash
比使用 keccak256(addr.code)
更便宜。
如果與 addr
相關聯的帳戶為空或不存在(即它沒有代碼、零余額和零 nonce,如 EIP-161 所定義),則 addr.codehash
的輸出可能為 0。如果該帳戶沒有代碼,但有非零余額或 nonce,則 addr.codehash
將輸出空數據的 Keccak-256 哈希(即 keccak256("")
,其結果為 c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
)。
注意,所有合約都可以轉換為 address
類型,因此可以使用 address(this).balance
查詢當前合約的余額。
合約類型(Contract Types)
每個合約都定義了自己的類型。你可以將合約隱式轉換為它繼承的合約類型。合約類型可以顯式地轉換為 address
類型,反之亦然。
顯式轉換到 address payable
類型僅在合約類型有 receive
或 payable
回退函數時才可能。轉換仍然通過 address(x)
進行。如果合約類型沒有 receive
或 payable
回退函數,可以使用 payable(address(x))
進行轉換。
注意
1.如果你聲明一個合約類型的局部變量(例如 MyContract c
),你可以在該合約上調用函數。需要注意的是,必須從與之相同的合約類型賦值給該變量。
2.你也可以實例化合約(這意味著它們是新創建的)。你可以在“通過 new 創建合約”部分找到更多的細節。
3.合約的數據顯示方式與 address
類型相同,并且這種類型也用于 ABI 中。
4.合約不支持任何操作符。
5.合約類型的成員是該合約的外部函數,包括任何標記為 public
的狀態變量。
6.對于合約 C
,你可以使用 type(C)
來訪問有關該合約的類型信息。
固定大小字節數組(Fixed-size byte arrays)
值類型 bytes1
, bytes2
, bytes3
, …, bytes32
用于存儲從 1 到 32 字節的字節序列。
操作符:
- 比較操作符:
<=
,<
,==
,!=
,>=
,>
(返回布爾值) - 位操作符:
&
,|
,^
(按位異或),~
(按位取反) - 移位操作符:
<<
(左移),>>
(右移) - 索引訪問:如果
x
是類型bytesI
,則x[k]
(0 <= k < I)返回第k
個字節(只讀)。
移位操作符與無符號整數類型作為右操作數一起工作(但返回左操作數的類型),表示要移位的位數。使用有符號類型進行移位會導致編譯錯誤。
成員.length
可以返回字節數組的固定長度(只讀)。
注意
類型 bytes1[]
是字節的數組,但由于填充規則,對于每個元素,它浪費 31 字節的空間(在存儲中除外)。最好使用 bytes
類型。
地址字面量(Address Literals)
地址字面量是通過地址校驗和測試的十六進制字面量,例如 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF
是 address
類型。長度在 39 到 41 位之間且未通過校驗和測試的十六進制字面量會產生錯誤。
我們可以通過在前面(對于整數類型)或后面(對于 bytesNN
類型)加零來去除錯誤。