Skip to content
This repository was archived by the owner on Oct 20, 2024. It is now read-only.

Commit c076254

Browse files
authored
Use custom JS tracing (#54)
1 parent 78964ac commit c076254

File tree

8 files changed

+299
-81
lines changed

8 files changed

+299
-81
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ See the `Bundler` documentation at [docs.stackup.sh](https://docs.stackup.sh/doc
1313
## Prerequisites
1414

1515
- Go 1.19 or later
16+
- Access to a node with `debug` API enabled for custom tracing.
1617

1718
## Setup
1819

internal/config/values.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/gin-gonic/gin"
1010
"github.com/spf13/viper"
1111
"github.com/stackup-wallet/stackup-bundler/pkg/signer"
12+
"github.com/stackup-wallet/stackup-bundler/pkg/tracer"
1213
)
1314

1415
type Values struct {
@@ -22,7 +23,8 @@ type Values struct {
2223
Beneficiary string
2324

2425
// Undocumented variables.
25-
GinMode string
26+
GinMode string
27+
BundlerCollectorTracer string
2628
}
2729

2830
func envArrayToAddressSlice(s string) []common.Address {
@@ -85,15 +87,22 @@ func GetValues() *Values {
8587
viper.SetDefault("erc4337_bundler_beneficiary", s.Address.String())
8688
}
8789

90+
// Load js tracer from embedded file
91+
bct, err := tracer.Load()
92+
if err != nil {
93+
panic(err)
94+
}
95+
8896
// Return Values
8997
return &Values{
90-
PrivateKey: viper.GetString("erc4337_bundler_private_key"),
91-
EthClientUrl: viper.GetString("erc4337_bundler_eth_client_url"),
92-
Port: viper.GetInt("erc4337_bundler_port"),
93-
DataDirectory: viper.GetString("erc4337_bundler_data_directory"),
94-
SupportedEntryPoints: envArrayToAddressSlice(viper.GetString("erc4337_bundler_supported_entry_points")),
95-
Beneficiary: viper.GetString("erc4337_bundler_beneficiary"),
96-
MaxVerificationGas: big.NewInt(int64(viper.GetInt("erc4337_bundler_max_verification_gas"))),
97-
GinMode: viper.GetString("erc4337_bundler_gin_mode"),
98+
PrivateKey: viper.GetString("erc4337_bundler_private_key"),
99+
EthClientUrl: viper.GetString("erc4337_bundler_eth_client_url"),
100+
Port: viper.GetInt("erc4337_bundler_port"),
101+
DataDirectory: viper.GetString("erc4337_bundler_data_directory"),
102+
SupportedEntryPoints: envArrayToAddressSlice(viper.GetString("erc4337_bundler_supported_entry_points")),
103+
Beneficiary: viper.GetString("erc4337_bundler_beneficiary"),
104+
MaxVerificationGas: big.NewInt(int64(viper.GetInt("erc4337_bundler_max_verification_gas"))),
105+
GinMode: viper.GetString("erc4337_bundler_gin_mode"),
106+
BundlerCollectorTracer: bct,
98107
}
99108
}

internal/start/private.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func PrivateMode() {
7777
log.Fatal(err)
7878
}
7979

80-
check := checks.New(rpc, conf.MaxVerificationGas)
80+
check := checks.New(rpc, conf.MaxVerificationGas, conf.BundlerCollectorTracer)
8181
relayer := relay.New(db, eoa, eth, chain, beneficiary, logr)
8282
paymaster := paymaster.New(db)
8383

pkg/entrypoint/simulation.go

Lines changed: 45 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -14,44 +14,28 @@ import (
1414
"github.com/ethereum/go-ethereum/crypto"
1515
"github.com/ethereum/go-ethereum/ethclient"
1616
"github.com/ethereum/go-ethereum/rpc"
17+
"github.com/stackup-wallet/stackup-bundler/pkg/tracer"
1718
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
1819
)
1920

2021
var (
2122
// A dummy private key used to build *bind.TransactOpts for simulation.
2223
dummyPk, _ = crypto.GenerateKey()
2324

24-
// A marker to delimit between account and paymaster simulation.
25-
markerOpCode = "NUMBER"
25+
// Pre number marker represents account validation.
26+
accountNumberLevel = "0"
2627

27-
// Pre-marker represents account validation.
28-
preMarker = "account"
29-
30-
// Post-marker represents paymaster validation.
31-
postMarker = "paymaster"
32-
33-
// All opcodes executed at this depth are from the EntryPoint and allowed.
34-
allowedDepth = float64(1)
35-
36-
// The gas opcode is only allowed if followed immediately by callOpcodes.
37-
gasOpCode = "GAS"
28+
// Post number marker represents paymaster validation.
29+
paymasterNumberLevel = "1"
3830

3931
// Only one create2 opcode is allowed if these two conditions are met:
4032
// 1. op.initcode.length != 0
4133
// 2. During account simulation (i.e. before markerOpCode)
4234
create2OpCode = "CREATE2"
4335

44-
// List of opcodes related to CALL.
45-
callOpcodes = mapset.NewSet(
46-
"CALL",
47-
"DELEGATECALL",
48-
"CALLCODE",
49-
"STATICCALL",
50-
)
51-
52-
// List of opcodes not allowed during simulation for depth > allowedDepth (i.e. account, paymaster, or
53-
// contracts called by them).
54-
baseForbiddenOpCodes = mapset.NewSet(
36+
// List of opcodes not allowed during simulation for depth > 1 (i.e. account, paymaster, or contracts
37+
// called by them).
38+
bannedOpCodes = mapset.NewSet(
5539
"GASPRICE",
5640
"GASLIMIT",
5741
"DIFFICULTY",
@@ -62,6 +46,7 @@ var (
6246
"SELFBALANCE",
6347
"BALANCE",
6448
"ORIGIN",
49+
"GAS",
6550
"CREATE",
6651
"COINBASE",
6752
)
@@ -94,36 +79,25 @@ func SimulateValidation(rpc *rpc.Client, entryPoint common.Address, op *userop.U
9479
return sim, nil
9580
}
9681

97-
type structLog struct {
98-
Depth float64 `json:"depth"`
99-
Gas float64 `json:"gas"`
100-
GasCost float64 `json:"gasCost"`
101-
Op string `json:"op"`
102-
Pc float64 `json:"pc"`
103-
Stack []string `json:"stack"`
104-
}
105-
106-
type traceCallRes struct {
107-
Failed bool `json:"failed"`
108-
Gas float64 `json:"gas"`
109-
ReturnValue []byte `json:"returnValue"`
110-
StructLogs []structLog `json:"structLogs"`
111-
}
112-
11382
type traceCallReq struct {
11483
From common.Address `json:"from"`
11584
To common.Address `json:"to"`
11685
Data hexutil.Bytes `json:"data"`
11786
}
11887

11988
type traceCallOpts struct {
120-
DisableStorage bool `json:"disableStorage"`
121-
DisableMemory bool `json:"disableMemory"`
89+
Tracer string `json:"tracer"`
12290
}
12391

12492
// TraceSimulateValidation makes a debug_traceCall to Entrypoint.simulateValidation(userop) and returns the
12593
// results without any state changes.
126-
func TraceSimulateValidation(rpc *rpc.Client, entryPoint common.Address, op *userop.UserOperation, chainID *big.Int) error {
94+
func TraceSimulateValidation(
95+
rpc *rpc.Client,
96+
entryPoint common.Address,
97+
op *userop.UserOperation,
98+
chainID *big.Int,
99+
customTracer string,
100+
) error {
127101
ep, err := NewEntrypoint(entryPoint, ethclient.NewClient(rpc))
128102
if err != nil {
129103
return err
@@ -139,48 +113,51 @@ func TraceSimulateValidation(rpc *rpc.Client, entryPoint common.Address, op *use
139113
return err
140114
}
141115

142-
var res traceCallRes
143116
req := traceCallReq{
144117
From: common.HexToAddress("0x"),
145118
To: entryPoint,
146119
Data: tx.Data(),
147120
}
121+
122+
var res tracer.BundlerCollectorReturn
148123
opts := traceCallOpts{
149-
DisableStorage: false,
150-
DisableMemory: false,
124+
Tracer: customTracer,
151125
}
152126
if err := rpc.CallContext(context.Background(), &res, "debug_traceCall", &req, "latest", &opts); err != nil {
153127
return err
154128
}
155129

156-
var prev structLog
157-
create2count := 0
158-
simFor := preMarker
159-
for _, sl := range res.StructLogs {
160-
if sl.Depth == allowedDepth {
161-
if sl.Op == markerOpCode {
162-
simFor = postMarker
163-
}
164-
continue
165-
}
130+
var accountOpCodes, paymasterOpCodes tracer.Counts
131+
if len(res.NumberLevels) == 1 {
132+
accountOpCodes = res.NumberLevels[accountNumberLevel].Opcodes
133+
paymasterOpCodes = make(tracer.Counts)
134+
} else if len(res.NumberLevels) == 2 {
135+
accountOpCodes = res.NumberLevels[accountNumberLevel].Opcodes
136+
paymasterOpCodes = res.NumberLevels[paymasterNumberLevel].Opcodes
137+
} else {
138+
return fmt.Errorf("unexpected tracing result for op: %s", op.GetUserOpHash(entryPoint, chainID))
139+
}
166140

167-
if prev.Op == gasOpCode && !callOpcodes.Contains(sl.Op) {
168-
return fmt.Errorf("%s: uses opcode %s incorrectly", simFor, gasOpCode)
141+
for key := range accountOpCodes {
142+
if bannedOpCodes.Contains(key) {
143+
return fmt.Errorf("account contains banned opcode: %s", key)
169144
}
145+
}
170146

171-
if sl.Op == create2OpCode {
172-
create2count++
173-
174-
if create2count > 1 || len(op.InitCode) == 0 || simFor != preMarker {
175-
return fmt.Errorf("%s: uses opcode %s incorrectly", simFor, sl.Op)
176-
}
147+
for key := range paymasterOpCodes {
148+
if bannedOpCodes.Contains(key) {
149+
return fmt.Errorf("paymaster contains banned opcode: %s", key)
177150
}
151+
}
178152

179-
if baseForbiddenOpCodes.Contains(sl.Op) {
180-
return fmt.Errorf("%s: uses forbidden opcode %s", simFor, sl.Op)
181-
}
153+
create2Count, ok := accountOpCodes[create2OpCode]
154+
if ok && (create2Count > 1 || len(op.InitCode) == 0) {
155+
return fmt.Errorf("account with too many %s", create2OpCode)
156+
}
182157

183-
prev = sl
158+
_, ok = paymasterOpCodes[create2OpCode]
159+
if ok {
160+
return fmt.Errorf("paymaster uses banned %s opcode: %s", create2OpCode, op.GetPaymaster())
184161
}
185162

186163
return nil

pkg/modules/checks/standalone.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ type Standalone struct {
2020
rpc *rpc.Client
2121
eth *ethclient.Client
2222
maxVerificationGas *big.Int
23+
tracer string
2324
}
2425

2526
// New returns a Standalone instance with methods that can be used in Client and Bundler modules to perform
2627
// standard checks as specified in EIP-4337.
27-
func New(rpc *rpc.Client, maxVerificationGas *big.Int) *Standalone {
28+
func New(rpc *rpc.Client, maxVerificationGas *big.Int, tracer string) *Standalone {
2829
eth := ethclient.NewClient(rpc)
29-
return &Standalone{rpc, eth, maxVerificationGas}
30+
return &Standalone{rpc, eth, maxVerificationGas, tracer}
3031
}
3132

3233
// ValidateOpValues returns a UserOpHandler that runs through some first line sanity checks for new UserOps
@@ -58,7 +59,7 @@ func (s *Standalone) SimulateOp() modules.UserOpHandlerFunc {
5859
return err
5960
})
6061
g.Go(func() error {
61-
return entrypoint.TraceSimulateValidation(s.rpc, ctx.EntryPoint, ctx.UserOp, ctx.ChainID)
62+
return entrypoint.TraceSimulateValidation(s.rpc, ctx.EntryPoint, ctx.UserOp, ctx.ChainID, s.tracer)
6263
})
6364

6465
return g.Wait()

0 commit comments

Comments
 (0)