跳至內容
0%

Yul storage 如何運作?

發布於 2023年8月1日閱讀時間 2 分鐘

Yul 是一種中介程式語言,可以用來在智能合約中撰寫組合語言的一種形式。理解 Yul 可以提升你的智能合約,並幫助你節省使用者的 gas 成本。要開始用 Yul 撰寫智能合約,理解 storage 的運作方式非常重要。

Yul storage 是如何運作的?

在深入了解 Yul 之前,我們需要先充分理解智能合約中 storage 的運作方式。Storage 由一系列的 slot 組成。一個智能合約有 2²⁵⁶ 個 slot。

在宣告變數時,我們從 slot 0 開始,依序遞增。每個 slot 長度為 256 位元(32 bytes),這也是 uint256 和 bytes32 名稱的由來。所有變數都會被轉換為十六進位。

如果使用的是像 uint128 這樣的變數,我們不會用整個 slot 來儲存這個變數。相反地,它會在左側被填補 0。讓我們透過一個範例來加深理解。

var1:由於 uint256 變數等於 32 bytes,var1 會佔用整個 slot 0。

以下是 slot 0 中所儲存的內容:

0x0000000000000000000000000000000000000000000000000000000000000100

var2:地址稍微複雜一些。由於地址只佔用 20 bytes 的儲存空間,地址會在左側被填補 0。

以下是 slot 1 中所儲存的內容:

0x0000000000000000000000009acc1d6aa9b846083e8a497a661853aae07f0f00.

var3:這個看起來很簡單,slot 2 完全被這個 bytes32 變數佔用。

var4 和 var5:還記得我提到 uint128 會被填補 0 嗎?如果我們安排變數順序,讓它們的儲存總和小於 32 bytes,我們就可以把它們一起放進同一個 slot!這稱為 packing variables(打包變數),可以幫你節省 gas。

讓我們看看 slot 3 中儲存了什麼:

0x0000000000000000000000000000000200000000000000000000000000000001. 

注意 0x0000000000000000000000000000020x000000000000000000000000000001 恰好完美地放進同一個 slot 中。這是因為它們各自佔用 16 bytes(一個 slot 的一半)。

Instruction
Explanation

sload(p)

Loads the variable in slot p from storage.

sstore(p,v)

Assigns storage slot p value v.

v.slot

Returns the storage slot of variable v.

v.offset

Returns the index in bytes of where variable v begins in a storage slot. Variables are packed from right to left.

我們再來看另一個範例!

x = 3。這說得通,因為我們知道 var5 被打包進 slot 3。

y = 16。這也應該說得通,因為我們知道 var4 佔用 slot 3 的一半。由於變數是從右到左打包的,所以 var5 的起始索引是 byte 16。

z = 1。sstore() 將 slot 0 的值設為 1。接著,我們用 sload() 將 slot 0 的值賦予給 z。

在繼續之前,你應該把這個函式加進你的 remix 檔案中。它可以幫助你查看每個 storage slot 中儲存的內容。

現在讓我們來看一些更複雜的資料結構!

在處理靜態陣列時,EVM 知道要為我們的資料分配多少個 slot。以這個陣列為例,我們每個 slot 打包了 2 個元素。

所以如果你呼叫 getValInHex\(4\),它會回傳 0x0000000000000000000000000000000100000000000000000000000000000000

如我們所預期的,從右到左讀取,我們會看到 value 0 和 value 1。

Slot 5 包含 0x0000000000000000000000000000000300000000000000000000000000000002

接下來我們要看動態陣列。

試著呼叫 getValInHex\(6\)。你會看到它回傳 0x00。因為 EVM 不知道需要分配多少個 storage slot,所以我們無法把陣列儲存在這裡。

相反地,目前 storage slot(slot 6)的 keccak256 雜湊值會被用作陣列的起始索引。從這裡開始,我們只需要加上想要的元素索引,就可以取得該值。

以下是示範如何找到動態陣列中某個元素的程式碼範例:

我們先取得陣列的 slot,接著執行 add() 運算並搭配 sload() 來取得我們想要的陣列元素值。

你可能會問,什麼可以防止我們與其他變數的 slot 發生碰撞?

這種情況完全有可能發生,但由於 2²⁵⁶ 是一個非常大的數字,發生的機率極低。

Mappings 的行為與動態陣列類似,差別在於我們會把 slot 與 key 一起雜湊。

在這個示範中,我設定了 mapping 的值 var8[1] = 2。現在讓我們看一個範例,說明如何取得 mapping 中某個 key 的值。

如你所見,這段程式碼看起來與我們在動態陣列中找元素時非常相似。主要差別在於我們把 key 和 slot 一起雜湊。

本節關於 storage 的最後一部分是學習巢狀 mapping。在繼續閱讀之前,我建議你根據目前所學,自己試著寫出如何讀取巢狀 map 值的實作方式。

在這個範例中,我設定了 mapping 的值 var9[0][1] = 2。

以下是程式碼,我們來深入了解!

以下是運作的過程:

  1. 我們先取得第一個 key(0)的雜湊值。
  2. 接著把這個雜湊值與第二個 key(1)一起再做一次雜湊。
  3. 最後,從 storage 讀取該 slot 以取得我們的值。

恭喜,你已經完成了 Yul storage 這個章節!

延伸閱讀 Yul MemoryYul Contract Calls 以及 Yul Packed Storage Variables

Background gradient

打造區塊鏈魔法

Alchemy 結合最強大的 Web3 開發者產品與工具,並提供資源、社群與卓越的支援。