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

Commit 848c994

Browse files
authored
Account for 63/64 rule in callGasLimit estimate (#207)
1 parent f6725b6 commit 848c994

File tree

5 files changed

+95
-74
lines changed

5 files changed

+95
-74
lines changed

pkg/entrypoint/execution/trace.go

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,26 @@ import (
2020
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
2121
)
2222

23+
type TraceInput struct {
24+
Rpc *ethRpc.Client
25+
EntryPoint common.Address
26+
Op *userop.UserOperation
27+
ChainID *big.Int
28+
29+
// Tracer for debug_traceCall
30+
CustomTracer string
31+
32+
// Optional params for simulateHandleOps
33+
Target common.Address
34+
Data []byte
35+
}
36+
37+
type TraceOutput struct {
38+
Trace *tracer.BundlerErrorReturn
39+
Result *reverts.ExecutionResultRevert
40+
Event *entrypoint.EntrypointUserOperationEvent
41+
}
42+
2343
func parseUserOperationEvent(
2444
entryPoint common.Address,
2545
ep *entrypoint.Entrypoint,
@@ -50,82 +70,79 @@ func parseUserOperationEvent(
5070
return ev, nil
5171
}
5272

53-
func TraceSimulateHandleOp(
54-
rpc *ethRpc.Client,
55-
entryPoint common.Address,
56-
op *userop.UserOperation,
57-
chainID *big.Int,
58-
customTracer string,
59-
target common.Address,
60-
data []byte,
61-
) (*reverts.ExecutionResultRevert, *entrypoint.EntrypointUserOperationEvent, error) {
62-
ep, err := entrypoint.NewEntrypoint(entryPoint, ethclient.NewClient(rpc))
73+
func TraceSimulateHandleOp(in *TraceInput) (*TraceOutput, error) {
74+
ep, err := entrypoint.NewEntrypoint(in.EntryPoint, ethclient.NewClient(in.Rpc))
6375
if err != nil {
64-
return nil, nil, err
76+
return nil, err
6577
}
66-
auth, err := bind.NewKeyedTransactorWithChainID(utils.DummyPk, chainID)
78+
auth, err := bind.NewKeyedTransactorWithChainID(utils.DummyPk, in.ChainID)
6779
if err != nil {
68-
return nil, nil, err
80+
return nil, err
6981
}
7082
auth.GasLimit = math.MaxUint64
7183
auth.NoSend = true
72-
tx, err := ep.SimulateHandleOp(auth, entrypoint.UserOperation(*op), target, data)
84+
tx, err := ep.SimulateHandleOp(auth, entrypoint.UserOperation(*in.Op), in.Target, in.Data)
7385
if err != nil {
74-
return nil, nil, err
86+
return nil, err
7587
}
88+
out := &TraceOutput{}
7689

7790
var res tracer.BundlerErrorReturn
7891
req := utils.TraceCallReq{
7992
From: common.HexToAddress("0x"),
80-
To: entryPoint,
93+
To: in.EntryPoint,
8194
Data: tx.Data(),
8295
}
8396
opts := utils.TraceCallOpts{
84-
Tracer: customTracer,
97+
Tracer: in.CustomTracer,
8598
}
86-
if err := rpc.CallContext(context.Background(), &res, "debug_traceCall", &req, "latest", &opts); err != nil {
87-
return nil, nil, err
99+
if err := in.Rpc.CallContext(context.Background(), &res, "debug_traceCall", &req, "latest", &opts); err != nil {
100+
return nil, err
88101
}
89102
outErr, err := errors.ParseHexToRpcDataError(res.Output)
90103
if err != nil {
91-
return nil, nil, err
104+
return nil, err
92105
}
93106
if res.ValidationOOG {
94-
return nil, nil, errors.NewRPCError(errors.EXECUTION_REVERTED, "validation OOG", nil)
107+
return nil, errors.NewRPCError(errors.EXECUTION_REVERTED, "validation OOG", nil)
95108
}
109+
out.Trace = &res
96110

97111
sim, simErr := reverts.NewExecutionResult(outErr)
98112
if simErr != nil {
99113
fo, foErr := reverts.NewFailedOp(outErr)
100114
if foErr != nil {
101-
return nil, nil, fmt.Errorf("%s, %s", simErr, foErr)
115+
return nil, fmt.Errorf("%s, %s", simErr, foErr)
102116
}
103-
return nil, nil, errors.NewRPCError(errors.REJECTED_BY_EP_OR_ACCOUNT, fo.Reason, fo)
117+
return nil, errors.NewRPCError(errors.REJECTED_BY_EP_OR_ACCOUNT, fo.Reason, fo)
104118
}
119+
out.Result = sim
105120

106121
if len(res.Reverts) != 0 {
107122
data, err := hexutil.Decode(res.Reverts[len(res.Reverts)-1])
108123
if err != nil {
109-
return sim, nil, err
124+
return out, err
110125
}
111126

112127
if len(data) == 0 {
113128
if res.ExecutionOOG {
114-
return sim, nil, errors.NewRPCError(errors.EXECUTION_REVERTED, "execution OOG", nil)
129+
return out, errors.NewRPCError(errors.EXECUTION_REVERTED, "execution OOG", nil)
115130
}
116-
return sim, nil, errors.NewRPCError(errors.EXECUTION_REVERTED, "execution reverted", nil)
131+
return out, errors.NewRPCError(errors.EXECUTION_REVERTED, "execution reverted", nil)
117132
}
118133

119134
reason, err := errors.DecodeRevert(data)
120135
if err != nil {
121-
return sim, nil, err
136+
return out, err
122137
}
123-
return sim, nil, errors.NewRPCError(errors.EXECUTION_REVERTED, reason, reason)
138+
return out, errors.NewRPCError(errors.EXECUTION_REVERTED, reason, reason)
124139
}
125140

126-
ev, err := parseUserOperationEvent(entryPoint, ep, res.UserOperationEvent)
141+
ev, err := parseUserOperationEvent(in.EntryPoint, ep, res.UserOperationEvent)
127142
if err != nil {
128-
return nil, nil, err
143+
return out, err
129144
}
130-
return sim, ev, nil
145+
out.Event = ev
146+
147+
return out, nil
131148
}

pkg/gas/estimate.go

Lines changed: 28 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,13 @@ func EstimateGas(
6666
if err != nil {
6767
return 0, 0, err
6868
}
69-
sim, _, err := execution.TraceSimulateHandleOp(
70-
rpc,
71-
from,
72-
simOp,
73-
chainID,
74-
tracer,
75-
common.Address{},
76-
[]byte{},
77-
)
69+
out, err := execution.TraceSimulateHandleOp(&execution.TraceInput{
70+
Rpc: rpc,
71+
EntryPoint: from,
72+
Op: simOp,
73+
ChainID: chainID,
74+
CustomTracer: tracer,
75+
})
7876
simErr = err
7977
if err != nil {
8078
if isPrefundNotPaid(err) {
@@ -95,7 +93,7 @@ func EstimateGas(
9593

9694
// Optimal VGL found.
9795
data["verificationGasLimit"] = hexutil.EncodeBig(
98-
big.NewInt(0).Sub(sim.PreOpGas, op.PreVerificationGas),
96+
big.NewInt(0).Sub(out.Result.PreOpGas, op.PreVerificationGas),
9997
)
10098
break
10199
}
@@ -112,26 +110,24 @@ func EstimateGas(
112110
if err != nil {
113111
return 0, 0, err
114112
}
115-
sim, ev, err := execution.TraceSimulateHandleOp(
116-
rpc,
117-
from,
118-
simOp,
119-
chainID,
120-
tracer,
121-
common.Address{},
122-
[]byte{},
123-
)
113+
out, err := execution.TraceSimulateHandleOp(&execution.TraceInput{
114+
Rpc: rpc,
115+
EntryPoint: from,
116+
Op: simOp,
117+
ChainID: chainID,
118+
CustomTracer: tracer,
119+
})
124120
if err != nil {
125121
return 0, 0, err
126122
}
127123

128124
// Calculate final values for verificationGasLimit and callGasLimit.
129125
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
126+
cg := big.NewInt(0).Sub(out.Event.ActualGasUsed, out.Result.PreOpGas)
127+
cgb := big.NewInt(int64(out.Trace.ExecutionGasBuffer))
128+
cgl := big.NewInt(0).Add(cg, cgb)
129+
if cgl.Cmp(ov.NonZeroValueCall()) < 0 {
130+
cgl = ov.NonZeroValueCall()
135131
}
136132

137133
// Run a final simulation to check wether or not value transfers are still okay when factoring in the
@@ -144,17 +140,15 @@ func EstimateGas(
144140
if err != nil {
145141
return 0, 0, err
146142
}
147-
_, _, err = execution.TraceSimulateHandleOp(
148-
rpc,
149-
from,
150-
simOp,
151-
chainID,
152-
tracer,
153-
common.Address{},
154-
[]byte{},
155-
)
143+
_, err = execution.TraceSimulateHandleOp(&execution.TraceInput{
144+
Rpc: rpc,
145+
EntryPoint: from,
146+
Op: simOp,
147+
ChainID: chainID,
148+
CustomTracer: tracer,
149+
})
156150
if err != nil {
157151
return 0, 0, err
158152
}
159-
return vgl.Uint64(), cgl.Uint64(), nil
153+
return simOp.VerificationGasLimit.Uint64(), simOp.CallGasLimit.Uint64(), nil
160154
}

pkg/gas/overhead.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type Overhead struct {
2020
nonZeroByte float64
2121
minBundleSize float64
2222
warmStorageRead float64
23-
nonZeroValueCall float64
23+
callWithValue float64
2424
callOpcode float64
2525
nonZeroValueStipend float64
2626
sanitizedPVG *big.Int
@@ -40,7 +40,7 @@ func NewDefaultOverhead() *Overhead {
4040
nonZeroByte: 16,
4141
minBundleSize: 1,
4242
warmStorageRead: 100,
43-
nonZeroValueCall: 9000,
43+
callWithValue: 9000,
4444
callOpcode: 700,
4545
nonZeroValueStipend: 2300,
4646
sanitizedPVG: big.NewInt(100000),
@@ -137,12 +137,12 @@ func (ov *Overhead) CalcPreVerificationGasWithBuffer(op *userop.UserOperation) (
137137
return utils.AddBuffer(pvg, ov.pvgBufferFactor), nil
138138
}
139139

140-
// NonZeroValueCall returns an expected gas cost of using the CALL opcode in the context of EIP-4337.
140+
// NonZeroValueCall returns an expected gas cost of using the CALL opcode with non-zero value.
141141
// See https://github.com/wolflo/evm-opcodes/blob/main/gas.md#aa-1-call.
142142
func (ov *Overhead) NonZeroValueCall() *big.Int {
143143
return big.NewInt(
144144
int64(
145-
ov.intrinsicFixed + ov.warmStorageRead + ov.nonZeroValueCall + ov.callOpcode + ov.nonZeroValueStipend,
145+
ov.callOpcode + ov.callWithValue + ov.warmStorageRead + ov.nonZeroValueStipend,
146146
),
147147
)
148148
}

pkg/tracer/BundlerErrorTracer.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ var tracer = {
22
reverts: [],
33
validationOOG: false,
44
executionOOG: false,
5+
executionGasBuffer: 0,
56

7+
_depth: 0,
68
_marker: 0,
79
_validationMarker: 1,
810
_executionMarker: 3,
@@ -46,25 +48,32 @@ var tracer = {
4648
reverts: this.reverts,
4749
validationOOG: this.validationOOG,
4850
executionOOG: this.executionOOG,
51+
executionGasBuffer: this.executionGasBuffer,
4952
userOperationEvent: this.userOperationEvent,
5053
output: toHex(ctx.output),
5154
};
5255
},
5356

5457
enter: function enter(frame) {},
5558
exit: function exit(frame) {
56-
if (frame.getError() !== undefined && this._isExecution()) {
57-
this.reverts.push(toHex(frame.getOutput()));
59+
if (this._isExecution()) {
60+
if (frame.getError() !== undefined) {
61+
this.reverts.push(toHex(frame.getOutput()));
62+
}
63+
64+
if (this._depth > 2) {
65+
this.executionGasBuffer += 2300 + Math.ceil(frame.getGasUsed() / 63);
66+
}
5867
}
5968
},
6069

6170
step: function step(log, db) {
6271
var opcode = log.op.toString();
63-
var depth = log.getDepth();
64-
if (depth === 1 && opcode === "NUMBER") this._marker++;
72+
this._depth = log.getDepth();
73+
if (this._depth === 1 && opcode === "NUMBER") this._marker++;
6574

6675
if (
67-
depth <= 2 &&
76+
this._depth <= 2 &&
6877
opcode.startsWith("LOG") &&
6978
this._isUserOperationEvent(log)
7079
)

pkg/tracer/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ type BundlerErrorReturn struct {
5959
Reverts []string `json:"reverts"`
6060
ValidationOOG bool `json:"validationOOG"`
6161
ExecutionOOG bool `json:"executionOOG"`
62+
ExecutionGasBuffer float64 `json:"executionGasBuffer"`
6263
UserOperationEvent *LogInfo `json:"userOperationEvent,omitempty"`
6364
Output string `json:"output"`
6465
}

0 commit comments

Comments
 (0)