From d9dba75202c28a93007bca6ca42e9fda91b41087 Mon Sep 17 00:00:00 2001 From: Arun Dhyani Date: Mon, 6 Nov 2023 19:10:29 +0800 Subject: [PATCH] new changes and unit test fixes --- core/vm/errors.go | 130 +++++++++++++++++++++++++++++++++++++++++ core/vm/evm.go | 4 +- core/vm/interpreter.go | 6 +- eth/tracers/js/goja.go | 3 +- 4 files changed, 136 insertions(+), 7 deletions(-) diff --git a/core/vm/errors.go b/core/vm/errors.go index a45a7b0a928..219c15beb43 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -19,6 +19,7 @@ package vm import ( "errors" "fmt" + "math" ) // List evm execution errors @@ -75,3 +76,132 @@ type ErrInvalidOpCode struct { } func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: %s", e.opcode) } + +// rpcError is the same interface as the one defined in rpc/errors.go +// but we do not want to depend on rpc package here so we redefine it. +// +// It's used to ensure that the VMError implements the RPC error interface. +type rpcError interface { + Error() string // returns the message + ErrorCode() int // returns the code +} + +var _ rpcError = (*VMError)(nil) + +// VMError wraps a VM error with an additional stable error code. The error +// field is the original error that caused the VM error and must be one of the +// VM error defined at the top of this file. +// +// If the error is not one of the known error above, the error code will be +// set to VMErrorCodeUnknown. +type VMError struct { + error + code int +} + +func VMErrorFromErr(err error) error { + if err == nil { + return nil + } + + return &VMError{ + error: fmt.Errorf("%w", err), + code: vmErrorCodeFromErr(err), + } +} + +func (e *VMError) Error() string { + return e.error.Error() +} + +func (e *VMError) Unwrap() error { + return errors.Unwrap(e.error) +} + +func (e *VMError) ErrorCode() int { + return e.code +} + +const ( + // We start the error code at 1 so that we can use 0 later for some possible extension. There + // is no unspecified value for the code today because it should always be set to a valid value + // that could be VMErrorCodeUnknown if the error is not mapped to a known error code. + + VMErrorCodeOutOfGas = 1 + iota + VMErrorCodeCodeStoreOutOfGas + VMErrorCodeDepth + VMErrorCodeInsufficientBalance + VMErrorCodeContractAddressCollision + VMErrorCodeExecutionReverted + VMErrorCodeMaxInitCodeSizeExceeded + VMErrorCodeMaxCodeSizeExceeded + VMErrorCodeInvalidJump + VMErrorCodeWriteProtection + VMErrorCodeReturnDataOutOfBounds + VMErrorCodeGasUintOverflow + VMErrorCodeInvalidCode + VMErrorCodeNonceUintOverflow + VMErrorCodeStackUnderflow + VMErrorCodeStackOverflow + VMErrorCodeInvalidOpCode + VMErrorInvalidSubroutineEntry + VMErrorInvalidRetsub + VMErrorReturnStackExceeded + + // VMErrorCodeUnknown explicitly marks an error as unknown, this is useful when error is converted + // from an actual `error` in which case if the mapping is not known, we can use this value to indicate that. + VMErrorCodeUnknown = math.MaxInt - 1 +) + +func vmErrorCodeFromErr(err error) int { + switch { + case errors.Is(err, ErrOutOfGas): + return VMErrorCodeOutOfGas + case errors.Is(err, ErrCodeStoreOutOfGas): + return VMErrorCodeCodeStoreOutOfGas + case errors.Is(err, ErrDepth): + return VMErrorCodeDepth + case errors.Is(err, ErrInsufficientBalance): + return VMErrorCodeInsufficientBalance + case errors.Is(err, ErrContractAddressCollision): + return VMErrorCodeContractAddressCollision + case errors.Is(err, ErrExecutionReverted): + return VMErrorCodeExecutionReverted + case errors.Is(err, ErrMaxCodeSizeExceeded): + return VMErrorCodeMaxCodeSizeExceeded + case errors.Is(err, ErrInvalidJump): + return VMErrorCodeInvalidJump + case errors.Is(err, ErrWriteProtection): + return VMErrorCodeWriteProtection + case errors.Is(err, ErrReturnDataOutOfBounds): + return VMErrorCodeReturnDataOutOfBounds + case errors.Is(err, ErrGasUintOverflow): + return VMErrorCodeGasUintOverflow + case errors.Is(err, ErrInvalidCode): + return VMErrorCodeInvalidCode + case errors.Is(err, ErrNonceUintOverflow): + return VMErrorCodeNonceUintOverflow + case errors.Is(err, ErrInvalidSubroutineEntry): + return VMErrorInvalidSubroutineEntry + case errors.Is(err, ErrInvalidRetsub): + return VMErrorInvalidRetsub + case errors.Is(err, ErrReturnStackExceeded): + return VMErrorReturnStackExceeded + + default: + // Dynamic errors + if v := (*ErrStackUnderflow)(nil); errors.As(err, &v) { + return VMErrorCodeStackUnderflow + } + + if v := (*ErrStackOverflow)(nil); errors.As(err, &v) { + return VMErrorCodeStackOverflow + } + + if v := (*ErrInvalidOpCode)(nil); errors.As(err, &v) { + return VMErrorCodeInvalidOpCode + } + + return VMErrorCodeUnknown + } +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 5123db9b467..c545b924db3 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -488,8 +488,8 @@ func (evm *EVM) captureEnd(isRoot bool, typ OpCode, startGas uint64, leftOverGas } if isRoot { - tracer.CaptureEnd(ret, startGas-leftOverGas, err) + tracer.CaptureEnd(ret, startGas-leftOverGas, VMErrorFromErr(err)) } else { - tracer.CaptureExit(ret, startGas-leftOverGas, err) + tracer.CaptureExit(ret, startGas-leftOverGas, VMErrorFromErr(err)) } } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 1b51a330579..6c8a8f35fb2 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -225,9 +225,9 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // first: capture data/memory/state/depth/etc... then clenup them if in.cfg.Debug && err != nil { if !logged { - in.cfg.Tracer.CaptureState(pcCopy, op, gasCopy, cost, callContext, in.returnData, in.depth, err) //nolint:errcheck + in.cfg.Tracer.CaptureState(pcCopy, op, gasCopy, cost, callContext, in.returnData, in.depth, VMErrorFromErr(err)) //nolint:errcheck } else { - in.cfg.Tracer.CaptureFault(pcCopy, op, gasCopy, cost, callContext, in.depth, err) + in.cfg.Tracer.CaptureFault(pcCopy, op, gasCopy, cost, callContext, in.depth, VMErrorFromErr(err)) } } // this function must execute _after_: the `CaptureState` needs the stacks before @@ -299,7 +299,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } if in.cfg.Debug { in.cfg.Tracer.OnGasChange(gasCopy, gasCopy-cost, GasChangeCallOpCode) - in.cfg.Tracer.CaptureState(_pc, op, gasCopy, cost, callContext, in.returnData, in.depth, err) //nolint:errcheck + in.cfg.Tracer.CaptureState(_pc, op, gasCopy, cost, callContext, in.returnData, in.depth, VMErrorFromErr(err)) //nolint:errcheck logged = true } // execute the operation diff --git a/eth/tracers/js/goja.go b/eth/tracers/js/goja.go index d6962b3fbb1..0ff07151ffa 100644 --- a/eth/tracers/js/goja.go +++ b/eth/tracers/js/goja.go @@ -106,7 +106,6 @@ type jsTracer struct { activePrecompiles []libcommon.Address // List of active precompiles at current block traceStep bool // True if tracer object exposes a `step()` method traceFrame bool // True if tracer object exposes the `enter()` and `exit()` methods - gasLimit uint64 // Amount of gas bought for the whole tx err error // Any error that should stop tracing obj *goja.Object // Trace object @@ -225,7 +224,7 @@ func (t *jsTracer) CaptureTxStart(env *vm.EVM, tx types.Transaction) { rules := env.ChainConfig().Rules(env.Context().BlockNumber, env.Context().Time) t.activePrecompiles = vm.ActivePrecompiles(rules) t.ctx["block"] = t.vm.ToValue(t.env.Context().BlockNumber) - t.ctx["gasPrice"] = t.vm.ToValue(t.env.TxContext().GasPrice) + t.ctx["gasPrice"] = t.vm.ToValue(t.env.TxContext().GasPrice.ToBig()) } // CaptureTxEnd implements the Tracer interface and is invoked at the end of