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

Commit 9f9a40f

Browse files
authored
Use a binary search approach to determine gas limits (#169)
1 parent a42d516 commit 9f9a40f

File tree

5 files changed

+173
-17
lines changed

5 files changed

+173
-17
lines changed

pkg/entrypoint/execution/trace.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ func TraceSimulateHandleOp(
5959
if err != nil {
6060
return nil, err
6161
}
62+
if res.ValidationOOG {
63+
return nil, errors.NewRPCError(errors.EXECUTION_REVERTED, "validation OOG", nil)
64+
}
6265

6366
sim, simErr := reverts.NewExecutionResult(outErr)
6467
if simErr != nil {
@@ -76,6 +79,9 @@ func TraceSimulateHandleOp(
7679
}
7780

7881
if len(data) == 0 {
82+
if res.ExecutionOOG {
83+
return sim, errors.NewRPCError(errors.EXECUTION_REVERTED, "execution OOG", nil)
84+
}
7985
return sim, errors.NewRPCError(errors.EXECUTION_REVERTED, "execution reverted", nil)
8086
}
8187

pkg/gas/estimate.go

Lines changed: 129 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,140 @@ package gas
22

33
import (
44
"math/big"
5+
"strings"
56

67
"github.com/ethereum/go-ethereum/common"
78
"github.com/ethereum/go-ethereum/common/hexutil"
89
"github.com/ethereum/go-ethereum/rpc"
910
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/execution"
11+
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/reverts"
1012
"github.com/stackup-wallet/stackup-bundler/pkg/errors"
1113
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
1214
)
1315

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+
14139
// EstimateGas uses the simulateHandleOp method on the EntryPoint to derive an estimate for
15140
// verificationGasLimit and callGasLimit.
16141
func EstimateGas(
@@ -21,30 +146,22 @@ func EstimateGas(
21146
chainID *big.Int,
22147
tracer string,
23148
) (verificationGas uint64, callGas uint64, err error) {
149+
// Skip if maxFeePerGas is zero.
24150
if op.MaxFeePerGas.Cmp(big.NewInt(0)) != 1 {
25151
return 0, 0, errors.NewRPCError(
26152
errors.INVALID_FIELDS,
27153
"maxFeePerGas must be more than 0",
28154
nil,
29155
)
30156
}
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-
}
42157

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)
44160
if err != nil {
45161
return 0, 0, err
46162
}
47163

164+
// Return verificationGasLimit and callGasLimit.
48165
tg := big.NewInt(0).Div(sim.Paid, op.MaxFeePerGas)
49166
cgl := big.NewInt(0).Add(big.NewInt(0).Sub(tg, sim.PreOpGas), big.NewInt(int64(ov.fixed)))
50167
min := ov.NonZeroValueCall()

pkg/gas/values.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package gas
2+
3+
var (
4+
// The maximum value to start a binary search from. A lower value reduces the upper limit of RPC calls to
5+
// the debug_traceCall method. This value is the current gas limit of a block.
6+
MaxGasLimit = 30000000
7+
)

pkg/tracer/BundlerErrorTracer.js

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,48 @@
11
var tracer = {
22
reverts: [],
3-
numberCounter: 0,
3+
validationOOG: false,
4+
executionOOG: false,
5+
6+
_marker: 0,
7+
_validationMarker: 1,
8+
_executionMarker: 3,
9+
10+
_isValidation: function () {
11+
return (
12+
this._marker >= this._validationMarker &&
13+
this._marker < this._executionMarker
14+
);
15+
},
16+
17+
_isExecution: function () {
18+
return this._marker === this._executionMarker;
19+
},
420

521
fault: function fault(log, db) {},
622
result: function result(ctx, db) {
723
return {
824
reverts: this.reverts,
25+
validationOOG: this.validationOOG,
26+
executionOOG: this.executionOOG,
927
output: toHex(ctx.output),
1028
};
1129
},
1230

1331
enter: function enter(frame) {},
1432
exit: function exit(frame) {
15-
if (frame.getError() !== undefined && this.numberCounter === 3) {
33+
if (frame.getError() !== undefined && this._isExecution()) {
1634
this.reverts.push(toHex(frame.getOutput()));
1735
}
1836
},
1937

2038
step: function step(log, db) {
2139
var opcode = log.op.toString();
22-
if (log.getDepth() === 1 && opcode === "NUMBER") this.numberCounter++;
40+
if (log.getDepth() === 1 && opcode === "NUMBER") this._marker++;
41+
42+
if (log.getGas() < log.getCost() && this._isValidation())
43+
this.validationOOG = true;
44+
45+
if (log.getGas() < log.getCost() && this._isExecution())
46+
this.executionOOG = true;
2347
},
2448
};

pkg/tracer/types.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ type BundlerCollectorReturn struct {
5656

5757
// BundlerErrorReturn is the return value from performing an EVM trace with BundlerErrorTracer.js.
5858
type BundlerErrorReturn struct {
59-
Reverts []string `json:"reverts"`
60-
Output string `json:"output"`
59+
Reverts []string `json:"reverts"`
60+
ValidationOOG bool `json:"validationOOG"`
61+
ExecutionOOG bool `json:"executionOOG"`
62+
Output string `json:"output"`
6163
}

0 commit comments

Comments
 (0)