From 787260e501e8ea2a97e7b94a2c336c690689ab94 Mon Sep 17 00:00:00 2001
From: ryanmorphl2 <163962984+ryanmorphl2@users.noreply.github.com>
Date: Mon, 13 Jan 2025 13:54:51 +0800
Subject: [PATCH 1/5] feat(tracer): add withLog to callTracer
---
core/state_transition.go | 7 +
core/vm/access_list_tracer.go | 4 +
core/vm/logger.go | 11 +
core/vm/logger_json.go | 4 +
core/vm/runtime/runtime_test.go | 6 +-
eth/tracers/api.go | 18 +-
.../internal/tracetest/calltrace_test.go | 6 +-
eth/tracers/js/tracer.go | 6 +-
eth/tracers/js/tracer_test.go | 26 +-
eth/tracers/native/4byte.go | 37 +--
eth/tracers/native/call.go | 239 +++++++++++++-----
eth/tracers/native/gen_account_json.go | 56 ++++
eth/tracers/native/gen_callframe_json.go | 107 ++++++++
eth/tracers/native/noop.go | 8 +-
eth/tracers/native/prestate.go | 73 ++++--
eth/tracers/native/tracer.go | 50 ++--
eth/tracers/tracers.go | 6 +-
rollup/tracing/mux_tracer.go | 12 +
rollup/tracing/tracing.go | 2 +-
19 files changed, 501 insertions(+), 177 deletions(-)
create mode 100644 eth/tracers/native/gen_account_json.go
create mode 100644 eth/tracers/native/gen_callframe_json.go
diff --git a/core/state_transition.go b/core/state_transition.go
index 60aad1d2b..d46b6f4d2 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -355,6 +355,13 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
return nil, err
}
+ if st.evm.Config.Debug {
+ st.evm.Config.Tracer.CaptureTxStart(st.initialGas)
+ defer func() {
+ st.evm.Config.Tracer.CaptureTxEnd(st.gas)
+ }()
+ }
+
var (
msg = st.msg
sender = vm.AccountRef(msg.From())
diff --git a/core/vm/access_list_tracer.go b/core/vm/access_list_tracer.go
index a5da0782a..b0f272e4b 100644
--- a/core/vm/access_list_tracer.go
+++ b/core/vm/access_list_tracer.go
@@ -175,6 +175,10 @@ func (*AccessListTracer) CaptureEnter(typ OpCode, from common.Address, to common
func (*AccessListTracer) CaptureExit(output []byte, gasUsed uint64, err error) {}
+func (t *AccessListTracer) CaptureTxStart(gasLimit uint64) {}
+
+func (t *AccessListTracer) CaptureTxEnd(restGas uint64) {}
+
// AccessList returns the current accesslist maintained by the tracer.
func (a *AccessListTracer) AccessList() types.AccessList {
return a.list.accessList()
diff --git a/core/vm/logger.go b/core/vm/logger.go
index f8b70538b..c587433ba 100644
--- a/core/vm/logger.go
+++ b/core/vm/logger.go
@@ -127,6 +127,9 @@ func (s *StructLog) ErrorString() string {
// Note that reference types are actual VM data structures; make copies
// if you need to retain them beyond the current call.
type EVMLogger interface {
+ // Transaction level
+ CaptureTxStart(gasLimit uint64)
+ CaptureTxEnd(restGas uint64)
CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int)
CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
CaptureStateAfter(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
@@ -340,6 +343,10 @@ func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {
}
+func (t *StructLogger) CaptureTxStart(gasLimit uint64) {}
+
+func (t *StructLogger) CaptureTxEnd(restGas uint64) {}
+
// UpdatedAccounts is used to collect all "touched" accounts
func (l *StructLogger) UpdatedAccounts() map[common.Address]struct{} {
return l.statesAffected
@@ -487,6 +494,10 @@ func (t *mdLogger) CaptureEnter(typ OpCode, from common.Address, to common.Addre
func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
+func (t *mdLogger) CaptureTxStart(gasLimit uint64) {}
+
+func (t *mdLogger) CaptureTxEnd(restGas uint64) {}
+
// FormatLogs formats EVM returned structured logs for json output
func FormatLogs(logs []*StructLog) []*types.StructLogRes {
formatted := make([]*types.StructLogRes, 0, len(logs))
diff --git a/core/vm/logger_json.go b/core/vm/logger_json.go
index 61a3a656d..38ac2ecc6 100644
--- a/core/vm/logger_json.go
+++ b/core/vm/logger_json.go
@@ -98,3 +98,7 @@ func (l *JSONLogger) CaptureEnter(typ OpCode, from common.Address, to common.Add
}
func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
+
+func (t *JSONLogger) CaptureTxStart(gasLimit uint64) {}
+
+func (t *JSONLogger) CaptureTxEnd(restGas uint64) {}
diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go
index d327d9d97..57ca64190 100644
--- a/core/vm/runtime/runtime_test.go
+++ b/core/vm/runtime/runtime_test.go
@@ -376,7 +376,7 @@ func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
cfg.GasLimit = gas
if len(tracerCode) > 0 {
- tracer, err := tracers.New(tracerCode, new(tracers.Context))
+ tracer, err := tracers.New(tracerCode, new(tracers.Context), nil)
if err != nil {
b.Fatal(err)
}
@@ -877,7 +877,7 @@ func TestRuntimeJSTracer(t *testing.T) {
statedb.SetCode(common.HexToAddress("0xee"), calleeCode)
statedb.SetCode(common.HexToAddress("0xff"), depressedCode)
- tracer, err := tracers.New(jsTracer, new(tracers.Context))
+ tracer, err := tracers.New(jsTracer, new(tracers.Context), nil)
if err != nil {
t.Fatal(err)
}
@@ -912,7 +912,7 @@ func TestJSTracerCreateTx(t *testing.T) {
code := []byte{byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN)}
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
- tracer, err := tracers.New(jsTracer, new(tracers.Context))
+ tracer, err := tracers.New(jsTracer, new(tracers.Context), nil)
if err != nil {
t.Fatal(err)
}
diff --git a/eth/tracers/api.go b/eth/tracers/api.go
index e5fbf27a9..360abcbd3 100644
--- a/eth/tracers/api.go
+++ b/eth/tracers/api.go
@@ -20,6 +20,7 @@ import (
"bufio"
"bytes"
"context"
+ "encoding/json"
"errors"
"fmt"
"io/ioutil"
@@ -173,15 +174,15 @@ type TraceConfig struct {
Tracer *string
Timeout *string
Reexec *uint64
+ // Config specific to given tracer. Note struct logger
+ // config are historically embedded in main object.
+ TracerConfig json.RawMessage
}
// TraceCallConfig is the config for traceCall API. It holds one more
// field to override the state for tracing.
type TraceCallConfig struct {
- *vm.LogConfig
- Tracer *string
- Timeout *string
- Reexec *uint64
+ TraceConfig
StateOverrides *ethapi.StateOverride
}
@@ -898,12 +899,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
var traceConfig *TraceConfig
if config != nil {
- traceConfig = &TraceConfig{
- LogConfig: config.LogConfig,
- Tracer: config.Tracer,
- Timeout: config.Timeout,
- Reexec: config.Reexec,
- }
+ traceConfig = &config.TraceConfig
}
signer := types.MakeSigner(api.backend.ChainConfig(), block.Number())
@@ -936,7 +932,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
return nil, err
}
}
- if t, err := New(*config.Tracer, txctx); err != nil {
+ if t, err := New(*config.Tracer, txctx, config.TracerConfig); err != nil {
return nil, err
} else {
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go
index dccd5e629..8d9fb5e08 100644
--- a/eth/tracers/internal/tracetest/calltrace_test.go
+++ b/eth/tracers/internal/tracetest/calltrace_test.go
@@ -184,7 +184,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
}
_, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
)
- tracer, err := tracers.New(tracerName, new(tracers.Context))
+ tracer, err := tracers.New(tracerName, new(tracers.Context), nil)
if err != nil {
t.Fatalf("failed to create call tracer: %v", err)
}
@@ -302,7 +302,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
- tracer, err := tracers.New(tracerName, new(tracers.Context))
+ tracer, err := tracers.New(tracerName, new(tracers.Context), nil)
if err != nil {
b.Fatalf("failed to create call tracer: %v", err)
}
@@ -372,7 +372,7 @@ func TestZeroValueToNotExitCall(t *testing.T) {
}
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
// Create the tracer, the EVM environment and run it
- tracer, err := tracers.New("callTracer", nil)
+ tracer, err := tracers.New("callTracer", nil, nil)
if err != nil {
t.Fatalf("failed to create call tracer: %v", err)
}
diff --git a/eth/tracers/js/tracer.go b/eth/tracers/js/tracer.go
index e305bf189..808852afd 100644
--- a/eth/tracers/js/tracer.go
+++ b/eth/tracers/js/tracer.go
@@ -425,7 +425,7 @@ type jsTracer struct {
// New instantiates a new tracer instance. code specifies a Javascript snippet,
// which must evaluate to an expression returning an object with 'step', 'fault'
// and 'result' functions.
-func newJsTracer(code string, ctx *tracers2.Context) (tracers2.Tracer, error) {
+func newJsTracer(code string, ctx *tracers2.Context, cfg json.RawMessage) (tracers2.Tracer, error) {
if c, ok := assetTracers[code]; ok {
code = c
}
@@ -831,6 +831,10 @@ func (jst *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
}
}
+func (t *jsTracer) CaptureTxStart(gasLimit uint64) {}
+
+func (t *jsTracer) CaptureTxEnd(restGas uint64) {}
+
// GetResult calls the Javascript 'result' function and returns its value, or any accumulated error
func (jst *jsTracer) GetResult() (json.RawMessage, error) {
// Transform the context into a JavaScript object and inject into the state
diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go
index 571512d01..aeded0d34 100644
--- a/eth/tracers/js/tracer_test.go
+++ b/eth/tracers/js/tracer_test.go
@@ -80,7 +80,7 @@ func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCon
func TestTracer(t *testing.T) {
execTracer := func(code string) ([]byte, string) {
t.Helper()
- tracer, err := newJsTracer(code, nil)
+ tracer, err := newJsTracer(code, nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -130,7 +130,7 @@ func TestTracer(t *testing.T) {
func TestHalt(t *testing.T) {
t.Skip("duktape doesn't support abortion")
timeout := errors.New("stahp")
- tracer, err := newJsTracer("{step: function() { while(1); }, result: function() { return null; }, fault: function(){}}", nil)
+ tracer, err := newJsTracer("{step: function() { while(1); }, result: function() { return null; }, fault: function(){}}", nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -144,7 +144,7 @@ func TestHalt(t *testing.T) {
}
func TestHaltBetweenSteps(t *testing.T) {
- tracer, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }}", nil)
+ tracer, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }}", nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -168,7 +168,7 @@ func TestHaltBetweenSteps(t *testing.T) {
func TestNoStepExec(t *testing.T) {
execTracer := func(code string) []byte {
t.Helper()
- tracer, err := newJsTracer(code, nil)
+ tracer, err := newJsTracer(code, nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -203,7 +203,7 @@ func TestIsPrecompile(t *testing.T) {
chaincfg.BerlinBlock = big.NewInt(300)
chaincfg.ArchimedesBlock = big.NewInt(400)
txCtx := vm.TxContext{GasPrice: big.NewInt(100000)}
- tracer, err := newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
+ tracer, err := newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -217,7 +217,7 @@ func TestIsPrecompile(t *testing.T) {
t.Errorf("Tracer should not consider blake2f as precompile in byzantium")
}
- tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
+ tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil)
blockCtx = vm.BlockContext{BlockNumber: big.NewInt(250)}
res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg)
if err != nil {
@@ -228,7 +228,7 @@ func TestIsPrecompile(t *testing.T) {
}
// test sha disabled in archimedes
- tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000002'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
+ tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000002'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil)
blockCtx = vm.BlockContext{BlockNumber: big.NewInt(450)}
res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg)
if err != nil {
@@ -238,7 +238,7 @@ func TestIsPrecompile(t *testing.T) {
t.Errorf("Tracer should not consider blake2f as precompile in archimedes")
}
- tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000003'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
+ tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000003'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil)
blockCtx = vm.BlockContext{BlockNumber: big.NewInt(450)}
res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg)
if err != nil {
@@ -249,7 +249,7 @@ func TestIsPrecompile(t *testing.T) {
}
// test blake2f disabled in archimedes
- tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
+ tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil)
res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg)
if err != nil {
t.Error(err)
@@ -259,7 +259,7 @@ func TestIsPrecompile(t *testing.T) {
}
// test ecrecover enabled in archimedes
- tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000001'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
+ tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000001'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil)
res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg)
if err != nil {
t.Error(err)
@@ -271,14 +271,14 @@ func TestIsPrecompile(t *testing.T) {
func TestEnterExit(t *testing.T) {
// test that either both or none of enter() and exit() are defined
- if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(tracers.Context)); err == nil {
+ if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(tracers.Context), nil); err == nil {
t.Fatal("tracer creation should've failed without exit() definition")
}
- if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(tracers.Context)); err != nil {
+ if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(tracers.Context), nil); err != nil {
t.Fatal(err)
}
// test that the enter and exit method are correctly invoked and the values passed
- tracer, err := newJsTracer("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(tracers.Context))
+ tracer, err := newJsTracer("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(tracers.Context), nil)
if err != nil {
t.Fatal(err)
}
diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go
index a30fdf753..d184f1980 100644
--- a/eth/tracers/native/4byte.go
+++ b/eth/tracers/native/4byte.go
@@ -21,7 +21,6 @@ import (
"math/big"
"strconv"
"sync/atomic"
- "time"
"github.com/morph-l2/go-ethereum/common"
"github.com/morph-l2/go-ethereum/core/vm"
@@ -47,20 +46,20 @@ func init() {
// 0xc281d19e-0: 1
// }
type fourByteTracer struct {
- env *vm.EVM
+ noopTracer
ids map[string]int // ids aggregates the 4byte ids found
- interrupt uint32 // Atomic flag to signal execution interruption
+ interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
activePrecompiles []common.Address // Updated on CaptureStart based on given rules
}
// newFourByteTracer returns a native go tracer which collects
// 4 byte-identifiers of a tx, and implements vm.EVMLogger.
-func newFourByteTracer() tracers.Tracer {
+func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
t := &fourByteTracer{
ids: make(map[string]int),
}
- return t
+ return t, nil
}
// isPrecompiled returns whether the addr is a precompile. Logic borrowed from newJsTracer in eth/tracers/js/tracer.go
@@ -81,8 +80,6 @@ func (t *fourByteTracer) store(id []byte, size int) {
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
func (t *fourByteTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
- t.env = env
-
// Update list of precompiles based on current block
rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Time.Uint64())
t.activePrecompiles = vm.ActivePrecompiles(rules)
@@ -93,19 +90,10 @@ func (t *fourByteTracer) CaptureStart(env *vm.EVM, from common.Address, to commo
}
}
-// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
-func (t *fourByteTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
-}
-
-// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
-func (t *fourByteTracer) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
-}
-
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *fourByteTracer) CaptureEnter(op vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
// Skip if tracing was interrupted
- if atomic.LoadUint32(&t.interrupt) > 0 {
- t.env.Cancel()
+ if t.interrupt.Load() {
return
}
if len(input) < 4 {
@@ -123,19 +111,6 @@ func (t *fourByteTracer) CaptureEnter(op vm.OpCode, from common.Address, to comm
t.store(input[0:4], len(input)-4)
}
-// CaptureExit is called when EVM exits a scope, even if the scope didn't
-// execute any code.
-func (t *fourByteTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
-}
-
-// CaptureFault implements the EVMLogger interface to trace an execution fault.
-func (t *fourByteTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
-}
-
-// CaptureEnd is called after the call finishes to finalize the tracing.
-func (t *fourByteTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
-}
-
// GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`).
func (t *fourByteTracer) GetResult() (json.RawMessage, error) {
@@ -149,5 +124,5 @@ func (t *fourByteTracer) GetResult() (json.RawMessage, error) {
// Stop terminates execution of the tracer at the first opportune moment.
func (t *fourByteTracer) Stop(err error) {
t.reason = err
- atomic.StoreUint32(&t.interrupt, 1)
+ t.interrupt.Store(true)
}
diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go
index 9fe34f524..ff480bdcd 100644
--- a/eth/tracers/native/call.go
+++ b/eth/tracers/native/call.go
@@ -25,100 +25,203 @@ import (
"sync/atomic"
"time"
+ "github.com/morph-l2/go-ethereum/accounts/abi"
"github.com/morph-l2/go-ethereum/common"
+ "github.com/morph-l2/go-ethereum/common/hexutil"
"github.com/morph-l2/go-ethereum/core/vm"
"github.com/morph-l2/go-ethereum/eth/tracers"
+ "github.com/morph-l2/go-ethereum/log"
)
+//go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe_json.go
+
func init() {
register("callTracer", newCallTracer)
}
+type callLog struct {
+ Address common.Address `json:"address"`
+ Topics []common.Hash `json:"topics"`
+ Data hexutil.Bytes `json:"data"`
+ // Position of the log relative to subcalls within the same trace
+ // See https://github.com/ethereum/go-ethereum/pull/28389 for details
+ Position hexutil.Uint `json:"position"`
+}
+
type callFrame struct {
- Type string `json:"type"`
- From string `json:"from"`
- To string `json:"to,omitempty"`
- Value string `json:"value,omitempty"`
- Gas string `json:"gas"`
- GasUsed string `json:"gasUsed"`
- Input string `json:"input"`
- Output string `json:"output,omitempty"`
- Error string `json:"error,omitempty"`
- Calls []callFrame `json:"calls,omitempty"`
+ Type vm.OpCode `json:"-"`
+ From common.Address `json:"from"`
+ Gas uint64 `json:"gas"`
+ GasUsed uint64 `json:"gasUsed"`
+ To *common.Address `json:"to,omitempty" rlp:"optional"`
+ Input []byte `json:"input" rlp:"optional"`
+ Output []byte `json:"output,omitempty" rlp:"optional"`
+ Error string `json:"error,omitempty" rlp:"optional"`
+ RevertReason string `json:"revertReason,omitempty"`
+ Calls []callFrame `json:"calls,omitempty" rlp:"optional"`
+ Logs []callLog `json:"logs,omitempty" rlp:"optional"`
+ // Placed at end on purpose. The RLP will be decoded to 0 instead of
+ // nil if there are non-empty elements after in the struct.
+ Value *big.Int `json:"value,omitempty" rlp:"optional"`
+}
+
+func (f callFrame) TypeString() string {
+ return f.Type.String()
+}
+
+func (f callFrame) failed() bool {
+ return len(f.Error) > 0
+}
+
+func (f *callFrame) processOutput(output []byte, err error) {
+ output = common.CopyBytes(output)
+ if err == nil {
+ f.Output = output
+ return
+ }
+ f.Error = err.Error()
+ if f.Type == vm.CREATE || f.Type == vm.CREATE2 {
+ f.To = nil
+ }
+ if !errors.Is(err, vm.ErrExecutionReverted) || len(output) == 0 {
+ return
+ }
+ f.Output = output
+ if len(output) < 4 {
+ return
+ }
+ if unpacked, err := abi.UnpackRevert(output); err == nil {
+ f.RevertReason = unpacked
+ }
+}
+
+type callFrameMarshaling struct {
+ TypeString string `json:"type"`
+ Gas hexutil.Uint64
+ GasUsed hexutil.Uint64
+ Value *hexutil.Big
+ Input hexutil.Bytes
+ Output hexutil.Bytes
}
type callTracer struct {
- env *vm.EVM
+ noopTracer
callstack []callFrame
- interrupt uint32 // Atomic flag to signal execution interruption
- reason error // Textual reason for the interruption
+ config callTracerConfig
+ gasLimit uint64
+ interrupt atomic.Bool // Atomic flag to signal execution interruption
+ reason error // Textual reason for the interruption
+}
+
+type callTracerConfig struct {
+ OnlyTopCall bool `json:"onlyTopCall"` // If true, call tracer won't collect any subcalls
+ WithLog bool `json:"withLog"` // If true, call tracer will collect event logs
}
// newCallTracer returns a native go tracer which tracks
// call frames of a tx, and implements vm.EVMLogger.
-func newCallTracer() tracers.Tracer {
+func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
+ var config callTracerConfig
+ if cfg != nil {
+ if err := json.Unmarshal(cfg, &config); err != nil {
+ return nil, err
+ }
+ }
// First callframe contains tx context info
// and is populated on start and end.
- t := &callTracer{callstack: make([]callFrame, 1)}
- return t
+ return &callTracer{callstack: make([]callFrame, 1), config: config}, nil
}
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
- t.env = env
+ toCopy := to
t.callstack[0] = callFrame{
- Type: "CALL",
- From: addrToHex(from),
- To: addrToHex(to),
- Input: bytesToHex(input),
- Gas: uintToHex(gas),
- Value: bigToHex(value),
+ Type: vm.CALL,
+ From: from,
+ To: &toCopy,
+ Input: common.CopyBytes(input),
+ Gas: t.gasLimit,
+ Value: value,
}
if create {
- t.callstack[0].Type = "CREATE"
+ t.callstack[0].Type = vm.CREATE
}
}
// CaptureEnd is called after the call finishes to finalize the tracing.
func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
- t.callstack[0].GasUsed = uintToHex(gasUsed)
- if err != nil {
- t.callstack[0].Error = err.Error()
- if err.Error() == "execution reverted" && len(output) > 0 {
- t.callstack[0].Output = bytesToHex(output)
- }
- } else {
- t.callstack[0].Output = bytesToHex(output)
- }
+ t.callstack[0].processOutput(output, err)
}
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
-}
+ // skip if the previous op caused an error
+ if err != nil {
+ return
+ }
+ // Only logs need to be captured via opcode processing
+ if !t.config.WithLog {
+ return
+ }
+ // Avoid processing nested calls when only caring about top call
+ if t.config.OnlyTopCall && depth > 1 {
+ return
+ }
+ // Skip if tracing was interrupted
+ if t.interrupt.Load() {
+ return
+ }
+ switch op {
+ case vm.LOG0, vm.LOG1, vm.LOG2, vm.LOG3, vm.LOG4:
+ size := int(op - vm.LOG0)
-// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
-func (t *callTracer) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
-}
+ stack := scope.Stack
+ stackData := stack.Data()
-// CaptureFault implements the EVMLogger interface to trace an execution fault.
-func (t *callTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
+ // Don't modify the stack
+ mStart := stackData[len(stackData)-1]
+ mSize := stackData[len(stackData)-2]
+ topics := make([]common.Hash, size)
+ for i := 0; i < size; i++ {
+ topic := stackData[len(stackData)-2-(i+1)]
+ topics[i] = common.Hash(topic.Bytes32())
+ }
+
+ data, err := tracers.GetMemoryCopyPadded(scope.Memory, int64(mStart.Uint64()), int64(mSize.Uint64()))
+ if err != nil {
+ // mSize was unrealistically large
+ log.Warn("failed to copy CREATE2 input", "err", err, "tracer", "callTracer", "offset", mStart, "size", mSize)
+ return
+ }
+
+ log := callLog{
+ Address: scope.Contract.Address(),
+ Topics: topics,
+ Data: hexutil.Bytes(data),
+ Position: hexutil.Uint(len(t.callstack[len(t.callstack)-1].Calls)),
+ }
+ t.callstack[len(t.callstack)-1].Logs = append(t.callstack[len(t.callstack)-1].Logs, log)
+ }
}
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
+ if t.config.OnlyTopCall {
+ return
+ }
// Skip if tracing was interrupted
- if atomic.LoadUint32(&t.interrupt) > 0 {
- t.env.Cancel()
+ if t.interrupt.Load() {
return
}
+ toCopy := to
call := callFrame{
- Type: typ.String(),
- From: addrToHex(from),
- To: addrToHex(to),
- Input: bytesToHex(input),
- Gas: uintToHex(gas),
- Value: bigToHex(value),
+ Type: typ,
+ From: from,
+ To: &toCopy,
+ Input: common.CopyBytes(input),
+ Gas: gas,
+ Value: value,
}
t.callstack = append(t.callstack, call)
}
@@ -126,6 +229,9 @@ func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.
// CaptureExit is called when EVM exits a scope, even if the scope didn't
// execute any code.
func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
+ if t.config.OnlyTopCall {
+ return
+ }
size := len(t.callstack)
if size <= 1 {
return
@@ -135,24 +241,30 @@ func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
t.callstack = t.callstack[:size-1]
size -= 1
- call.GasUsed = uintToHex(gasUsed)
- if err == nil {
- call.Output = bytesToHex(output)
- } else {
- call.Error = err.Error()
- if call.Type == "CREATE" || call.Type == "CREATE2" {
- call.To = ""
- }
- }
+ call.GasUsed = gasUsed
+ call.processOutput(output, err)
t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call)
}
+func (t *callTracer) CaptureTxStart(gasLimit uint64) {
+ t.gasLimit = gasLimit
+}
+
+func (t *callTracer) CaptureTxEnd(restGas uint64) {
+ t.callstack[0].GasUsed = t.gasLimit - restGas
+ if t.config.WithLog {
+ // Logs are not emitted when the call fails
+ clearFailedLogs(&t.callstack[0], false)
+ }
+}
+
// GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`).
func (t *callTracer) GetResult() (json.RawMessage, error) {
if len(t.callstack) != 1 {
return nil, errors.New("incorrect number of top-level calls")
}
+
res, err := json.Marshal(t.callstack[0])
if err != nil {
return nil, err
@@ -163,7 +275,20 @@ func (t *callTracer) GetResult() (json.RawMessage, error) {
// Stop terminates execution of the tracer at the first opportune moment.
func (t *callTracer) Stop(err error) {
t.reason = err
- atomic.StoreUint32(&t.interrupt, 1)
+ t.interrupt.Store(true)
+}
+
+// clearFailedLogs clears the logs of a callframe and all its children
+// in case of execution failure.
+func clearFailedLogs(cf *callFrame, parentFailed bool) {
+ failed := cf.failed() || parentFailed
+ // Clear own logs
+ if failed {
+ cf.Logs = nil
+ }
+ for i := range cf.Calls {
+ clearFailedLogs(&cf.Calls[i], failed)
+ }
}
func bytesToHex(s []byte) string {
diff --git a/eth/tracers/native/gen_account_json.go b/eth/tracers/native/gen_account_json.go
new file mode 100644
index 000000000..602800f55
--- /dev/null
+++ b/eth/tracers/native/gen_account_json.go
@@ -0,0 +1,56 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package native
+
+import (
+ "encoding/json"
+ "math/big"
+
+ "github.com/morph-l2/go-ethereum/common"
+ "github.com/morph-l2/go-ethereum/common/hexutil"
+)
+
+var _ = (*accountMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (a account) MarshalJSON() ([]byte, error) {
+ type account struct {
+ Balance *hexutil.Big `json:"balance,omitempty"`
+ Code hexutil.Bytes `json:"code,omitempty"`
+ Nonce uint64 `json:"nonce,omitempty"`
+ Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
+ }
+ var enc account
+ enc.Balance = (*hexutil.Big)(a.Balance)
+ enc.Code = a.Code
+ enc.Nonce = a.Nonce
+ enc.Storage = a.Storage
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (a *account) UnmarshalJSON(input []byte) error {
+ type account struct {
+ Balance *hexutil.Big `json:"balance,omitempty"`
+ Code *hexutil.Bytes `json:"code,omitempty"`
+ Nonce *uint64 `json:"nonce,omitempty"`
+ Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
+ }
+ var dec account
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.Balance != nil {
+ a.Balance = (*big.Int)(dec.Balance)
+ }
+ if dec.Code != nil {
+ a.Code = *dec.Code
+ }
+ if dec.Nonce != nil {
+ a.Nonce = *dec.Nonce
+ }
+ if dec.Storage != nil {
+ a.Storage = dec.Storage
+ }
+ return nil
+}
diff --git a/eth/tracers/native/gen_callframe_json.go b/eth/tracers/native/gen_callframe_json.go
new file mode 100644
index 000000000..0267bf6bb
--- /dev/null
+++ b/eth/tracers/native/gen_callframe_json.go
@@ -0,0 +1,107 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package native
+
+import (
+ "encoding/json"
+ "math/big"
+
+ "github.com/morph-l2/go-ethereum/common"
+ "github.com/morph-l2/go-ethereum/common/hexutil"
+ "github.com/morph-l2/go-ethereum/core/vm"
+)
+
+var _ = (*callFrameMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (c callFrame) MarshalJSON() ([]byte, error) {
+ type callFrame0 struct {
+ Type vm.OpCode `json:"-"`
+ From common.Address `json:"from"`
+ Gas hexutil.Uint64 `json:"gas"`
+ GasUsed hexutil.Uint64 `json:"gasUsed"`
+ To *common.Address `json:"to,omitempty" rlp:"optional"`
+ Input hexutil.Bytes `json:"input" rlp:"optional"`
+ Output hexutil.Bytes `json:"output,omitempty" rlp:"optional"`
+ Error string `json:"error,omitempty" rlp:"optional"`
+ RevertReason string `json:"revertReason,omitempty"`
+ Calls []callFrame `json:"calls,omitempty" rlp:"optional"`
+ Logs []callLog `json:"logs,omitempty" rlp:"optional"`
+ Value *hexutil.Big `json:"value,omitempty" rlp:"optional"`
+ TypeString string `json:"type"`
+ }
+ var enc callFrame0
+ enc.Type = c.Type
+ enc.From = c.From
+ enc.Gas = hexutil.Uint64(c.Gas)
+ enc.GasUsed = hexutil.Uint64(c.GasUsed)
+ enc.To = c.To
+ enc.Input = c.Input
+ enc.Output = c.Output
+ enc.Error = c.Error
+ enc.RevertReason = c.RevertReason
+ enc.Calls = c.Calls
+ enc.Logs = c.Logs
+ enc.Value = (*hexutil.Big)(c.Value)
+ enc.TypeString = c.TypeString()
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (c *callFrame) UnmarshalJSON(input []byte) error {
+ type callFrame0 struct {
+ Type *vm.OpCode `json:"-"`
+ From *common.Address `json:"from"`
+ Gas *hexutil.Uint64 `json:"gas"`
+ GasUsed *hexutil.Uint64 `json:"gasUsed"`
+ To *common.Address `json:"to,omitempty" rlp:"optional"`
+ Input *hexutil.Bytes `json:"input" rlp:"optional"`
+ Output *hexutil.Bytes `json:"output,omitempty" rlp:"optional"`
+ Error *string `json:"error,omitempty" rlp:"optional"`
+ RevertReason *string `json:"revertReason,omitempty"`
+ Calls []callFrame `json:"calls,omitempty" rlp:"optional"`
+ Logs []callLog `json:"logs,omitempty" rlp:"optional"`
+ Value *hexutil.Big `json:"value,omitempty" rlp:"optional"`
+ }
+ var dec callFrame0
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.Type != nil {
+ c.Type = *dec.Type
+ }
+ if dec.From != nil {
+ c.From = *dec.From
+ }
+ if dec.Gas != nil {
+ c.Gas = uint64(*dec.Gas)
+ }
+ if dec.GasUsed != nil {
+ c.GasUsed = uint64(*dec.GasUsed)
+ }
+ if dec.To != nil {
+ c.To = dec.To
+ }
+ if dec.Input != nil {
+ c.Input = *dec.Input
+ }
+ if dec.Output != nil {
+ c.Output = *dec.Output
+ }
+ if dec.Error != nil {
+ c.Error = *dec.Error
+ }
+ if dec.RevertReason != nil {
+ c.RevertReason = *dec.RevertReason
+ }
+ if dec.Calls != nil {
+ c.Calls = dec.Calls
+ }
+ if dec.Logs != nil {
+ c.Logs = dec.Logs
+ }
+ if dec.Value != nil {
+ c.Value = (*big.Int)(dec.Value)
+ }
+ return nil
+}
diff --git a/eth/tracers/native/noop.go b/eth/tracers/native/noop.go
index 48f3d2a45..33ab891cb 100644
--- a/eth/tracers/native/noop.go
+++ b/eth/tracers/native/noop.go
@@ -35,8 +35,8 @@ func init() {
type noopTracer struct{}
// newNoopTracer returns a new noop tracer.
-func newNoopTracer() tracers.Tracer {
- return &noopTracer{}
+func newNoopTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
+ return &noopTracer{}, nil
}
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
@@ -68,6 +68,10 @@ func (t *noopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.
func (t *noopTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
}
+func (t *noopTracer) CaptureTxStart(gasLimit uint64) {}
+
+func (t *noopTracer) CaptureTxEnd(restGas uint64) {}
+
// GetResult returns an empty json object.
func (t *noopTracer) GetResult() (json.RawMessage, error) {
return json.RawMessage(`{}`), nil
diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go
index b3981acb2..4218f0852 100644
--- a/eth/tracers/native/prestate.go
+++ b/eth/tracers/native/prestate.go
@@ -8,12 +8,15 @@ import (
"time"
"github.com/morph-l2/go-ethereum/common"
+ "github.com/morph-l2/go-ethereum/common/hexutil"
"github.com/morph-l2/go-ethereum/core/vm"
"github.com/morph-l2/go-ethereum/crypto"
"github.com/morph-l2/go-ethereum/eth/tracers"
"github.com/morph-l2/go-ethereum/log"
)
+//go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go
+
func init() {
register("prestateTracer", newPrestateTracer)
}
@@ -21,19 +24,10 @@ func init() {
type state = map[common.Address]*account
type account struct {
- Balance *big.Int
- Code []byte
- Nonce uint64
- Storage map[common.Hash]common.Hash
-}
-
-func (a *account) marshal() accountMarshaling {
- return accountMarshaling{
- Balance: bigToHex(a.Balance),
- Code: bytesToHex(a.Code),
- Nonce: a.Nonce,
- Storage: a.Storage,
- }
+ Balance *big.Int `json:"balance,omitempty"`
+ Code []byte `json:"code,omitempty"`
+ Nonce uint64 `json:"nonce,omitempty"`
+ Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
}
func (a *account) exists() bool {
@@ -41,10 +35,8 @@ func (a *account) exists() bool {
}
type accountMarshaling struct {
- Balance string `json:"balance,omitempty"`
- Code string `json:"code,omitempty"`
- Nonce uint64 `json:"nonce,omitempty"`
- Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
+ Balance *hexutil.Big
+ Code hexutil.Bytes
}
type prestateTracer struct {
@@ -54,20 +46,32 @@ type prestateTracer struct {
post state
create bool
to common.Address
- gasLimit uint64 // Amount of gas bought for the whole tx
+ gasLimit uint64 // Amount of gas bought for the whole tx
+ config prestateTracerConfig
interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
created map[common.Address]bool
deleted map[common.Address]bool
}
-func newPrestateTracer() tracers.Tracer {
+type prestateTracerConfig struct {
+ DiffMode bool `json:"diffMode"` // If true, this tracer will return state modifications
+}
+
+func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
+ var config prestateTracerConfig
+ if cfg != nil {
+ if err := json.Unmarshal(cfg, &config); err != nil {
+ return nil, err
+ }
+ }
return &prestateTracer{
pre: state{},
post: state{},
+ config: config,
created: make(map[common.Address]bool),
deleted: make(map[common.Address]bool),
- }
+ }, nil
}
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
@@ -92,10 +96,18 @@ func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to commo
fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas))
t.pre[from].Balance = fromBal
t.pre[from].Nonce--
+
+ if create && t.config.DiffMode {
+ t.created[to] = true
+ }
}
// CaptureEnd is called after the call finishes to finalize the tracing.
-func (t *prestateTracer) CaptureEnd(output []byte, gasUsed uint64, d time.Duration, err error) {
+func (t *prestateTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
+ if t.config.DiffMode {
+ return
+ }
+
if t.create {
// Keep existing account prior to contract creation at that address
if s := t.pre[t.to]; s != nil && !s.exists() {
@@ -157,6 +169,10 @@ func (t *prestateTracer) CaptureTxStart(gasLimit uint64) {
}
func (t *prestateTracer) CaptureTxEnd(restGas uint64) {
+ if !t.config.DiffMode {
+ return
+ }
+
for addr, state := range t.pre {
// The deleted account's state is pruned from `post` but kept in `pre`
if _, ok := t.deleted[addr]; ok {
@@ -218,15 +234,20 @@ func (t *prestateTracer) CaptureTxEnd(restGas uint64) {
// GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`).
func (t *prestateTracer) GetResult() (json.RawMessage, error) {
- pre := make(map[string]accountMarshaling)
- for addr, state := range t.pre {
- pre[addrToHex(addr)] = state.marshal()
+ var res []byte
+ var err error
+ if t.config.DiffMode {
+ res, err = json.Marshal(struct {
+ Post state `json:"post"`
+ Pre state `json:"pre"`
+ }{t.post, t.pre})
+ } else {
+ res, err = json.Marshal(t.pre)
}
- res, err := json.Marshal(pre)
if err != nil {
return nil, err
}
- return res, t.reason
+ return json.RawMessage(res), t.reason
}
// Stop terminates execution of the tracer at the first opportune moment.
diff --git a/eth/tracers/native/tracer.go b/eth/tracers/native/tracer.go
index 81289bb0e..857abd2e7 100644
--- a/eth/tracers/native/tracer.go
+++ b/eth/tracers/native/tracer.go
@@ -14,29 +14,24 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-/*
-Package native is a collection of tracers written in go.
-
-In order to add a native tracer and have it compiled into the binary, a new
-file needs to be added to this folder, containing an implementation of the
-`eth.tracers.Tracer` interface.
-
-Aside from implementing the tracer, it also needs to register itself, using the
-`register` method -- and this needs to be done in the package initialization.
-
-Example:
-
-```golang
-
- func init() {
- register("noopTracerNative", newNoopTracer)
- }
-
-```
-*/
+// Package native is a collection of tracers written in go.
+//
+// In order to add a native tracer and have it compiled into the binary, a new
+// file needs to be added to this folder, containing an implementation of the
+// `eth.tracers.Tracer` interface.
+//
+// Aside from implementing the tracer, it also needs to register itself, using the
+// `register` method -- and this needs to be done in the package initialization.
+//
+// Example:
+//
+// func init() {
+// register("noopTracerNative", newNoopTracer)
+// }
package native
import (
+ "encoding/json"
"errors"
"github.com/morph-l2/go-ethereum/eth/tracers"
@@ -47,6 +42,9 @@ func init() {
tracers.RegisterLookup(false, lookup)
}
+// ctorFn is the constructor signature of a native tracer.
+type ctorFn = func(*tracers.Context, json.RawMessage) (tracers.Tracer, error)
+
/*
ctors is a map of package-local tracer constructors.
@@ -59,23 +57,23 @@ The go spec (https://golang.org/ref/spec#Package_initialization) says
Hence, we cannot make the map in init, but must make it upon first use.
*/
-var ctors map[string]func() tracers.Tracer
+var ctors map[string]ctorFn
// register is used by native tracers to register their presence.
-func register(name string, ctor func() tracers.Tracer) {
+func register(name string, ctor ctorFn) {
if ctors == nil {
- ctors = make(map[string]func() tracers.Tracer)
+ ctors = make(map[string]ctorFn)
}
ctors[name] = ctor
}
// lookup returns a tracer, if one can be matched to the given name.
-func lookup(name string, ctx *tracers.Context) (tracers.Tracer, error) {
+func lookup(name string, ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
if ctors == nil {
- ctors = make(map[string]func() tracers.Tracer)
+ ctors = make(map[string]ctorFn)
}
if ctor, ok := ctors[name]; ok {
- return ctor(), nil
+ return ctor(ctx, cfg)
}
return nil, errors.New("no tracer found")
}
diff --git a/eth/tracers/tracers.go b/eth/tracers/tracers.go
index de6d25b4b..d2b9a6479 100644
--- a/eth/tracers/tracers.go
+++ b/eth/tracers/tracers.go
@@ -43,7 +43,7 @@ type Tracer interface {
Stop(err error)
}
-type lookupFunc func(string, *Context) (Tracer, error)
+type lookupFunc func(string, *Context, json.RawMessage) (Tracer, error)
var (
lookups []lookupFunc
@@ -63,9 +63,9 @@ func RegisterLookup(wildcard bool, lookup lookupFunc) {
// New returns a new instance of a tracer, by iterating through the
// registered lookups.
-func New(code string, ctx *Context) (Tracer, error) {
+func New(code string, ctx *Context, cfg json.RawMessage) (Tracer, error) {
for _, lookup := range lookups {
- if tracer, err := lookup(code, ctx); err == nil {
+ if tracer, err := lookup(code, ctx, cfg); err == nil {
return tracer, nil
}
}
diff --git a/rollup/tracing/mux_tracer.go b/rollup/tracing/mux_tracer.go
index 585af1c7e..612f98582 100644
--- a/rollup/tracing/mux_tracer.go
+++ b/rollup/tracing/mux_tracer.go
@@ -67,3 +67,15 @@ func (t *MuxTracer) CaptureEnd(output []byte, gasUsed uint64, d time.Duration, e
tracer.CaptureEnd(output, gasUsed, d, err)
}
}
+
+func (t *MuxTracer) CaptureTxStart(gasLimit uint64) {
+ for _, tracer := range t.tracers {
+ tracer.CaptureTxStart(gasLimit)
+ }
+}
+
+func (t *MuxTracer) CaptureTxEnd(restGas uint64) {
+ for _, tracer := range t.tracers {
+ tracer.CaptureTxEnd(restGas)
+ }
+}
diff --git a/rollup/tracing/tracing.go b/rollup/tracing/tracing.go
index 52fccfb69..c349366d7 100644
--- a/rollup/tracing/tracing.go
+++ b/rollup/tracing/tracing.go
@@ -310,7 +310,7 @@ func (env *TraceEnv) getTxResult(state *state.StateDB, index int, block *types.B
TxIndex: index,
TxHash: tx.Hash(),
}
- callTracer, err := tracers.New("callTracer", &tracerContext)
+ callTracer, err := tracers.New("callTracer", &tracerContext, nil)
if err != nil {
return fmt.Errorf("failed to create callTracer: %w", err)
}
From 185c209cc896a0f9e6c1237a9554705faa084c25 Mon Sep 17 00:00:00 2001
From: ryanmorphl2 <163962984+ryanmorphl2@users.noreply.github.com>
Date: Mon, 13 Jan 2025 15:53:41 +0800
Subject: [PATCH 2/5] feat(tracer): add tracer logger
---
common/hexutil/json.go | 45 ++
common/hexutil/json_test.go | 60 +++
eth/tracers/api.go | 82 ++--
.../internal/tracers/4byte_tracer_legacy.js | 2 +-
.../js/internal/tracers/call_tracer_legacy.js | 2 +-
eth/tracers/logger/access_list_tracer.go | 183 +++++++
eth/tracers/logger/gen_structlog.go | 118 +++++
eth/tracers/logger/logger.go | 459 ++++++++++++++++++
eth/tracers/logger/logger_json.go | 102 ++++
eth/tracers/logger/logger_test.go | 107 ++++
10 files changed, 1106 insertions(+), 54 deletions(-)
create mode 100644 eth/tracers/logger/access_list_tracer.go
create mode 100644 eth/tracers/logger/gen_structlog.go
create mode 100644 eth/tracers/logger/logger.go
create mode 100644 eth/tracers/logger/logger_json.go
create mode 100644 eth/tracers/logger/logger_test.go
diff --git a/common/hexutil/json.go b/common/hexutil/json.go
index 50db20811..e0ac98f52 100644
--- a/common/hexutil/json.go
+++ b/common/hexutil/json.go
@@ -23,6 +23,8 @@ import (
"math/big"
"reflect"
"strconv"
+
+ "github.com/holiman/uint256"
)
var (
@@ -30,6 +32,7 @@ var (
bigT = reflect.TypeOf((*Big)(nil))
uintT = reflect.TypeOf(Uint(0))
uint64T = reflect.TypeOf(Uint64(0))
+ u256T = reflect.TypeOf((*uint256.Int)(nil))
)
// Bytes marshals/unmarshals as a JSON string with 0x prefix.
@@ -225,6 +228,48 @@ func (b *Big) UnmarshalGraphQL(input interface{}) error {
return err
}
+// U256 marshals/unmarshals as a JSON string with 0x prefix.
+// The zero value marshals as "0x0".
+type U256 uint256.Int
+
+// MarshalText implements encoding.TextMarshaler
+func (b U256) MarshalText() ([]byte, error) {
+ u256 := (*uint256.Int)(&b)
+ return []byte(u256.Hex()), nil
+}
+
+// UnmarshalJSON implements json.Unmarshaler.
+func (b *U256) UnmarshalJSON(input []byte) error {
+ // The uint256.Int.UnmarshalJSON method accepts "dec", "0xhex"; we must be
+ // more strict, hence we check string and invoke SetFromHex directly.
+ if !isString(input) {
+ return errNonString(u256T)
+ }
+ // The hex decoder needs to accept empty string ("") as '0', which uint256.Int
+ // would reject.
+ if len(input) == 2 {
+ (*uint256.Int)(b).Clear()
+ return nil
+ }
+ err := (*uint256.Int)(b).SetFromHex(string(input[1 : len(input)-1]))
+ if err != nil {
+ return &json.UnmarshalTypeError{Value: err.Error(), Type: u256T}
+ }
+ return nil
+}
+
+// UnmarshalText implements encoding.TextUnmarshaler
+func (b *U256) UnmarshalText(input []byte) error {
+ // The uint256.Int.UnmarshalText method accepts "dec", "0xhex"; we must be
+ // more strict, hence we check string and invoke SetFromHex directly.
+ return (*uint256.Int)(b).SetFromHex(string(input))
+}
+
+// String returns the hex encoding of b.
+func (b *U256) String() string {
+ return (*uint256.Int)(b).Hex()
+}
+
// Uint64 marshals/unmarshals as a JSON string with 0x prefix.
// The zero value marshals as "0x0".
type Uint64 uint64
diff --git a/common/hexutil/json_test.go b/common/hexutil/json_test.go
index ed7d6fad1..7cca30095 100644
--- a/common/hexutil/json_test.go
+++ b/common/hexutil/json_test.go
@@ -23,6 +23,8 @@ import (
"errors"
"math/big"
"testing"
+
+ "github.com/holiman/uint256"
)
func checkError(t *testing.T, input string, got, want error) bool {
@@ -176,6 +178,64 @@ func TestUnmarshalBig(t *testing.T) {
}
}
+var unmarshalU256Tests = []unmarshalTest{
+ // invalid encoding
+ {input: "", wantErr: errJSONEOF},
+ {input: "null", wantErr: errNonString(u256T)},
+ {input: "10", wantErr: errNonString(u256T)},
+ {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, u256T)},
+ {input: `"0x"`, wantErr: wrapTypeError(ErrEmptyNumber, u256T)},
+ {input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, u256T)},
+ {input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, u256T)},
+ {input: `"0x1zz01"`, wantErr: wrapTypeError(ErrSyntax, u256T)},
+ {
+ input: `"0x10000000000000000000000000000000000000000000000000000000000000000"`,
+ wantErr: wrapTypeError(ErrBig256Range, u256T),
+ },
+
+ // valid encoding
+ {input: `""`, want: big.NewInt(0)},
+ {input: `"0x0"`, want: big.NewInt(0)},
+ {input: `"0x2"`, want: big.NewInt(0x2)},
+ {input: `"0x2F2"`, want: big.NewInt(0x2f2)},
+ {input: `"0X2F2"`, want: big.NewInt(0x2f2)},
+ {input: `"0x1122aaff"`, want: big.NewInt(0x1122aaff)},
+ {input: `"0xbBb"`, want: big.NewInt(0xbbb)},
+ {input: `"0xfffffffff"`, want: big.NewInt(0xfffffffff)},
+ {
+ input: `"0x112233445566778899aabbccddeeff"`,
+ want: referenceBig("112233445566778899aabbccddeeff"),
+ },
+ {
+ input: `"0xffffffffffffffffffffffffffffffffffff"`,
+ want: referenceBig("ffffffffffffffffffffffffffffffffffff"),
+ },
+ {
+ input: `"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"`,
+ want: referenceBig("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
+ },
+}
+
+func TestUnmarshalU256(t *testing.T) {
+ for _, test := range unmarshalU256Tests {
+ var v U256
+ err := json.Unmarshal([]byte(test.input), &v)
+ if !checkError(t, test.input, err, test.wantErr) {
+ continue
+ }
+ if test.want == nil {
+ continue
+ }
+ want := new(uint256.Int)
+ want.SetFromBig(test.want.(*big.Int))
+ have := (*uint256.Int)(&v)
+ if want.Cmp(have) != 0 {
+ t.Errorf("input %s: value mismatch: have %x, want %x", test.input, have, want)
+ continue
+ }
+ }
+}
+
func BenchmarkUnmarshalBig(b *testing.B) {
input := []byte(`"0x123456789abcdef123456789abcdef"`)
for i := 0; i < b.N; i++ {
diff --git a/eth/tracers/api.go b/eth/tracers/api.go
index 360abcbd3..f02bb4dcd 100644
--- a/eth/tracers/api.go
+++ b/eth/tracers/api.go
@@ -38,6 +38,7 @@ import (
"github.com/morph-l2/go-ethereum/core/state"
"github.com/morph-l2/go-ethereum/core/types"
"github.com/morph-l2/go-ethereum/core/vm"
+ "github.com/morph-l2/go-ethereum/eth/tracers/logger"
"github.com/morph-l2/go-ethereum/ethdb"
"github.com/morph-l2/go-ethereum/internal/ethapi"
"github.com/morph-l2/go-ethereum/log"
@@ -917,75 +918,52 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig, l1DataFee *big.Int) (interface{}, error) {
// Assemble the structured logger or the JavaScript tracer
var (
- tracer vm.EVMLogger
+ tracer Tracer
err error
+ timeout = defaultTraceTimeout
txContext = core.NewEVMTxContext(message)
)
- switch {
- case config == nil:
- tracer = vm.NewStructLogger(nil)
- case config.Tracer != nil:
- // Define a meaningful timeout of a single transaction trace
- timeout := defaultTraceTimeout
- if config.Timeout != nil {
- if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
- return nil, err
- }
- }
- if t, err := New(*config.Tracer, txctx, config.TracerConfig); err != nil {
+ if config == nil {
+ config = &TraceConfig{}
+ }
+ // Default tracer is the struct logger
+ tracer = logger.NewStructLogger(config.LogConfig)
+ if config.Tracer != nil {
+ tracer, err = New(*config.Tracer, txctx, config.TracerConfig)
+ if err != nil {
return nil, err
- } else {
- deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
- go func() {
- <-deadlineCtx.Done()
- if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) {
- t.Stop(errors.New("execution timeout"))
- }
- }()
- defer cancel()
- tracer = t
}
- default:
- tracer = vm.NewStructLogger(config.LogConfig)
}
// Run the transaction with tracing enabled.
vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true})
+ // Define a meaningful timeout of a single transaction trace
+ if config.Timeout != nil {
+ if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
+ return nil, err
+ }
+ }
+ deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
+ go func() {
+ <-deadlineCtx.Done()
+ if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) {
+ tracer.Stop(errors.New("execution timeout"))
+ // Stop evm execution. Note cancellation is not necessarily immediate.
+ vmenv.Cancel()
+ }
+ }()
+ defer cancel()
+
// If gasPrice is 0, make sure that the account has sufficient balance to cover `l1DataFee`.
if message.GasPrice().Cmp(big.NewInt(0)) == 0 {
statedb.AddBalance(message.From(), l1DataFee)
}
-
// Call Prepare to clear out the statedb access list
statedb.SetTxContext(txctx.TxHash, txctx.TxIndex)
-
- result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()), l1DataFee)
- if err != nil {
+ if _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()), l1DataFee); err != nil {
return nil, fmt.Errorf("tracing failed: %w", err)
}
-
- // Depending on the tracer type, format and return the output.
- switch tracer := tracer.(type) {
- case *vm.StructLogger:
- // If the result contains a revert reason, return it.
- returnVal := fmt.Sprintf("%x", result.Return())
- if len(result.Revert()) > 0 {
- returnVal = fmt.Sprintf("%x", result.Revert())
- }
- return &types.ExecutionResult{
- Gas: result.UsedGas,
- Failed: result.Failed(),
- ReturnValue: returnVal,
- StructLogs: vm.FormatLogs(tracer.StructLogs()),
- L1DataFee: (*hexutil.Big)(result.L1DataFee),
- }, nil
-
- case Tracer:
- return tracer.GetResult()
-
- default:
- panic(fmt.Sprintf("bad tracer type %T", tracer))
- }
+ return tracer.GetResult()
}
// APIs return the collection of RPC services the tracer package offers.
diff --git a/eth/tracers/js/internal/tracers/4byte_tracer_legacy.js b/eth/tracers/js/internal/tracers/4byte_tracer_legacy.js
index 462b4ad4c..e4714b8bf 100644
--- a/eth/tracers/js/internal/tracers/4byte_tracer_legacy.js
+++ b/eth/tracers/js/internal/tracers/4byte_tracer_legacy.js
@@ -46,7 +46,7 @@
return false;
},
- // store save the given indentifier and datasize.
+ // store save the given identifier and datasize.
store: function(id, size){
var key = "" + toHex(id) + "-" + size;
this.ids[key] = this.ids[key] + 1 || 1;
diff --git a/eth/tracers/js/internal/tracers/call_tracer_legacy.js b/eth/tracers/js/internal/tracers/call_tracer_legacy.js
index 3ca737773..054512735 100644
--- a/eth/tracers/js/internal/tracers/call_tracer_legacy.js
+++ b/eth/tracers/js/internal/tracers/call_tracer_legacy.js
@@ -220,7 +220,7 @@
return this.finalize(result);
},
- // finalize recreates a call object using the final desired field oder for json
+ // finalize recreates a call object using the final desired field order for json
// serialization. This is a nicety feature to pass meaningfully ordered results
// to users who don't interpret it, just display it.
finalize: function(call) {
diff --git a/eth/tracers/logger/access_list_tracer.go b/eth/tracers/logger/access_list_tracer.go
new file mode 100644
index 000000000..ffc679fde
--- /dev/null
+++ b/eth/tracers/logger/access_list_tracer.go
@@ -0,0 +1,183 @@
+// Copyright 2021 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 logger
+
+import (
+ "math/big"
+
+ "github.com/morph-l2/go-ethereum/common"
+ "github.com/morph-l2/go-ethereum/core/types"
+ "github.com/morph-l2/go-ethereum/core/vm"
+)
+
+// accessList is an accumulator for the set of accounts and storage slots an EVM
+// contract execution touches.
+type accessList map[common.Address]accessListSlots
+
+// accessListSlots is an accumulator for the set of storage slots within a single
+// contract that an EVM contract execution touches.
+type accessListSlots map[common.Hash]struct{}
+
+// newAccessList creates a new accessList.
+func newAccessList() accessList {
+ return make(map[common.Address]accessListSlots)
+}
+
+// addAddress adds an address to the accesslist.
+func (al accessList) addAddress(address common.Address) {
+ // Set address if not previously present
+ if _, present := al[address]; !present {
+ al[address] = make(map[common.Hash]struct{})
+ }
+}
+
+// addSlot adds a storage slot to the accesslist.
+func (al accessList) addSlot(address common.Address, slot common.Hash) {
+ // Set address if not previously present
+ al.addAddress(address)
+
+ // Set the slot on the surely existent storage set
+ al[address][slot] = struct{}{}
+}
+
+// equal checks if the content of the current access list is the same as the
+// content of the other one.
+func (al accessList) equal(other accessList) bool {
+ // Cross reference the accounts first
+ if len(al) != len(other) {
+ return false
+ }
+ // Given that len(al) == len(other), we only need to check that
+ // all the items from al are in other.
+ for addr := range al {
+ if _, ok := other[addr]; !ok {
+ return false
+ }
+ }
+
+ // Accounts match, cross reference the storage slots too
+ for addr, slots := range al {
+ otherslots := other[addr]
+
+ if len(slots) != len(otherslots) {
+ return false
+ }
+ // Given that len(slots) == len(otherslots), we only need to check that
+ // all the items from slots are in otherslots.
+ for hash := range slots {
+ if _, ok := otherslots[hash]; !ok {
+ return false
+ }
+ }
+ }
+ return true
+}
+
+// accesslist converts the accesslist to a types.AccessList.
+func (al accessList) accessList() types.AccessList {
+ acl := make(types.AccessList, 0, len(al))
+ for addr, slots := range al {
+ tuple := types.AccessTuple{Address: addr, StorageKeys: []common.Hash{}}
+ for slot := range slots {
+ tuple.StorageKeys = append(tuple.StorageKeys, slot)
+ }
+ acl = append(acl, tuple)
+ }
+ return acl
+}
+
+// AccessListTracer is a tracer that accumulates touched accounts and storage
+// slots into an internal set.
+type AccessListTracer struct {
+ excl map[common.Address]struct{} // Set of account to exclude from the list
+ list accessList // Set of accounts and storage slots touched
+}
+
+// NewAccessListTracer creates a new tracer that can generate AccessLists.
+// An optional AccessList can be specified to occupy slots and addresses in
+// the resulting accesslist.
+func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompiles []common.Address) *AccessListTracer {
+ excl := map[common.Address]struct{}{
+ from: {}, to: {},
+ }
+ for _, addr := range precompiles {
+ excl[addr] = struct{}{}
+ }
+ list := newAccessList()
+ for _, al := range acl {
+ if _, ok := excl[al.Address]; !ok {
+ list.addAddress(al.Address)
+ }
+ for _, slot := range al.StorageKeys {
+ list.addSlot(al.Address, slot)
+ }
+ }
+ return &AccessListTracer{
+ excl: excl,
+ list: list,
+ }
+}
+
+func (a *AccessListTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+}
+
+// CaptureState captures all opcodes that touch storage or addresses and adds them to the accesslist.
+func (a *AccessListTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
+ stack := scope.Stack
+ stackData := stack.Data()
+ stackLen := len(stackData)
+ if (op == vm.SLOAD || op == vm.SSTORE) && stackLen >= 1 {
+ slot := common.Hash(stackData[stackLen-1].Bytes32())
+ a.list.addSlot(scope.Contract.Address(), slot)
+ }
+ if (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT) && stackLen >= 1 {
+ addr := common.Address(stackData[stackLen-1].Bytes20())
+ if _, ok := a.excl[addr]; !ok {
+ a.list.addAddress(addr)
+ }
+ }
+ if (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE) && stackLen >= 5 {
+ addr := common.Address(stackData[stackLen-2].Bytes20())
+ if _, ok := a.excl[addr]; !ok {
+ a.list.addAddress(addr)
+ }
+ }
+}
+
+func (*AccessListTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
+}
+
+func (*AccessListTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {}
+
+func (*AccessListTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
+}
+
+func (*AccessListTracer) CaptureExit(output []byte, gasUsed uint64, err error) {}
+
+func (*AccessListTracer) CaptureTxStart(gasLimit uint64) {}
+
+func (*AccessListTracer) CaptureTxEnd(restGas uint64) {}
+
+// AccessList returns the current accesslist maintained by the tracer.
+func (a *AccessListTracer) AccessList() types.AccessList {
+ return a.list.accessList()
+}
+
+// Equal returns if the content of two access list traces are equal.
+func (a *AccessListTracer) Equal(other *AccessListTracer) bool {
+ return a.list.equal(other.list)
+}
diff --git a/eth/tracers/logger/gen_structlog.go b/eth/tracers/logger/gen_structlog.go
new file mode 100644
index 000000000..e67950ca7
--- /dev/null
+++ b/eth/tracers/logger/gen_structlog.go
@@ -0,0 +1,118 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package logger
+
+import (
+ "encoding/json"
+
+ "github.com/holiman/uint256"
+ "github.com/morph-l2/go-ethereum/common"
+ "github.com/morph-l2/go-ethereum/common/hexutil"
+ "github.com/morph-l2/go-ethereum/common/math"
+ "github.com/morph-l2/go-ethereum/core/vm"
+)
+
+var _ = (*structLogMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (s StructLog) MarshalJSON() ([]byte, error) {
+ type StructLog struct {
+ Pc uint64 `json:"pc"`
+ Op vm.OpCode `json:"op"`
+ Gas math.HexOrDecimal64 `json:"gas"`
+ GasCost math.HexOrDecimal64 `json:"gasCost"`
+ Memory hexutil.Bytes `json:"memory,omitempty"`
+ MemorySize int `json:"memSize"`
+ Stack []hexutil.U256 `json:"stack"`
+ ReturnData hexutil.Bytes `json:"returnData,omitempty"`
+ Storage map[common.Hash]common.Hash `json:"-"`
+ Depth int `json:"depth"`
+ RefundCounter uint64 `json:"refund"`
+ Err error `json:"-"`
+ OpName string `json:"opName"`
+ ErrorString string `json:"error,omitempty"`
+ }
+ var enc StructLog
+ enc.Pc = s.Pc
+ enc.Op = s.Op
+ enc.Gas = math.HexOrDecimal64(s.Gas)
+ enc.GasCost = math.HexOrDecimal64(s.GasCost)
+ enc.Memory = s.Memory
+ enc.MemorySize = s.MemorySize
+ if s.Stack != nil {
+ enc.Stack = make([]hexutil.U256, len(s.Stack))
+ for k, v := range s.Stack {
+ enc.Stack[k] = hexutil.U256(v)
+ }
+ }
+ enc.ReturnData = s.ReturnData
+ enc.Storage = s.Storage
+ enc.Depth = s.Depth
+ enc.RefundCounter = s.RefundCounter
+ enc.Err = s.Err
+ enc.OpName = s.OpName()
+ enc.ErrorString = s.ErrorString()
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (s *StructLog) UnmarshalJSON(input []byte) error {
+ type StructLog struct {
+ Pc *uint64 `json:"pc"`
+ Op *vm.OpCode `json:"op"`
+ Gas *math.HexOrDecimal64 `json:"gas"`
+ GasCost *math.HexOrDecimal64 `json:"gasCost"`
+ Memory *hexutil.Bytes `json:"memory,omitempty"`
+ MemorySize *int `json:"memSize"`
+ Stack []hexutil.U256 `json:"stack"`
+ ReturnData *hexutil.Bytes `json:"returnData,omitempty"`
+ Storage map[common.Hash]common.Hash `json:"-"`
+ Depth *int `json:"depth"`
+ RefundCounter *uint64 `json:"refund"`
+ Err error `json:"-"`
+ }
+ var dec StructLog
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.Pc != nil {
+ s.Pc = *dec.Pc
+ }
+ if dec.Op != nil {
+ s.Op = *dec.Op
+ }
+ if dec.Gas != nil {
+ s.Gas = uint64(*dec.Gas)
+ }
+ if dec.GasCost != nil {
+ s.GasCost = uint64(*dec.GasCost)
+ }
+ if dec.Memory != nil {
+ s.Memory = *dec.Memory
+ }
+ if dec.MemorySize != nil {
+ s.MemorySize = *dec.MemorySize
+ }
+ if dec.Stack != nil {
+ s.Stack = make([]uint256.Int, len(dec.Stack))
+ for k, v := range dec.Stack {
+ s.Stack[k] = uint256.Int(v)
+ }
+ }
+ if dec.ReturnData != nil {
+ s.ReturnData = *dec.ReturnData
+ }
+ if dec.Storage != nil {
+ s.Storage = dec.Storage
+ }
+ if dec.Depth != nil {
+ s.Depth = *dec.Depth
+ }
+ if dec.RefundCounter != nil {
+ s.RefundCounter = *dec.RefundCounter
+ }
+ if dec.Err != nil {
+ s.Err = dec.Err
+ }
+ return nil
+}
diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go
new file mode 100644
index 000000000..3d5d4dcf5
--- /dev/null
+++ b/eth/tracers/logger/logger.go
@@ -0,0 +1,459 @@
+// Copyright 2021 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 logger
+
+import (
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "io"
+ "math/big"
+ "strings"
+ "sync/atomic"
+ "time"
+
+ "github.com/holiman/uint256"
+ "github.com/morph-l2/go-ethereum/common"
+ "github.com/morph-l2/go-ethereum/common/hexutil"
+ "github.com/morph-l2/go-ethereum/common/math"
+ "github.com/morph-l2/go-ethereum/core/types"
+ "github.com/morph-l2/go-ethereum/core/vm"
+)
+
+// Storage represents a contract's storage.
+type Storage map[common.Hash]common.Hash
+
+// Copy duplicates the current storage.
+func (s Storage) Copy() Storage {
+ cpy := make(Storage, len(s))
+ for key, value := range s {
+ cpy[key] = value
+ }
+ return cpy
+}
+
+//go:generate go run github.com/fjl/gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go
+
+// StructLog is emitted to the EVM each cycle and lists information about the current internal state
+// prior to the execution of the statement.
+type StructLog struct {
+ Pc uint64 `json:"pc"`
+ Op vm.OpCode `json:"op"`
+ Gas uint64 `json:"gas"`
+ GasCost uint64 `json:"gasCost"`
+ Memory []byte `json:"memory,omitempty"`
+ MemorySize int `json:"memSize"`
+ Stack []uint256.Int `json:"stack"`
+ ReturnData []byte `json:"returnData,omitempty"`
+ Storage map[common.Hash]common.Hash `json:"-"`
+ Depth int `json:"depth"`
+ RefundCounter uint64 `json:"refund"`
+ Err error `json:"-"`
+}
+
+// overrides for gencodec
+type structLogMarshaling struct {
+ Gas math.HexOrDecimal64
+ GasCost math.HexOrDecimal64
+ Memory hexutil.Bytes
+ ReturnData hexutil.Bytes
+ Stack []hexutil.U256
+ OpName string `json:"opName"` // adds call to OpName() in MarshalJSON
+ ErrorString string `json:"error,omitempty"` // adds call to ErrorString() in MarshalJSON
+}
+
+// OpName formats the operand name in a human-readable format.
+func (s *StructLog) OpName() string {
+ return s.Op.String()
+}
+
+// ErrorString formats the log's error as a string.
+func (s *StructLog) ErrorString() string {
+ if s.Err != nil {
+ return s.Err.Error()
+ }
+ return ""
+}
+
+// StructLogger is an EVM state logger and implements EVMLogger.
+//
+// StructLogger can capture state based on the given Log configuration and also keeps
+// a track record of modified storage which is used in reporting snapshots of the
+// contract their storage.
+type StructLogger struct {
+ cfg vm.LogConfig
+ env *vm.EVM
+
+ storage map[common.Address]Storage
+ logs []StructLog
+ output []byte
+ err error
+ gasLimit uint64
+ usedGas uint64
+
+ interrupt atomic.Bool // Atomic flag to signal execution interruption
+ reason error // Textual reason for the interruption
+}
+
+// NewStructLogger returns a new logger
+func NewStructLogger(cfg *vm.LogConfig) *StructLogger {
+ logger := &StructLogger{
+ storage: make(map[common.Address]Storage),
+ }
+ if cfg != nil {
+ logger.cfg = *cfg
+ }
+ return logger
+}
+
+// Reset clears the data held by the logger.
+func (l *StructLogger) Reset() {
+ l.storage = make(map[common.Address]Storage)
+ l.output = make([]byte, 0)
+ l.logs = l.logs[:0]
+ l.err = nil
+}
+
+// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
+func (l *StructLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+ l.env = env
+}
+
+// CaptureState logs a new structured log message and pushes it out to the environment
+//
+// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
+func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
+ // If tracing was interrupted, set the error and stop
+ if l.interrupt.Load() {
+ return
+ }
+ // check if already accumulated the specified number of logs
+ if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) {
+ return
+ }
+
+ memory := scope.Memory
+ stack := scope.Stack
+ contract := scope.Contract
+ // Copy a snapshot of the current memory state to a new buffer
+ var mem []byte
+ if l.cfg.EnableMemory {
+ mem = make([]byte, len(memory.Data()))
+ copy(mem, memory.Data())
+ }
+ // Copy a snapshot of the current stack state to a new buffer
+ var stck []uint256.Int
+ if !l.cfg.DisableStack {
+ stck = make([]uint256.Int, len(stack.Data()))
+ for i, item := range stack.Data() {
+ stck[i] = item
+ }
+ }
+ stackData := stack.Data()
+ stackLen := len(stackData)
+ // Copy a snapshot of the current storage to a new container
+ var storage Storage
+ if !l.cfg.DisableStorage && (op == vm.SLOAD || op == vm.SSTORE) {
+ // initialise new changed values storage container for this contract
+ // if not present.
+ if l.storage[contract.Address()] == nil {
+ l.storage[contract.Address()] = make(Storage)
+ }
+ // capture SLOAD opcodes and record the read entry in the local storage
+ if op == vm.SLOAD && stackLen >= 1 {
+ var (
+ address = common.Hash(stackData[stackLen-1].Bytes32())
+ value = l.env.StateDB.GetState(contract.Address(), address)
+ )
+ l.storage[contract.Address()][address] = value
+ storage = l.storage[contract.Address()].Copy()
+ } else if op == vm.SSTORE && stackLen >= 2 {
+ // capture SSTORE opcodes and record the written entry in the local storage.
+ var (
+ value = common.Hash(stackData[stackLen-2].Bytes32())
+ address = common.Hash(stackData[stackLen-1].Bytes32())
+ )
+ l.storage[contract.Address()][address] = value
+ storage = l.storage[contract.Address()].Copy()
+ }
+ }
+ var rdata []byte
+ if l.cfg.EnableReturnData {
+ rdata = make([]byte, len(rData))
+ copy(rdata, rData)
+ }
+ // create a new snapshot of the EVM.
+ log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err}
+ l.logs = append(l.logs, log)
+}
+
+// CaptureFault implements the EVMLogger interface to trace an execution fault
+// while running an opcode.
+func (l *StructLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
+}
+
+// CaptureEnd is called after the call finishes to finalize the tracing.
+func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
+ l.output = output
+ l.err = err
+ if l.cfg.Debug {
+ fmt.Printf("%#x\n", output)
+ if err != nil {
+ fmt.Printf(" error: %v\n", err)
+ }
+ }
+}
+
+func (l *StructLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
+}
+
+// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
+func (l *StructLogger) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
+}
+
+func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {
+}
+
+func (l *StructLogger) GetResult() (json.RawMessage, error) {
+ // Tracing aborted
+ if l.reason != nil {
+ return nil, l.reason
+ }
+ failed := l.err != nil
+ returnData := common.CopyBytes(l.output)
+ // Return data when successful and revert reason when reverted, otherwise empty.
+ returnVal := fmt.Sprintf("%x", returnData)
+ if failed && l.err != vm.ErrExecutionReverted {
+ returnVal = ""
+ }
+ return json.Marshal(&ExecutionResult{
+ Gas: l.usedGas,
+ Failed: failed,
+ ReturnValue: returnVal,
+ StructLogs: formatLogs(l.StructLogs()),
+ })
+}
+
+// Stop terminates execution of the tracer at the first opportune moment.
+func (l *StructLogger) Stop(err error) {
+ l.reason = err
+ l.interrupt.Store(true)
+}
+
+func (l *StructLogger) CaptureTxStart(gasLimit uint64) {
+ l.gasLimit = gasLimit
+}
+
+func (l *StructLogger) CaptureTxEnd(restGas uint64) {
+ l.usedGas = l.gasLimit - restGas
+}
+
+// StructLogs returns the captured log entries.
+func (l *StructLogger) StructLogs() []StructLog { return l.logs }
+
+// Error returns the VM error captured by the trace.
+func (l *StructLogger) Error() error { return l.err }
+
+// Output returns the VM return value captured by the trace.
+func (l *StructLogger) Output() []byte { return l.output }
+
+// WriteTrace writes a formatted trace to the given writer
+func WriteTrace(writer io.Writer, logs []StructLog) {
+ for _, log := range logs {
+ fmt.Fprintf(writer, "%-16spc=%08d gas=%v cost=%v", log.Op, log.Pc, log.Gas, log.GasCost)
+ if log.Err != nil {
+ fmt.Fprintf(writer, " ERROR: %v", log.Err)
+ }
+ fmt.Fprintln(writer)
+
+ if len(log.Stack) > 0 {
+ fmt.Fprintln(writer, "Stack:")
+ for i := len(log.Stack) - 1; i >= 0; i-- {
+ fmt.Fprintf(writer, "%08d %s\n", len(log.Stack)-i-1, log.Stack[i].Hex())
+ }
+ }
+ if len(log.Memory) > 0 {
+ fmt.Fprintln(writer, "Memory:")
+ fmt.Fprint(writer, hex.Dump(log.Memory))
+ }
+ if len(log.Storage) > 0 {
+ fmt.Fprintln(writer, "Storage:")
+ for h, item := range log.Storage {
+ fmt.Fprintf(writer, "%x: %x\n", h, item)
+ }
+ }
+ if len(log.ReturnData) > 0 {
+ fmt.Fprintln(writer, "ReturnData:")
+ fmt.Fprint(writer, hex.Dump(log.ReturnData))
+ }
+ fmt.Fprintln(writer)
+ }
+}
+
+// WriteLogs writes vm logs in a readable format to the given writer
+func WriteLogs(writer io.Writer, logs []*types.Log) {
+ for _, log := range logs {
+ fmt.Fprintf(writer, "LOG%d: %x bn=%d txi=%x\n", len(log.Topics), log.Address, log.BlockNumber, log.TxIndex)
+
+ for i, topic := range log.Topics {
+ fmt.Fprintf(writer, "%08d %x\n", i, topic)
+ }
+
+ fmt.Fprint(writer, hex.Dump(log.Data))
+ fmt.Fprintln(writer)
+ }
+}
+
+type mdLogger struct {
+ out io.Writer
+ cfg *vm.LogConfig
+ env *vm.EVM
+}
+
+// NewMarkdownLogger creates a logger which outputs information in a format adapted
+// for human readability, and is also a valid markdown table
+func NewMarkdownLogger(cfg *vm.LogConfig, writer io.Writer) *mdLogger {
+ l := &mdLogger{out: writer, cfg: cfg}
+ if l.cfg == nil {
+ l.cfg = &vm.LogConfig{}
+ }
+ return l
+}
+
+func (t *mdLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+ t.env = env
+ if !create {
+ fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `%#x`\nGas: `%d`\nValue `%v` wei\n",
+ from.String(), to.String(),
+ input, gas, value)
+ } else {
+ fmt.Fprintf(t.out, "From: `%v`\nCreate at: `%v`\nData: `%#x`\nGas: `%d`\nValue `%v` wei\n",
+ from.String(), to.String(),
+ input, gas, value)
+ }
+
+ fmt.Fprintf(t.out, `
+| Pc | Op | Cost | Stack | RStack | Refund |
+|-------|-------------|------|-----------|-----------|---------|
+`)
+}
+
+// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
+func (t *mdLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
+ stack := scope.Stack
+ fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost)
+
+ if !t.cfg.DisableStack {
+ // format stack
+ var a []string
+ for _, elem := range stack.Data() {
+ a = append(a, elem.Hex())
+ }
+ b := fmt.Sprintf("[%v]", strings.Join(a, ","))
+ fmt.Fprintf(t.out, "%10v |", b)
+ }
+ fmt.Fprintf(t.out, "%10v |", t.env.StateDB.GetRefund())
+ fmt.Fprintln(t.out, "")
+ if err != nil {
+ fmt.Fprintf(t.out, "Error: %v\n", err)
+ }
+}
+
+func (t *mdLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
+ fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err)
+}
+
+func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
+ fmt.Fprintf(t.out, "\nOutput: `%#x`\nConsumed gas: `%d`\nError: `%v`\n",
+ output, gasUsed, err)
+}
+
+func (t *mdLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
+}
+
+func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
+
+func (*mdLogger) CaptureTxStart(gasLimit uint64) {}
+
+func (*mdLogger) CaptureTxEnd(restGas uint64) {}
+
+// ExecutionResult groups all structured logs emitted by the EVM
+// while replaying a transaction in debug mode as well as transaction
+// execution status, the amount of gas used and the return value
+type ExecutionResult struct {
+ Gas uint64 `json:"gas"`
+ Failed bool `json:"failed"`
+ ReturnValue string `json:"returnValue"`
+ StructLogs []StructLogRes `json:"structLogs"`
+}
+
+// StructLogRes stores a structured log emitted by the EVM while replaying a
+// transaction in debug mode
+type StructLogRes struct {
+ Pc uint64 `json:"pc"`
+ Op string `json:"op"`
+ Gas uint64 `json:"gas"`
+ GasCost uint64 `json:"gasCost"`
+ Depth int `json:"depth"`
+ Error string `json:"error,omitempty"`
+ Stack *[]string `json:"stack,omitempty"`
+ ReturnData string `json:"returnData,omitempty"`
+ Memory *[]string `json:"memory,omitempty"`
+ Storage *map[string]string `json:"storage,omitempty"`
+ RefundCounter uint64 `json:"refund,omitempty"`
+}
+
+// formatLogs formats EVM returned structured logs for json output
+func formatLogs(logs []StructLog) []StructLogRes {
+ formatted := make([]StructLogRes, len(logs))
+ for index, trace := range logs {
+ formatted[index] = StructLogRes{
+ Pc: trace.Pc,
+ Op: trace.Op.String(),
+ Gas: trace.Gas,
+ GasCost: trace.GasCost,
+ Depth: trace.Depth,
+ Error: trace.ErrorString(),
+ RefundCounter: trace.RefundCounter,
+ }
+ if trace.Stack != nil {
+ stack := make([]string, len(trace.Stack))
+ for i, stackValue := range trace.Stack {
+ stack[i] = stackValue.Hex()
+ }
+ formatted[index].Stack = &stack
+ }
+ if trace.ReturnData != nil && len(trace.ReturnData) > 0 {
+ formatted[index].ReturnData = hexutil.Bytes(trace.ReturnData).String()
+ }
+ if trace.Memory != nil {
+ memory := make([]string, 0, (len(trace.Memory)+31)/32)
+ for i := 0; i+32 <= len(trace.Memory); i += 32 {
+ memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32]))
+ }
+ formatted[index].Memory = &memory
+ }
+ if trace.Storage != nil {
+ storage := make(map[string]string)
+ for i, storageValue := range trace.Storage {
+ storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
+ }
+ formatted[index].Storage = &storage
+ }
+ }
+ return formatted
+}
diff --git a/eth/tracers/logger/logger_json.go b/eth/tracers/logger/logger_json.go
new file mode 100644
index 000000000..e4b553a48
--- /dev/null
+++ b/eth/tracers/logger/logger_json.go
@@ -0,0 +1,102 @@
+// Copyright 2021 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 logger
+
+import (
+ "encoding/json"
+ "io"
+ "math/big"
+
+ "github.com/morph-l2/go-ethereum/common"
+ "github.com/morph-l2/go-ethereum/common/math"
+ "github.com/morph-l2/go-ethereum/core/vm"
+)
+
+type JSONLogger struct {
+ encoder *json.Encoder
+ cfg *vm.LogConfig
+ env *vm.EVM
+}
+
+// NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects
+// into the provided stream.
+func NewJSONLogger(cfg *vm.LogConfig, writer io.Writer) *JSONLogger {
+ l := &JSONLogger{encoder: json.NewEncoder(writer), cfg: cfg}
+ if l.cfg == nil {
+ l.cfg = &vm.LogConfig{}
+ }
+ return l
+}
+
+func (l *JSONLogger) CaptureStart(env *vm.EVM, from, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+ l.env = env
+}
+
+func (l *JSONLogger) 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
+ l.CaptureState(pc, op, gas, cost, scope, nil, depth, err)
+}
+
+// CaptureState outputs state information on the logger.
+func (l *JSONLogger) 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 := StructLog{
+ Pc: pc,
+ Op: op,
+ Gas: gas,
+ GasCost: cost,
+ MemorySize: memory.Len(),
+ Depth: depth,
+ RefundCounter: l.env.StateDB.GetRefund(),
+ Err: err,
+ }
+ if l.cfg.EnableMemory {
+ log.Memory = memory.Data()
+ }
+ if !l.cfg.DisableStack {
+ log.Stack = stack.Data()
+ }
+ if l.cfg.EnableReturnData {
+ log.ReturnData = rData
+ }
+ l.encoder.Encode(log)
+}
+
+// CaptureEnd is triggered at end of execution.
+func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
+ type endLog struct {
+ Output string `json:"output"`
+ GasUsed math.HexOrDecimal64 `json:"gasUsed"`
+ Err string `json:"error,omitempty"`
+ }
+ var errMsg string
+ if err != nil {
+ errMsg = err.Error()
+ }
+ l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), errMsg})
+}
+
+func (l *JSONLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
+}
+
+func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
+
+func (l *JSONLogger) CaptureTxStart(gasLimit uint64) {}
+
+func (l *JSONLogger) CaptureTxEnd(restGas uint64) {}
diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go
new file mode 100644
index 000000000..4247e2311
--- /dev/null
+++ b/eth/tracers/logger/logger_test.go
@@ -0,0 +1,107 @@
+// Copyright 2021 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 logger
+
+import (
+ "encoding/json"
+ "errors"
+ "math/big"
+ "testing"
+
+ "github.com/morph-l2/go-ethereum/common"
+ "github.com/morph-l2/go-ethereum/core/state"
+ "github.com/morph-l2/go-ethereum/core/vm"
+ "github.com/morph-l2/go-ethereum/params"
+)
+
+type dummyContractRef struct {
+ calledForEach bool
+}
+
+func (dummyContractRef) Address() common.Address { return common.Address{} }
+func (dummyContractRef) Value() *big.Int { return new(big.Int) }
+func (dummyContractRef) SetCode(common.Hash, []byte) {}
+func (d *dummyContractRef) ForEachStorage(callback func(key, value common.Hash) bool) {
+ d.calledForEach = true
+}
+func (d *dummyContractRef) SubBalance(amount *big.Int) {}
+func (d *dummyContractRef) AddBalance(amount *big.Int) {}
+func (d *dummyContractRef) SetBalance(*big.Int) {}
+func (d *dummyContractRef) SetNonce(uint64) {}
+func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) }
+
+type dummyStatedb struct {
+ state.StateDB
+}
+
+func (*dummyStatedb) GetRefund() uint64 { return 1337 }
+func (*dummyStatedb) GetState(_ common.Address, _ common.Hash) common.Hash { return common.Hash{} }
+func (*dummyStatedb) SetState(_ common.Address, _ common.Hash, _ common.Hash) {}
+
+func TestStoreCapture(t *testing.T) {
+ var (
+ logger = NewStructLogger(nil)
+ env = vm.NewEVM(vm.BlockContext{}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: logger})
+ contract = vm.NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 100000)
+ )
+ contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)}
+ var index common.Hash
+ logger.CaptureStart(env, common.Address{}, contract.Address(), false, nil, 0, nil)
+ _, err := env.Interpreter().Run(contract, []byte{}, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(logger.storage[contract.Address()]) == 0 {
+ t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(),
+ len(logger.storage[contract.Address()]))
+ }
+ exp := common.BigToHash(big.NewInt(1))
+ if logger.storage[contract.Address()][index] != exp {
+ t.Errorf("expected %x, got %x", exp, logger.storage[contract.Address()][index])
+ }
+}
+
+// Tests that blank fields don't appear in logs when JSON marshalled, to reduce
+// logs bloat and confusion. See https://github.com/ethereum/go-ethereum/issues/24487
+func TestStructLogMarshalingOmitEmpty(t *testing.T) {
+ tests := []struct {
+ name string
+ log *StructLog
+ want string
+ }{
+ {"empty err and no fields", &StructLog{},
+ `{"pc":0,"op":0,"gas":"0x0","gasCost":"0x0","memSize":0,"stack":null,"depth":0,"refund":0,"opName":"STOP"}`},
+ {"with err", &StructLog{Err: errors.New("this failed")},
+ `{"pc":0,"op":0,"gas":"0x0","gasCost":"0x0","memSize":0,"stack":null,"depth":0,"refund":0,"opName":"STOP","error":"this failed"}`},
+ {"with mem", &StructLog{Memory: make([]byte, 2), MemorySize: 2},
+ `{"pc":0,"op":0,"gas":"0x0","gasCost":"0x0","memory":"0x0000","memSize":2,"stack":null,"depth":0,"refund":0,"opName":"STOP"}`},
+ {"with 0-size mem", &StructLog{Memory: make([]byte, 0)},
+ `{"pc":0,"op":0,"gas":"0x0","gasCost":"0x0","memSize":0,"stack":null,"depth":0,"refund":0,"opName":"STOP"}`},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ blob, err := json.Marshal(tt.log)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if have, want := string(blob), tt.want; have != want {
+ t.Fatalf("mismatched results\n\thave: %v\n\twant: %v", have, want)
+ }
+ })
+ }
+}
From 9422f8684c34e99580917fcf5189a7a944f2ced8 Mon Sep 17 00:00:00 2001
From: ryanmorphl2 <163962984+ryanmorphl2@users.noreply.github.com>
Date: Mon, 13 Jan 2025 16:29:33 +0800
Subject: [PATCH 3/5] StructLog: add L1DataFee
---
eth/tracers/api.go | 8 +++++++-
eth/tracers/logger/logger.go | 4 ++++
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/eth/tracers/api.go b/eth/tracers/api.go
index f02bb4dcd..845fb1452 100644
--- a/eth/tracers/api.go
+++ b/eth/tracers/api.go
@@ -960,9 +960,15 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
}
// Call Prepare to clear out the statedb access list
statedb.SetTxContext(txctx.TxHash, txctx.TxIndex)
- if _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()), l1DataFee); err != nil {
+ result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()), l1DataFee)
+ if err != nil {
return nil, fmt.Errorf("tracing failed: %w", err)
}
+
+ l, ok := tracer.(*logger.StructLogger)
+ if ok {
+ l.ResultL1DataFee = result.L1DataFee
+ }
return tracer.GetResult()
}
diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go
index 3d5d4dcf5..e293bd435 100644
--- a/eth/tracers/logger/logger.go
+++ b/eth/tracers/logger/logger.go
@@ -107,6 +107,8 @@ type StructLogger struct {
interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
+
+ ResultL1DataFee *big.Int
}
// NewStructLogger returns a new logger
@@ -245,6 +247,7 @@ func (l *StructLogger) GetResult() (json.RawMessage, error) {
Failed: failed,
ReturnValue: returnVal,
StructLogs: formatLogs(l.StructLogs()),
+ L1DataFee: (*hexutil.Big)(l.ResultL1DataFee),
})
}
@@ -399,6 +402,7 @@ type ExecutionResult struct {
Failed bool `json:"failed"`
ReturnValue string `json:"returnValue"`
StructLogs []StructLogRes `json:"structLogs"`
+ L1DataFee *hexutil.Big `json:"l1DataFee,omitempty"`
}
// StructLogRes stores a structured log emitted by the EVM while replaying a
From b0dd86bbcc62c94921d83fa5f270ab3107f81c7d Mon Sep 17 00:00:00 2001
From: ryanmorphl2 <163962984+ryanmorphl2@users.noreply.github.com>
Date: Mon, 13 Jan 2025 19:42:27 +0800
Subject: [PATCH 4/5] revert tracer logger
---
core/types/l2trace.go | 1 +
core/vm/logger.go | 91 ++++-
eth/tracers/api.go | 5 +-
eth/tracers/logger/access_list_tracer.go | 183 ---------
eth/tracers/logger/gen_structlog.go | 118 ------
eth/tracers/logger/logger.go | 463 -----------------------
eth/tracers/logger/logger_json.go | 102 -----
eth/tracers/logger/logger_test.go | 107 ------
8 files changed, 92 insertions(+), 978 deletions(-)
delete mode 100644 eth/tracers/logger/access_list_tracer.go
delete mode 100644 eth/tracers/logger/gen_structlog.go
delete mode 100644 eth/tracers/logger/logger.go
delete mode 100644 eth/tracers/logger/logger_json.go
delete mode 100644 eth/tracers/logger/logger_test.go
diff --git a/core/types/l2trace.go b/core/types/l2trace.go
index 2ef09b588..a125a7556 100644
--- a/core/types/l2trace.go
+++ b/core/types/l2trace.go
@@ -85,6 +85,7 @@ type StructLogRes struct {
Depth int `json:"depth"`
Error string `json:"error,omitempty"`
Stack []string `json:"stack,omitempty"`
+ ReturnData string `json:"returnData,omitempty"`
Memory []string `json:"memory,omitempty"`
Storage map[string]string `json:"storage,omitempty"`
RefundCounter uint64 `json:"refund,omitempty"`
diff --git a/core/vm/logger.go b/core/vm/logger.go
index c587433ba..f50f634ce 100644
--- a/core/vm/logger.go
+++ b/core/vm/logger.go
@@ -19,10 +19,12 @@ package vm
import (
"bytes"
"encoding/hex"
+ "encoding/json"
"fmt"
"io"
"math/big"
"strings"
+ "sync/atomic"
"time"
"github.com/holiman/uint256"
@@ -165,6 +167,14 @@ type StructLogger struct {
logs []*StructLog
output []byte
err error
+
+ gasLimit uint64
+ usedGas uint64
+
+ interrupt atomic.Bool // Atomic flag to signal execution interruption
+ reason error // Textual reason for the interruption
+
+ ResultL1DataFee *big.Int
}
// NewStructLogger returns a new logger
@@ -217,6 +227,11 @@ func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Add
//
// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
func (l *StructLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, opErr error) {
+ // If tracing was interrupted, set the error and stop
+ if l.interrupt.Load() {
+ return
+ }
+
memory := scope.Memory
stack := scope.Stack
contract := scope.Contract
@@ -343,9 +358,13 @@ func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {
}
-func (t *StructLogger) CaptureTxStart(gasLimit uint64) {}
+func (l *StructLogger) CaptureTxStart(gasLimit uint64) {
+ l.gasLimit = gasLimit
+}
-func (t *StructLogger) CaptureTxEnd(restGas uint64) {}
+func (l *StructLogger) CaptureTxEnd(restGas uint64) {
+ l.usedGas = l.gasLimit - restGas
+}
// UpdatedAccounts is used to collect all "touched" accounts
func (l *StructLogger) UpdatedAccounts() map[common.Address]struct{} {
@@ -374,6 +393,33 @@ func (l *StructLogger) Error() error { return l.err }
// Output returns the VM return value captured by the trace.
func (l *StructLogger) Output() []byte { return l.output }
+func (l *StructLogger) GetResult() (json.RawMessage, error) {
+ // Tracing aborted
+ if l.reason != nil {
+ return nil, l.reason
+ }
+ failed := l.err != nil
+ returnData := common.CopyBytes(l.output)
+ // Return data when successful and revert reason when reverted, otherwise empty.
+ returnVal := fmt.Sprintf("%x", returnData)
+ if failed && l.err != ErrExecutionReverted {
+ returnVal = ""
+ }
+ return json.Marshal(&types.ExecutionResult{
+ Gas: l.usedGas,
+ Failed: failed,
+ ReturnValue: returnVal,
+ StructLogs: formatLogs(l.StructLogs()),
+ L1DataFee: (*hexutil.Big)(l.ResultL1DataFee),
+ })
+}
+
+// Stop terminates execution of the tracer at the first opportune moment.
+func (l *StructLogger) Stop(err error) {
+ l.reason = err
+ l.interrupt.Store(true)
+}
+
// WriteTrace writes a formatted trace to the given writer
func WriteTrace(writer io.Writer, logs []*StructLog) {
for _, log := range logs {
@@ -522,3 +568,44 @@ func FormatLogs(logs []*StructLog) []*types.StructLogRes {
}
return formatted
}
+
+// formatLogs formats EVM returned structured logs for json output
+func formatLogs(logs []*StructLog) []*types.StructLogRes {
+ formatted := make([]*types.StructLogRes, len(logs))
+ for index, trace := range logs {
+ formatted[index] = &types.StructLogRes{
+ Pc: trace.Pc,
+ Op: trace.Op.String(),
+ Gas: trace.Gas,
+ GasCost: trace.GasCost,
+ Depth: trace.Depth,
+ Error: trace.ErrorString(),
+ RefundCounter: trace.RefundCounter,
+ }
+ if trace.Stack != nil {
+ stack := make([]string, len(trace.Stack))
+ for i, stackValue := range trace.Stack {
+ stack[i] = stackValue.Hex()
+ }
+ formatted[index].Stack = stack
+ }
+ if trace.ReturnData.Len() > 0 {
+ formatted[index].ReturnData = hexutil.Bytes(trace.ReturnData.Bytes()).String()
+ }
+ if trace.Memory.Len() > 0 {
+ memory := make([]string, 0, (trace.Memory.Len()+31)/32)
+ for i := 0; i+32 <= trace.Memory.Len(); i += 32 {
+ memory = append(memory, fmt.Sprintf("%x", trace.Memory.Bytes()[i:i+32]))
+ }
+ formatted[index].Memory = memory
+ }
+ if trace.Storage != nil {
+ storage := make(map[string]string)
+ for i, storageValue := range trace.Storage {
+ storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
+ }
+ formatted[index].Storage = storage
+ }
+ }
+ return formatted
+}
diff --git a/eth/tracers/api.go b/eth/tracers/api.go
index 845fb1452..ba40a0f85 100644
--- a/eth/tracers/api.go
+++ b/eth/tracers/api.go
@@ -38,7 +38,6 @@ import (
"github.com/morph-l2/go-ethereum/core/state"
"github.com/morph-l2/go-ethereum/core/types"
"github.com/morph-l2/go-ethereum/core/vm"
- "github.com/morph-l2/go-ethereum/eth/tracers/logger"
"github.com/morph-l2/go-ethereum/ethdb"
"github.com/morph-l2/go-ethereum/internal/ethapi"
"github.com/morph-l2/go-ethereum/log"
@@ -927,7 +926,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
config = &TraceConfig{}
}
// Default tracer is the struct logger
- tracer = logger.NewStructLogger(config.LogConfig)
+ tracer = vm.NewStructLogger(config.LogConfig)
if config.Tracer != nil {
tracer, err = New(*config.Tracer, txctx, config.TracerConfig)
if err != nil {
@@ -965,7 +964,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
return nil, fmt.Errorf("tracing failed: %w", err)
}
- l, ok := tracer.(*logger.StructLogger)
+ l, ok := tracer.(*vm.StructLogger)
if ok {
l.ResultL1DataFee = result.L1DataFee
}
diff --git a/eth/tracers/logger/access_list_tracer.go b/eth/tracers/logger/access_list_tracer.go
deleted file mode 100644
index ffc679fde..000000000
--- a/eth/tracers/logger/access_list_tracer.go
+++ /dev/null
@@ -1,183 +0,0 @@
-// Copyright 2021 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 logger
-
-import (
- "math/big"
-
- "github.com/morph-l2/go-ethereum/common"
- "github.com/morph-l2/go-ethereum/core/types"
- "github.com/morph-l2/go-ethereum/core/vm"
-)
-
-// accessList is an accumulator for the set of accounts and storage slots an EVM
-// contract execution touches.
-type accessList map[common.Address]accessListSlots
-
-// accessListSlots is an accumulator for the set of storage slots within a single
-// contract that an EVM contract execution touches.
-type accessListSlots map[common.Hash]struct{}
-
-// newAccessList creates a new accessList.
-func newAccessList() accessList {
- return make(map[common.Address]accessListSlots)
-}
-
-// addAddress adds an address to the accesslist.
-func (al accessList) addAddress(address common.Address) {
- // Set address if not previously present
- if _, present := al[address]; !present {
- al[address] = make(map[common.Hash]struct{})
- }
-}
-
-// addSlot adds a storage slot to the accesslist.
-func (al accessList) addSlot(address common.Address, slot common.Hash) {
- // Set address if not previously present
- al.addAddress(address)
-
- // Set the slot on the surely existent storage set
- al[address][slot] = struct{}{}
-}
-
-// equal checks if the content of the current access list is the same as the
-// content of the other one.
-func (al accessList) equal(other accessList) bool {
- // Cross reference the accounts first
- if len(al) != len(other) {
- return false
- }
- // Given that len(al) == len(other), we only need to check that
- // all the items from al are in other.
- for addr := range al {
- if _, ok := other[addr]; !ok {
- return false
- }
- }
-
- // Accounts match, cross reference the storage slots too
- for addr, slots := range al {
- otherslots := other[addr]
-
- if len(slots) != len(otherslots) {
- return false
- }
- // Given that len(slots) == len(otherslots), we only need to check that
- // all the items from slots are in otherslots.
- for hash := range slots {
- if _, ok := otherslots[hash]; !ok {
- return false
- }
- }
- }
- return true
-}
-
-// accesslist converts the accesslist to a types.AccessList.
-func (al accessList) accessList() types.AccessList {
- acl := make(types.AccessList, 0, len(al))
- for addr, slots := range al {
- tuple := types.AccessTuple{Address: addr, StorageKeys: []common.Hash{}}
- for slot := range slots {
- tuple.StorageKeys = append(tuple.StorageKeys, slot)
- }
- acl = append(acl, tuple)
- }
- return acl
-}
-
-// AccessListTracer is a tracer that accumulates touched accounts and storage
-// slots into an internal set.
-type AccessListTracer struct {
- excl map[common.Address]struct{} // Set of account to exclude from the list
- list accessList // Set of accounts and storage slots touched
-}
-
-// NewAccessListTracer creates a new tracer that can generate AccessLists.
-// An optional AccessList can be specified to occupy slots and addresses in
-// the resulting accesslist.
-func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompiles []common.Address) *AccessListTracer {
- excl := map[common.Address]struct{}{
- from: {}, to: {},
- }
- for _, addr := range precompiles {
- excl[addr] = struct{}{}
- }
- list := newAccessList()
- for _, al := range acl {
- if _, ok := excl[al.Address]; !ok {
- list.addAddress(al.Address)
- }
- for _, slot := range al.StorageKeys {
- list.addSlot(al.Address, slot)
- }
- }
- return &AccessListTracer{
- excl: excl,
- list: list,
- }
-}
-
-func (a *AccessListTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
-}
-
-// CaptureState captures all opcodes that touch storage or addresses and adds them to the accesslist.
-func (a *AccessListTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
- stack := scope.Stack
- stackData := stack.Data()
- stackLen := len(stackData)
- if (op == vm.SLOAD || op == vm.SSTORE) && stackLen >= 1 {
- slot := common.Hash(stackData[stackLen-1].Bytes32())
- a.list.addSlot(scope.Contract.Address(), slot)
- }
- if (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT) && stackLen >= 1 {
- addr := common.Address(stackData[stackLen-1].Bytes20())
- if _, ok := a.excl[addr]; !ok {
- a.list.addAddress(addr)
- }
- }
- if (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE) && stackLen >= 5 {
- addr := common.Address(stackData[stackLen-2].Bytes20())
- if _, ok := a.excl[addr]; !ok {
- a.list.addAddress(addr)
- }
- }
-}
-
-func (*AccessListTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
-}
-
-func (*AccessListTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {}
-
-func (*AccessListTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
-}
-
-func (*AccessListTracer) CaptureExit(output []byte, gasUsed uint64, err error) {}
-
-func (*AccessListTracer) CaptureTxStart(gasLimit uint64) {}
-
-func (*AccessListTracer) CaptureTxEnd(restGas uint64) {}
-
-// AccessList returns the current accesslist maintained by the tracer.
-func (a *AccessListTracer) AccessList() types.AccessList {
- return a.list.accessList()
-}
-
-// Equal returns if the content of two access list traces are equal.
-func (a *AccessListTracer) Equal(other *AccessListTracer) bool {
- return a.list.equal(other.list)
-}
diff --git a/eth/tracers/logger/gen_structlog.go b/eth/tracers/logger/gen_structlog.go
deleted file mode 100644
index e67950ca7..000000000
--- a/eth/tracers/logger/gen_structlog.go
+++ /dev/null
@@ -1,118 +0,0 @@
-// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
-
-package logger
-
-import (
- "encoding/json"
-
- "github.com/holiman/uint256"
- "github.com/morph-l2/go-ethereum/common"
- "github.com/morph-l2/go-ethereum/common/hexutil"
- "github.com/morph-l2/go-ethereum/common/math"
- "github.com/morph-l2/go-ethereum/core/vm"
-)
-
-var _ = (*structLogMarshaling)(nil)
-
-// MarshalJSON marshals as JSON.
-func (s StructLog) MarshalJSON() ([]byte, error) {
- type StructLog struct {
- Pc uint64 `json:"pc"`
- Op vm.OpCode `json:"op"`
- Gas math.HexOrDecimal64 `json:"gas"`
- GasCost math.HexOrDecimal64 `json:"gasCost"`
- Memory hexutil.Bytes `json:"memory,omitempty"`
- MemorySize int `json:"memSize"`
- Stack []hexutil.U256 `json:"stack"`
- ReturnData hexutil.Bytes `json:"returnData,omitempty"`
- Storage map[common.Hash]common.Hash `json:"-"`
- Depth int `json:"depth"`
- RefundCounter uint64 `json:"refund"`
- Err error `json:"-"`
- OpName string `json:"opName"`
- ErrorString string `json:"error,omitempty"`
- }
- var enc StructLog
- enc.Pc = s.Pc
- enc.Op = s.Op
- enc.Gas = math.HexOrDecimal64(s.Gas)
- enc.GasCost = math.HexOrDecimal64(s.GasCost)
- enc.Memory = s.Memory
- enc.MemorySize = s.MemorySize
- if s.Stack != nil {
- enc.Stack = make([]hexutil.U256, len(s.Stack))
- for k, v := range s.Stack {
- enc.Stack[k] = hexutil.U256(v)
- }
- }
- enc.ReturnData = s.ReturnData
- enc.Storage = s.Storage
- enc.Depth = s.Depth
- enc.RefundCounter = s.RefundCounter
- enc.Err = s.Err
- enc.OpName = s.OpName()
- enc.ErrorString = s.ErrorString()
- return json.Marshal(&enc)
-}
-
-// UnmarshalJSON unmarshals from JSON.
-func (s *StructLog) UnmarshalJSON(input []byte) error {
- type StructLog struct {
- Pc *uint64 `json:"pc"`
- Op *vm.OpCode `json:"op"`
- Gas *math.HexOrDecimal64 `json:"gas"`
- GasCost *math.HexOrDecimal64 `json:"gasCost"`
- Memory *hexutil.Bytes `json:"memory,omitempty"`
- MemorySize *int `json:"memSize"`
- Stack []hexutil.U256 `json:"stack"`
- ReturnData *hexutil.Bytes `json:"returnData,omitempty"`
- Storage map[common.Hash]common.Hash `json:"-"`
- Depth *int `json:"depth"`
- RefundCounter *uint64 `json:"refund"`
- Err error `json:"-"`
- }
- var dec StructLog
- if err := json.Unmarshal(input, &dec); err != nil {
- return err
- }
- if dec.Pc != nil {
- s.Pc = *dec.Pc
- }
- if dec.Op != nil {
- s.Op = *dec.Op
- }
- if dec.Gas != nil {
- s.Gas = uint64(*dec.Gas)
- }
- if dec.GasCost != nil {
- s.GasCost = uint64(*dec.GasCost)
- }
- if dec.Memory != nil {
- s.Memory = *dec.Memory
- }
- if dec.MemorySize != nil {
- s.MemorySize = *dec.MemorySize
- }
- if dec.Stack != nil {
- s.Stack = make([]uint256.Int, len(dec.Stack))
- for k, v := range dec.Stack {
- s.Stack[k] = uint256.Int(v)
- }
- }
- if dec.ReturnData != nil {
- s.ReturnData = *dec.ReturnData
- }
- if dec.Storage != nil {
- s.Storage = dec.Storage
- }
- if dec.Depth != nil {
- s.Depth = *dec.Depth
- }
- if dec.RefundCounter != nil {
- s.RefundCounter = *dec.RefundCounter
- }
- if dec.Err != nil {
- s.Err = dec.Err
- }
- return nil
-}
diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go
deleted file mode 100644
index e293bd435..000000000
--- a/eth/tracers/logger/logger.go
+++ /dev/null
@@ -1,463 +0,0 @@
-// Copyright 2021 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 logger
-
-import (
- "encoding/hex"
- "encoding/json"
- "fmt"
- "io"
- "math/big"
- "strings"
- "sync/atomic"
- "time"
-
- "github.com/holiman/uint256"
- "github.com/morph-l2/go-ethereum/common"
- "github.com/morph-l2/go-ethereum/common/hexutil"
- "github.com/morph-l2/go-ethereum/common/math"
- "github.com/morph-l2/go-ethereum/core/types"
- "github.com/morph-l2/go-ethereum/core/vm"
-)
-
-// Storage represents a contract's storage.
-type Storage map[common.Hash]common.Hash
-
-// Copy duplicates the current storage.
-func (s Storage) Copy() Storage {
- cpy := make(Storage, len(s))
- for key, value := range s {
- cpy[key] = value
- }
- return cpy
-}
-
-//go:generate go run github.com/fjl/gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go
-
-// StructLog is emitted to the EVM each cycle and lists information about the current internal state
-// prior to the execution of the statement.
-type StructLog struct {
- Pc uint64 `json:"pc"`
- Op vm.OpCode `json:"op"`
- Gas uint64 `json:"gas"`
- GasCost uint64 `json:"gasCost"`
- Memory []byte `json:"memory,omitempty"`
- MemorySize int `json:"memSize"`
- Stack []uint256.Int `json:"stack"`
- ReturnData []byte `json:"returnData,omitempty"`
- Storage map[common.Hash]common.Hash `json:"-"`
- Depth int `json:"depth"`
- RefundCounter uint64 `json:"refund"`
- Err error `json:"-"`
-}
-
-// overrides for gencodec
-type structLogMarshaling struct {
- Gas math.HexOrDecimal64
- GasCost math.HexOrDecimal64
- Memory hexutil.Bytes
- ReturnData hexutil.Bytes
- Stack []hexutil.U256
- OpName string `json:"opName"` // adds call to OpName() in MarshalJSON
- ErrorString string `json:"error,omitempty"` // adds call to ErrorString() in MarshalJSON
-}
-
-// OpName formats the operand name in a human-readable format.
-func (s *StructLog) OpName() string {
- return s.Op.String()
-}
-
-// ErrorString formats the log's error as a string.
-func (s *StructLog) ErrorString() string {
- if s.Err != nil {
- return s.Err.Error()
- }
- return ""
-}
-
-// StructLogger is an EVM state logger and implements EVMLogger.
-//
-// StructLogger can capture state based on the given Log configuration and also keeps
-// a track record of modified storage which is used in reporting snapshots of the
-// contract their storage.
-type StructLogger struct {
- cfg vm.LogConfig
- env *vm.EVM
-
- storage map[common.Address]Storage
- logs []StructLog
- output []byte
- err error
- gasLimit uint64
- usedGas uint64
-
- interrupt atomic.Bool // Atomic flag to signal execution interruption
- reason error // Textual reason for the interruption
-
- ResultL1DataFee *big.Int
-}
-
-// NewStructLogger returns a new logger
-func NewStructLogger(cfg *vm.LogConfig) *StructLogger {
- logger := &StructLogger{
- storage: make(map[common.Address]Storage),
- }
- if cfg != nil {
- logger.cfg = *cfg
- }
- return logger
-}
-
-// Reset clears the data held by the logger.
-func (l *StructLogger) Reset() {
- l.storage = make(map[common.Address]Storage)
- l.output = make([]byte, 0)
- l.logs = l.logs[:0]
- l.err = nil
-}
-
-// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
-func (l *StructLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
- l.env = env
-}
-
-// CaptureState logs a new structured log message and pushes it out to the environment
-//
-// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
-func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
- // If tracing was interrupted, set the error and stop
- if l.interrupt.Load() {
- return
- }
- // check if already accumulated the specified number of logs
- if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) {
- return
- }
-
- memory := scope.Memory
- stack := scope.Stack
- contract := scope.Contract
- // Copy a snapshot of the current memory state to a new buffer
- var mem []byte
- if l.cfg.EnableMemory {
- mem = make([]byte, len(memory.Data()))
- copy(mem, memory.Data())
- }
- // Copy a snapshot of the current stack state to a new buffer
- var stck []uint256.Int
- if !l.cfg.DisableStack {
- stck = make([]uint256.Int, len(stack.Data()))
- for i, item := range stack.Data() {
- stck[i] = item
- }
- }
- stackData := stack.Data()
- stackLen := len(stackData)
- // Copy a snapshot of the current storage to a new container
- var storage Storage
- if !l.cfg.DisableStorage && (op == vm.SLOAD || op == vm.SSTORE) {
- // initialise new changed values storage container for this contract
- // if not present.
- if l.storage[contract.Address()] == nil {
- l.storage[contract.Address()] = make(Storage)
- }
- // capture SLOAD opcodes and record the read entry in the local storage
- if op == vm.SLOAD && stackLen >= 1 {
- var (
- address = common.Hash(stackData[stackLen-1].Bytes32())
- value = l.env.StateDB.GetState(contract.Address(), address)
- )
- l.storage[contract.Address()][address] = value
- storage = l.storage[contract.Address()].Copy()
- } else if op == vm.SSTORE && stackLen >= 2 {
- // capture SSTORE opcodes and record the written entry in the local storage.
- var (
- value = common.Hash(stackData[stackLen-2].Bytes32())
- address = common.Hash(stackData[stackLen-1].Bytes32())
- )
- l.storage[contract.Address()][address] = value
- storage = l.storage[contract.Address()].Copy()
- }
- }
- var rdata []byte
- if l.cfg.EnableReturnData {
- rdata = make([]byte, len(rData))
- copy(rdata, rData)
- }
- // create a new snapshot of the EVM.
- log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err}
- l.logs = append(l.logs, log)
-}
-
-// CaptureFault implements the EVMLogger interface to trace an execution fault
-// while running an opcode.
-func (l *StructLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
-}
-
-// CaptureEnd is called after the call finishes to finalize the tracing.
-func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
- l.output = output
- l.err = err
- if l.cfg.Debug {
- fmt.Printf("%#x\n", output)
- if err != nil {
- fmt.Printf(" error: %v\n", err)
- }
- }
-}
-
-func (l *StructLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
-}
-
-// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
-func (l *StructLogger) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
-}
-
-func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {
-}
-
-func (l *StructLogger) GetResult() (json.RawMessage, error) {
- // Tracing aborted
- if l.reason != nil {
- return nil, l.reason
- }
- failed := l.err != nil
- returnData := common.CopyBytes(l.output)
- // Return data when successful and revert reason when reverted, otherwise empty.
- returnVal := fmt.Sprintf("%x", returnData)
- if failed && l.err != vm.ErrExecutionReverted {
- returnVal = ""
- }
- return json.Marshal(&ExecutionResult{
- Gas: l.usedGas,
- Failed: failed,
- ReturnValue: returnVal,
- StructLogs: formatLogs(l.StructLogs()),
- L1DataFee: (*hexutil.Big)(l.ResultL1DataFee),
- })
-}
-
-// Stop terminates execution of the tracer at the first opportune moment.
-func (l *StructLogger) Stop(err error) {
- l.reason = err
- l.interrupt.Store(true)
-}
-
-func (l *StructLogger) CaptureTxStart(gasLimit uint64) {
- l.gasLimit = gasLimit
-}
-
-func (l *StructLogger) CaptureTxEnd(restGas uint64) {
- l.usedGas = l.gasLimit - restGas
-}
-
-// StructLogs returns the captured log entries.
-func (l *StructLogger) StructLogs() []StructLog { return l.logs }
-
-// Error returns the VM error captured by the trace.
-func (l *StructLogger) Error() error { return l.err }
-
-// Output returns the VM return value captured by the trace.
-func (l *StructLogger) Output() []byte { return l.output }
-
-// WriteTrace writes a formatted trace to the given writer
-func WriteTrace(writer io.Writer, logs []StructLog) {
- for _, log := range logs {
- fmt.Fprintf(writer, "%-16spc=%08d gas=%v cost=%v", log.Op, log.Pc, log.Gas, log.GasCost)
- if log.Err != nil {
- fmt.Fprintf(writer, " ERROR: %v", log.Err)
- }
- fmt.Fprintln(writer)
-
- if len(log.Stack) > 0 {
- fmt.Fprintln(writer, "Stack:")
- for i := len(log.Stack) - 1; i >= 0; i-- {
- fmt.Fprintf(writer, "%08d %s\n", len(log.Stack)-i-1, log.Stack[i].Hex())
- }
- }
- if len(log.Memory) > 0 {
- fmt.Fprintln(writer, "Memory:")
- fmt.Fprint(writer, hex.Dump(log.Memory))
- }
- if len(log.Storage) > 0 {
- fmt.Fprintln(writer, "Storage:")
- for h, item := range log.Storage {
- fmt.Fprintf(writer, "%x: %x\n", h, item)
- }
- }
- if len(log.ReturnData) > 0 {
- fmt.Fprintln(writer, "ReturnData:")
- fmt.Fprint(writer, hex.Dump(log.ReturnData))
- }
- fmt.Fprintln(writer)
- }
-}
-
-// WriteLogs writes vm logs in a readable format to the given writer
-func WriteLogs(writer io.Writer, logs []*types.Log) {
- for _, log := range logs {
- fmt.Fprintf(writer, "LOG%d: %x bn=%d txi=%x\n", len(log.Topics), log.Address, log.BlockNumber, log.TxIndex)
-
- for i, topic := range log.Topics {
- fmt.Fprintf(writer, "%08d %x\n", i, topic)
- }
-
- fmt.Fprint(writer, hex.Dump(log.Data))
- fmt.Fprintln(writer)
- }
-}
-
-type mdLogger struct {
- out io.Writer
- cfg *vm.LogConfig
- env *vm.EVM
-}
-
-// NewMarkdownLogger creates a logger which outputs information in a format adapted
-// for human readability, and is also a valid markdown table
-func NewMarkdownLogger(cfg *vm.LogConfig, writer io.Writer) *mdLogger {
- l := &mdLogger{out: writer, cfg: cfg}
- if l.cfg == nil {
- l.cfg = &vm.LogConfig{}
- }
- return l
-}
-
-func (t *mdLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
- t.env = env
- if !create {
- fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `%#x`\nGas: `%d`\nValue `%v` wei\n",
- from.String(), to.String(),
- input, gas, value)
- } else {
- fmt.Fprintf(t.out, "From: `%v`\nCreate at: `%v`\nData: `%#x`\nGas: `%d`\nValue `%v` wei\n",
- from.String(), to.String(),
- input, gas, value)
- }
-
- fmt.Fprintf(t.out, `
-| Pc | Op | Cost | Stack | RStack | Refund |
-|-------|-------------|------|-----------|-----------|---------|
-`)
-}
-
-// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
-func (t *mdLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
- stack := scope.Stack
- fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost)
-
- if !t.cfg.DisableStack {
- // format stack
- var a []string
- for _, elem := range stack.Data() {
- a = append(a, elem.Hex())
- }
- b := fmt.Sprintf("[%v]", strings.Join(a, ","))
- fmt.Fprintf(t.out, "%10v |", b)
- }
- fmt.Fprintf(t.out, "%10v |", t.env.StateDB.GetRefund())
- fmt.Fprintln(t.out, "")
- if err != nil {
- fmt.Fprintf(t.out, "Error: %v\n", err)
- }
-}
-
-func (t *mdLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
- fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err)
-}
-
-func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
- fmt.Fprintf(t.out, "\nOutput: `%#x`\nConsumed gas: `%d`\nError: `%v`\n",
- output, gasUsed, err)
-}
-
-func (t *mdLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
-}
-
-func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
-
-func (*mdLogger) CaptureTxStart(gasLimit uint64) {}
-
-func (*mdLogger) CaptureTxEnd(restGas uint64) {}
-
-// ExecutionResult groups all structured logs emitted by the EVM
-// while replaying a transaction in debug mode as well as transaction
-// execution status, the amount of gas used and the return value
-type ExecutionResult struct {
- Gas uint64 `json:"gas"`
- Failed bool `json:"failed"`
- ReturnValue string `json:"returnValue"`
- StructLogs []StructLogRes `json:"structLogs"`
- L1DataFee *hexutil.Big `json:"l1DataFee,omitempty"`
-}
-
-// StructLogRes stores a structured log emitted by the EVM while replaying a
-// transaction in debug mode
-type StructLogRes struct {
- Pc uint64 `json:"pc"`
- Op string `json:"op"`
- Gas uint64 `json:"gas"`
- GasCost uint64 `json:"gasCost"`
- Depth int `json:"depth"`
- Error string `json:"error,omitempty"`
- Stack *[]string `json:"stack,omitempty"`
- ReturnData string `json:"returnData,omitempty"`
- Memory *[]string `json:"memory,omitempty"`
- Storage *map[string]string `json:"storage,omitempty"`
- RefundCounter uint64 `json:"refund,omitempty"`
-}
-
-// formatLogs formats EVM returned structured logs for json output
-func formatLogs(logs []StructLog) []StructLogRes {
- formatted := make([]StructLogRes, len(logs))
- for index, trace := range logs {
- formatted[index] = StructLogRes{
- Pc: trace.Pc,
- Op: trace.Op.String(),
- Gas: trace.Gas,
- GasCost: trace.GasCost,
- Depth: trace.Depth,
- Error: trace.ErrorString(),
- RefundCounter: trace.RefundCounter,
- }
- if trace.Stack != nil {
- stack := make([]string, len(trace.Stack))
- for i, stackValue := range trace.Stack {
- stack[i] = stackValue.Hex()
- }
- formatted[index].Stack = &stack
- }
- if trace.ReturnData != nil && len(trace.ReturnData) > 0 {
- formatted[index].ReturnData = hexutil.Bytes(trace.ReturnData).String()
- }
- if trace.Memory != nil {
- memory := make([]string, 0, (len(trace.Memory)+31)/32)
- for i := 0; i+32 <= len(trace.Memory); i += 32 {
- memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32]))
- }
- formatted[index].Memory = &memory
- }
- if trace.Storage != nil {
- storage := make(map[string]string)
- for i, storageValue := range trace.Storage {
- storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
- }
- formatted[index].Storage = &storage
- }
- }
- return formatted
-}
diff --git a/eth/tracers/logger/logger_json.go b/eth/tracers/logger/logger_json.go
deleted file mode 100644
index e4b553a48..000000000
--- a/eth/tracers/logger/logger_json.go
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2021 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 logger
-
-import (
- "encoding/json"
- "io"
- "math/big"
-
- "github.com/morph-l2/go-ethereum/common"
- "github.com/morph-l2/go-ethereum/common/math"
- "github.com/morph-l2/go-ethereum/core/vm"
-)
-
-type JSONLogger struct {
- encoder *json.Encoder
- cfg *vm.LogConfig
- env *vm.EVM
-}
-
-// NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects
-// into the provided stream.
-func NewJSONLogger(cfg *vm.LogConfig, writer io.Writer) *JSONLogger {
- l := &JSONLogger{encoder: json.NewEncoder(writer), cfg: cfg}
- if l.cfg == nil {
- l.cfg = &vm.LogConfig{}
- }
- return l
-}
-
-func (l *JSONLogger) CaptureStart(env *vm.EVM, from, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
- l.env = env
-}
-
-func (l *JSONLogger) 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
- l.CaptureState(pc, op, gas, cost, scope, nil, depth, err)
-}
-
-// CaptureState outputs state information on the logger.
-func (l *JSONLogger) 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 := StructLog{
- Pc: pc,
- Op: op,
- Gas: gas,
- GasCost: cost,
- MemorySize: memory.Len(),
- Depth: depth,
- RefundCounter: l.env.StateDB.GetRefund(),
- Err: err,
- }
- if l.cfg.EnableMemory {
- log.Memory = memory.Data()
- }
- if !l.cfg.DisableStack {
- log.Stack = stack.Data()
- }
- if l.cfg.EnableReturnData {
- log.ReturnData = rData
- }
- l.encoder.Encode(log)
-}
-
-// CaptureEnd is triggered at end of execution.
-func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
- type endLog struct {
- Output string `json:"output"`
- GasUsed math.HexOrDecimal64 `json:"gasUsed"`
- Err string `json:"error,omitempty"`
- }
- var errMsg string
- if err != nil {
- errMsg = err.Error()
- }
- l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), errMsg})
-}
-
-func (l *JSONLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
-}
-
-func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
-
-func (l *JSONLogger) CaptureTxStart(gasLimit uint64) {}
-
-func (l *JSONLogger) CaptureTxEnd(restGas uint64) {}
diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go
deleted file mode 100644
index 4247e2311..000000000
--- a/eth/tracers/logger/logger_test.go
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright 2021 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 logger
-
-import (
- "encoding/json"
- "errors"
- "math/big"
- "testing"
-
- "github.com/morph-l2/go-ethereum/common"
- "github.com/morph-l2/go-ethereum/core/state"
- "github.com/morph-l2/go-ethereum/core/vm"
- "github.com/morph-l2/go-ethereum/params"
-)
-
-type dummyContractRef struct {
- calledForEach bool
-}
-
-func (dummyContractRef) Address() common.Address { return common.Address{} }
-func (dummyContractRef) Value() *big.Int { return new(big.Int) }
-func (dummyContractRef) SetCode(common.Hash, []byte) {}
-func (d *dummyContractRef) ForEachStorage(callback func(key, value common.Hash) bool) {
- d.calledForEach = true
-}
-func (d *dummyContractRef) SubBalance(amount *big.Int) {}
-func (d *dummyContractRef) AddBalance(amount *big.Int) {}
-func (d *dummyContractRef) SetBalance(*big.Int) {}
-func (d *dummyContractRef) SetNonce(uint64) {}
-func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) }
-
-type dummyStatedb struct {
- state.StateDB
-}
-
-func (*dummyStatedb) GetRefund() uint64 { return 1337 }
-func (*dummyStatedb) GetState(_ common.Address, _ common.Hash) common.Hash { return common.Hash{} }
-func (*dummyStatedb) SetState(_ common.Address, _ common.Hash, _ common.Hash) {}
-
-func TestStoreCapture(t *testing.T) {
- var (
- logger = NewStructLogger(nil)
- env = vm.NewEVM(vm.BlockContext{}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: logger})
- contract = vm.NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 100000)
- )
- contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)}
- var index common.Hash
- logger.CaptureStart(env, common.Address{}, contract.Address(), false, nil, 0, nil)
- _, err := env.Interpreter().Run(contract, []byte{}, false)
- if err != nil {
- t.Fatal(err)
- }
- if len(logger.storage[contract.Address()]) == 0 {
- t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(),
- len(logger.storage[contract.Address()]))
- }
- exp := common.BigToHash(big.NewInt(1))
- if logger.storage[contract.Address()][index] != exp {
- t.Errorf("expected %x, got %x", exp, logger.storage[contract.Address()][index])
- }
-}
-
-// Tests that blank fields don't appear in logs when JSON marshalled, to reduce
-// logs bloat and confusion. See https://github.com/ethereum/go-ethereum/issues/24487
-func TestStructLogMarshalingOmitEmpty(t *testing.T) {
- tests := []struct {
- name string
- log *StructLog
- want string
- }{
- {"empty err and no fields", &StructLog{},
- `{"pc":0,"op":0,"gas":"0x0","gasCost":"0x0","memSize":0,"stack":null,"depth":0,"refund":0,"opName":"STOP"}`},
- {"with err", &StructLog{Err: errors.New("this failed")},
- `{"pc":0,"op":0,"gas":"0x0","gasCost":"0x0","memSize":0,"stack":null,"depth":0,"refund":0,"opName":"STOP","error":"this failed"}`},
- {"with mem", &StructLog{Memory: make([]byte, 2), MemorySize: 2},
- `{"pc":0,"op":0,"gas":"0x0","gasCost":"0x0","memory":"0x0000","memSize":2,"stack":null,"depth":0,"refund":0,"opName":"STOP"}`},
- {"with 0-size mem", &StructLog{Memory: make([]byte, 0)},
- `{"pc":0,"op":0,"gas":"0x0","gasCost":"0x0","memSize":0,"stack":null,"depth":0,"refund":0,"opName":"STOP"}`},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- blob, err := json.Marshal(tt.log)
- if err != nil {
- t.Fatal(err)
- }
- if have, want := string(blob), tt.want; have != want {
- t.Fatalf("mismatched results\n\thave: %v\n\twant: %v", have, want)
- }
- })
- }
-}
From 099f66d42280ac9dae4f03ca9effd58f6dc934d3 Mon Sep 17 00:00:00 2001
From: ryanmorphl2 <163962984+ryanmorphl2@users.noreply.github.com>
Date: Mon, 13 Jan 2025 20:03:17 +0800
Subject: [PATCH 5/5] revert hexutil
---
common/hexutil/json.go | 45 ----------------------------
common/hexutil/json_test.go | 60 -------------------------------------
2 files changed, 105 deletions(-)
diff --git a/common/hexutil/json.go b/common/hexutil/json.go
index e0ac98f52..50db20811 100644
--- a/common/hexutil/json.go
+++ b/common/hexutil/json.go
@@ -23,8 +23,6 @@ import (
"math/big"
"reflect"
"strconv"
-
- "github.com/holiman/uint256"
)
var (
@@ -32,7 +30,6 @@ var (
bigT = reflect.TypeOf((*Big)(nil))
uintT = reflect.TypeOf(Uint(0))
uint64T = reflect.TypeOf(Uint64(0))
- u256T = reflect.TypeOf((*uint256.Int)(nil))
)
// Bytes marshals/unmarshals as a JSON string with 0x prefix.
@@ -228,48 +225,6 @@ func (b *Big) UnmarshalGraphQL(input interface{}) error {
return err
}
-// U256 marshals/unmarshals as a JSON string with 0x prefix.
-// The zero value marshals as "0x0".
-type U256 uint256.Int
-
-// MarshalText implements encoding.TextMarshaler
-func (b U256) MarshalText() ([]byte, error) {
- u256 := (*uint256.Int)(&b)
- return []byte(u256.Hex()), nil
-}
-
-// UnmarshalJSON implements json.Unmarshaler.
-func (b *U256) UnmarshalJSON(input []byte) error {
- // The uint256.Int.UnmarshalJSON method accepts "dec", "0xhex"; we must be
- // more strict, hence we check string and invoke SetFromHex directly.
- if !isString(input) {
- return errNonString(u256T)
- }
- // The hex decoder needs to accept empty string ("") as '0', which uint256.Int
- // would reject.
- if len(input) == 2 {
- (*uint256.Int)(b).Clear()
- return nil
- }
- err := (*uint256.Int)(b).SetFromHex(string(input[1 : len(input)-1]))
- if err != nil {
- return &json.UnmarshalTypeError{Value: err.Error(), Type: u256T}
- }
- return nil
-}
-
-// UnmarshalText implements encoding.TextUnmarshaler
-func (b *U256) UnmarshalText(input []byte) error {
- // The uint256.Int.UnmarshalText method accepts "dec", "0xhex"; we must be
- // more strict, hence we check string and invoke SetFromHex directly.
- return (*uint256.Int)(b).SetFromHex(string(input))
-}
-
-// String returns the hex encoding of b.
-func (b *U256) String() string {
- return (*uint256.Int)(b).Hex()
-}
-
// Uint64 marshals/unmarshals as a JSON string with 0x prefix.
// The zero value marshals as "0x0".
type Uint64 uint64
diff --git a/common/hexutil/json_test.go b/common/hexutil/json_test.go
index 7cca30095..ed7d6fad1 100644
--- a/common/hexutil/json_test.go
+++ b/common/hexutil/json_test.go
@@ -23,8 +23,6 @@ import (
"errors"
"math/big"
"testing"
-
- "github.com/holiman/uint256"
)
func checkError(t *testing.T, input string, got, want error) bool {
@@ -178,64 +176,6 @@ func TestUnmarshalBig(t *testing.T) {
}
}
-var unmarshalU256Tests = []unmarshalTest{
- // invalid encoding
- {input: "", wantErr: errJSONEOF},
- {input: "null", wantErr: errNonString(u256T)},
- {input: "10", wantErr: errNonString(u256T)},
- {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, u256T)},
- {input: `"0x"`, wantErr: wrapTypeError(ErrEmptyNumber, u256T)},
- {input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, u256T)},
- {input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, u256T)},
- {input: `"0x1zz01"`, wantErr: wrapTypeError(ErrSyntax, u256T)},
- {
- input: `"0x10000000000000000000000000000000000000000000000000000000000000000"`,
- wantErr: wrapTypeError(ErrBig256Range, u256T),
- },
-
- // valid encoding
- {input: `""`, want: big.NewInt(0)},
- {input: `"0x0"`, want: big.NewInt(0)},
- {input: `"0x2"`, want: big.NewInt(0x2)},
- {input: `"0x2F2"`, want: big.NewInt(0x2f2)},
- {input: `"0X2F2"`, want: big.NewInt(0x2f2)},
- {input: `"0x1122aaff"`, want: big.NewInt(0x1122aaff)},
- {input: `"0xbBb"`, want: big.NewInt(0xbbb)},
- {input: `"0xfffffffff"`, want: big.NewInt(0xfffffffff)},
- {
- input: `"0x112233445566778899aabbccddeeff"`,
- want: referenceBig("112233445566778899aabbccddeeff"),
- },
- {
- input: `"0xffffffffffffffffffffffffffffffffffff"`,
- want: referenceBig("ffffffffffffffffffffffffffffffffffff"),
- },
- {
- input: `"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"`,
- want: referenceBig("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
- },
-}
-
-func TestUnmarshalU256(t *testing.T) {
- for _, test := range unmarshalU256Tests {
- var v U256
- err := json.Unmarshal([]byte(test.input), &v)
- if !checkError(t, test.input, err, test.wantErr) {
- continue
- }
- if test.want == nil {
- continue
- }
- want := new(uint256.Int)
- want.SetFromBig(test.want.(*big.Int))
- have := (*uint256.Int)(&v)
- if want.Cmp(have) != 0 {
- t.Errorf("input %s: value mismatch: have %x, want %x", test.input, have, want)
- continue
- }
- }
-}
-
func BenchmarkUnmarshalBig(b *testing.B) {
input := []byte(`"0x123456789abcdef123456789abcdef"`)
for i := 0; i < b.N; i++ {