@@ -2,15 +2,140 @@ package gas
2
2
3
3
import (
4
4
"math/big"
5
+ "strings"
5
6
6
7
"github.com/ethereum/go-ethereum/common"
7
8
"github.com/ethereum/go-ethereum/common/hexutil"
8
9
"github.com/ethereum/go-ethereum/rpc"
9
10
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/execution"
11
+ "github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/reverts"
10
12
"github.com/stackup-wallet/stackup-bundler/pkg/errors"
11
13
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
12
14
)
13
15
16
+ func isPrefundNotPaid (err error ) bool {
17
+ return strings .HasPrefix (err .Error (), "AA21" ) || strings .HasPrefix (err .Error (), "AA31" )
18
+ }
19
+
20
+ func isValidationOOG (err error ) bool {
21
+ return strings .HasPrefix (err .Error (), "AA13" ) || strings .Contains (err .Error (), "validation OOG" )
22
+ }
23
+
24
+ func isExecutionOOG (err error ) bool {
25
+ return strings .Contains (err .Error (), "execution OOG" )
26
+ }
27
+
28
+ func runSimulations (rpc * rpc.Client ,
29
+ from common.Address ,
30
+ op * userop.UserOperation ,
31
+ chainID * big.Int ,
32
+ tracer string ) (* reverts.ExecutionResultRevert , error ) {
33
+ data , err := op .ToMap ()
34
+ if err != nil {
35
+ return nil , err
36
+ }
37
+
38
+ // Set MaxPriorityFeePerGas = MaxFeePerGas to simplify downstream calculations.
39
+ data ["maxPriorityFeePerGas" ] = hexutil .EncodeBig (op .MaxFeePerGas )
40
+
41
+ // Setting default values for gas limits.
42
+ data ["verificationGasLimit" ] = hexutil .EncodeBig (big .NewInt (0 ))
43
+ data ["callGasLimit" ] = hexutil .EncodeBig (big .NewInt (0 ))
44
+
45
+ // Maintain an outer reference to the latest simulation error.
46
+ var simErr error
47
+
48
+ // Find the maximal verificationGasLimit to simulate from.
49
+ l := 0
50
+ r := MaxGasLimit
51
+ for l <= r {
52
+ m := (l + r ) / 2
53
+
54
+ data ["verificationGasLimit" ] = hexutil .EncodeBig (big .NewInt (int64 (m )))
55
+ simOp , err := userop .New (data )
56
+ if err != nil {
57
+ return nil , err
58
+ }
59
+ sim , err := execution .TraceSimulateHandleOp (
60
+ rpc ,
61
+ from ,
62
+ simOp ,
63
+ chainID ,
64
+ tracer ,
65
+ common.Address {},
66
+ []byte {},
67
+ )
68
+ simErr = err
69
+ if err != nil {
70
+ if isPrefundNotPaid (err ) {
71
+ // VGL too high, go lower.
72
+ r = m - 1
73
+ continue
74
+ }
75
+ if isValidationOOG (err ) {
76
+ // VGL too low, go higher.
77
+ l = m + 1
78
+ continue
79
+ }
80
+ // CGL is set to 0 and execution will always be OOG. Ignore it.
81
+ if ! isExecutionOOG (err ) {
82
+ return nil , err
83
+ }
84
+ }
85
+
86
+ data ["verificationGasLimit" ] = hexutil .EncodeBig (sim .PreOpGas )
87
+ break
88
+ }
89
+ if simErr != nil && ! isExecutionOOG (simErr ) {
90
+ return nil , simErr
91
+ }
92
+
93
+ // Find the maximal callGasLimit to simulate from.
94
+ l = 0
95
+ r = MaxGasLimit
96
+ var res * reverts.ExecutionResultRevert
97
+ for l <= r {
98
+ m := (l + r ) / 2
99
+
100
+ data ["callGasLimit" ] = hexutil .EncodeBig (big .NewInt (int64 (m )))
101
+ simOp , err := userop .New (data )
102
+ if err != nil {
103
+ return nil , err
104
+ }
105
+ sim , err := execution .TraceSimulateHandleOp (
106
+ rpc ,
107
+ from ,
108
+ simOp ,
109
+ chainID ,
110
+ tracer ,
111
+ common.Address {},
112
+ []byte {},
113
+ )
114
+ simErr = err
115
+ if err != nil {
116
+ if isPrefundNotPaid (err ) {
117
+ // CGL too high, go lower.
118
+ r = m - 1
119
+ continue
120
+ }
121
+ if isExecutionOOG (err ) {
122
+ // CGL too low, go higher.
123
+ l = m + 1
124
+ continue
125
+ }
126
+ return nil , err
127
+ }
128
+
129
+ res = sim
130
+ break
131
+ }
132
+ if simErr != nil {
133
+ return nil , simErr
134
+ }
135
+
136
+ return res , nil
137
+ }
138
+
14
139
// EstimateGas uses the simulateHandleOp method on the EntryPoint to derive an estimate for
15
140
// verificationGasLimit and callGasLimit.
16
141
func EstimateGas (
@@ -21,30 +146,22 @@ func EstimateGas(
21
146
chainID * big.Int ,
22
147
tracer string ,
23
148
) (verificationGas uint64 , callGas uint64 , err error ) {
149
+ // Skip if maxFeePerGas is zero.
24
150
if op .MaxFeePerGas .Cmp (big .NewInt (0 )) != 1 {
25
151
return 0 , 0 , errors .NewRPCError (
26
152
errors .INVALID_FIELDS ,
27
153
"maxFeePerGas must be more than 0" ,
28
154
nil ,
29
155
)
30
156
}
31
- data , err := op .ToMap ()
32
- if err != nil {
33
- return 0 , 0 , err
34
- }
35
-
36
- // Set MaxPriorityFeePerGas = MaxFeePerGas to simplify callGasLimit calculation.
37
- data ["maxPriorityFeePerGas" ] = hexutil .EncodeBig (op .MaxFeePerGas )
38
- simOp , err := userop .New (data )
39
- if err != nil {
40
- return 0 , 0 , err
41
- }
42
157
43
- sim , err := execution .TraceSimulateHandleOp (rpc , from , simOp , chainID , tracer , common.Address {}, []byte {})
158
+ // Estimate gas limits using a binary search approach.
159
+ sim , err := runSimulations (rpc , from , op , chainID , tracer )
44
160
if err != nil {
45
161
return 0 , 0 , err
46
162
}
47
163
164
+ // Return verificationGasLimit and callGasLimit.
48
165
tg := big .NewInt (0 ).Div (sim .Paid , op .MaxFeePerGas )
49
166
cgl := big .NewInt (0 ).Add (big .NewInt (0 ).Sub (tg , sim .PreOpGas ), big .NewInt (int64 (ov .fixed )))
50
167
min := ov .NonZeroValueCall ()
0 commit comments