跳至主要內容
本頁面使用機器翻譯自英語,可能包含錯誤或不清楚的語言。如需最準確的信息,請參閱英文原文。由於更新頻繁,部分內容可能與英文原文有出入。請加入我們在 Crowdin 上的努力,幫助我們改進本頁面的翻譯。 (Crowdin translation page, Contributing guide)

如何在 Solidity 智慧合約中優化瓦斯費用

本指南提供實用的逐步演練,說明在 Solidity 中撰寫智慧型契約時,如何優化瓦斯成本。

為什麼瓦斯最佳化很重要?

氣體最佳化是智慧型契約開發的重要部分。 它有助於確保智慧型契約即使在網路高度擁塞的情況下,仍能保持高效率與成本效益。 透過降低合約執行的計算開銷,開發人員可以降低交易費用、加快確認時間,並改善其 dApp 的整體可擴展性。

對開發人員而言,氣體最佳化就是編寫乾淨、安全且可預測的程式碼,以減少不必要的計算。 對使用者而言,這是確保他們能與您的合約互動,而無需支付過高的費用。

為什麼對 Kaia 特別重要

隨著超過 83 個 Mini dApp 的推出,Kaia 區塊鏈已經成為交易量領先的 EVM 相容鏈,其主要動力來自這些上鏈應用程式的爆炸性增長。

每個 Mini dApp 都依賴智慧型契約來執行上鏈動作。 無論是鑄造物品、下注或管理遊戲中的資產,每一次契約互動都會消耗瓦斯。 如果不進行優化,這些 dApp 可能很快就會變得太昂貴,讓使用者無法與之互動,尤其是在規模較大的情況下。

這就是為什麼瓦斯效率不僅僅是一個很好的條件。 這是必要的。 在 Kaia 上進行建置的開發人員必須確保每個函式呼叫都經過最佳化,在保留功能性與安全性的同時,將成本降至最低。

氣體最佳化技術

儲存包裝

在區塊鏈上儲存和檢索資料是最耗氣的作業之一,尤其是當資料必須跨交易和區塊持續存在時。 在 Solidity 中,這些資料會儲存在合約儲存中,這是永久性的,並且會產生瓦斯成本。 為了降低這些成本,開發人員必須仔細優化儲存的使用方式,尤其是在宣告狀態變數時。

Kaia 虛擬機器 (KVM) 將合約資料儲存在稱為儲存槽的單元中。 每個儲存槽正好可以容納 256 位元組 (32 位元組) 的資料。 Solidity 資料類型有不同的大小 - 例如,bool 是 1 位元組,而位址是 20 位元組。

透過一種稱為儲存包裝的技術,我們可以將較小的變數緊密排列,使其能夠容納在單一 32 位元組的儲存槽中。 這有助於減少瓦斯用量,因為從一個儲存槽讀取或寫入的成本遠低於存取多個儲存槽。

讓我們考慮以下範例:

分解:

在未最佳化版本 (SlotUnOptimized) 中,Solidity 會這樣儲存結構:

  • 位址至 -> 需要 20 位元組 -> 儲存在插槽 0 中
  • uint256 numConfirmations -> 需要 32 位元組 -> 儲存在插槽 1 中
  • uint80 值 (10 位元組) 和 bool 執行 (1 位元組) -> 儲存於插槽 2

儘管valueexecuted變數很小,除非明確地重新排序,否則 Solidity 會因為對齊填充的關係,將它們放在自己的儲存槽中。 因此,此結構使用 3 個儲存槽,也就是儲存作業的瓦斯成本是一般的 3 倍。 但是,address (20 位元組) + uint80 (10 位元組) + bool (1 位元組)的總大小為 31 位元組,落在單一插槽的 32 位元組限制之內。 只要重新排列宣告的順序,讓較小的變數集中在一起,Solidity 就可以將它們打包到同一個槽中。 這就是儲存包裝的精髓。

如上圖所示,在最佳化版本 (SlotOptimized) 中,所有較小的變數都會彼此相鄰放置,讓編譯器可以將變數儲存在較少的插槽中 - 降低部署和執行時的瓦斯成本。

快取記憶體儲存

除了變數在儲存槽中的佈局方式之外,瞭解與存取和修改儲存相關的瓦斯成本也很重要。

Kaia 虛擬機器上每個儲存插槽的成本: 初始化 (第一次寫入) 需 20,000 gas 更新 (後續寫入) 需 5,000 gas 正因如此,將直接儲存讀取和寫入的次數降至最低是非常重要的,尤其是在經常被呼叫的函式中。 一種有效的模式是,當您需要在函式中多次存取儲存變數時,將它們快取至記憶體中。

讓我們考慮以下範例:

避免將變數初始化為預設值

在 Solidity 中,每個資料類型都有預先定義的預設值。 例如,address 預設為 address(0),bool 預設為 false,而 uint 預設為 0。 當開發人員在變數宣告時明確指定這些預設值,例如寫成 bool isActive = falseuint total = 0 時,就會出現常見的低效率情況。

雖然這在功能上是正確的,但卻在部署時引入不必要的氣體成本,因為 Solidity 已經預設設定這些值。 透過宣告狀態變數而不為其赋值,可以減少契約的 bytecode 大小,並避免額外的儲存操作。 這個小小的調整有助於讓您的智慧型契約更有效率,也更容易維護,尤其是在處理多重變數時。

讓我們考慮以下範例:

最小化連鎖資料

我們很清楚交易的大部分瓦斯成本來自於合約儲存的資料。 最好經常詢問哪些資料實際上需要儲存在鏈上或鏈下,並考慮兩種選項的取捨。 我們可以從完全 onchain NFT 的案例中看到這一點,以及相較於具有離鏈元資料的傳統 NFT,它們有多麼昂貴。 這意味著,您可以透過在鏈外儲存資訊,大幅減少智慧契約的耗氣量,只因為您分配給儲存的變數較少。

釋放未使用的儲存空間

有時候,我們會忘記釋放合約中未使用的資料,這無形中有時會增加瓦斯成本,也會造成網路膨脹。 在任何情況下,釋放未使用的儲存空間都很簡單,只要確定不再使用該值後,將其設定回 0 即可。 您也可以在 solidity 中使用特殊關鍵字 delete 來釋放任何資料類型。

讓我們考慮以下範例:

針對某些函數參數,將資料儲存在 calldata 中,而非記憶體中

一種有效的瓦斯高爾夫技巧是在函數中使用 calldata 來取得只讀的陣列參數。 Calldata 是一個不可修改、非持久性的區域,在外部呼叫時會儲存函式參數。 由於它不涉及任何儲存分配或複製,因此比記憶體便宜得多。

當您的函式只需要讀取輸入的陣列或字串而不需要修改時,將參數宣告為 calldata 有助於減少耗氣量。 這對於經常被呼叫或操作大量輸入資料的功能特別有利,例如批次傳輸或多收件人空投。

讓我們考慮以下範例:

使用映射取代陣列

在 Solidity 中,有兩種主要的資料結構用來管理資料:陣列映射。 陣列儲存項目的集合,其中每個元素都指定了特定的索引,因此適用於有序清單。 另一方面,映射的功能是允許透過唯一鍵直接存取值的鍵值儲存。

在處理陣列時,擷取特定的值通常需要在整個集合中循環,這會為每個計算步驟帶來氣體成本。 這使得陣列的查詢效率降低,尤其是在較大的資料集中。 除非有必要對類似項目進行有序的迭代或群組,否則使用映射來管理資料清單會更有效率。

映射提供恆定時間存取,並避免陣列遍歷的相關開銷,使其成為許多智慧型契約優化氣體使用的首選。

讓我們考慮以下範例:

固定大小陣列優於動態陣列

雖然映射通常比陣列更有效率,但在某些情況下,陣列也是必要的。 在這種情況下,當編譯時已知元素數量時,建議使用固定大小的陣列。 固定大小的陣列提供可預測的儲存模式,並避免與調整大小作業相關的開銷。

相反地,動態陣列的大小會在合約執行過程中不斷增加,因此會為記憶體分配和邊界檢查帶來額外的氣體成本。 盡可能選擇固定大小的陣列,有助於減少耗氣量,並提高智慧型契約的整體效能。

讓我們來看看這個範例:

使用不可變和常數

在 Solidity 中優化氣體成本的有效方法是宣告變數為常數或不可變數。 這些特殊的變數類型只會被指定一次值,如果是常數,則會在編譯時指定,如果是不可變數,則會在契約部署時指定,之後變成只讀值。 由於它們的值直接嵌入到合約的 bytecode 中,因此避免了儲存存取的需要,而儲存存取通常是智慧型合約執行中最昂貴的作業之一。 這使得它們成為減少氣體使用量的強大工具,同時維持程式碼的清晰度與效率。

讓我們來看看這個範例:

最佳化錯誤處理

在 Solidity 中優化氣體時,保持簡單和高效也適用於錯誤處理。 與使用字串訊息的傳統 require 語句相比,自訂錯誤提供了省油的替代方案。 與字串式錯誤不同 - 字串式錯誤會儲存在合約的 bytecode 中,並根據訊息的長度來增加大小,而自訂錯誤的成本明顯較低。

它們使用精簡的 4 位元組選擇器,從錯誤簽章的 keccak256 哈希值衍生出來,類似於函式選擇器的計算方式。 不論是在 require 或 if 語句中使用,自訂錯誤都有助於減少 bytecode 的大小和執行時的瓦斯成本,同時在除錯時仍能提供清晰度。

讓我們來看看這個範例:

使用外部可見度修改器

當您確定某個函式只會從契約外部被呼叫時,不論是外部擁有的帳戶或其他智慧契約,最好的做法是將函式的可見性聲明為外部。 此指引是基於 Solidity 如何處理函式參數和記憶體分配。

外部函數直接從呼叫資料讀取其參數,呼叫資料是針對外部輸入最佳化的唯讀區段。 另一方面,內部和外部都可以存取公共函式。 當公開函式從契約外部呼叫時,它會像外部函式一樣從呼叫資料讀取參數。

但是,在內部呼叫時,會透過記憶體傳遞參數,由於需要額外的記憶體分配和複製,因此成本較高。 透過將功能標示為外部功能,尤其是當功能並非供內部使用時,可以減少記憶體的操作並提高氣體效率。 這個小小的改變就能讓智慧型契約更優化、更具成本效益。

讓我們來看看這個範例:

使用直列組裝

內嵌式程式集允許開發人員直接與 KVM 作業碼互動。 與標準的 Solidity 程式碼相比,在某些情況下,此技術可使執行更有效率。 儘管它繞過了 Solidity 的安全檢查並降低了可讀性,但對於執行關鍵操作,如直接存取記憶體、位操作或自訂控制流程,它是特別有用的。 只要小心使用,線上組裝可精確控制機罩下的作業執行方式,有助於降低瓦斯成本。

讓我們來看看這個範例:

總結

最佳化瓦斯成本是在 Solidity 中撰寫有效率且符合成本效益的智慧型契約的重要部分。 雖然在 Kaia 上部署已為使用者提供較低的交易成本,但開發人員仍有責任應用行之有效的氣體最佳化技術。 透過遵循本指南中概述的實務,您可以大幅降低執行成本、改善合約的可擴充性,並為使用者提供更順暢且可持續的體驗。

讓這個頁面變得更好