天天看點

10個可以減少Gas開銷的Solidity代碼模式

在以太坊區塊鍊上,Gas被用來補償礦工為智能合約的存儲與執行所提供的算力。目前以太坊的利用在逐漸增長,而交易手續費成本也水漲傳告 —— 現在每天的gas成本已經高達數百萬美元。随着以太坊生态系統的擴大,Solidity智能合約開發者也需要關注gas利用的優化問題了。本文将介紹在使用Solidity開發以太坊智能合約時常用的一些Gas優化模式。

以太坊教程連結: Dapp入門 | 電商Dapp實戰 Token實戰 Php對接 Java對接 Python對接 C#對接 Dart對接

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智能合約時,我們引入的庫通常隻需要用到其中的部分功能,這意味着其中可能會包含大量對于你的智能合約而言其實是備援的solidity代碼。如果可以在你自己的合約裡安全有效地實作所依賴的庫功能,那麼就能夠達到優化solidity合約的gas利用的目的。

例如,在下面的solidity代碼中,我們的以太坊合約隻是用到了SafeMath庫的

add

方法:

import './SafeMath.sol' as SafeMath;

contract SafeAddition {
  function safeAdd(uint a, uint b) public pure returns(uint) {
    return SafeMath.add(a, b);
  }
}           

通過參考SafeMath的這部分代碼的實作,可以把對這個solidity庫的依賴剔除掉:

contract SafeAddition {
  function safeAdd(uint a, uint b) public pure returns(uint) {
    uint c = a + b;
    require(c >= a, "Addition overflow");
    return c;
  }
}           

3、顯式聲明Solidity合約函數的可見性

在Solidity合約開發種,顯式聲明函數的可見性不僅可以提高智能合約的安全性,同時也有利于優化合約執行的gas成本。例如,通過顯式地标記函數為外部函數(External),可以強制将函數參數的存儲位置設定為

calldata

,這會節約每次函數執行時所需的以太坊gas成本。

4、使用正确的Solidity資料類型

在Solidity中,有些資料類型要比另外一些資料類型的gas成本高。有必要了解可用資料類型的gas利用情況,以便根據你的需求選擇效率最高的那種。下面是關于solidity資料類型gas消耗情況的一些規則:

  • 在任何可以使用

    uint

    類型的情況下,不要使用

    string

    類型
  • 存儲uint256要比存儲uint8的gas成本低,為什麼?點選這裡檢視 原文
  • 當可以使用

    bytes

    類型時,不要在solidity合約種使用

    byte[]

  • 如果

    bytes

    的長度有可以預計的上限,那麼盡可能改用bytes1~bytes32這些具有固定長度的solidity類型
  • bytes32所需的gas成本要低于string類型

5、避免Solidity智能合約中的死代碼

死代碼(Dead code)是指那些永遠也不會執行的Solidity代碼,例如那些執行條件永遠也不可能滿足的代碼,就像下面的兩個自相沖突的條件判斷裡的Solidity代碼塊,消耗了以太坊gas資源但沒有任何作用:

function deadCode(uint x) public pure {
  if(x < 1) {
    if(x > 2) {
      return x;
    }
  }
}           

6、避免使用不必要的條件判斷

有些條件斷言的結果不需要Solidity代碼的執行就可以了解,那麼這樣的條件判斷就可以精簡掉。例如下面的Solidity合約代碼中的兩級判斷條件,最外層的判斷是在浪費寶貴的以太坊gas資源:

function opaquePredicate(uint x) public pure {
  if(x < 1) {
    if(x < 0) {
      return x;
    }
  }
}           

7、避免在循環中執行gas成本高的操作

由于

SLOAD

SSTORE

操作碼的成本高昂,是以管理storage變量的gas成本要遠遠高于記憶體變量,是以要避免在循環中操作storage變量。例如下面的solidity代碼中,

num

變量是一個storage變量,那麼未知循環次數的若幹次操作,很可能會造成solidity開發者意料之外的以太坊gas消耗黑洞:

uint num = 0;

function expensiveLoop(uint x) public {
  for(uint i = 0; i < x; i++) {
    num += 1;
  }
}           

解決上述反模式以太坊合約代碼的方法,是建立一個solidity臨時變量來代替上述全局變量參與循環,然後在循環結束後重新将臨時變量的值賦給全局變量:

uint num = 0;

function lessExpensiveLoop(uint x) public {
  uint temp = num;
  for(uint i = 0; i < x; i++) {
    temp += 1;
  }
  num = temp;
}           

8、避免為可預測的結果使用Solidity循環

如果一個循環計算的結果是無需編譯執行Solidity代碼就可以預測的,那麼就不要使用循環,這可以可觀地節省gas。例如下面的以太坊合約代碼就可以直接設定num變量的值:

function constantOutcome() public pure returns(uint) {
  uint num = 0;
  for(uint i = 0; i < 100; i++) {
    num += 1;
  }
  return num;
}           

9、循環合并模式

有時候在Solidity智能合約中,你會發現兩個循環的判斷條件一緻,那麼在這種情況下就沒有理由不合并它們。例如下面的以太坊合約代碼:

function loopFusion(uint x, uint y) public pure returns(uint) {
  for(uint i = 0; i < 100; i++) {
    x += 1;
  }
  for(uint i = 0; i < 100; i++) {
    y += 1;
  }
  return x + y;
}           

10、避免循環中的重複計算

如果循環中的某個Solidity表達式在每次疊代都産生同樣的結果,那麼就可以将其移出循環先行計算,進而節省掉循環中額外的gas成本。如果表達式中使用的變量是storage變量,這就更重要了。例如下面的智能合約代碼中表達式

a*b

的值,并不需要每次疊代重新計算:

uint a = 4;
uint b = 5;
function repeatedComputations(uint x) public returns(uint) {
  uint sum = 0;
  for(uint i = 0; i <= x; i++) {
    sum = sum + a * b;
  }
}           

原文連結:

Solidity Gas優化的10個代碼模式 — 彙智網