Solidity

如何編寫優化的(gas-cost)智能合約?

  • December 10, 2021

眾所周知,決定一個好的智能合約的因素很多,比如:

  • 安全性:它具有最小/零漏洞,因此它們不能被對手利用。免疫攻擊。
  • 費用:一共多少錢
 (a) smart contract deployment costs, 

 (b) running/invoking each function of it costs. 
  • 正確性:按計劃執行。

在這個問題中,我想關注智能合約的成本。

問題一:如何做出高性價比的智能合約?

問題2:如何避免使用一些昂貴的功能,有哪些替代方案?換句話說,有沒有眾所周知的比其他操作更昂貴的函式,我們可以用更好的函式替換它們嗎?

問題3:一般來說,以最低成本編寫智能合約的良好做法是什麼?

簡而言之: 我們如何節省 gas : ether : money?

在乙太坊中,交易需要gas,因此需要ether。交易的 gas 消耗取決於 EVM 必須執行的操作碼。每個 Opcode 的 gas 成本可以在這個問題中找到。很少有常見的操作碼和氣體是,

Operation         Gas           Description

ADD/SUB           3             Arithmetic operation
MUL/DIV           5             Arithmetic operation
ADDMOD/MULMOD     8             Arithmetic operation
AND/OR/XOR        3             Bitwise logic operation
LT/GT/SLT/SGT/EQ  3             Comparison operation
POP               2             Stack operation 
PUSH/DUP/SWAP     3             Stack operation
MLOAD/MSTORE      3             Memory operation
JUMP              8             Unconditional jump
JUMPI             10            Conditional jump
SLOAD             200           Storage operation
SSTORE            5,000/20,000  Storage operation
BALANCE           400           Get balance of an account
CREATE            32,000        Create a new account using CREATE
CALL              25,000        Create a new account using CALL

當涉及到智能合約時,這是一個問題,因為還涉及交易,並且在設計合約時考慮天然氣成本很重要。

減少合約消耗的gas在兩種情況下很重要,

  1. 部署合約的成本
  2. 呼叫合約函式的成本

部署合約的成本

為此,大部分優化都是在編譯時完成的,如文件常見問題解答中所述,

已部署的契約中是否包含註釋,它們是否會增加部署氣體?

不,所有不需要執行的東西都會在編譯期間被刪除。其中包括註釋、變數名稱和類型名稱。

優化器的詳細資訊可以在這裡找到。

另一種減小大小的方法是刪除無用的程式碼。例如:

1 function p1 ( uint x ){ 
2    if ( x > 5)
3     if ( x*x < 20)
4        XXX }

在上面的程式碼中,第 3 行和第 4 行永遠不會被執行,這些類型的無用程式碼可以通過仔細檢查合約邏輯來避免,這將減少智能合約的大小。

呼叫合約函式的成本

當呼叫合約的函式時,函式的執行會消耗gas。因此,優化功能以使用更少的氣體很重要。當考慮個人契約時,可以有許多不同的方法。以下是一些可能在執行期間節省氣體的方法,

  1. 減少昂貴的操作

昂貴的操作是具有更多氣體值的操作碼,例如SSTORE. 以下是一些減少昂貴操作的方法。

A) 使用短路規則

運營商 || && 應用常見的短路規則。這意味著在表達式 f(x) || g(y),如果 f(x) 計算結果為真,即使 g(y) 可能有副作用,也不會計算 g(y)。

因此,如果一個邏輯操作包括一個昂貴的操作和一個低成本的操作,以一種可以將昂貴的操作短路的方式安排將在某些執行中減少 gas。

如果 f(x) 成本低而 g(y) 成本高,則安排邏輯操作

  • 或者 : f(x) || g(y)
  • 和 : f(x) && g(y)

如果短路會節省更多的氣體。

如果f(x)與 相比,返回 false 的機率要高得多,則g(y)安排 AND 操作f(x) && g(y)可能會通過短路在執行中節省更多的 gas。

與 相比,如果f(x)返回 true 的機率要高得多,則g(y)安排 OR 操作f(x) || g(y)可能會通過短路在執行中節省更多的 gas。

B)循環中的昂貴操作

例如:

uint sum = 0;
function p3 ( uint x ){
    for ( uint i = 0 ; i < x ; i++)
        sum += i; }

在上面的程式碼中,由於sum儲存變數在循環內每次都被讀寫,因此每次迭代都會發生昂貴的儲存操作。這可以通過引入一個局部變數來避免,如下所示以節省氣體。

uint sum = 0;
function p3 ( uint x ){
    uint temp = 0;
    for ( uint i = 0 ; i < x ; i++)
        temp += i; }
    sum += temp;
  1. 其他循環相關模式

循環組合

function p5 ( uint x ){
   uint m = 0;
   uint v = 0;
   for ( uint i = 0 ; i < x ; i++) //loop-1
       m += i;
   for ( uint j = 0 ; j < x ; j++) //loop-2
       v -= j; }

loop-1 和 loop-2 可以組合,可以節省氣體,

function p5 ( uint x ){
   uint m = 0;
   uint v = 0;
   for ( uint i = 0 ; i < x ; i++) //loop-1
      m += i;
      v -= j; }

在這裡可以找到更多的循環模式。

  1. 使用固定大小的字節數組

文件

可以使用字節數組作為字節

$$ $$,但是在傳遞呼叫時,它浪費了大量空間,每個元素 31 個字節。最好使用字節。

根據經驗,對任意長度的原始字節數據使用字節,對任意長度的字元串 (UTF-8) 數據使用字元串。如果您可以將長度限制為一定數量的字節,請始終使用 bytes1 到 bytes32 之一,因為它們便宜得多。

具有固定長度總是可以節省氣體。也參考這個問題

  1. 如果可以在函式內部完成,那麼即使在執行函式時,刪除前面在合約部署中解釋的無用程式碼也會節省燃料。
  2. 在實現功能時不使用庫對於簡單的用途來說更便宜。

為簡單用途呼叫庫可能代價高昂。如果該功能在合約內部實現簡單可行,因為它避免了呼叫庫的步驟。僅功能的執行成本對於兩者仍然相同。

  1. 對僅從外部訪問的函式使用可見性external強制calldata用作參數位置,這在函式執行時節省了一些氣體。
  2. 盡可能在本地使用memory函式中的變數可以節省訪問storage.

這些是一些節省氣體的方法,根據需要可能還有許多其他方法。

引用自:https://ethereum.stackexchange.com/questions/28813