以太坊EVM原理與實現(xiàn)
太坊底層通過EVM模塊支持合約的執(zhí)行與調(diào)用尿庐,調(diào)用時根據(jù)合約地址獲取到代碼,生成環(huán)境后載入到EVM中運行呢堰。通常智能合約的開發(fā)流程是用solidlity編寫邏輯代碼抄瑟,再通過編譯器編譯元數(shù)據(jù),最后再發(fā)布到以太坊上暮胧。
代碼結(jié)構(gòu)
.
├── analysis.go //跳轉(zhuǎn)目標(biāo)判定
├── common.go
├── contract.go //合約數(shù)據(jù)結(jié)構(gòu)
├── contracts.go //預(yù)編譯好的合約
├── errors.go
├── evm.go //執(zhí)行器 對外提供一些外部接口
├── gas.go //call gas花費計算 一級指令耗費gas級別
├── gas_table.go //指令耗費計算函數(shù)表
├── gen_structlog.go
├── instructions.go //指令操作
├── interface.go
├── interpreter.go //解釋器 調(diào)用核心
├── intpool.go //int值池
├── int_pool_verifier_empty.go
├── int_pool_verifier.go
├── jump_table.go //指令和指令操作(操作锐借,花費,驗證)對應(yīng)表
├── logger.go //狀態(tài)日志
├── memory.go //EVM 內(nèi)存
├── memory_table.go //EVM 內(nèi)存操作表 主要衡量操作所需內(nèi)存大小
├── noop.go
├── opcodes.go //Op指令 以及一些對應(yīng)關(guān)系
├── runtime
│ ├── env.go //執(zhí)行環(huán)境
│ ├── fuzz.go
│ └── runtime.go //運行接口 測試使用
├── stack.go //棧
└── stack_table.go //棧驗證
指令 OpCode
opcodes.go中定義了所有的OpCode往衷,該值是一個byte钞翔,合約編譯出來的bytecode中,一個OpCode就是上面的一位席舍。opcodes按功能分為9組(運算相關(guān)布轿,塊操作,加密相關(guān)等)来颤。
// 0x0 range - arithmetic ops.
const (...)
// 0x10 range - comparison ops.
const (...)
// 0x30 range - closure state.
const (...)
// 0x40 range - block operations.
const (
BLOCKHASH OpCode = 0x40 + iota
COINBASE
TIMESTAMP
NUMBER
DIFFICULTY
GASLIMIT
)
// 0x50 range - 'storage' and execution.
const (...)
// 0x60 range.
const (...)
// 0xa0 range - logging ops.
const (...)
// unofficial opcodes used for parsing.
const (...)
// 0xf0 range - closures.
const (...)
instruction
jump.table.go定義了四種指令集合汰扭,每個集合實質(zhì)上是個256長度的數(shù)組,名字翻譯過來是(荒地福铅,農(nóng)莊萝毛,拜占庭,君士坦丁堡)對應(yīng)了EVM的四個發(fā)展階段滑黔。指令集向前兼容笆包。
var (
frontierInstructionSet = newFrontierInstructionSet()
homesteadInstructionSet = newHomesteadInstructionSet()
byzantiumInstructionSet = newByzantiumInstructionSet()
constantinopleInstructionSet = newConstantinopleInstructionSet()
)
具體每條指令結(jié)構(gòu)如下,字段意思見注釋
type operation struct {
// execute is the operation function
execute executionFunc//執(zhí)行函數(shù)
// gasCost is the gas function and returns the gas required for execution
gasCost gasFunc//gas消耗函數(shù)
// validateStack validates the stack (size) for the operation
validateStack stackValidationFunc//堆棧大小驗證函數(shù)
// memorySize returns the memory size required for the operation
memorySize memorySizeFunc //需要的內(nèi)存大小
halts bool // 表示操作是否停止進(jìn)一步執(zhí)行 運算終止 indicates whether the operation should halt further execution
jumps bool // 指示程序計數(shù)器是否不增加 跳轉(zhuǎn)(for) indicates whether the program counter should not increment
writes bool // 確定這是否是一個狀態(tài)修改操作 是否寫入determines whether this a state modifying operation
valid bool // 指示檢索到的操作是否有效并且已知 操作是否有效 indication whether the retrieved operation is valid and known
reverts bool // 確定操作是否恢復(fù)狀態(tài)(隱式停止) 出錯回滾determines whether the operation reverts state (implicitly halts)
returns bool // 確定操作是否設(shè)置了返回數(shù)據(jù)內(nèi)容 返回determines whether the operations sets the return data content
}
按下面的sha3指令為例
定義
SHA3: {
execute: opSha3,
gasCost: gasSha3,
validateStack: makeStackFunc(2, 1),
memorySize: memorySha3,
valid: true,
},
操作
不同的操作有所不同略荡,操作對象根據(jù)指令不同可能影響棧庵佣、內(nèi)存、statedb汛兜。
func opSha3(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
//從堆棧中獲取內(nèi)存的地址(offset+size)從內(nèi)存中取出來數(shù)據(jù)
offset, size := stack.pop(), stack.pop()
data := memory.Get(offset.Int64(), size.Int64())
//keccak256處理
if interpreter.hasher == nil {
interpreter.hasher = sha3.NewLegacyKeccak256().(keccakState)
} else {
interpreter.hasher.Reset()
}
interpreter.hasher.Write(data)
interpreter.hasher.Read(interpreter.hasherBuf[:])
evm := interpreter.evm
if evm.vmConfig.EnablePreimageRecording {
//把(hash巴粪,data)當(dāng)作preimage寫到數(shù)據(jù)庫中
evm.StateDB.AddPreimage(interpreter.hasherBuf, data)
}
//hash入棧
stack.push(interpreter.intPool.get().SetBytes(interpreter.hasherBuf[:]))
interpreter.intPool.put(offset, size)
return nil, nil
}
gas費用
不同的操作有不同的初始值和對應(yīng)的計算方法,具體的方法都定義在gas_table里面粥谬,按sha3為例
func gasSha3(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var overflow bool
gas, err := memoryGasCost(mem, memorySize)
if err != nil {
return 0, err
}
//+30 Once per SHA3 operation.
if gas, overflow = math.SafeAdd(gas, params.Sha3Gas); overflow {
return 0, errGasUintOverflow
}
wordGas, overflow := bigUint64(stack.Back(1))
if overflow {
return 0, errGasUintOverflow
}
//*6 Once per word of the SHA3 operation's data.
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow {
return 0, errGasUintOverflow
}
//gas + wordGas
if gas, overflow = math.SafeAdd(gas, wordGas); overflow {
return 0, errGasUintOverflow
}
return gas, nil
}
有兩個定義會影響gas的計算肛根,通常作為量化的一個單位。
//github.com/ethereum/go-ethereum/core/vm/gas.go
// Gas costs
const (
GasQuickStep uint64 = 2
GasFastestStep uint64 = 3
GasFastStep uint64 = 5
GasMidStep uint64 = 8
GasSlowStep uint64 = 10
GasExtStep uint64 = 20
GasReturn uint64 = 0
GasStop uint64 = 0
GasContractByte uint64 = 200
)
//github.com/ethereum/go-ethereum/params/gas_table.go
// GasTable organizes gas prices for different ethereum phases.
type GasTable struct {
ExtcodeSize uint64
ExtcodeCopy uint64
ExtcodeHash uint64
Balance uint64
SLoad uint64
Calls uint64
Suicide uint64
ExpByte uint64
// CreateBySuicide occurs when the
// refunded account is one that does
// not exist. This logic is similar
// to call. May be left nil. Nil means
// not charged.
CreateBySuicide uint64
}
memorysize
sha3根據(jù)棧頂?shù)膬蓚€數(shù)據(jù)漏策,計算memorysize晶通。有些操作不需要申請內(nèi)存因而默認(rèn)為0
func memorySha3(stack *Stack) *big.Int {
return calcMemSize(stack.Back(0), stack.Back(1))
}
棧驗證
先驗證棧上的操作數(shù)夠不夠,在驗證棧是否超出最大限制哟玷,sha3在這里僅需要驗證其參數(shù)夠不夠,運算之后棧是要減一的makeStackFunc(2, 1)
func makeStackFunc(pop, push int) stackValidationFunc {
return func(stack *Stack) error {
//深度驗證
if err := stack.require(pop); err != nil {
return err
}
//最大值驗證 StackLimit = 1024
if stack.len()+push-pop > int(params.StackLimit) {
return fmt.Errorf("stack limit reached %d (%d)", stack.len(), params.StackLimit)
}
return nil
}
}
智能合約
合約是EVM智能合約的存儲單位也是解釋器執(zhí)行的基本單位,包含了代碼巢寡,調(diào)用人喉脖,所有人,gas的相關(guān)信息抑月。
// EVM是以太坊虛擬機(jī)基礎(chǔ)對象树叽,并提供必要的工具,以使用提供的上下文運行給定狀態(tài)的合約谦絮。
// 應(yīng)該指出的是题诵,任何調(diào)用產(chǎn)生的任何錯誤都應(yīng)該被認(rèn)為是一種回滾修改狀態(tài)和消耗所有GAS操作,
// 不應(yīng)該執(zhí)行對具體錯誤的檢查层皱。 解釋器確保生成的任何錯誤都被認(rèn)為是錯誤的代碼性锭。
type EVM struct {
Context // Context provides auxiliary blockchain related information
StateDB StateDB // StateDB gives access to the underlying state
depth int //當(dāng)前的調(diào)用堆棧
// chainConfig contains information about the current chain
chainConfig *params.ChainConfig
// chain rules contains the chain rules for the current epoch
chainRules params.Rules
// virtual machine configuration options used to initialise the
// evm.
vmConfig Config
// global (to this context) ethereum virtual machine
// used throughout the execution of the tx.
interpreters []Interpreter
interpreter Interpreter
// abort is used to abort the EVM calling operations
// NOTE: must be set atomically
abort int32
// callGasTemp holds the gas available for the current call. This is needed because the
// available gas is calculated in gasCall* according to the 63/64 rule and later
// applied in opCall*.
callGasTemp uint64
}
EVM原生編譯了一批合約,定義在contracts.go里面叫胖,主要用于加密操作草冈。
// PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum
// contracts used in the Byzantium release.
var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{},
common.BytesToAddress([]byte{6}): &bn256Add{},
common.BytesToAddress([]byte{7}): &bn256ScalarMul{},
common.BytesToAddress([]byte{8}): &bn256Pairing{},
}
執(zhí)行機(jī)
棧
EVM中棧用于保存操作數(shù),每個操作數(shù)的類型是big.int,故說EVM是256位虛擬機(jī)瓮增。執(zhí)行opcode的時候怎棱,從上往下彈出操作數(shù),作為操作的參數(shù)绷跑。
// Stack is an object for basic stack operations. Items popped to the stack are
// expected to be changed and modified. stack does not take care of adding newly
// initialised objects.
type Stack struct {
data []*big.Int
}
//追加到末尾
func (st *Stack) push(d *big.Int) {
// NOTE push limit (1024) is checked in baseCheck
//stackItem := new(big.Int).Set(d)
//st.data = append(st.data, stackItem)
st.data = append(st.data, d)
}
//從最末尾取出
func (st *Stack) pop() (ret *big.Int) {
ret = st.data[len(st.data)-1]
st.data = st.data[:len(st.data)-1]
return
}
//交換棧頂元素和里棧頂n距離的元素的值
func (st *Stack) swap(n int) {
st.data[st.len()-n], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-n]
}
//相復(fù)制指定位置的值到棧頂
func (st *Stack) dup(pool *intPool, n int) {
st.push(pool.get().Set(st.data[st.len()-n]))
}
//取棧頂?shù)脑?func (st *Stack) peek() *big.Int {
return st.data[st.len()-1]
}
內(nèi)存
內(nèi)存用于一些內(nèi)存操作(MLOAD,MSTORE,MSTORE8)及合約調(diào)用的參數(shù)拷貝(CALL拳恋,CALLCODE)。
內(nèi)存數(shù)據(jù)結(jié)構(gòu)砸捏,維護(hù)了一個byte數(shù)組谬运,MLOAD,MSTORE讀取存入的時候都要指定位置及長度才能準(zhǔn)確的讀寫带膜。
// Memory implements a simple memory model for the ethereum virtual machine.
type Memory struct {
store []byte
lastGasCost uint64
}
// Set sets offset + size to value
func (m *Memory) Set(offset, size uint64, value []byte) {
// It's possible the offset is greater than 0 and size equals 0. This is because
// the calcMemSize (common.go) could potentially return 0 when size is zero (NO-OP)
if size > 0 {
// length of store may never be less than offset + size.
// The store should be resized PRIOR to setting the memory
if offset+size > uint64(len(m.store)) {
panic("invalid memory: store empty")
}
copy(m.store[offset:offset+size], value)
}
}
// Get returns offset + size as a new slice
func (m *Memory) Get(offset, size int64) (cpy []byte) {
if size == 0 {
return nil
}
if len(m.store) > int(offset) {
cpy = make([]byte, size)
copy(cpy, m.store[offset:offset+size])
return
}
return
}
內(nèi)存操作指令詳見 EVM7種重要指令實現(xiàn)原理 一文
stateDb
合約本身不保存數(shù)據(jù)吩谦,那么合約的數(shù)據(jù)是保存在哪里呢?合約及其調(diào)用類似于數(shù)據(jù)庫的日志膝藕,保存了合約定義以及對他的一系列操作式廷,只要將這些操作執(zhí)行一遍就能獲取當(dāng)前的結(jié)果肴茄,但是如果每次都要去執(zhí)行就太慢了润樱,因而這部分?jǐn)?shù)據(jù)是會持久化到stateDb里面的舱痘。code中定義了兩條指令SSTORE SLOAD用于從db中讀寫合約當(dāng)前的狀態(tài)币喧。詳見 EVM7種重要指令實現(xiàn)原理 一文
執(zhí)行過程
執(zhí)行入口定義在evm.go中析校,功能就是組裝執(zhí)行環(huán)境(代碼幸乒,執(zhí)行人關(guān)系垃它,參數(shù)等)
//Call 執(zhí)行于給定的input作為參數(shù)與addr相關(guān)聯(lián)的合約
//他還處理所需的任何必要的轉(zhuǎn)賬操作迁霎,并采取必要的步驟來創(chuàng)建賬戶
//并在任意錯誤的情況下回滾所做的操作
//Call方法, 無論我們轉(zhuǎn)賬或者是執(zhí)行合約代碼都會調(diào)用到這里辛馆, 同時合約里面的call指令也會執(zhí)行到這里
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
if evm.vmConfig.NoRecursion && evm.depth > 0 {
return nil, gas, nil
}
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { //調(diào)用深度最多1024
return nil, gas, ErrDepth
}
// Fail if we're trying to transfer more than the available balance
//查看我們的賬戶是否有足夠的金錢
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, gas, ErrInsufficientBalance
}
var (
to = AccountRef(addr)
snapshot = evm.StateDB.Snapshot()
)
if !evm.StateDB.Exist(addr) { //查看指定地址是否存在
//如果地址不存在俺陋,查看是否是 native go 的合約豁延, native go 的合約在 contracts.go 文件里面
precompiles := PrecompiledContractsHomestead
if evm.ChainConfig().IsByzantium(evm.BlockNumber) {
precompiles = PrecompiledContractsByzantium
}
if precompiles[addr] == nil && evm.ChainConfig().IsEIP158(evm.BlockNumber) && value.Sign() == 0 {
//如果不是指定的合約地址,并且value的值為0那么返回正常腊状,而且這次調(diào)用沒有消耗Gas
// Calling a non existing account, don't do anything, but ping the tracer
if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil)
}
return nil, gas, nil
}
//負(fù)責(zé)在本地狀態(tài)創(chuàng)建addr
evm.StateDB.CreateAccount(addr)
}
//執(zhí)行轉(zhuǎn)賬
evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
contract := NewContract(caller, to, value, gas)
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
// Even if the account has no code, we need to continue because it might be a precompile
start := time.Now()
// Capture the tracer start/end events in debug mode
if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
defer func() { // Lazy evaluation of the parameters
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
}()
}
ret, err = run(evm, contract, input, false)
// When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally
// when we're in homestead this also counts for code storage gas errors.
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
if err != errExecutionReverted {
// 如果是由revert指令觸發(fā)的錯誤诱咏,因為ICO一般設(shè)置了人數(shù)限制或者資金限制
// 在大家搶購的時候很可能會觸發(fā)這些限制條件,導(dǎo)致被抽走不少錢缴挖。這個時候
// 又不能設(shè)置比較低的GasPrice和GasLimit袋狞。因為要速度快。
// 那么不會使用剩下的全部Gas映屋,而是只會使用代碼執(zhí)行的Gas
// 不然會被抽走 GasLimit *GasPrice的錢苟鸯,那可不少。
contract.UseGas(contract.Gas)
}
}
return ret, contract.Gas, err
}
類似的函數(shù)有四個棚点。詳細(xì)區(qū)別見最后的參考早处。
- Call A->B A,B的環(huán)境獨立
- CallCode、 和Call類似乙濒,與Call不同的地方在與它使用caller的context來執(zhí)行給定地址的代碼
- DelegateCall陕赃、 和CallCode類似,區(qū)別在于msg.send不一樣 caller被設(shè)置為caller的caller
- StaticCall 和call相似 不允許執(zhí)行任何修改狀態(tài)的操作
Contract和參數(shù)構(gòu)造完成后調(diào)用執(zhí)行函數(shù)颁股,執(zhí)行函數(shù)會檢查調(diào)用的是否會之前編譯好的原生合約么库,如果是原生合約則調(diào)用原生合約,否則調(diào)用解釋器執(zhí)行函數(shù)運算合約甘有。
// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) {
if contract.CodeAddr != nil {
precompiles := PrecompiledContractsHomestead
if evm.ChainConfig().IsByzantium(evm.BlockNumber) {
precompiles = PrecompiledContractsByzantium
}
if p := precompiles[*contract.CodeAddr]; p != nil {
return RunPrecompiledContract(p, input, contract)
}
}
for _, interpreter := range evm.interpreters {
if interpreter.CanRun(contract.Code) {
if evm.interpreter != interpreter {
// Ensure that the interpreter pointer is set back
// to its current value upon return.
defer func(i Interpreter) {
evm.interpreter = I
}(evm.interpreter)
evm.interpreter = interpreter
}
return interpreter.Run(contract, input, readOnly)
}
}
return nil, ErrNoCompatibleInterpreter
}
解釋器
// Run loops and evaluates the contract's code with the given input data and returns
// the return byte-slice and an error if one occurred.
//
// It's important to note that any errors returned by the interpreter should be
// considered a revert-and-consume-all-gas operation except for
// errExecutionReverted which means revert-and-keep-gas-left.
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
if in.intPool == nil {
in.intPool = poolOfIntPools.get()
defer func() {
poolOfIntPools.put(in.intPool)
in.intPool = nil
}()
}
// Increment the call depth which is restricted to 1024
in.evm.depth++
defer func() { in.evm.depth-- }()
// Make sure the readOnly is only set if we aren't in readOnly yet.
// This makes also sure that the readOnly flag isn't removed for child calls.
if readOnly && !in.readOnly {
in.readOnly = true
defer func() { in.readOnly = false }()
}
// Reset the previous call's return data. It's unimportant to preserve the old buffer
// as every returning call will return new data anyway.
in.returnData = nil
// Don't bother with the execution if there's no code.
if len(contract.Code) == 0 {
return nil, nil
}
var (
op OpCode // current opcode 當(dāng)前指令
mem = NewMemory() // bound memory 內(nèi)存
stack = newstack() // local stack 棧
// For optimisation reason we're using uint64 as the program counter.
// It's theoretically possible to go above 2^64. The YP defines the PC
// to be uint256. Practically much less so feasible.
pc = uint64(0) // program counter 指令位置
cost uint64 //gas 花費
// copies used by tracer
pcCopy uint64 // needed for the deferred Tracer debug使用
gasCopy uint64 // for Tracer to log gas remaining before execution debug使用
logged bool // deferred Tracer should ignore already logged steps debug使用
)
contract.Input = input
// Reclaim the stack as an int pool when the execution stops
defer func() { in.intPool.put(stack.data...) }()
//解釋器的主要循環(huán)诉儒,直到遇到STOP,RETURN亏掀,SELFDESTRUCT指令被執(zhí)行忱反,或者是遇到任意錯誤,或者說done被父context設(shè)置滤愕。
for atomic.LoadInt32(&in.evm.abort) == 0 {
// Get the operation from the jump table and validate the stack to ensure there are
// enough stack items available to perform the operation.
op = contract.GetOp(pc)//獲取一條指令及指令對應(yīng)的操作
operation := in.cfg.JumpTable[op]
if !operation.valid {
return nil, fmt.Errorf("invalid opcode 0x%x", int(op))
}
if err := operation.validateStack(stack); err != nil {
return nil, err
}
// If the operation is valid, enforce and write restrictions
if err := in.enforceRestrictions(op, operation, stack); err != nil {
return nil, err
}
var memorySize uint64
// calculate the new memory size and expand the memory to fit
// the operation
//計算內(nèi)存温算,按操作所需要的操作數(shù)來算
if operation.memorySize != nil {
memSize, overflow := bigUint64(operation.memorySize(stack))
if overflow {
return nil, errGasUintOverflow
}
// memory is expanded in words of 32 bytes. Gas
// is also calculated in words.
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
return nil, errGasUintOverflow
}
}
// consume the gas and return an error if not enough gas is available.
// cost is explicitly set so that the capture state defer method can get the proper cost
//校驗cost 調(diào)用前面提到的constfunc 計算本次操作cost消耗
cost, err = operation.gasCost(in.gasTable, in.evm, contract, stack, mem, memorySize)
if err != nil || !contract.UseGas(cost) {
return nil, ErrOutOfGas
}
if memorySize > 0 {//擴(kuò)大內(nèi)存范圍
mem.Resize(memorySize)
}
// execute the operation
//執(zhí)行操作
res, err := operation.execute(&pc, in, contract, mem, stack)
// if the operation clears the return data (e.g. it has returning data)
// set the last return to the result of the operation.
//如果遇到return 設(shè)置返回值
if operation.returns {
in.returnData = res
}
switch {
case err != nil:
return nil, err //報錯
case operation.reverts:
return res, errExecutionReverted //出錯回滾
case operation.halts:
return res, nil //停止
case !operation.jumps: //跳轉(zhuǎn)
pc++
}
}
return nil, nil
}
我們直接看解析器處理的主循環(huán),之前的代碼都是在初始化一些臨時變量间影。
1注竿,首先調(diào)用contract.GetOp(pc)從和約二進(jìn)制數(shù)據(jù)里取得第pc個opcode,opcode是以太坊虛擬機(jī)指令魂贬,一共不超過256個巩割,正好一個byte大小能裝下。
2付燥,從解析器的JumpTable表中查到op對應(yīng)的operation宣谈。比如opcode是SHA3(0x20),取到的op就是上面呢舉例的operation键科。
3闻丑,如果operation可用漩怎,解析器棧不超過1024,且讀寫不沖突
4嗦嗡,計算operation的memorysize扬卷,不能大于64位。
5酸钦,根據(jù)不同的指令,指令的memorysize等咱枉,調(diào)用operation.gasCost()方法計算執(zhí)行operation指令需要消耗的gas卑硫。
6,調(diào)用operation.execute(&pc, in.evm, contract, mem, stack)執(zhí)行指令對應(yīng)的方法蚕断。
7欢伏,operation.reverts值是true或者operation.halts值是true的指令,會跳出主循環(huán)亿乳,否則繼續(xù)遍歷下個op硝拧。
8,operation指令集里面有4個特殊的指令LOG0葛假,LOG1障陶,LOG2,LOG3聊训,它們的指令執(zhí)行方法makeLog()會產(chǎn)生日志數(shù)據(jù)抱究,日志內(nèi)容包括EVM解析棧內(nèi)容,指令內(nèi)存數(shù)據(jù)带斑,區(qū)塊信息鼓寺,合約信息等。這些日志數(shù)據(jù)會寫入到tx的Receipt的logs里面勋磕,并存入本地ldb數(shù)據(jù)庫妈候。
Solidity案例
和其他語言類似,有了字節(jié)碼運行機(jī)挂滓,就可以在字節(jié)碼上面再組織其他高級語言苦银,而solidlity語言就是實現(xiàn)了這樣的語言編譯器,方便了合約編寫杂彭,有利于推廣以太坊dapp開發(fā)墓毒。
pragma solidity >=0.4.21 <0.6.0;
contract simple {
uint num =0;
constructor() public{
num =1;
}
function add(uint i ) public returns(uint){
uint m =99;
num =num*i +m;
return num;
}
}
生成的Opcodes碼
使用remix編譯上述合約,得到bytecode
JUMPDEST 函數(shù)入口
PUSH + JUMPI/JUMP 類似于調(diào)用函數(shù)
CALLDATASIZE + CALLDATALOAD 大約是獲取函數(shù)參數(shù)
.code
PUSH 80 contract simple {\n uint nu...
PUSH 40 contract simple {\n uint nu...
MSTORE contract simple {\n uint nu...
PUSH 0 0
DUP1 uint num =0
SSTORE uint num =0
CALLVALUE constructor() public{\n ...
DUP1 olidity >
ISZERO a
PUSH [tag] 1 a
JUMPI a
PUSH 0 0
DUP1 .
REVERT 4.21 <0.6.0;
tag 1 a
JUMPDEST a
POP constructor() public{\n ...
PUSH 1 1
PUSH 0 num
DUP2 num =1
SWAP1 num =1
SSTORE num =1
POP num =1
PUSH #[$] 0000000000000000000000000000000000000000000000000000000000000000 contract simple {\n uint nu...
DUP1 contract simple {\n uint nu...
PUSH [$] 0000000000000000000000000000000000000000000000000000000000000000 contract simple {\n uint nu...
PUSH 0 contract simple {\n uint nu...
CODECOPY contract simple {\n uint nu...
PUSH 0 contract simple {\n uint nu...
RETURN contract simple {\n uint nu...
.data
0:
.code
PUSH 80 contract simple {\n uint nu...
PUSH 40 contract simple {\n uint nu...
MSTORE contract simple {\n uint nu...
PUSH 4 contract simple {\n uint nu...
CALLDATASIZE contract simple {\n uint nu...
LT contract simple {\n uint nu...
PUSH [tag] 1 contract simple {\n uint nu...
JUMPI contract simple {\n uint nu...
PUSH 0 contract simple {\n uint nu...
CALLDATALOAD contract simple {\n uint nu...
PUSH 100000000000000000000000000000000000000000000000000000000 contract simple {\n uint nu...
SWAP1 contract simple {\n uint nu...
DIV contract simple {\n uint nu...
PUSH FFFFFFFF contract simple {\n uint nu...
AND contract simple {\n uint nu...
DUP1 contract simple {\n uint nu...
PUSH 1003E2D2 contract simple {\n uint nu...
EQ contract simple {\n uint nu...
PUSH [tag] 2 contract simple {\n uint nu...
JUMPI contract simple {\n uint nu...
tag 1 contract simple {\n uint nu...
JUMPDEST contract simple {\n uint nu...
PUSH 0 contract simple {\n uint nu...
DUP1 contract simple {\n uint nu...
REVERT contract simple {\n uint nu...
tag 2 function add(uint i ) public r...
JUMPDEST function add(uint i ) public r...
CALLVALUE function add(uint i ) public r...
DUP1 olidity >
ISZERO a
PUSH [tag] 3 a
JUMPI a
PUSH 0 0
DUP1 .
REVERT 4.21 <0.6.0;
tag 3 a
JUMPDEST a
POP function add(uint i ) public r...
PUSH [tag] 4 function add(uint i ) public r...
PUSH 4 function add(uint i ) public r...
DUP1 function add(uint i ) public r...
CALLDATASIZE function add(uint i ) public r...
SUB function add(uint i ) public r...
DUP2 function add(uint i ) public r...
ADD function add(uint i ) public r...
SWAP1 function add(uint i ) public r...
DUP1 function add(uint i ) public r...
DUP1 function add(uint i ) public r...
CALLDATALOAD function add(uint i ) public r...
SWAP1 function add(uint i ) public r...
PUSH 20 function add(uint i ) public r...
ADD function add(uint i ) public r...
SWAP1 function add(uint i ) public r...
SWAP3 function add(uint i ) public r...
SWAP2 function add(uint i ) public r...
SWAP1 function add(uint i ) public r...
POP function add(uint i ) public r...
POP function add(uint i ) public r...
POP function add(uint i ) public r...
PUSH [tag] 5 function add(uint i ) public r...
JUMP function add(uint i ) public r...
tag 4 function add(uint i ) public r...
JUMPDEST function add(uint i ) public r...
PUSH 40 function add(uint i ) public r...
MLOAD function add(uint i ) public r...
DUP1 function add(uint i ) public r...
DUP3 function add(uint i ) public r...
DUP2 function add(uint i ) public r...
MSTORE function add(uint i ) public r...
PUSH 20 function add(uint i ) public r...
ADD function add(uint i ) public r...
SWAP2 function add(uint i ) public r...
POP function add(uint i ) public r...
POP function add(uint i ) public r...
PUSH 40 function add(uint i ) public r...
MLOAD function add(uint i ) public r...
DUP1 function add(uint i ) public r...
SWAP2 function add(uint i ) public r...
SUB function add(uint i ) public r...
SWAP1 function add(uint i ) public r...
RETURN function add(uint i ) public r...
tag 5 function add(uint i ) public r...
JUMPDEST function add(uint i ) public r...
PUSH 0 uint
DUP1 uint m
PUSH 63 99
SWAP1 uint m =99
POP uint m =99
DUP1 m
DUP4 I
PUSH 0 num
SLOAD num
MUL num*I
ADD num*I +m
PUSH 0 num
DUP2 num =num*I +m
SWAP1 num =num*I +m
SSTORE num =num*I +m
POP num =num*I +m
PUSH 0 num
SLOAD num
SWAP2 return num
POP return num
POP function add(uint i ) public r...
SWAP2 function add(uint i ) public r...
SWAP1 function add(uint i ) public r...
POP function add(uint i ) public r...
JUMP [out] function add(uint i ) public r...
.data