以太坊 EVM解析

作者: walker_1992 | 来源:发表于2021-08-28 20:25 被阅读0次

    以太坊EVM原理与实现

    太坊底层通过EVM模块支持合约的执行与调用,调用时根据合约地址获取到代码,生成环境后载入到EVM中运行。通常智能合约的开发流程是用solidlity编写逻辑代码,再通过编译器编译元数据,最后再发布到以太坊上。

    image
    代码结构
    .
    ├── analysis.go            //跳转目标判定
    ├── common.go
    ├── contract.go            //合约数据结构
    ├── contracts.go           //预编译好的合约
    ├── errors.go
    ├── evm.go                 //执行器 对外提供一些外部接口   
    ├── gas.go                 //call gas花费计算 一级指令耗费gas级别
    ├── gas_table.go           //指令耗费计算函数表
    ├── gen_structlog.go       
    ├── instructions.go        //指令操作
    ├── interface.go           
    ├── interpreter.go         //解释器 调用核心
    ├── intpool.go             //int值池
    ├── int_pool_verifier_empty.go
    ├── int_pool_verifier.go
    ├── jump_table.go           //指令和指令操作(操作,花费,验证)对应表
    ├── logger.go               //状态日志
    ├── memory.go               //EVM 内存
    ├── memory_table.go         //EVM 内存操作表 主要衡量操作所需内存大小
    ├── noop.go
    ├── opcodes.go              //Op指令 以及一些对应关系     
    ├── runtime
    │   ├── env.go              //执行环境 
    │   ├── fuzz.go
    │   └── runtime.go          //运行接口 测试使用
    ├── stack.go                //栈
    └── stack_table.go          //栈验证
    
    

    指令 OpCode

    opcodes.go中定义了所有的OpCode,该值是一个byte,合约编译出来的bytecode中,一个OpCode就是上面的一位。opcodes按功能分为9组(运算相关,块操作,加密相关等)。

    // 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定义了四种指令集合,每个集合实质上是个256长度的数组,名字翻译过来是(荒地,农庄,拜占庭,君士坦丁堡)对应了EVM的四个发展阶段。指令集向前兼容。

    var (
        frontierInstructionSet       = newFrontierInstructionSet()
        homesteadInstructionSet      = newHomesteadInstructionSet()
        byzantiumInstructionSet      = newByzantiumInstructionSet()
        constantinopleInstructionSet = newConstantinopleInstructionSet()
    )
    

    具体每条指令结构如下,字段意思见注释

    type operation struct {
        // execute is the operation function
        execute executionFunc//执行函数
        // gasCost is the gas function and returns the gas required for execution
        gasCost gasFunc//gas消耗函数
        // validateStack validates the stack (size) for the operation
        validateStack stackValidationFunc//堆栈大小验证函数
        // memorySize returns the memory size required for the operation
        memorySize memorySizeFunc //需要的内存大小
    
        halts   bool // 表示操作是否停止进一步执行 运算终止 indicates whether the operation should halt further execution 
        jumps   bool // 指示程序计数器是否不增加 跳转(for) indicates whether the program counter should not increment 
        writes  bool // 确定这是否是一个状态修改操作  是否写入determines whether this a state modifying operation 
        valid   bool // 指示检索到的操作是否有效并且已知   操作是否有效 indication whether the retrieved operation is valid and known 
        reverts bool // 确定操作是否恢复状态(隐式停止) 出错回滚determines whether the operation reverts state (implicitly halts)
        returns bool // 确定操作是否设置了返回数据内容  返回determines whether the operations sets the return data content 
    }
    

    按下面的sha3指令为例

    定义

        SHA3: {
            execute:       opSha3,
            gasCost:       gasSha3,
            validateStack: makeStackFunc(2, 1),
            memorySize:    memorySha3,
            valid:         true,
        },
    

    操作

    不同的操作有所不同,操作对象根据指令不同可能影响栈、内存、statedb。

    func opSha3(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
        //从堆栈中获取内存的地址(offset+size)从内存中取出来数据
        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)当作preimage写到数据库中
            evm.StateDB.AddPreimage(interpreter.hasherBuf, data)
        }
        //hash入栈
        stack.push(interpreter.intPool.get().SetBytes(interpreter.hasherBuf[:]))
    
        interpreter.intPool.put(offset, size)
        return nil, nil
    }
    

    gas费用

    不同的操作有不同的初始值和对应的计算方法,具体的方法都定义在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根据栈顶的两个数据,计算memorysize。有些操作不需要申请内存因而默认为0

    func memorySha3(stack *Stack) *big.Int {
        return calcMemSize(stack.Back(0), stack.Back(1))
    }
    

    栈验证

    先验证栈上的操作数够不够,在验证栈是否超出最大限制,sha3在这里仅需要验证其参数够不够,运算之后栈是要减一的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智能合约的存储单位也是解释器执行的基本单位,包含了代码,调用人,所有人,gas的相关信息。

    // EVM是以太坊虚拟机基础对象,并提供必要的工具,以使用提供的上下文运行给定状态的合约。
    // 应该指出的是,任何调用产生的任何错误都应该被认为是一种回滚修改状态和消耗所有GAS操作,
    // 不应该执行对具体错误的检查。 解释器确保生成的任何错误都被认为是错误的代码。
    type EVM struct {
        Context // Context provides auxiliary blockchain related information
        StateDB StateDB // StateDB gives access to the underlying state
        
        depth int //当前的调用堆栈
    
        // 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{},
    }
    

    执行机

    EVM中栈用于保存操作数,每个操作数的类型是big.int,故说EVM是256位虚拟机。执行opcode的时候,从上往下弹出操作数,作为操作的参数。

    // 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]
    }
    
    //相复制指定位置的值到栈顶
    func (st *Stack) dup(pool *intPool, n int) {
        st.push(pool.get().Set(st.data[st.len()-n]))
    }
    
    //取栈顶的元素
    func (st *Stack) peek() *big.Int {
        return st.data[st.len()-1]
    }
    

    内存

    内存用于一些内存操作(MLOAD,MSTORE,MSTORE8)及合约调用的参数拷贝(CALL,CALLCODE)。
    内存数据结构,维护了一个byte数组,MLOAD,MSTORE读取存入的时候都要指定位置及长度才能准确的读写。

    // 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
    }
    

    内存操作指令详见 EVM7种重要指令实现原理 一文

    stateDb

    合约本身不保存数据,那么合约的数据是保存在哪里呢?合约及其调用类似于数据库的日志,保存了合约定义以及对他的一系列操作,只要将这些操作执行一遍就能获取当前的结果,但是如果每次都要去执行就太慢了,因而这部分数据是会持久化到stateDb里面的。code中定义了两条指令SSTORE SLOAD用于从db中读写合约当前的状态。详见 EVM7种重要指令实现原理 一文

    执行过程

    执行入口定义在evm.go中,功能就是组装执行环境(代码,执行人关系,参数等)

    //Call 执行于给定的input作为参数与addr相关联的合约
    //他还处理所需的任何必要的转账操作,并采取必要的步骤来创建账户
    //并在任意错误的情况下回滚所做的操作
    //Call方法, 无论我们转账或者是执行合约代码都会调用到这里, 同时合约里面的call指令也会执行到这里
    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) { //调用深度最多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那么返回正常,而且这次调用没有消耗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
            }
            //负责在本地状态创建addr
            evm.StateDB.CreateAccount(addr)
        }
        //执行转账
        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指令触发的错误,因为ICO一般设置了人数限制或者资金限制
                // 在大家抢购的时候很可能会触发这些限制条件,导致被抽走不少钱。这个时候
                // 又不能设置比较低的GasPrice和GasLimit。因为要速度快。
                // 那么不会使用剩下的全部Gas,而是只会使用代码执行的Gas
                // 不然会被抽走 GasLimit *GasPrice的钱,那可不少。
                contract.UseGas(contract.Gas)
            }
        }
        return ret, contract.Gas, err
    }
    

    类似的函数有四个。详细区别见最后的参考。

    • Call A->B A,B的环境独立
    • CallCode、 和Call类似,与Call不同的地方在与它使用caller的context来执行给定地址的代码
    • DelegateCall、 和CallCode类似,区别在于msg.send不一样 caller被设置为caller的caller
    • StaticCall 和call相似 不允许执行任何修改状态的操作

    Contract和参数构造完成后调用执行函数,执行函数会检查调用的是否会之前编译好的原生合约,如果是原生合约则调用原生合约,否则调用解释器执行函数运算合约。

    // 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  当前指令
            mem   = NewMemory() // bound memory    内存
            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...) }()
    
        //解释器的主要循环,直到遇到STOP,RETURN,SELFDESTRUCT指令被执行,或者是遇到任意错误,或者说done被父context设置。
        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)//获取一条指令及指令对应的操作
            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
            //计算内存,按操作所需要的操作数来算
            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 调用前面提到的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 {//扩大内存范围
                mem.Resize(memorySize)
            }
    
            // execute the operation
            //执行操作
            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 设置返回值
            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:    //跳转
                pc++
            }
        }
        return nil, nil
    }
    

    我们直接看解析器处理的主循环,之前的代码都是在初始化一些临时变量。
    1,首先调用contract.GetOp(pc)从和约二进制数据里取得第pc个opcode,opcode是以太坊虚拟机指令,一共不超过256个,正好一个byte大小能装下。
    2,从解析器的JumpTable表中查到op对应的operation。比如opcode是SHA3(0x20),取到的op就是上面呢举例的operation。
    3,如果operation可用,解析器栈不超过1024,且读写不冲突
    4,计算operation的memorysize,不能大于64位。
    5,根据不同的指令,指令的memorysize等,调用operation.gasCost()方法计算执行operation指令需要消耗的gas。
    6,调用operation.execute(&pc, in.evm, contract, mem, stack)执行指令对应的方法。
    7,operation.reverts值是true或者operation.halts值是true的指令,会跳出主循环,否则继续遍历下个op。
    8,operation指令集里面有4个特殊的指令LOG0,LOG1,LOG2,LOG3,它们的指令执行方法makeLog()会产生日志数据,日志内容包括EVM解析栈内容,指令内存数据,区块信息,合约信息等。这些日志数据会写入到tx的Receipt的logs里面,并存入本地ldb数据库。

    Solidity案例

    和其他语言类似,有了字节码运行机,就可以在字节码上面再组织其他高级语言,而solidlity语言就是实现了这样的语言编译器,方便了合约编写,有利于推广以太坊dapp开发。

    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 函数入口
    PUSH + JUMPI/JUMP 类似于调用函数
    CALLDATASIZE + CALLDATALOAD 大约是获取函数参数

    .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
    

    相关文章

      网友评论

        本文标题:以太坊 EVM解析

        本文链接:https://www.haomeiwen.com/subject/tsiviltx.html