跳至內容
0%

什麼是 Solidity 二進位檔?

Alchemy team headshot

作者 Alchemy

發布於 2023年7月10日閱讀時間 1 分鐘

透過智慧合約發布到 Ethereum 區塊鏈上的原始資料是位元組碼(bytecode),也就是一長串十六進位字元。雖然開發者是用人類可讀的 Solidity 程式碼來撰寫與閱讀智慧合約,但發布到區塊鏈上的並不是這種文字。

同樣地,每一次智慧合約的「呼叫」——也就是對智慧合約公開的外部可見函式所發出的請求——形式上都是原始位元組碼,或稱「binaries」。

以下是一個上傳至 Ethereum mainnet 的智慧合約,具有以下(Solidity 編碼的)結構:

假設某位使用者想要呼叫函式 baz,並帶入參數 69 和 true。

以下是這個請求以位元組碼傳輸時實際的樣子:

0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450...

很難讀懂,對吧?

在這篇文章中,我們會討論為什麼 Ethereum Virtual Machine 會把所有東西都編碼成位元組碼、了解什麼是 ABI 以及如何使用它,並學習一些基本工具,把位元組碼反編譯回人類可讀的 Solidity

**備註:**本文中的範例取自官方的 Solidity ABI 文件

為什麼 Solidity 要用二進位來編碼智慧合約?

由於在 Ethereum 區塊鏈上儲存資料的成本極高,而且上傳的每一位元組資料都需要複製到區塊鏈上所有的全節點,因此讀寫原始位元組碼會比上傳 Solidity 程式碼更有成本效益。

解析並儲存人類可讀的程式碼可能會多消耗一個數量級的資料量,而智慧合約在 mainnet 上部署的成本本身就已經可能高達數千美元,這會是一個問題。

什麼是 Solidity ABI?為什麼你需要它才能讀懂智慧合約?

智慧合約發布時,會在發布到 Ethereum 之前自動被轉譯成位元組碼。但問題是——一旦發布到網路上,使用者要怎麼知道該如何與這個智慧合約互動?光看一長串位元組碼,幾乎不可能理解有哪些函式可以呼叫。

答案就是 Application Binary Interface,簡稱 ABI

ABI 是一份人類可讀、公開的方法清單,描述了可以對某個特定智慧合約發出的呼叫,以及每個呼叫會回傳什麼。

有了 ABI,智慧合約的使用者就不需要閱讀位元組碼,可以把呼叫轉換成位元組碼來與智慧合約互動。

ABI 和傳統 Web2 架構中的 API(Application Programming Interface)非常相似。不過主要的差異在於:Solidity ABI 讓使用者可以存取以二進位編碼的智慧合約中的方法,而 API 則是讓使用者可以存取線上伺服器端點提供的方法。

由於 ABI 是設計給人閱讀與使用的,智慧合約開發者不會把智慧合約的 ABI 發布到區塊鏈上,因為那樣做成本會非常高。

取而代之,你可以透過以下方式取得 ABI:

  1. 從智慧合約開發者那裡取得公開的合約原始碼,並用它來產生 ABI。
  2. 如果智慧合約已在 Etherscan 上驗證過,可以從 Etherscan 的合約資訊中取得。
  3. 從智慧合約位元組碼反向工程出 ABI(不建議這麼做)。

ABI 通常是以 JSON 格式編碼發布,內容是 Solidity 智慧合約中公開函式宣告的編碼。

以下面這個智慧合約的函式定義為例:

對應的 JSON 編碼看起來會像這樣:

如何解讀 Solidity 呼叫資料的二進位內容

雖然你不會想手動解析 Solidity 二進位資料來反推函式呼叫——因為這個過程複雜、不直觀,而且很容易出錯——但大致了解 Solidity 的二進位資料是怎麼組成的還是很有幫助,這樣你就能快速瀏覽呼叫資料,或是快速核對數值。

我們會在下一節連結幾個工具,這些工具應該能幫你處理大部分這類轉譯工作。

以上面的範例為例。

假設某位使用者想要呼叫智慧合約中的函式 baz,並帶入參數 69 和 true。以下是這個請求以位元組碼呈現的樣子,總長度為 68 位元組:

0xcdcd77c0000000000000000000000000000000000000000000000000000000000000004500...

1. 使用呼叫資料的前 4 個位元組來識別方法 ID。

在這個例子中,0xcdcd77c 用來識別方法 baz,這是取簽章 baz(uint32,bool) 的 ASCII 形式的 Keccak 雜湊值的前 4 個位元組而得。

2. 使用接下來的 32 個位元組來識別第一個參數

0x00000000000000000000000000000000000000000000000000000000000000045 識別出第一個參數 69,這是一個 uint32 值、補齊到 32 位元組。所謂補齊,就是加上一些 0,以確保整串字串(在這個例子中)長度固定為 32 位元組,不論實際數字有多大。

0x00000000000000000000000000000000000000000000000000000000000000045 

3. 使用最後 32 個位元組來識別第二個參數

第二個參數是 true,這是一個補齊到 32 位元組的 bool 值:

0x0000000000000000000000000000000000000000000000000000000000000001

若參數包含動態型別(dynamic types),編碼方式會稍有不同,因為不像 address、bool 或 uint32 這類靜態型別是就地編碼,動態型別會被編碼在另外分配的位置

如何解讀 Solidity 的事件資料二進位內容?

事件(event)是智慧合約在執行方法呼叫時發布的一種紀錄(log),事件會以二進位資料的形式發布。

事件可以帶入參數,用來指定事件會輸出什麼內容。這些參數可以被設為索引(indexed),意思是之後可以用這個索引參數作為篩選條件來搜尋該事件。這些索引參數在 Solidity 的術語中也稱為 topics!

大致上,一個 Solidity 事件會遵循以下結構:

  • address:合約的位址
  • topics[n]:0 到 4 個 topic,也就是索引參數
  • 任意長度的二進位資料,可以依照 ABI 來解析。

我應該用什麼工具來反編譯 Solidity 二進位資料?

市面上有多種 EVM 反編譯器可以幫你把 Solidity 二進位資料還原成較易讀的版本,包括 EtherVM DecompilerPanoramix decompiler

這些 EVM 反編譯器並不會完美還原出原始的原始碼(為了縮小二進位檔案大小,名稱或其他重要資訊可能會被移除),但它們應該能讓你大致理解該合約允許哪些 ABI 請求。

Background gradient

打造區塊鏈魔法

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