以太坊 EVM解析

以太坊EVM原理與實現(xiàn)

太坊底層通過EVM模塊支持合約的執(zhí)行與調(diào)用尿庐,調(diào)用時根據(jù)合約地址獲取到代碼,生成環(huán)境后載入到EVM中運行呢堰。通常智能合約的開發(fā)流程是用solidlity編寫邏輯代碼抄瑟,再通過編譯器編譯元數(shù)據(jù),最后再發(fā)布到以太坊上暮胧。

image
代碼結(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末亲怠,一起剝皮案震驚了整個濱河市所计,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌团秽,老刑警劉巖主胧,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叭首,死亡現(xiàn)場離奇詭異,居然都是意外死亡踪栋,警方通過查閱死者的電腦和手機(jī)焙格,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夷都,“玉大人眷唉,你說我怎么就攤上這事《诠伲” “怎么了冬阳?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長党饮。 經(jīng)常有香客問我肝陪,道長,這世上最難降的妖魔是什么刑顺? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任氯窍,我火速辦了婚禮,結(jié)果婚禮上蹲堂,老公的妹妹穿的比我還像新娘狼讨。我一直安慰自己,他們只是感情好贯城,可當(dāng)我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布熊楼。 她就那樣靜靜地躺著,像睡著了一般能犯。 火紅的嫁衣襯著肌膚如雪鲫骗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天踩晶,我揣著相機(jī)與錄音执泰,去河邊找鬼。 笑死渡蜻,一個胖子當(dāng)著我的面吹牛术吝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播茸苇,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼排苍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了学密?” 一聲冷哼從身側(cè)響起淘衙,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎腻暮,沒想到半個月后彤守,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體毯侦,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年具垫,在試婚紗的時候發(fā)現(xiàn)自己被綠了侈离。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡筝蚕,死狀恐怖卦碾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情起宽,我是刑警寧澤蔗坯,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站燎含,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏腿短。R本人自食惡果不足惜屏箍,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望橘忱。 院中可真熱鬧赴魁,春花似錦、人聲如沸钝诚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凝颇。三九已至潘拱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拧略,已是汗流浹背芦岂。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留垫蛆,地道東北人禽最。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像袱饭,于是被迫代替她去往敵國和親川无。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容