1. 使用短路模式排序 Solidity 操作#
短路(short-circuiting)是一種使用或 / 與邏輯來排序不同成本操作的 solidity 合約 開發模式,它將低 gas 成本的操作放在前面,高 gas 成本的操作放在後面,這樣如果前面的低成本操作可行,就可以跳過(短路)後面的高成本以太坊虛擬機操作了。
// f(x) 是低gas成本的操作
// g(y) 是高gas成本的操作
// 按如下排序不同gas成本的操作
f(x) || g(y)
f(x) && g(y)
2. 精確聲明 Solidity 合約函數的可見性#
在 Solidity 合約開發中,顯式聲明函數的可見性不僅可以提高智能合約的安全性, 同時也有利於優化合約執行的 gas 成本。例如,通過顯式地標記函數為外部函數(External),可以強制將函數參數的存儲位置設置為calldata
,這會節省每次函數執行時所需的以太坊 gas 成本。
External 可見性比 public 消耗 gas 少。
3. 使用適合的數據類型#
在 Solidity 中,有些數據類型要比另外一些數據類型的 gas 成本高。有必要 了解可用數據類型的 gas 利用情況,以便根據你的需求選擇效率最高的那種。 下面是關於 solidity 數據類型 gas 消耗情況的一些規則:
- 在任何可以使用
uint
類型的情況下,不要使用string
類型 - 存儲 uint256 要比存儲 uint8 的 gas 成本低,為什麼?點擊這裡查看原文
- 當可以使用
bytes
類型時,不要在 solidity 合約種使用byte[]
類型 - 如果
bytes
的長度有可以預計的上限,那麼盡可能改用 bytes1~bytes32 這些具有固定長度的 solidity 類型 - bytes32 所需的 gas 成本要低於 string 類型
4.避免 Solidity 智能合約中的死代碼#
死代碼(Dead code)是指那些永遠也不會執行的 Solidity 代碼,例如那些執行條件永遠也不可能滿足的代碼,就像下面的兩個自相矛盾的條件判斷裡的 Solidity 代碼塊,消耗了以太坊 gas 資源但沒有任何作用:
function deadCode(uint x)public pure {
if(x < 1 {
if(x > 2) {
return x;
}
}
}
5.避免使用不必要的條件判斷#
有些條件斷言的結果不需要 Solidity 代碼的執行就可以知道結果,那麼這樣的條件判斷就可以精簡掉。例如下面的 Solidity 合約代碼中的兩級判斷條件,內層的判斷是在浪費寶貴的以太坊 gas 資源:
function opaquePredicate(uint x) public pure {
if(x < 1) {
if(x < 0 ) {// uint 不可能小於0
return x;
}
}
}
6.避免在迴圈中執行 gas 成本高的操作#
由於SLOAD
和SSTORE
操作碼的成本高昂,因此管理 storage 變數的 gas 成本 要遠遠高於內存變數,所以要避免在迴圈中操作 storage 變數。例如下面的 solidity 代碼中,num
變數是一個 storage 變數,那麼未知迴圈次數的若干次操作,很可能會造成 solidity 開發者意料之外的以太坊 gas 消耗黑洞:
uint num = 0;
functionexpensiveLoop(uint x)public {
for(uint i = 0; i < x; i++) {
num += 1;
}
}
解決上述以太坊合約代碼問題的方法,是創建一個 solidity 臨時變數 來代替上述全局變數參與迴圈,然後在迴圈結束後重新將臨時變數的值賦給全局狀態變數:
uint num = 0;
functionlessExpensiveLoop(uint x)public {
uint temp = num;
for(uint i = 0; i < x; i++) {
temp += 1;
}
num = temp;
}
7. 避免使用常量結果的迴圈#
如果一個迴圈計算的結果是無需編譯執行 Solidity 代碼就可以預測的,那麼 就不要使用迴圈,這可以可觀地節省 gas。例如下面的以太坊合約代碼就可以 直接設置 num 變數的值:
functionconstantOutcome()publicpurereturns(uint) {
uint num = 0;
for(uint i = 0; i < 100; i++) {
num += 1;
}
return num;
}
8. 避免迴圈中的重複計算#
如果迴圈中的某個 Solidity 表達式在每次迭代都產生同樣的結果,那麼就可以將其 移出迴圈先行計算,從而節省掉迴圈中額外的 gas 成本。如果表達式中使用的變數是 storage 變數, 這就更重要了。例如下面的智能合約代碼中表達式a*b
的值,並不需要每次迭代重新計算:
uint a = 4;
uint b = 5;
functionrepeatedComputations(uint x)publicreturns(uint) {
uint sum = 0;
for(uint i = 0; i <= x; i++) {
sum = sum + a * b;
}
}
9. 去除迴圈中的比較運算#
如果在迴圈的每個迭代中執行比較運算,但每次的比較結果都相同,則應將其從迴圈中刪除。
functionunilateralOutcome(uint x)publicreturns(uint) {
uint sum = 0;
for(uint i = 0; i <= 100; i++) {
if(x > 1) {
sum += 1;
}
}
return sum;
}
10. 為常用函數選擇一個函數名稱使得 hash 值盡可能小#