Solidity 及 EVM 開發工具介紹
這篇文章將介紹 Ethereum 開發者的一些實用工具:Foundry 除了測試之外的功能及 VSCode 的 Solidity Visual Developer 插件
Foundry
Foundry 除了提供合約開發、測試和部署,其實還有許多其他實用的功能。
安裝 Foundry
如果是 Linux 或 macOS,先安裝 foundryup
,接著直接用 foundryup
指令就可以安裝。未來要升級 foundry 也只需要執行 foundryup
就好,非常簡單直覺。
// Install foundryup
curl -L https://foundry.paradigm.xyz | bash
// Install or update Foundry
foundryup
詳細可以參考 Foundry book 的 Installation 頁面。
Auto Completion
Foundry 安裝完後會有 forge
、cast
及 anvil
三個指令,每個指令也都有許多 option,這時有 Auto Completion 會非常省事。例如產生 bash 用的 Auto Completion script:
mkdir -p $HOME/.local/share/bash-completion/completions
forge completions bash > $HOME/.local/share/bash-completion/completions/forge
cast completions bash > $HOME/.local/share/bash-completion/completions/cast
anvil completions bash > $HOME/.local/share/bash-completion/completions/anvil
exec bash
其他 Shell script 的產生方式可以參考 Foundry book 的 Shell Autocompletion 頁面。
anvil
其實就和 Ganache 及 hardhat node
一樣:跑起一個節點。可以是全新的一個本地節點,也可以是背後連上 Forked State(例如 Forked Mainnet、Forked Goerli)的節點。如果你平常都是習慣用來跑一個短暫的全新測試節點就直接下 anvil
即可。
如果你會想在一個 Forked State 內實驗或測試的話,多加上 --fork-url
和 --fork-block-number
的 option。另外可以留意 --compute-units-per-second
這個 option,它會控制每秒請求的計算量,避免取 Forked State 太頻繁導致遇到像是 Alchemy Rate Limit 的問題。
更詳細的節點設置可以參考 Foundry book 的 Anvil Reference 頁面。
cast
cast 是用來取鏈上資料非常好用的工具(也有送交易的工具但個人覺得不太實用)。因為有很多功能,這裡我只會列出我常用及覺得實用的功能。
註:要讀取鏈的狀態記得要在 foundry.toml 檔裡附上 endpoint url(eth_rpc_url=$URL
)或透過 --rpc-url $URL
的方式。
首先是抓取交易資訊:
cast tx/receipt
:抓取交易或 receipt 資訊cast run
:印出一筆已被收入的交易的 execution trace 或進行 live debug(加上--debug
option),為了得到準確的交易前狀態,預設會模擬執行同一區塊裡在該筆交易前面所有的交易,但這可能很耗時,如果只想看交易執行過程且不需要最精準的執行結果的話,可以加上--quick
option,它會跳過前面交易的模擬,直接把該筆交易當作區塊的第一筆交易來執行
註:cast run --debug
的 debug 介面可能沒有那麼好讀,如果不需要看到執行過程中的 memory/stack/storage 等細節時可以直接用 ethtx.info 或 Tenderly ,好讀很多。
cast tx $TX_HASH
cast receipt $TX_HASH
cast run $TX_HASH --quick --debug
接著是從 Etherscan 抓取合約資訊:
cast etherscan-source
:會從 Etherscan 抓取指定地址的合約,可以加上-d
option 建立一個新的資料夾來放抓下來的合約們。記得用--etherscan-api-key
附上 Etherscan API key
// Download WETH9 contract file to tmp folder
cast etherscan-source -d tmp 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 --etherscan-api-key $API_KEY
註:如果想直接在瀏覽器開啟 VSCode 環境來瀏覽、編輯合約的話,可以使用 deth.net。還可以透過書籤一鍵開啟,非常方便,請參考這則 twitter。
接著是 encode/decode ABI 的功能:
cast 4b/4bd/4be
:反查 function selector、calldata 或是 event signature。在手上只有一筆交易或一個 event 的 raw data 時,可以透過這些工具來嘗試找出它要執行什麼函式或 emit 了什麼 eventcast calldata
:用 function signature 及參數組出 calldata。在為多簽檢查或組 calldata 的時候很方便
註:因為 function selector 只有 4 bytes,如果多個 function signature 都是同一個 function selector 的話,則反查會回傳所有可能 function signature
// Decode function selector of "transfer(address,uint256)"
cast 4b 0xa9059cbb
// Decode calldata of a transfer
cast 4bd 0xa9059cbb000000000000000000000000f8ed47951b8eb0997d9f038fb1fcea46b171ea2f0000000000000000000000000000000000000000000000000000000078ca2e7e
// Decode event signature of "Transfer(address,address,uint256)"
cast 4be 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
// Encode calldata for a transfer
cast calldata "transfer(address,uint256)" 0xf8ed47951B8eB0997D9F038FB1FCea46B171Ea2f 2026516094
最後是一些轉換或計算的小工具:
cast —-from-utf8/--to-ascii
:在 hex 或字串之間轉換cast sig
:計算 function selectorcast keccak
:計算 keccak hashcast compute-address
:計算CREATE
(不是CREATE2
)的地址
cast --from-utf8 "pls return my money"
// 0x706c732072657475726e206d79206d6f6e6579
cast --to-ascii 0x706c732072657475726e206d79206d6f6e6579
// "pls return my money"
cast sig "transfer(address,uint256)"
// 0xa9059cbb
cast keccak "transfer(address,uint256)"
// 0xa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b
cast compute-address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --nonce 123
// Computed Address: 0xB57130C323EC5A53F89b73c9cA6f9Fc3c11744f6
更多 cast 功能可以參考 Foundry book 的 cast Commands 頁面。你可以從中按照你的需求找到對你有幫助的常用指令。
forge inspect
forge 的 inspect 指令則是用來挖出一個合約更進階的資訊。它不是去拉鏈上的資料,而是作用在本地端的檔案(例如 MyContract
),所以如果你需要分析(鏈上的)其他合約,你需要先把它下載下來。
forge inspect MyContract ...
註:在分析前 forge
會先編譯合約,會花一些時間。
首先是列出合約基本資料的功能:
forge inspect MyContract abi/bytecode/deployedBytecode
:列出MyContract
的 ABI/編譯完的 bytecode/實際部署到鏈上後的 bytecode,方便 export ABI 或是比對部署在鏈上的 bytecodeforge inspect MyContract methods/gas
:列出MyContract
的(public 的) function 列表(包含 function signature 及 selector)/ 每個 function 的預估 gas 消耗 。用cast 4b/4bd
其實就能反查特定 function selector,這個功能比較像是能看到一個合約完整的 function 資訊forge inspect MyContract storage
:列出MyContract
的 storage,包含每個變數的 storageslot
和offset
(如果有多個變數被 pack 在一起的話,offset
會是每個變數在該 slot 的起始位置)。但呈現的資料比較醜一點,可以改用 slither 來分析合約 storage 或讀取合約變數的值,它還可以拉鏈上的合約來分析,不需要先下載合約下來。
接下來是比較進階的使用:irOptimized
(或 iro
),主要是你想再優化你合約的 gas 消耗時可以採用的方式。
forge inspect MyContract irOptimized
它會呈現你的合約編譯成(優化過的)Yul 後的長相。Yul 就像 assembly,它介於 Solidity 和 EVM opcode 之間,它可以讓你看到你寫的 Solidity function 背後實際上還做了哪些事情,如此你就可以開始刪去一些不需要的檢查。
這個工具是從這兩則 twitter(link1, link2)發現到的,這邊直接引用裡面的範例和圖片來搭配說明。以一個簡單的將 number
變數遞增的 Counter
合約為例,裡面有一個 setNumber
函式用來設置 number
的值及一個 increment
函式用來把 number
加 1
。右邊則是執行 forge inspect Counter irOptimized
後所輸出 Yul 版本的 Counter
合約:
可以看到一個簡單的 Solidity 合約編譯完後變得複雜許多。接下來以 increment
函式為例:
可以發現一個 number++
裡面其實包含了很多檢查,例如 not payable 和 overflow。但其實我們知道它只會以一次加 1
的方式遞增所以幾乎不可能會 overflow(加上 unchecked
),然後如果這個函式是可以接受 ether 的話,那就可以再省掉 not payable 的檢查(加上 payable
):
可以發現省去了許多執行步驟。
比起原地把 Solidity 程式碼替換成 assembly 程式碼這種比較冒險的優化方式,這是一個新的管道讓你能重新完整地檢視你的合約,並且可以看出修改前後的對比。但要注意還是以安全為優先,不要為了省一點的 gas 而拿掉你沒有把握的程式碼或檢查。
其他 forge inspect 功能可以參考 Foundry book 的 forge inspect 頁面。
VSCode
最後是介紹一個在 review 合約時很實用的 VSCode 插件:Solidity Visual Developer
註:以下介紹會是個人使用心得,所以會有特色或功能是沒有介紹到的。可以自己下載來試用看看,看能不能發現適合你的用途的功能。
裡面最常看到的就是它為你合約的變數套上框框並能連結到宣告變數的地方,以及函式的參數套上底色方便識別(雖然有時候顏色太多會看得有點眼花)。另外有 variable shadowing 的警告提醒,例如你的合約繼承了 Ownable
合約但某個函式宣告了一個 owner
參數,這時候這個 owner
參數就會被套上顯眼的紅色框框來提醒你:
keywords 例如 block.timestamp
、tx.origin
、msg.data
或是 external
等等關鍵字都有提供 hover 時顯示安全提醒:
如果你在 review 合約需要做筆記時,可以利用 @audit
及 @audit-ok
這兩個 tag,它會出現像 Bookmarks 那樣的圖示方便你回到筆記過的程式碼段落:
它還有提供一些分析整個合約的功能,讓你能得到不同合約間的繼承關係(inheritance
)和函式之間的關係(graph
),這邊直接使用官方的圖:
還有其他工具像是 flatten:
Solidity Visual Developer 這個插件對 review 或審計合約算是非常有幫助,但寫合約時幫助比較少。雖然對一般人來說裡面大部分的工具其實平常幾乎都用不太到,不過至少知道有哪些工具可以使用之後,未來有天當你需要 review 合約的時候它們就可以派上用場。