88 "github.com/ethereum/go-ethereum/common/hexutil"
99 "github.com/ethereum/go-ethereum/rpc"
1010 "github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/execution"
11- "github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/reverts"
1211 "github.com/stackup-wallet/stackup-bundler/pkg/errors"
1312 "github.com/stackup-wallet/stackup-bundler/pkg/userop"
1413)
@@ -25,38 +24,49 @@ func isExecutionOOG(err error) bool {
2524 return strings .Contains (err .Error (), "execution OOG" )
2625}
2726
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 ,
2931 from common.Address ,
3032 op * userop.UserOperation ,
33+ ov * Overhead ,
3134 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.
3347 data , err := op .ToMap ()
3448 if err != nil {
35- return nil , err
49+ return 0 , 0 , err
3650 }
37-
38- // Set MaxPriorityFeePerGas = MaxFeePerGas to simplify downstream calculations.
3951 data ["maxPriorityFeePerGas" ] = hexutil .EncodeBig (op .MaxFeePerGas )
40-
41- // Setting default values for gas limits.
4252 data ["verificationGasLimit" ] = hexutil .EncodeBig (big .NewInt (0 ))
4353 data ["callGasLimit" ] = hexutil .EncodeBig (big .NewInt (0 ))
4454
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.
4958 l := 0
5059 r := MaxGasLimit
60+ var simErr error
5161 for l <= r {
5262 m := (l + r ) / 2
5363
5464 data ["verificationGasLimit" ] = hexutil .EncodeBig (big .NewInt (int64 (m )))
5565 simOp , err := userop .New (data )
5666 if err != nil {
57- return nil , err
67+ return 0 , 0 , err
5868 }
59- sim , err := execution .TraceSimulateHandleOp (
69+ sim , _ , err := execution .TraceSimulateHandleOp (
6070 rpc ,
6171 from ,
6272 simOp ,
@@ -79,94 +89,72 @@ func runSimulations(rpc *rpc.Client,
7989 }
8090 // CGL is set to 0 and execution will always be OOG. Ignore it.
8191 if ! isExecutionOOG (err ) {
82- return nil , err
92+ return 0 , 0 , err
8393 }
8494 }
8595
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+ )
87100 break
88101 }
89102 if simErr != nil && ! isExecutionOOG (simErr ) {
90- return nil , simErr
103+ return 0 , 0 , simErr
91104 }
92105
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
131114 }
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
134126 }
135127
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
156135 }
157136
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 )
160144 if err != nil {
161145 return 0 , 0 , err
162146 }
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
170158 }
171- return sim . PreOpGas . Uint64 (), min .Uint64 (), nil
159+ return vgl . Uint64 (), cgl .Uint64 (), nil
172160}
0 commit comments