Photo by Kelly Sikkema on Unsplash

讓以太坊智能合約內容變成喜歡的樣子

Whien

--

合約一定要運行程式碼嗎?能不能存放我喜歡的內容呢?讓我們一起把合約部署了解一下吧!

|目標

很簡單,我們的目標就是要將合約內容留下喜歡的樣子,例如「0xcafebabe」,該怎麼做呢?

先在以下連結看一下真正的合約樣子

|在開始前

先準備基本工具,而整個流程所使用的工具為以下

  1. foundry — 智能合約開發工具
  2. infura — goerli 測試鏈 RPC
  3. visual studio code — 撰寫程式非常舒服
  4. iTerm — 計算機的上帝視角
  5. 準備好 evm.codes 網站

|先備知識

智能合約部署如何初始化?

例如有一份原始 Solidity 智能合約程式碼為以下

接著經過編譯器執行後,輸出為以下結果

6080604052348015600f57600080fd5b5060c08061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c8063412a5a6d146037578063dd0ecfff14603f575b600080fd5b603d606d565b005b6000546051906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f35b6c63cafebabe6000526004601cf3600052601060136000f060015556fea264697066735822122017ba0ec74c641d539e6a82a1f7f4baef6ddbff7a532d03d6808a8c15de55c84b64736f6c63430008110033

編譯器執行完指定 Solidity 程式碼,獲得的 Bytecode 是發送上鏈時,運行第一次初始化的過程,這些都是編譯器會自動幫我們加上的流程,不需要由開發者自行撰寫。

整個部署初始化包含了全域變數(整個智能合約都可讀的範圍)插槽配置,建構式(constructor)的執行與找到最終 runtime bytecode(用戶交易運行的程式碼)的操作。

來看個實際範例

可以在 soliditylang.org 編譯程式碼

將程式碼改為以下內容,把原本 subAddr 預先配置為 0x000000………001

再經過一次編譯,得到以下結果

6080604052600080546001600160a01b031916600117905534801561002357600080fd5b5060c0806100326000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c8063412a5a6d146037578063dd0ecfff14603f575b600080fd5b603d606d565b005b6000546051906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f35b6c63cafebabe6000526004601cf3600052601060136000f060015556fea264697066735822122012eb6f0792bef08c4db5b67e7d0dbe8c4a370536639e2002e0de22232550b96f64736f6c63430008110033

看出來與上一個有什麼變化嗎?應該很難一眼看出來吧?接著就是 evm.codes 要派上用場了,進入網站後,點擊 playground 選擇 Bytecode 並且把上面的 Bytecode 扔進去,點擊 Run 運行。

最後直接點擊右上角的 Continue excution 按鈕,直接執行到最後,會得到初始化的結果

看到這裡,應該很一目了然初始化發生什麼事情了吧。

整理一下

  • CONTRACT: 0x9bbfed6889322e016e0a02ee459d306fc19545d8 合約地址
  • SLOT / VALUE: 0000000000000000000000000000000000000000000000000000000000000000 / 01 表示為第一個插槽紀錄值為 0x0000000000000000000000000000000000000001 同等於 01
  • RETURN VALUE: 6080604052348015600f57600080fd5b506004361060325760003560e01c8063412a5a6d146037578063dd0ecfff14603f575b600080fd5b603d606d565b005b6000546051906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f35b6c63cafebabe6000526004601cf3600052601060136000f060015556fea264697066735822122012eb6f0792bef08c4db5b67e7d0dbe8c4a370536639e2002e0de22232550b96f64736f6c63430008110033 最終 runtime bytecode

如果有興趣,可以自行一步一步點擊 step into 來了解發生了什麼事情

|進入主題

有了以上的簡單介紹,大致可以了解部署後,是如何進行初始化,接著就是要認識 runtime bytecode 了。

如何取得並留下 runtime bytecode?

先來看一下先備知識所得到的正式合約內容長什麼子吧!

部署合約 inputData 內容

初始化後留下的 runtime bytecode

快速透過 evm.codes 來比對一下 RETURN VALUE 實際結果如何

完全一致!

嗅到什麼感覺了嗎?沒錯!當初始化執行完後最後所留下來的就是實際的 runtime bytecode,也就是發送交易至合約時實際運作的程式碼,前面的初始化內容都會消失。

runtime bytecode 是如何做到的?

很簡單,初始化完以後,會將交易互動實際運作的程式碼暫時儲存至 memory 中,然後再透過 return 指令碼來結束,結束時 return 的結果就會成為智能合約最後留下的 runtime bytecode

通過以下兩步驟來取得 runtime bytecode

  • (39) CODECOPY [destOffset, offset, size]
  • (f3) RETURN [offset, size]

前面 () 是指令碼,緊接著是 stack 參數,最後只要根據 return 參數位置,就可以結束初始化並拿到最後的 runtime bytecode 了。

來看一下實際流程

當 evm.codes 展示運作到 CODECOPY 時,可以看到 STACK 中為 [0, 32, c0] 因為都是 16 進制,所以實際為 [0, 50, 192] 的十進制。

0 為程式碼複製後,最後要偏移多少(複製後才運行)

0x32(50) 為要從多少偏移量以後開始複製程式碼(複製時最重要的開頭)

0xc0(192) 為,從偏移量以後開始複製多少量的程式碼(尾巴)

以上意思就是,「從整個程式碼中,偏移 50 個 bytes 後,開始到 192 個 bytes 的量。

黃色圈選部分是實際的 runtime bytecode ,而前面的內容字數 / 2 正好是 50 個,也就是 50bytes,最後留下的黃色圈選部分就是我們所謂的 runtime bytecode

也正好符合我們合約最後 Contract code 所留下來的結果。

|實作替換成喜歡的樣子

從上述看下來,應該可以知道整個部署是怎麼運作的了,再來就玩玩文章標題,「替換自己喜歡的樣子」!

這個實作會透過部署第一個智能合約,透過第一個智能合約來建立喜歡的新智能合約,包含內容。

直接看程式碼

這個程式碼是要部署的第一個智能合約,有一個 createContract 函式,這個函式區塊中使用了 assembly 撰寫,不要害怕,其實內容很簡單。

解釋 assembly 中的程式碼

這裡僅介紹有用到的指令內容,其他額外 assembly 相關如果有興趣可以翻找網路資料。

mstore 指令碼是將一個內容暫時存入 memory 中,帶有的參數是 [offset, value], offset是偏移量 value 是要帶入的 32bytes 內容。

mstore(0, 0x63cafebabe6000526004601CF3) 意思是 0 偏移量,0x63cafebabe6000526004601CF3 一共為 13 個字節,所以塞得進去。

mstore 執行前

mstore 執行後

現在 memory 裡就有了暫時存放的 63cafebabe6000526004601CF3 內容。

create 指令為建立新的智能合約,帶有的參數是 [value, offset, size], value 是建立時要一起發送進去的 wei 數量, offset 是 memory 中要複製的內容偏移量,最後 size 為要 memory 偏移量後複製的 byte 初始化程式碼大小。

create(0, 0x13, 16) 意思是不發送任何 wei,偏移第 0x13(19) 個位置,複製 0x16(22) bytes 大小程式碼。

以上皆完成後,會得到一個帶有 63cafebabe6000526004601CF3 的尚未初始化智能合約程式碼,並得到一個新的智能合約地址。

完成了嗎?但也快了,因為還沒搞清楚留下了什麼

一旦是新建立的智能合約,表示也需要經過初始化的流程,接著一起看一下這個智能合約發生了什麼事情。

將 63cafebabe6000526004601CF3 複製到 evm.codes 中。

會看見很簡單的六個指令碼,而 cafebabe 是第一步驟,這裡開始了新智能合約的初始化流程。

點擊右上角 step into 一步一步看,流程簡單如下

  • 將 cafebabe 四個 bytes 透過 PUSH4 [00, cafebabe] 指令碼,記錄至 memory 中
  • RETURN [1c, 4] 最後的 cafebabe 回傳為 runtime bytecode

最後就得到 RETURN VALUE 為

到此為止,定案了,最終這個新智能合約所留下的內容就是 cafebabe

|看一看自己喜愛的樣子一眼吧!

|最後說說 SSTORE2

其實這個技巧實際被運用在了所謂 SSTORE2 的方案中,透過合約儲存大資料的方法,可以比原本處存在 storage中 gas 消耗來得少。

--

--

Whien

遨遊在硬體與軟體世界中,對於計算機一切事物都充滿好奇及熱情。