Skip to content

Commit 62aa6b2

Browse files
shahafns1na
andauthored
eth/tracers/native: add erc7562 tracer (#31006)
This PR introduces a new native tracer for AA bundlers. Bundlers participating in the alternative mempool will need to validate userops. This tracer will return sufficient information for them to decide whether griefing is possible. Resolves #30546 --------- Co-authored-by: Sina M <1591639+s1na@users.noreply.github.com>
1 parent e4a8ecb commit 62aa6b2

File tree

6 files changed

+1299
-0
lines changed

6 files changed

+1299
-0
lines changed
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright 2025 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package tracetest
18+
19+
import (
20+
"encoding/json"
21+
"math/big"
22+
"os"
23+
"path/filepath"
24+
"strings"
25+
"testing"
26+
27+
"github.com/ethereum/go-ethereum/common"
28+
"github.com/ethereum/go-ethereum/common/hexutil"
29+
"github.com/ethereum/go-ethereum/core"
30+
"github.com/ethereum/go-ethereum/core/rawdb"
31+
"github.com/ethereum/go-ethereum/core/state"
32+
"github.com/ethereum/go-ethereum/core/types"
33+
"github.com/ethereum/go-ethereum/core/vm"
34+
"github.com/ethereum/go-ethereum/eth/tracers"
35+
"github.com/ethereum/go-ethereum/tests"
36+
"github.com/stretchr/testify/require"
37+
)
38+
39+
type accessedSlots struct {
40+
Reads map[string][]string `json:"reads"`
41+
Writes map[string]uint64 `json:"writes"`
42+
TransientReads map[string]uint64 `json:"transientReads"`
43+
TransientWrites map[string]uint64 `json:"transientWrites"`
44+
}
45+
type contractSizeWithOpcode struct {
46+
ContractSize int `json:"contractSize"`
47+
Opcode vm.OpCode `json:"opcode"`
48+
}
49+
50+
// erc7562Trace is the result of a erc7562Tracer run.
51+
type erc7562Trace struct {
52+
From common.Address `json:"from"`
53+
Gas *hexutil.Uint64 `json:"gas"`
54+
GasUsed *hexutil.Uint64 `json:"gasUsed"`
55+
To *common.Address `json:"to,omitempty" rlp:"optional"`
56+
Input hexutil.Bytes `json:"input" rlp:"optional"`
57+
Output hexutil.Bytes `json:"output,omitempty" rlp:"optional"`
58+
Error string `json:"error,omitempty" rlp:"optional"`
59+
RevertReason string `json:"revertReason,omitempty"`
60+
Logs []callLog `json:"logs,omitempty" rlp:"optional"`
61+
Value *hexutil.Big `json:"value,omitempty" rlp:"optional"`
62+
AccessedSlots accessedSlots `json:"accessedSlots"`
63+
ExtCodeAccessInfo []common.Address `json:"extCodeAccessInfo"`
64+
UsedOpcodes map[hexutil.Uint64]uint64 `json:"usedOpcodes"`
65+
ContractSize map[common.Address]*contractSizeWithOpcode `json:"contractSize"`
66+
OutOfGas bool `json:"outOfGas"`
67+
Calls []erc7562Trace `json:"calls,omitempty" rlp:"optional"`
68+
Keccak []hexutil.Bytes `json:"keccak,omitempty"`
69+
Type string `json:"type"`
70+
}
71+
72+
// erc7562TracerTest defines a single test to check the erc7562 tracer against.
73+
type erc7562TracerTest struct {
74+
tracerTestEnv
75+
Result *erc7562Trace `json:"result"`
76+
}
77+
78+
func TestErc7562Tracer(t *testing.T) {
79+
dirPath := "erc7562_tracer"
80+
tracerName := "erc7562Tracer"
81+
files, err := os.ReadDir(filepath.Join("testdata", dirPath))
82+
if err != nil {
83+
t.Fatalf("failed to retrieve tracer test suite: %v", err)
84+
}
85+
for _, file := range files {
86+
if !strings.HasSuffix(file.Name(), ".json") {
87+
continue
88+
}
89+
t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
90+
t.Parallel()
91+
92+
var (
93+
test = new(erc7562TracerTest)
94+
tx = new(types.Transaction)
95+
)
96+
// erc7562 tracer test found, read if from disk
97+
if blob, err := os.ReadFile(filepath.Join("testdata", dirPath, file.Name())); err != nil {
98+
t.Fatalf("failed to read testcase: %v", err)
99+
} else if err := json.Unmarshal(blob, test); err != nil {
100+
t.Fatalf("failed to parse testcase: %v", err)
101+
}
102+
if err := tx.UnmarshalBinary(common.FromHex(test.Input)); err != nil {
103+
t.Fatalf("failed to parse testcase input: %v", err)
104+
}
105+
// Configure a blockchain with the given prestate
106+
var (
107+
signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time))
108+
context = test.Context.toBlockContext(test.Genesis)
109+
st = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme)
110+
)
111+
st.Close()
112+
113+
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig, test.Genesis.Config)
114+
if err != nil {
115+
t.Fatalf("failed to create erc7562 tracer: %v", err)
116+
}
117+
logState := vm.StateDB(st.StateDB)
118+
if tracer.Hooks != nil {
119+
logState = state.NewHookedState(st.StateDB, tracer.Hooks)
120+
}
121+
msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
122+
if err != nil {
123+
t.Fatalf("failed to prepare transaction for tracing: %v", err)
124+
}
125+
evm := vm.NewEVM(context, logState, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
126+
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
127+
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
128+
if err != nil {
129+
t.Fatalf("failed to execute transaction: %v", err)
130+
}
131+
tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil)
132+
// Retrieve the trace result and compare against the expected.
133+
res, err := tracer.GetResult()
134+
if err != nil {
135+
t.Fatalf("failed to retrieve trace result: %v", err)
136+
}
137+
want, err := json.Marshal(test.Result)
138+
if err != nil {
139+
t.Fatalf("failed to marshal test: %v", err)
140+
}
141+
require.JSONEq(t, string(res), string(want))
142+
})
143+
}
144+
}

eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_deployer.json

Lines changed: 110 additions & 0 deletions
Large diffs are not rendered by default.

eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_paymaster.json

Lines changed: 110 additions & 0 deletions
Large diffs are not rendered by default.

eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_simple.json

Lines changed: 246 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)