嘗試拆解Solidity編譯後的bytecode(一)
以下範例皆參考openzeppelin opcode的系列文章(共六篇)撰寫。
-
Deconstructing a Solidity Contract — Part II: Creation vs. Runtime
-
Deconstructing a Solidity Contract — Part III: The Function Selector
-
Deconstructing a Solidity Contract — Part IV: Function Wrappers
-
Deconstructing a Solidity Contract — Part V: Function Bodies
-
Deconstructing a Solidity Contract — Part VI: The Metadata Hash
本篇文章建議有 solidity 開發經驗的讀者閱讀,初學者不適合本篇文章。本文與 Solidity opcode基礎 有相關性,建議閱讀後再讀本篇文章。
本文目的不在教會讀者寫 opcode,而是要帶領讀者了解 solidity bytecode 的基本架構!本文所述,皆參考 openzeppelin opcode 的系列文章,及使用 remix debug 工具「觀察」bytecode 執行時「memory和stack的變化」所寫出。因此不保證涵蓋能解讀「完整」的 solidity bytecode 架構,這也是本文命名為,嘗試拆解 Solidity 編譯後。
範例程式使用「solidity v0.4.24」,對齊 openzeppelin 範例的版本,讀者操作時,請注意版本號。另外 https://www.evm.codes/?fork=grayGlacier 的 opcode 使用 solidity 最新版本編譯,使用該網站轉譯 bytecode 成 opcode,「執行結果」與 openzeppelin opcode 系列文章會「略有不同」! 佈署時 constructor 的 _initialSupply 請帶「10000」!!!
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.4.24;
contract BasicToken {
uint256 totalSupply_;
mapping(address => uint256) balances;
constructor(uint256 _initialSupply) public {
totalSupply_ = _initialSupply;
balances[msg.sender] = _initialSupply;
}
function totalSupply() public view returns (uint256) {
return totalSupply_;
}
function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[msg.sender]);
balances[msg.sender] = balances[msg.sender] - _value;
balances[_to] = balances[_to] + _value;
return true;
}
function balanceOf(address _owner) public view returns (uint256) {
return balances[_owner];
}
}
將範例合約佈署至測試鏈後,可以拿到以下 bytecode。(建議實際佈署到測試鏈體驗)
可參閱作者佈署至 goerli 測試鏈的合約:https://goerli.etherscan.io/address/0xd048f37a6fea76560907cf4234df58bf5ff10765#code
- verify前:
// runtime code
608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806318160ddd1461005c57806370a0823114610087578063a9059cbb146100de575b600080fd5b34801561006857600080fd5b50610071610143565b6040518082815260200191505060405180910390f35b34801561009357600080fd5b506100c8600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061014c565b6040518082815260200191505060405180910390f35b3480156100ea57600080fd5b50610129600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610195565b604051808215151515815260200191505060405180910390f35b60008054905090565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141515156101d257600080fd5b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054821115151561022057600080fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205403600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205401600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555060019050929150505600a165627a7a72305820ff03d0d0bf3e0cec0938afdb696e0bd258ee5a360172bb63c2f7548f624a0f650029
- verify後:
// creation code + runtime code + constructor arguments
608060405234801561001057600080fd5b506040516020806103ee833981018060405281019080805190602001909291905050508060008190555080600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550506103608061008e6000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806318160ddd1461005c57806370a0823114610087578063a9059cbb146100de575b600080fd5b34801561006857600080fd5b50610071610143565b6040518082815260200191505060405180910390f35b34801561009357600080fd5b506100c8600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061014c565b6040518082815260200191505060405180910390f35b3480156100ea57600080fd5b50610129600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610195565b604051808215151515815260200191505060405180910390f35b60008054905090565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141515156101d257600080fd5b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054821115151561022057600080fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205403600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205401600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555060019050929150505600a165627a7a72305820ff03d0d0bf3e0cec0938afdb696e0bd258ee5a360172bb63c2f7548f624a0f6500290000000000000000000000000000000000000000000000000000000000002710
- 利用remix查閱creation code + runtime code:(object為bytecode,可直接轉譯為opcodes。opcodes是PUSH1 0x80 PUSH1 0x40…以下省略)
// creation code + runtime code
608060405234801561001057600080fd5b506040516020806103ee833981018060405281019080805190602001909291905050508060008190555080600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550506103608061008e6000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806318160ddd1461005c57806370a0823114610087578063a9059cbb146100de575b600080fd5b34801561006857600080fd5b50610071610143565b6040518082815260200191505060405180910390f35b34801561009357600080fd5b506100c8600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061014c565b6040518082815260200191505060405180910390f35b3480156100ea57600080fd5b50610129600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610195565b604051808215151515815260200191505060405180910390f35b60008054905090565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141515156101d257600080fd5b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054821115151561022057600080fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205403600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205401600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555060019050929150505600a165627a7a72305820ff03d0d0bf3e0cec0938afdb696e0bd258ee5a360172bb63c2f7548f624a0f650029
- evm codes 網站可將 bytecode 直接轉為 opcode,以便追蹤 opcode(請複製creation code + runtime code 將其轉換為可閱讀之 opcode)
- 接續第4點,按下run及右上角的播放鍵後,可以在 memory 得到一組bytecode。查閱過後(用 ctrl + f 查閱),可以發現該組 btyecode 是 runtime code
前置作業完成,讓我們正式開始拆解!
Solidity的bytecode可以拆成兩個大項目,creation code和runtime code。
-
creation code:代表constructor的程式碼
-
runtime code:代表非constructor的程式碼,也就是developer寫的各個function,最終會留在以太鏈的bytecode(程式碼)
拆解一份bytecode,要先找到程式的終止位置及 CODECOPY opcode。終止程式的 opcode 有stop、return、revert、selfdestruct(忘記可以回去複習 Solidity opcode基礎)。CODECOPY opcode的用途是將指定區間的opcode複製到memory。
綜上所述,讓我們回到前置作業第5點的圖片,同步尋找codecopy和終止程式的opcode。我們發現89的位置是codecopy,8c的位置是return。若我們用step into一步步執行opcode,會發現執行到8c程式就把8e後的程式直接return了!(如下圖)
用 remix debug工具觀察 codecopy 吃的參數
現在統整一下剛剛的操作。
-
我們執行了 creation code + runtime code。注意,程式的第 1 部份是 creation code,第 2 部份是 runtime code。「程式從 creation code 開始執行,也就是從 constructor 開始執行!」
-
creation code 佔據 142bytes,142 的 16 進制為 8d
-
codecopy 從8e開始複製 opcode 到最後一個 bytecode
-
codecopy 執行過到8c(return opcode)直接return記憶體的資料回鏈上,終止程式。「記憶體內的資料正是 runtime code!」