From 6b8469282d3696f436128f82c0a6054ef8fb7a21 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 22 May 2024 13:41:46 +0200 Subject: [PATCH] Tosca CT integration --- core/vm/evm.go | 21 +++ core/vm/interpreter.go | 227 ++++++++++++++++---------------- core/vm/tosca_ct_integration.go | 165 +++++++++++++++++++++++ 3 files changed, 302 insertions(+), 111 deletions(-) create mode 100644 core/vm/tosca_ct_integration.go diff --git a/core/vm/evm.go b/core/vm/evm.go index c18353a973..6ca8d3d227 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -125,6 +125,9 @@ type EVM struct { // available gas is calculated in gasCall* according to the 63/64 rule and later // applied in opCall*. callGasTemp uint64 + + // An optional override to intercept EVM calls. + CallContext CallContextInterceptor } // NewEVM returns a new EVM. The returned EVM is not thread safe and should @@ -181,6 +184,9 @@ func (evm *EVM) Interpreter() *EVMInterpreter { // the necessary steps to create accounts and reverses the state in case of an // execution error or failed value transfer. func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) { + if evm.CallContext != nil { + return evm.CallContext.Call(evm, caller, addr, input, new(big.Int).SetUint64(gas), value.ToBig()) + } // Capture the tracer start/end events in debug mode if evm.Config.Tracer != nil { evm.captureBegin(evm.depth, CALL, caller.Address(), addr, input, gas, value.ToBig()) @@ -253,6 +259,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // CallCode differs from Call in the sense that it executes the given address' // code with the caller as context. func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) { + if evm.CallContext != nil { + return evm.CallContext.CallCode(evm, caller, addr, input, new(big.Int).SetUint64(gas), value.ToBig()) + } // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Tracer != nil { evm.captureBegin(evm.depth, CALLCODE, caller.Address(), addr, input, gas, value.ToBig()) @@ -304,6 +313,9 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // DelegateCall differs from CallCode in the sense that it executes the given address' // code with the caller as context and the caller is set to the caller of the caller. func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { + if evm.CallContext != nil { + return evm.CallContext.DelegateCall(evm, caller, addr, input, new(big.Int).SetUint64(gas)) + } // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Tracer != nil { // NOTE: caller must, at all times be a contract. It should never happen @@ -349,6 +361,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // Opcodes that attempt to perform such modifications will result in exceptions // instead of performing the modifications. func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { + if evm.CallContext != nil { + return evm.CallContext.StaticCall(evm, caller, addr, input, new(big.Int).SetUint64(gas)) + } // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Tracer != nil { evm.captureBegin(evm.depth, STATICCALL, caller.Address(), addr, input, gas, nil) @@ -520,6 +535,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Create creates a new contract using code as deployment code. func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { + if evm.CallContext != nil { + return evm.CallContext.Create(evm, caller, code, new(big.Int).SetUint64(gas), value.ToBig()) + } contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address())) return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr, CREATE) } @@ -529,6 +547,9 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *uint2 // The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:] // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { + if evm.CallContext != nil { + return evm.CallContext.Create2(evm, caller, code, new(big.Int).SetUint64(gas), endowment.ToBig(), salt) + } codeAndHash := &codeAndHash{code: code} contractAddr = crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 406927e321..7d7c62859f 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -147,7 +147,11 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { // 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) { +func (in *EVMInterpreter) run(state *InterpreterState, input []byte, readOnly bool) (ret []byte, err error) { + defer func() { + state.finished = true + }() + // Increment the call depth which is restricted to 1024 in.evm.depth++ defer func() { in.evm.depth-- }() @@ -164,38 +168,15 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( in.returnData = nil // Don't bother with the execution if there's no code. - if len(contract.Code) == 0 { + if len(state.Contract.Code) == 0 { return nil, nil } - var ( - op OpCode // current opcode - mem = NewMemory() // bound memory - stack = newstack() // local stack - callContext = &ScopeContext{ - Memory: mem, - Stack: stack, - Contract: contract, - } - // 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 - // copies used by tracer - pcCopy uint64 // needed for the deferred EVMLogger - gasCopy uint64 // for EVMLogger to log gas remaining before execution - logged bool // deferred EVMLogger should ignore already logged steps - res []byte // result of the opcode execution function - debug = in.evm.Config.Tracer != nil - ) - // Don't move this deferred function, it's placed before the OnOpcode-deferred method, - // so that it gets executed _after_: the OnOpcode needs the stacks before - // they are returned to the pools - defer func() { - returnStack(stack) - }() - contract.Input = input + gethState := NewGethState(state.Contract, state.Memory, state.Stack, state.pc) + gethState.Contract.Input = input + + debug := in.evm.Config.Tracer != nil + var logged bool if debug { defer func() { // this deferred method handles exit-with-error @@ -203,101 +184,125 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( return } if !logged && in.evm.Config.Tracer.OnOpcode != nil { - in.evm.Config.Tracer.OnOpcode(pcCopy, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err)) + in.evm.Config.Tracer.OnOpcode(gethState.pcCopy, byte(gethState.op), gethState.gasCopy, gethState.cost, gethState.CallContext, in.returnData, in.evm.depth, VMErrorFromErr(err)) } if logged && in.evm.Config.Tracer.OnFault != nil { - in.evm.Config.Tracer.OnFault(pcCopy, byte(op), gasCopy, cost, callContext, in.evm.depth, VMErrorFromErr(err)) + in.evm.Config.Tracer.OnFault(gethState.pcCopy, byte(gethState.op), gethState.gasCopy, gethState.cost, gethState.CallContext, in.evm.depth, VMErrorFromErr(err)) } }() } + // The Interpreter main run loop (contextual). This loop runs until either an // explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during // the execution of one of the operations or until the done flag is set by the // parent context. + steps := 0 for { - if debug { - // Capture pre-execution values for tracing. - logged, pcCopy, gasCopy = false, pc, contract.Gas - } - // 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.table[op] - cost = operation.constantGas // For tracing - // Validate stack - if sLen := stack.len(); sLen < operation.minStack { - return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack} - } else if sLen > operation.maxStack { - return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} - } - if !contract.UseGas(cost, in.evm.Config.Tracer, tracing.GasChangeIgnored) { - return nil, ErrOutOfGas - } - - if operation.dynamicGas != nil { - // All ops with a dynamic memory usage also has a dynamic gas cost. - var memorySize uint64 - // calculate the new memory size and expand the memory to fit - // the operation - // Memory check needs to be done prior to evaluating the dynamic gas portion, - // to detect calculation overflows - if operation.memorySize != nil { - memSize, overflow := 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 - var dynamicCost uint64 - dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize) - cost += dynamicCost // for tracing - if err != nil { - return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) - } - if !contract.UseGas(dynamicCost, in.evm.Config.Tracer, tracing.GasChangeIgnored) { - return nil, ErrOutOfGas - } - - // Do tracing before memory expansion - if debug { - if in.evm.Config.Tracer.OnGasChange != nil { - in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode) - } - if in.evm.Config.Tracer.OnOpcode != nil { - in.evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err)) - logged = true - } - } - if memorySize > 0 { - mem.Resize(memorySize) - } - } else if debug { - if in.evm.Config.Tracer.OnGasChange != nil { - in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode) - } - if in.evm.Config.Tracer.OnOpcode != nil { - in.evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err)) - logged = true - } - } - - // execute the operation - res, err = operation.execute(&pc, in, callContext) - if err != nil { + steps++ + if in.evm.abort.Load() { + break + } + + if !in.Step(gethState) { break } - pc++ } - if err == errStopToken { - err = nil // clear stop token error + if gethState.Err == errStopToken { + gethState.Err = nil // clear stop token error } - return res, err + return gethState.Result, gethState.Err +} + +func (in *EVMInterpreter) Step(state *GethState) bool { + debug := in.evm.Config.Tracer != nil + if debug { + // Capture pre-execution values for tracing. + state.logged, state.pcCopy, state.gasCopy = false, state.Pc, state.Contract.Gas + } + // Get the operation from the jump table and validate the stack to ensure there are + // enough stack items available to perform the operation. + state.op = state.Contract.GetOp(state.Pc) + operation := in.table[state.op] + cost := operation.constantGas // For tracing + // Validate stack + if sLen := state.Stack.len(); sLen < operation.minStack { + state.Err = &ErrStackUnderflow{stackLen: sLen, required: operation.minStack} + return false + + } else if sLen > operation.maxStack { + state.Err = &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} + return false + } + if !state.Contract.UseGas(cost, in.evm.Config.Tracer, tracing.GasChangeIgnored) { + state.Err = ErrOutOfGas + return false + } + + if operation.dynamicGas != nil { + // All ops with a dynamic memory usage also has a dynamic gas cost. + var memorySize uint64 + // calculate the new memory size and expand the memory to fit + // the operation + // Memory check needs to be done prior to evaluating the dynamic gas portion, + // to detect calculation overflows + if operation.memorySize != nil { + memSize, overflow := operation.memorySize(state.Stack) + if overflow { + state.Err = ErrGasUintOverflow + return false + } + // memory is expanded in words of 32 bytes. Gas + // is also calculated in words. + if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow { + state.Err = ErrGasUintOverflow + return false + } + } + // 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 + var dynamicCost uint64 + dynamicCost, state.Err = operation.dynamicGas(in.evm, state.Contract, state.Stack, state.Memory, memorySize) + cost += dynamicCost // for tracing + if state.Err != nil { + state.Err = fmt.Errorf("%w: %v", ErrOutOfGas, state.Err) + return false + } + if !state.Contract.UseGas(dynamicCost, in.evm.Config.Tracer, tracing.GasChangeIgnored) { + state.Err = ErrOutOfGas + return false + } + + // Do tracing before memory expansion + if debug { + if in.evm.Config.Tracer.OnGasChange != nil { + in.evm.Config.Tracer.OnGasChange(state.gasCopy, state.gasCopy-cost, tracing.GasChangeCallOpCode) + } + if in.evm.Config.Tracer.OnOpcode != nil { + in.evm.Config.Tracer.OnOpcode(state.Pc, byte(state.op), state.gasCopy, cost, state.CallContext, in.returnData, in.evm.depth, VMErrorFromErr(state.Err)) + state.logged = true + } + } + if memorySize > 0 { + state.Memory.Resize(memorySize) + } + } else if debug { + if in.evm.Config.Tracer.OnGasChange != nil { + in.evm.Config.Tracer.OnGasChange(state.gasCopy, state.gasCopy-cost, tracing.GasChangeCallOpCode) + } + if in.evm.Config.Tracer.OnOpcode != nil { + in.evm.Config.Tracer.OnOpcode(state.Pc, byte(state.op), state.gasCopy, cost, state.CallContext, in.returnData, in.evm.depth, VMErrorFromErr(state.Err)) + state.logged = true + } + } + + // execute the operation + state.Result, state.Err = operation.execute(&state.Pc, in, state.CallContext) + if state.Err != nil { + return false + } + state.Pc++ + + return state.Err == nil } diff --git a/core/vm/tosca_ct_integration.go b/core/vm/tosca_ct_integration.go new file mode 100644 index 0000000000..a8081818da --- /dev/null +++ b/core/vm/tosca_ct_integration.go @@ -0,0 +1,165 @@ +package vm + +import ( + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/holiman/uint256" +) + +// Error +var ErrStopToken = errStopToken + +// Gas table +func MemoryGasCost(mem *Memory, wordSize uint64) (uint64, error) { + return memoryGasCost(mem, wordSize) +} + +// Stack +func NewStack() *Stack { + return newstack() +} + +func (st *Stack) Len() int { + return st.len() +} + +func (st *Stack) Push(d *uint256.Int) { + st.push(d) +} + +// EVM +func (evm *EVM) GetDepth() int { + return evm.depth +} + +func (evm *EVM) SetDepth(depth int) { + evm.depth = depth +} + +// Interpreter +func (g *EVMInterpreter) SetLastCallReturnData(data []byte) { + g.returnData = data +} + +func (g *EVMInterpreter) GetLastCallReturnData() []byte { + return g.returnData +} + +func (g *EVMInterpreter) SetReadOnly(readOnly bool) { + g.readOnly = readOnly +} + +func (g *EVMInterpreter) IsReadOnly() bool { + return g.readOnly +} + +// CallContext Call interceptor + +// CallContext provides a basic interface for the EVM calling conventions. The EVM +// depends on this context being implemented for doing subcalls and initialising new EVM contracts. +type CallContextInterceptor interface { + // Call calls another contract. + Call(env *EVM, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, uint64, error) + // CallCode takes another contracts code and execute within our own context + CallCode(env *EVM, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, uint64, error) + // DelegateCall is same as CallCode except sender and value is propagated from parent to child scope + DelegateCall(env *EVM, me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, uint64, error) + // Create creates a new contract + Create(env *EVM, me ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, uint64, error) + + StaticCall(env *EVM, me ContractRef, addr common.Address, input []byte, gas *big.Int) ([]byte, uint64, error) + Create2(env *EVM, me ContractRef, code []byte, gas *big.Int, value *big.Int, salt *uint256.Int) ([]byte, common.Address, uint64, error) +} + +// Interpreter interface +// EVMInterpreter defines an interface for different interpreter implementations. +type GethEVMInterpreter interface { + // Run the contract's code with the given input data and returns the return byte-slice + // and an error if one occurred. + Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) +} + +type InterpreterFactory func(evm *EVM, cfg Config) GethEVMInterpreter + +var interpreter_registry = map[string]InterpreterFactory{} + +func RegisterInterpreterFactory(name string, factory InterpreterFactory) { + interpreter_registry[strings.ToLower(name)] = factory +} + +func NewInterpreter(name string, evm *EVM, cfg Config) GethEVMInterpreter { + factory, found := interpreter_registry[strings.ToLower(name)] + if !found { + log.Error("no factory for interpreter registered", "name", name) + } + return factory(evm, cfg) +} + +func init() { + factory := func(evm *EVM, cfg Config) GethEVMInterpreter { + return NewEVMInterpreter(evm) + } + RegisterInterpreterFactory("", factory) + RegisterInterpreterFactory("geth", factory) +} + +// Abstracted interpreter with single step execution. + +// GethState represents the internal state of the interpreter. +type GethState struct { + Contract *Contract // processed contract + Memory *Memory // bound memory + Stack *Stack // 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 // program counter + Result []byte // result of the opcode execution function + Err error + CallContext *ScopeContext + ReadOnly bool + Halted bool + + op OpCode // current opcode + cost uint64 + + // copies used by tracer + pcCopy uint64 // needed for the deferred Tracer + gasCopy uint64 // for Tracer to log gas remaining before execution + logged bool // deferred Tracer should ignore already logged steps +} + +func NewGethState(contract *Contract, memory *Memory, stack *Stack, Pc uint64) *GethState { + return &GethState{ + Contract: contract, + Memory: memory, + Stack: stack, + Pc: Pc, + CallContext: &ScopeContext{ + Memory: memory, + Stack: stack, + Contract: contract, + }, + } +} + +type InterpreterState struct { + Contract *Contract + Stack *Stack + Memory *Memory + pc uint64 + finished bool +} + +func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) { + state := InterpreterState{ + Contract: contract, + Stack: NewStack(), + Memory: NewMemory(), + } + defer returnStack(state.Stack) + return in.run(&state, input, readOnly) +}