diff --git a/eth/tracers/native/gen_callframe_enter.go b/eth/tracers/native/gen_callframe_enter.go new file mode 100644 index 0000000000..e0066edaf9 --- /dev/null +++ b/eth/tracers/native/gen_callframe_enter.go @@ -0,0 +1,72 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package native + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/vm" +) + +var _ = (*callFrameEnterMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (c CallFrameEnter) MarshalJSON() ([]byte, error) { + type CallFrameEnter struct { + Op vm.OpCode `json:"op"` + From common.Address `json:"from"` + To common.Address `json:"to"` + Input hexutil.Bytes `json:"input"` + Gas math.HexOrDecimal64 `json:"gas"` + Value *hexutil.Big `json:"value"` + OpName string `json:"opName,omitempty"` + } + var enc CallFrameEnter + enc.Op = c.Op + enc.From = c.From + enc.To = c.To + enc.Input = c.Input + enc.Gas = math.HexOrDecimal64(c.Gas) + enc.Value = (*hexutil.Big)(c.Value) + enc.OpName = c.OpName() + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (c *CallFrameEnter) UnmarshalJSON(input []byte) error { + type CallFrameEnter struct { + Op *vm.OpCode `json:"op"` + From *common.Address `json:"from"` + To *common.Address `json:"to"` + Input *hexutil.Bytes `json:"input"` + Gas *math.HexOrDecimal64 `json:"gas"` + Value *hexutil.Big `json:"value"` + } + var dec CallFrameEnter + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Op != nil { + c.Op = *dec.Op + } + if dec.From != nil { + c.From = *dec.From + } + if dec.To != nil { + c.To = *dec.To + } + if dec.Input != nil { + c.Input = *dec.Input + } + if dec.Gas != nil { + c.Gas = uint64(*dec.Gas) + } + if dec.Value != nil { + c.Value = (*big.Int)(dec.Value) + } + return nil +} diff --git a/eth/tracers/native/gen_callframe_exit.go b/eth/tracers/native/gen_callframe_exit.go new file mode 100644 index 0000000000..0e00937963 --- /dev/null +++ b/eth/tracers/native/gen_callframe_exit.go @@ -0,0 +1,62 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package native + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" +) + +var _ = (*callFrameExitMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (c CallFrameExit) MarshalJSON() ([]byte, error) { + type CallFrameExit struct { + From common.Address `json:"from"` + To common.Address `json:"to"` + Output hexutil.Bytes `json:"output"` + GasUsed math.HexOrDecimal64 `json:"gasUsed"` + Error *string `json:"error,omitempty"` + } + var enc CallFrameExit + enc.From = c.From + enc.To = c.To + enc.Output = c.Output + enc.GasUsed = math.HexOrDecimal64(c.GasUsed) + enc.Error = c.Error + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (c *CallFrameExit) UnmarshalJSON(input []byte) error { + type CallFrameExit struct { + From *common.Address `json:"from"` + To *common.Address `json:"to"` + Output *hexutil.Bytes `json:"output"` + GasUsed *math.HexOrDecimal64 `json:"gasUsed"` + Error *string `json:"error,omitempty"` + } + var dec CallFrameExit + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.From != nil { + c.From = *dec.From + } + if dec.To != nil { + c.To = *dec.To + } + if dec.Output != nil { + c.Output = *dec.Output + } + if dec.GasUsed != nil { + c.GasUsed = uint64(*dec.GasUsed) + } + if dec.Error != nil { + c.Error = dec.Error + } + return nil +} diff --git a/eth/tracers/native/opcode.go b/eth/tracers/native/opcode.go new file mode 100644 index 0000000000..290756c5ea --- /dev/null +++ b/eth/tracers/native/opcode.go @@ -0,0 +1,227 @@ +// Copyright 2024 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package native + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/eth/tracers/logger" +) + +func init() { + tracers.DefaultDirectory.Register("opcodeTracer", newOpcodeTracer, false) +} + +//go:generate go run github.com/fjl/gencodec -type CallFrameEnter -field-override callFrameEnterMarshaling -out gen_callframe_enter.go + +// CallFrameEnter is emitted every call frame entered. +type CallFrameEnter struct { + Op vm.OpCode `json:"op"` + From common.Address `json:"from"` + To common.Address `json:"to"` + Input []byte `json:"input"` + Gas uint64 `json:"gas"` + Value *big.Int `json:"value"` +} + +// overrides for gencodec +type callFrameEnterMarshaling struct { + Input hexutil.Bytes + Gas math.HexOrDecimal64 + Value *hexutil.Big + OpName string `json:"opName,omitempty"` // adds call to OpName() in MarshalJSON +} + +// OpName formats the operand name in a human-readable format. +func (c *CallFrameEnter) OpName() string { + return c.Op.String() +} + +//go:generate go run github.com/fjl/gencodec -type CallFrameExit -field-override callFrameExitMarshaling -out gen_callframe_exit.go + +// CallFrameExit is emitted every call frame exits. +type CallFrameExit struct { + From common.Address `json:"from"` + To common.Address `json:"to"` + Output []byte `json:"output"` + GasUsed uint64 `json:"gasUsed"` + Error *string `json:"error,omitempty"` +} + +// overrides for gencodec +type callFrameExitMarshaling struct { + Output hexutil.Bytes + GasUsed math.HexOrDecimal64 +} + +type opcodeTracer struct { + noopTracer + traces []json.RawMessage + callStack []common.Address + cfg opcodeTracerConfig + env *vm.EVM +} + +type opcodeTracerConfig struct { + EnableMemory bool // enable memory capture + DisableStack bool // disable stack capture + EnableReturnData bool // enable return data capture + EnableCallFrames bool // enable call frame enter capture +} + +// newCallTracer returns a native go tracer which tracks +// call frames of a tx, and implements vm.EVMLogger. +func newOpcodeTracer(ctx *tracers.Context, config json.RawMessage) (tracers.Tracer, error) { + var cfg opcodeTracerConfig + if config != nil { + if err := json.Unmarshal(config, &cfg); err != nil { + return nil, err + } + } + // First callframe contains tx context info + // and is populated on start and end. + return &opcodeTracer{traces: make([]json.RawMessage, 0), callStack: make([]common.Address, 0), cfg: cfg}, nil +} + +func (t *opcodeTracer) appendTrace(item interface{}) { + data, err := json.Marshal(item) + if err != nil { + return + } + t.traces = append(t.traces, data) +} + +// CaptureStart implements the EVMLogger interface to initialize the tracing operation. +func (t *opcodeTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { + t.env = env + t.callStack = append(t.callStack, from, to) + if t.cfg.EnableCallFrames { + op := vm.CALL + if create { + op = vm.CREATE + } + t.appendTrace(CallFrameEnter{ + Op: op, + From: from, + To: to, + Gas: gas, + Value: value, + Input: input, + }) + } +} + +func (t *opcodeTracer) CaptureFault(pc uint64, op vm.OpCode, gas uint64, cost uint64, scope *vm.ScopeContext, depth int, err error) { + // TODO: Add rData to this interface as well + t.CaptureState(pc, op, gas, cost, scope, nil, depth, err) +} + +// CaptureState implements the EVMLogger interface to trace a single step of VM execution. +func (t *opcodeTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { + memory := scope.Memory + stack := scope.Stack + + log := logger.StructLog{ + Pc: pc, + Op: op, + Gas: gas, + GasCost: cost, + MemorySize: memory.Len(), + Depth: depth, + RefundCounter: t.env.StateDB.GetRefund(), + Err: err, + } + if t.cfg.EnableMemory { + log.Memory = memory.Data() + } + if !t.cfg.DisableStack { + log.Stack = stack.Data() + } + if t.cfg.EnableReturnData { + log.ReturnData = rData + } + t.appendTrace(log) +} + +// CaptureEnd is called after the call finishes to finalize the tracing. +func (t *opcodeTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { + if t.cfg.EnableCallFrames { + var errString *string + if err != nil { + errStr := err.Error() + errString = &errStr + } + t.appendTrace(CallFrameExit{ + From: t.callStack[len(t.callStack)-1], + To: t.callStack[len(t.callStack)-2], + Output: output, + GasUsed: gasUsed, + Error: errString, + }) + } + t.callStack = t.callStack[:len(t.callStack)-2] +} + +// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). +func (t *opcodeTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + t.callStack = append(t.callStack, to) + if t.cfg.EnableCallFrames { + t.appendTrace(CallFrameEnter{ + Op: typ, + From: from, + To: to, + Gas: gas, + Value: value, + Input: input, + }) + } +} + +// CaptureExit is called when EVM exits a scope. +func (t *opcodeTracer) CaptureExit(output []byte, gasUsed uint64, err error) { + if t.cfg.EnableCallFrames { + var errString *string + if err != nil { + errStr := err.Error() + errString = &errStr + } + t.appendTrace(CallFrameExit{ + From: t.callStack[len(t.callStack)-1], + To: t.callStack[len(t.callStack)-2], + Output: output, + GasUsed: gasUsed, + Error: errString, + }) + } + t.callStack = t.callStack[:len(t.callStack)-1] +} + +// GetResult returns the json-encoded nested list of call traces, and any +// error arising from the encoding or forceful termination (via `Stop`). +func (t *opcodeTracer) GetResult() (json.RawMessage, error) { + res, err := json.Marshal(t.traces) + if err != nil { + return nil, err + } + return json.RawMessage(res), nil +}