跳至內容
0%

Yul 中的記憶體如何運作?

發布於 2023年7月28日閱讀時間 4 分鐘

Yul 是一種中階程式語言,可以用來在智能合約內撰寫組合語言。學會 Yul storage 以及如何讀寫 packed storage 變數之後,接下來要學習的是 memory 在 Yul 智能合約中如何運作。

Yul 中的 memory 如何運作?

Memory 的行為和 storage 不同。Memory 不是持久性的,也就是說函式執行結束後,所有變數都會被清除。

Memory 可以比擬為其他語言中的 heap,但沒有 garbage collector。

Memory 比 storage 便宜很多。前 22 個 word 的 memory 成本是線性計算的,但要小心,超過這個範圍後成本會變成二次方成長。

Memory 是以 32 byte 為單位排列的。稍後會更深入說明,但現在先了解 0x00 - 0x20 是一個序列(可以想成是一個 slot,如果這樣理解比較容易,但兩者其實不同)。

Solidity 將 0x00 - 0x40 配置為 scratch space。這段 memory 不保證是空的,會用於某些運算。

0x40 - 0x60 儲存的是 free memory pointer 的位置,用來寫入新資料到 memory 中。

0x60 - 0x80 是留空的間隔。

0x80 是我們開始進行操作的地方。

Memory 不會將數值 packed 在一起。從 storage 取出的值會各自存放在獨立的 32 byte 序列中(例如 0x80-0xa0)。

Yul 中哪些運算會用到 memory?

以下運算會用到 memory:

  • External call 的回傳值
  • External call 的函式設定值
  • External call 取得的數值
  • 用錯誤訊息 revert
  • Log 訊息
  • 用 keccak256() 進行雜湊運算
  • 建立其他智能合約

以下是一些常用於 memory 操作的 Yul 指令:

Instruction
Explanation

mload(p)

類似 sload(),但意思是讀取 p 之後的下 32 bytes

mstore(p, v)

類似 sstore(),但意思是把數值 v 存到 p 到 p + 32 bytes 的位置

mstore8(p, v)

類似 mstore(),但只針對單一 byte

msize()

回傳目前已存取過最大的 memory index

pop(x)

捨棄數值 x

return(p, s)

結束執行,並回傳位於 memory 中 p - v 位置的資料

revert(p, s)

結束執行且不儲存任何狀態變更,並回傳位於 memory 中 p - v 位置的資料

接著我們來看看更多資料結構!

如何在 Yul 中使用 struct 和 memory

Struct 和 fixed array 的行為其實一樣,但由於我們已經在 storage 章節看過 fixed array,這裡就來看看 struct。請看以下這個 struct。

這裡沒有什麼特別之處,就是一個簡單的 struct。

現在來看一些程式碼!

這裡我們把 s.subVar1 設定在 memory 位置 0x80 - 0xa0,把 s.subVar2 設定在 memory 位置 0xa0 - 0xc0。這就是為什麼我們回傳的是 0x80 - 0xc0。以下是交易結束前的 memory 配置表。

Memory Location
Value Stored

0x00

Scratch Space(空)

0x20

Scratch Space(空)

0x40

0xc0(Free Memory Pointer)

0x60

0x80

s.subVar1: 0x20(十進位為 32)

0xa0

s.subVar1: 0x40(十進位為 64)

0xc0

新的 Free Memory Pointer。這就是 msize() 會回傳的值。(空)

從中可以歸納出以下重點:

  • 0x00 - 0x40 為 scratch space,是空的
  • 0x40 給我們 free memory pointer
  • Solidity 在 0x60 留了一個間隔
  • 0x80 和 0xa0 用來儲存 struct 的值
  • 0xc0 是新的 free memory pointer

在 memory 這個章節的最後一部分,我要示範 dynamic array 在 memory 中是如何運作的。這個例子中我們會傳入 [0, 1, 2, 3] 作為參數 arr。作為額外示範,我們還會在這個陣列中新增一個元素。在正式環境這樣操作時要特別小心,因為可能會覆寫到其他 memory 變數。

程式碼如下:

這裡發生的事情是:

  • 取得陣列在 memory 中的儲存位置
  • 取得陣列的長度,這儲存在陣列的第一個 memory 位置
  • 將位置加上 32 bytes(跳過陣列長度)以找到下一個可用位置
  • 將陣列長度乘以 32 bytes,移動到下一個 memory 位置
  • 儲存我們的新值(4)
  • 將陣列長度更新加一
  • 更新 free memory pointer
  • 回傳陣列

我們再看一次 memory 的配置。

Memory Location
Value Stored

0x00

Scratch Space(空)

0x20

Scratch Space(空)

0x40

0x140(Free Memory Pointer)

0x60

0x80

陣列的新長度(6)

0xa0

arr[0](0)

0xc0

arr[1](1)

0xe0

arr[2](2)

0x100

arr[3](3)

0x120

arr[4](4)

0x140

Free Memory Pointer(空)

Memory 章節到此結束!

接下來,可以學習如何用 Yul 呼叫智能合約,或回去複習如何讀寫 packed storage 變數

Background gradient

打造區塊鏈魔法

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