1
1
package entrypoint
2
2
3
3
import (
4
+ "context"
4
5
"errors"
5
6
"fmt"
7
+ "math"
8
+ "math/big"
6
9
10
+ mapset "github.com/deckarep/golang-set/v2"
11
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
7
12
"github.com/ethereum/go-ethereum/common"
13
+ "github.com/ethereum/go-ethereum/common/hexutil"
14
+ "github.com/ethereum/go-ethereum/crypto"
8
15
"github.com/ethereum/go-ethereum/ethclient"
16
+ "github.com/ethereum/go-ethereum/rpc"
9
17
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
10
18
)
11
19
20
+ var (
21
+ // A dummy private key used to build *bind.TransactOpts for simulation.
22
+ dummyPk , _ = crypto .GenerateKey ()
23
+
24
+ // A marker to delimit between account and paymaster simulation.
25
+ markerOpCode = "NUMBER"
26
+
27
+ // All opcodes executed at this depth are from the EntryPoint and allowed.
28
+ allowedDepth = float64 (1 )
29
+
30
+ // The gas opcode is only allowed if followed immediately by callOpcodes.
31
+ // gasOpCode = "GAS"
32
+
33
+ // Only one create2 opcode is allowed if these two conditions are met:
34
+ // 1. op.initcode.length != 0
35
+ // 2. During account simulation (i.e. before markerOpCode)
36
+ // create2OpCode = "CREATE2"
37
+
38
+ // List of opcodes related to CALL.
39
+ // callOpcodes = mapset.NewSet(
40
+ // "CALL",
41
+ // "DELEGATECALL",
42
+ // "CALLCODE",
43
+ // "STATICCALL",
44
+ // )
45
+
46
+ // List of opcodes not allowed during simulation for depth > allowedDepth (i.e. account, paymaster, or
47
+ // contracts called by them).
48
+ baseForbiddenOpCodes = mapset .NewSet (
49
+ "GASPRICE" ,
50
+ "GASLIMIT" ,
51
+ "DIFFICULTY" ,
52
+ "TIMESTAMP" ,
53
+ "BASEFEE" ,
54
+ "BLOCKHASH" ,
55
+ "NUMBER" ,
56
+ "SELFBALANCE" ,
57
+ "BALANCE" ,
58
+ "ORIGIN" ,
59
+ "CREATE" ,
60
+ "COINBASE" ,
61
+ )
62
+ )
63
+
12
64
// SimulateValidation makes a static call to Entrypoint.simulateValidation(userop) and returns the
13
65
// results without any state changes.
14
- func SimulateValidation (eth * ethclient .Client , entryPoint common.Address , op * userop.UserOperation ) (* SimulationResultRevert , error ) {
15
- ep , err := NewEntrypoint (entryPoint , eth )
66
+ func SimulateValidation (rpc * rpc .Client , entryPoint common.Address , op * userop.UserOperation ) (* SimulationResultRevert , error ) {
67
+ ep , err := NewEntrypoint (entryPoint , ethclient . NewClient ( rpc ) )
16
68
if err != nil {
17
69
return nil , err
18
70
}
@@ -33,6 +85,82 @@ func SimulateValidation(eth *ethclient.Client, entryPoint common.Address, op *us
33
85
return nil , errors .New (fo .Reason )
34
86
}
35
87
36
- // TODO: Trace forbidden opcodes
37
88
return sim , nil
38
89
}
90
+
91
+ type structLog struct {
92
+ Depth float64 `json:"depth"`
93
+ Gas float64 `json:"gas"`
94
+ GasCost float64 `json:"gasCost"`
95
+ Op string `json:"op"`
96
+ Pc float64 `json:"pc"`
97
+ Stack []string `json:"stack"`
98
+ }
99
+
100
+ type traceCallRes struct {
101
+ Failed bool `json:"failed"`
102
+ Gas float64 `json:"gas"`
103
+ ReturnValue []byte `json:"returnValue"`
104
+ StructLogs []structLog `json:"structLogs"`
105
+ }
106
+
107
+ type traceCallReq struct {
108
+ From common.Address `json:"from"`
109
+ To common.Address `json:"to"`
110
+ Data hexutil.Bytes `json:"data"`
111
+ }
112
+
113
+ type traceCallOpts struct {
114
+ DisableStorage bool `json:"disableStorage"`
115
+ DisableMemory bool `json:"disableMemory"`
116
+ }
117
+
118
+ // TraceSimulateValidation makes a debug_traceCall to Entrypoint.simulateValidation(userop) and returns the
119
+ // results without any state changes.
120
+ func TraceSimulateValidation (rpc * rpc.Client , entryPoint common.Address , op * userop.UserOperation , chainID * big.Int ) error {
121
+ ep , err := NewEntrypoint (entryPoint , ethclient .NewClient (rpc ))
122
+ if err != nil {
123
+ return err
124
+ }
125
+ auth , err := bind .NewKeyedTransactorWithChainID (dummyPk , chainID )
126
+ if err != nil {
127
+ return err
128
+ }
129
+ auth .GasLimit = math .MaxUint64
130
+ auth .NoSend = true
131
+ tx , err := ep .SimulateValidation (auth , UserOperation (* op ))
132
+ if err != nil {
133
+ return err
134
+ }
135
+
136
+ var res traceCallRes
137
+ req := traceCallReq {
138
+ From : common .HexToAddress ("0x" ),
139
+ To : entryPoint ,
140
+ Data : tx .Data (),
141
+ }
142
+ opts := traceCallOpts {
143
+ DisableStorage : false ,
144
+ DisableMemory : false ,
145
+ }
146
+ if err := rpc .CallContext (context .Background (), & res , "debug_traceCall" , & req , "latest" , & opts ); err != nil {
147
+ return err
148
+ }
149
+
150
+ simFor := "account"
151
+ for _ , sl := range res .StructLogs {
152
+ if sl .Depth == allowedDepth && sl .Op == markerOpCode {
153
+ simFor = "paymaster"
154
+ }
155
+
156
+ if sl .Depth == allowedDepth {
157
+ continue
158
+ }
159
+
160
+ if baseForbiddenOpCodes .Contains (sl .Op ) {
161
+ return fmt .Errorf ("%s: uses forbidden opcode %s" , simFor , sl .Op )
162
+ }
163
+ }
164
+
165
+ return nil
166
+ }
0 commit comments