8
8
"github.com/ethereum/go-ethereum/common/hexutil"
9
9
"github.com/ethereum/go-ethereum/rpc"
10
10
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/execution"
11
- "github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/reverts"
12
11
"github.com/stackup-wallet/stackup-bundler/pkg/errors"
13
12
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
14
13
)
@@ -25,38 +24,49 @@ func isExecutionOOG(err error) bool {
25
24
return strings .Contains (err .Error (), "execution OOG" )
26
25
}
27
26
28
- func runSimulations (rpc * rpc.Client ,
27
+ // EstimateGas uses the simulateHandleOp method on the EntryPoint to derive an estimate for
28
+ // verificationGasLimit and callGasLimit.
29
+ func EstimateGas (
30
+ rpc * rpc.Client ,
29
31
from common.Address ,
30
32
op * userop.UserOperation ,
33
+ ov * Overhead ,
31
34
chainID * big.Int ,
32
- tracer string ) (* reverts.ExecutionResultRevert , error ) {
35
+ tracer string ,
36
+ ) (verificationGas uint64 , callGas uint64 , err error ) {
37
+ // Skip if maxFeePerGas is zero.
38
+ if op .MaxFeePerGas .Cmp (big .NewInt (0 )) != 1 {
39
+ return 0 , 0 , errors .NewRPCError (
40
+ errors .INVALID_FIELDS ,
41
+ "maxFeePerGas must be more than 0" ,
42
+ nil ,
43
+ )
44
+ }
45
+
46
+ // Set the initial conditions.
33
47
data , err := op .ToMap ()
34
48
if err != nil {
35
- return nil , err
49
+ return 0 , 0 , err
36
50
}
37
-
38
- // Set MaxPriorityFeePerGas = MaxFeePerGas to simplify downstream calculations.
39
51
data ["maxPriorityFeePerGas" ] = hexutil .EncodeBig (op .MaxFeePerGas )
40
-
41
- // Setting default values for gas limits.
42
52
data ["verificationGasLimit" ] = hexutil .EncodeBig (big .NewInt (0 ))
43
53
data ["callGasLimit" ] = hexutil .EncodeBig (big .NewInt (0 ))
44
54
45
- // Maintain an outer reference to the latest simulation error.
46
- var simErr error
47
-
48
- // Find the maximal verificationGasLimit to simulate from.
55
+ // Find the optimal verificationGasLimit with binary search. Setting gas price to 0 and maxing out the gas
56
+ // limit here would result in certain code paths not being executed which results in an inaccurate gas
57
+ // estimate.
49
58
l := 0
50
59
r := MaxGasLimit
60
+ var simErr error
51
61
for l <= r {
52
62
m := (l + r ) / 2
53
63
54
64
data ["verificationGasLimit" ] = hexutil .EncodeBig (big .NewInt (int64 (m )))
55
65
simOp , err := userop .New (data )
56
66
if err != nil {
57
- return nil , err
67
+ return 0 , 0 , err
58
68
}
59
- sim , err := execution .TraceSimulateHandleOp (
69
+ sim , _ , err := execution .TraceSimulateHandleOp (
60
70
rpc ,
61
71
from ,
62
72
simOp ,
@@ -79,94 +89,72 @@ func runSimulations(rpc *rpc.Client,
79
89
}
80
90
// CGL is set to 0 and execution will always be OOG. Ignore it.
81
91
if ! isExecutionOOG (err ) {
82
- return nil , err
92
+ return 0 , 0 , err
83
93
}
84
94
}
85
95
86
- data ["verificationGasLimit" ] = hexutil .EncodeBig (sim .PreOpGas )
96
+ // Optimal VGL found.
97
+ data ["verificationGasLimit" ] = hexutil .EncodeBig (
98
+ big .NewInt (0 ).Sub (sim .PreOpGas , op .PreVerificationGas ),
99
+ )
87
100
break
88
101
}
89
102
if simErr != nil && ! isExecutionOOG (simErr ) {
90
- return nil , simErr
103
+ return 0 , 0 , simErr
91
104
}
92
105
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
106
+ // Find the optimal callGasLimit by setting the gas price to 0 and maxing out the gas limit. We don't run
107
+ // into the same restrictions here as we do with verificationGasLimit.
108
+ data ["maxFeePerGas" ] = hexutil .EncodeBig (big .NewInt (0 ))
109
+ data ["maxPriorityFeePerGas" ] = hexutil .EncodeBig (big .NewInt (0 ))
110
+ data ["callGasLimit" ] = hexutil .EncodeBig (big .NewInt (int64 (MaxGasLimit )))
111
+ simOp , err := userop .New (data )
112
+ if err != nil {
113
+ return 0 , 0 , err
131
114
}
132
- if simErr != nil {
133
- return nil , simErr
115
+ sim , ev , err := execution .TraceSimulateHandleOp (
116
+ rpc ,
117
+ from ,
118
+ simOp ,
119
+ chainID ,
120
+ tracer ,
121
+ common.Address {},
122
+ []byte {},
123
+ )
124
+ if err != nil {
125
+ return 0 , 0 , err
134
126
}
135
127
136
- return res , nil
137
- }
138
-
139
- // EstimateGas uses the simulateHandleOp method on the EntryPoint to derive an estimate for
140
- // verificationGasLimit and callGasLimit.
141
- func EstimateGas (
142
- rpc * rpc.Client ,
143
- from common.Address ,
144
- op * userop.UserOperation ,
145
- ov * Overhead ,
146
- chainID * big.Int ,
147
- tracer string ,
148
- ) (verificationGas uint64 , callGas uint64 , err error ) {
149
- // Skip if maxFeePerGas is zero.
150
- if op .MaxFeePerGas .Cmp (big .NewInt (0 )) != 1 {
151
- return 0 , 0 , errors .NewRPCError (
152
- errors .INVALID_FIELDS ,
153
- "maxFeePerGas must be more than 0" ,
154
- nil ,
155
- )
128
+ // Calculate final values for verificationGasLimit and callGasLimit.
129
+ vgl := simOp .VerificationGasLimit
130
+ cgl := big .NewInt (0 ).
131
+ Add (big .NewInt (0 ).Sub (ev .ActualGasUsed , sim .PreOpGas ), big .NewInt (int64 (ov .intrinsicFixed )))
132
+ min := ov .NonZeroValueCall ()
133
+ if min .Cmp (cgl ) >= 1 {
134
+ cgl = min
156
135
}
157
136
158
- // Estimate gas limits using a binary search approach.
159
- sim , err := runSimulations (rpc , from , op , chainID , tracer )
137
+ // Run a final simulation to check wether or not value transfers are still okay when factoring in the
138
+ // expected gas cost.
139
+ data ["maxFeePerGas" ] = hexutil .EncodeBig (op .MaxFeePerGas )
140
+ data ["maxPriorityFeePerGas" ] = hexutil .EncodeBig (op .MaxFeePerGas )
141
+ data ["verificationGasLimit" ] = hexutil .EncodeBig (vgl )
142
+ data ["callGasLimit" ] = hexutil .EncodeBig (cgl )
143
+ simOp , err = userop .New (data )
160
144
if err != nil {
161
145
return 0 , 0 , err
162
146
}
163
-
164
- // Return verificationGasLimit and callGasLimit.
165
- tg := big .NewInt (0 ).Div (sim .Paid , op .MaxFeePerGas )
166
- cgl := big .NewInt (0 ).Add (big .NewInt (0 ).Sub (tg , sim .PreOpGas ), big .NewInt (int64 (ov .intrinsicFixed )))
167
- min := ov .NonZeroValueCall ()
168
- if cgl .Cmp (min ) >= 1 {
169
- return sim .PreOpGas .Uint64 (), cgl .Uint64 (), nil
147
+ _ , _ , err = execution .TraceSimulateHandleOp (
148
+ rpc ,
149
+ from ,
150
+ simOp ,
151
+ chainID ,
152
+ tracer ,
153
+ common.Address {},
154
+ []byte {},
155
+ )
156
+ if err != nil {
157
+ return 0 , 0 , err
170
158
}
171
- return sim . PreOpGas . Uint64 (), min .Uint64 (), nil
159
+ return vgl . Uint64 (), cgl .Uint64 (), nil
172
160
}
0 commit comments