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 <http://www.gnu.org/licenses/>.
 
-/*
-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 <http://www.gnu.org/licenses/>.
+
+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 <http://www.gnu.org/licenses/>.
+
+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 <http://www.gnu.org/licenses/>.
+
+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 <http://www.gnu.org/licenses/>.
+
+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 <http://www.gnu.org/licenses/>.
-
-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 <http://www.gnu.org/licenses/>.
-
-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 <http://www.gnu.org/licenses/>.
-
-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 <http://www.gnu.org/licenses/>.
-
-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++ {