4

2023 下半年,答應我要好好寫過一次 Huff. Huff 是一個 EVM 的低階程式語言,用以開發...

 1 year ago
source link: https://medium.com/taipei-ethereum-meetup/2023-%E4%B8%8B%E5%8D%8A%E5%B9%B4-%E7%AD%94%E6%87%89%E6%88%91%E8%A6%81%E5%A5%BD%E5%A5%BD%E5%AF%AB%E9%81%8E%E4%B8%80%E6%AC%A1-huff-e237423a97c1
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

2023 下半年,答應我要好好寫過一次 Huff

感謝

, 和 Bing’s defi tg 夥伴的意見

Huff 是一個 EVM 的低階程式語言,用以開發高度最佳化的合約。不是透過 IR 編譯出 bytecode,而是讓開發者直接手動操作 stack 來達成。這篇文章可以和 Openzeppelin — Deconstructing a Solidity Contract 對照著看,幫助讀者對 EVM opcodes 有更多的理解。

0*Iad8CTGSeMasMYHp

Installation

Huff 可以透過 curl 下載一個名為 huffup 的 command line tool,而 window 則要安裝 rust 然後用 cargo 安裝

linux / macOS

curl -L get.huff.sh | bash

windows

cargo install --git https://github.com/huff-language/huff-rs.git huff_cli --bins --locked

設定環境變數後,安裝編譯器的 binary

// ENV
export PATH="$PATH:$HOME/.huff/bin"// install huffc
huffup

Function Selector

Function selector 是 function signature 作雜湊後的前四個 bytes,用來告訴合約要執行哪個 function。而編譯成 binary 的合約則會執行以下流程:

  1. calldata region 擷取出 function selector
  2. 比對 function selector,如果有對應的 function selector,則執行該函式,通常會透過 jump 相關的 opcode 跳去對應的位置

更詳細的部分可以參考 Deconstructing a Solidity Contract — Part III: The Function Selector。Solidity 寫出來的合約基本上不用考慮這段流程,編譯器都有處理好,而 Huff 是一個低階語言,需要自己手寫這些流程。以下 Solidity 合約為例並且一步一步改寫成 Huff:

// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.16;

contract Counter {
uint256 private _value;

function current() external view returns (uint256) {
return _value;
}

function increase() external {
unchecked {
_value += 1;
}
}

function decrease() external {
unchecked {
_value -= 1;
}
}
}

定義 function signature

需要來定義 function signature,透過 #define 來定義,沒有回傳值也要寫明 returns

/* Interface */
#define function current() view returns (uint256)
#define function increase() nonpayable returns ()
#define function decrease() nonpayable returns ()

順帶一提,constant、event selector、error selector 還有 storage slot 都是以相同方式定義:

// constant
#define constant NUM = 0x420

// event
#define event Transfer(address, address, uint256)

// error
#define error PanicError(uint256)

// storage slot
#define constant VALUE_STORAGE = FREE_STORAGE_POINTER()

實作擷取 function selector

// huff
0x00 calldataload 0xE0 shr// compiled bytecode
// Init Calldata: 0x12341234??..??
// Init Stack: // []
PUSH1 0x00 // [ 0x00 ]
calldataload // [ 0x12341234??..?? (32 bytes) ]
PUSH1 0xE0 // [ 0xE0 0x12341234??..?? (32 bytes) ]
shr // [ 0x00..0012341234 ]

Huff 可以讓開發者直接將資料推入 stack,PUSH 相關的 opcodes 可以省略不寫,0x00 會被編譯成 PUSH1 0x00

擷取 function selector 是透過 calldataload 這個 opcode,但是 calldataload 一次會從 calldata region 讀取 32 bytes,所以透過 shift right 向右 224 (0xE0) bits,只留下 function selector 的部分。

比對 function selector

// huff
dup1 __FUNC_SIG(function_definition) eq ret_one jumpi// compiled bytecode
// Init Stack: // [ 0x12341234 ]
dup1 // [ 0x12341234 0x12341234 ]
PUSH4 selector // [ selector 0x12341234 0x12341234 ]
eq // [ true 0x12341234 ] // if match
PUSH2 label_position // [ position true 0x12341234 ]
jumpi // [ 0x12341234 ]

現在 stack 裡面只會有一個元素,這個元素為 function selector。現在可以用來比對合約是否有實作對應的 function signature 的函式。過程如下:

  1. 複製一個 function selector:比對的同時會消耗掉 stack 裡面的元素,詳情請見步驟 (3),需要複製一個 function selector 來做多次比對
  2. __FUNC_SIG() 是 Huff 實作的 helper function,可以傳入預先定義好的函式得到其 function selector,並放入 stack 中
  3. 現在 stack 裡面有來自 calldata 的 function selector 和定義好的 function selector,透過 eq 這個 opcode 來比較,如果比對成功,則將 0x01 作為 true 推入 stack,最後交由 jumpi 決定是否跳至對應的 label label_currentlabel_increaselabel_decrease。

最後程式碼如下:

/* Interface */
#define function current() view returns (uint256)
#define function increase() nonpayable returns ()
#define function decrease() nonpayable returns ()

#define macro MAIN() = takes (0) returns (0) {
0x00 calldataload 0xE0 shr
dup1 __FUNC_SIG(current) eq label_current jumpi
dup1 __FUNC_SIG(increase) eq label_increase jumpi
dup1 __FUNC_SIG(decrease) eq label_decrease jumpi

0x00 0x00 revert // not match

label_current:
// do something
label_increase:
// do something
label_decrease:
// do something
}

Function and Macro

Function 和 Macro 是 Huff 最主要使用的兩個功能,兩個定義很類似但是仍有一點差別,語法如下:

#define <macro|fn> TEST(args) = takes (1) returns (3) {
// ...
}

Macro 和 Function 都可以傳入參數,作為展開的依據。可以傳入 label、opcode 、 literal 或是 constant。

takes (n) returns (m) 是用來註明這段 bytecode 會在 stack 增加或是減少幾個元素,可加可不加。舉例 takes (2) returns (1) 表示的是這段 opcodes 會從 stack 消耗掉兩個元素作處理,最後會在 stack 推入一個元素。

Macro 顧名思義,寫在裡面的 bytecode 直接在呼叫的地方展開,不會有 jump 這類 opcodes 做跳轉。Function 則是將 bytecode 放置於合約尾端,從呼叫點透過 jump 這類 opcodes 做跳轉。更詳細的部分可以參考 Deconstructing a Solidity Contract — Part IV: Function WrappersDeconstructing a Solidity Contract — Part V: Function Bodies

Huff 有兩個作為特殊用途的 Macro:MAINCONSTRUCTOR。Main 是合約的進入點,CONSTRUCTOR 和 solidity constructor 類似,用於初始化合約用。

現在來實作 Counter 的函式,功能很單一,只有從 storage 取值做計算並儲存和回傳。首先要定義一個 storage slot,可以透過 huff 的 helper function FREE_STORAGE_POINTER() 取得一個沒有被使用的 slot,並定義成 constant。

Current macro 的實作是透過 sload 讀取定義好的 storage 的數值放入 stack 中。由於回傳的 opcode 只能讀取 memory region 的資料做回傳,所以要將 stack 的值存入 memory,最後再透過 return opcodes 做回傳。

Increase macro 的實作則是讀取定義好的 storage 的數值,並加上 1,然後再透過 sstore 寫回去原本的 storage slot 裡面。increase 的函式沒有回傳值,需要有 return 或是 stop 這些會終止執行的 opcode,告訴 EVM 執行到這邊就好,最後寫個 stop 即可。Decrease macro 以此類推。

/* State Variable */
#define constant VALUE_STORAGE = FREE_STORAGE_POINTER()

/* Methods */
#define macro CURRENT() = takes (0) returns (0) {
// init stack: []
[VALUE_STORAGE] // [value_storage]
sload // [value]
0x40 // [mem_ptr, value]
mstore // []
0x20 // [ret_size]
0x40 // [ret_offset, ret_size]
return // []
}

#define macro INCREASE() = takes (0) returns (0) {
// init stack: []
[VALUE_STORAGE] // [value_storage]
sload // [value]
0x01 // [1, value]
add // [(value + 1)]
[VALUE_STORAGE] // [value_storage, (value + 1)]
sstore // []
stop
}

補足其他部分,並將其填入 label 中:

/* Interface */
#define function current() view returns (uint256)
#define function increase() nonpayable returns ()
#define function decrease() nonpayable returns ()

/* State Variable */
#define constant VALUE_STORAGE = FREE_STORAGE_POINTER()

/* Methods */
#define macro CURRENT() = takes (0) returns (0) {
// init stack: []
[VALUE_STORAGE] // [value_storage]
sload // [value]
0x40 // [mem_ptr, value]
mstore // []
0x20 // [ret_size]
0x40 // [ret_offset, ret_size]
return // []
}

#define macro INCREASE() = takes (0) returns (0) {
// init stack: []
[VALUE_STORAGE] // [value_storage]
sload // [value]
0x01 // [1, value]
add // [(value + 1)]
[VALUE_STORAGE] // [value_storage, (value + 1)]
sstore // []
stop
}

#define macro DECREASE() = takes (0) returns (0) {
// init stack: []
0x01 // [1]
[VALUE_STORAGE] // [value_storage, 1]
sload // [value, 1]
sub // [(value - 1)]
[VALUE_STORAGE] // [value_storage, (value - 1)]
sstore // []
stop
}

#define macro MAIN() = takes (0) returns (0) {
0x00 calldataload 0xE0 shr
dup1 __FUNC_SIG(current) eq label_current jumpi
dup1 __FUNC_SIG(increase) eq label_increase jumpi
dup1 __FUNC_SIG(decrease) eq label_decrease jumpi

0x00 0x00 revert // not match

label_current:
CURRENT()
label_increase:
INCREASE()
label_decrease:
DECREASE()
}

Conclusions

最後來看一下 huff 和 solidity 寫出來的 bytecode 差別

透過 huffc 印出編譯過的 bytecode,這邊印出的是 creation code

huffc ./src/Counter.huff -b
60468060093d393df35f3560e01c80639fa6a6e314610029578063e8927fbc14610034578063d732d9551461003d575f5ffd5b5f5460405260206040f35b5f546001015f55005b60015f54035f5500

然後找出 solidity 的 creation code,參數 runs 為 200。runs 相關的最佳化策略是想辦法將 bytecode 再展開,增加部署成本,減低 runtime 的成本。連最低展開的 200 合約大小都比 huff 還大,其他的參數也不用測試了。

0x6080604052348015600e575f80fd5b5060a28061001b5f395ff3fe6080604052348015600e575f80fd5b5060043610603a575f3560e01c80639fa6a6e314603e578063d732d955146052578063e8927fbc146060575b5f80fd5b5f5460405190815260200160405180910390f35b605e5f80545f19019055565b005b605e5f8054600101905556fea2646970667358221220c232ece5cf5fa5af4cb1b5196324943a37da146ebfc8612575856034b155bba964736f6c63430008140033

Huff 提供給開發者可以實作出效能和 gas 近乎最佳化的合約,實作一些需要效能的東西或 library 非常適合,像是 weierstrudel 這類橢圓曲線的實作。一般開發者也可以從中學習 evm opcodes 的行為。

Reference

TEM Medium 2023 有獎徵稿

TEM Medium 目前正在進行有獎徵稿!詳情請參考:https://medium.com/taipei-ethereum-meetup/tem-medium-call-for-papers-with-prize-2023-q1-f384828f902f


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK