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

Commit 0698fc4

Browse files
authored
CGL estimate fallback to a more efficient binary search (#255)
1 parent 5058cf4 commit 0698fc4

File tree

5 files changed

+95
-9
lines changed

5 files changed

+95
-9
lines changed

.github/workflows/compliance.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,14 @@ jobs:
3838

3939
- name: Setup bundler spec test
4040
working-directory: ./bundler-spec-tests
41-
run: pdm install && pdm run update-deps
41+
run: |
42+
pdm install && \
43+
cd @account-abstraction && \
44+
yarn install --frozen-lockfile && \
45+
yarn compile && \
46+
cd ../spec && \
47+
yarn install --frozen-lockfile && \
48+
yarn build
4249
4350
- name: Install Geth
4451
run: |

e2e/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const config: IConfig = {
1414
bundlerUrl: "http://localhost:4337",
1515
testERC20Token: "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B",
1616
// https://github.com/stackup-wallet/contracts/blob/main/contracts/test/TestGas.sol
17-
testGas: "0xd98206114295d553625cFF946D8aEc3aa790db3A",
17+
testGas: "0xdea3d7b717F3A6E634876092470efc281f9b3de7",
1818
};
1919

2020
export default config;

e2e/src/abi.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ export const testGasABI = [
168168
inputs: [
169169
{ internalType: "uint256", name: "depth", type: "uint256" },
170170
{ internalType: "uint256", name: "width", type: "uint256" },
171+
{ internalType: "uint256", name: "discount", type: "uint256" },
171172
{ internalType: "uint256", name: "count", type: "uint256" },
172173
],
173174
name: "recursiveCall",

e2e/test/withoutPaymaster.test.ts

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ describe("Without Paymaster", () => {
143143
acc.execute(
144144
config.testGas,
145145
0,
146-
testGas.interface.encodeFunctionData("recursiveCall", [32, 0, 32])
146+
testGas.interface.encodeFunctionData("recursiveCall", [32, 0, 0, 32])
147147
),
148148
{ ...opChecks(provider) }
149149
);
@@ -157,8 +157,8 @@ describe("Without Paymaster", () => {
157157
[0, 2, 4, 8, 16].forEach((depth) => {
158158
test(`Sender can make contract interactions with ${depth} recursive calls`, async () => {
159159
let opts = opChecks(provider);
160-
if (depth === 8) opts = opCheckDeep(1195400);
161-
if (depth === 16) opts = opCheckDeep(4364942);
160+
if (depth === 8) opts = opCheckDeep(1195897);
161+
if (depth === 16) opts = opCheckDeep(4365893);
162162

163163
const response = await client.sendUserOperation(
164164
acc.execute(
@@ -167,6 +167,7 @@ describe("Without Paymaster", () => {
167167
testGas.interface.encodeFunctionData("recursiveCall", [
168168
depth,
169169
0,
170+
0,
170171
depth,
171172
])
172173
),
@@ -183,8 +184,8 @@ describe("Without Paymaster", () => {
183184
[0, 2, 4, 8, 16].forEach((depth) => {
184185
test(`Sender can make contract interactions with ${depth} recursive calls`, async () => {
185186
let opts = opChecks(provider);
186-
if (depth === 8) opts = opCheckDeep(1261728);
187-
if (depth === 16) opts = opCheckDeep(4498665);
187+
if (depth === 8) opts = opCheckDeep(1262227);
188+
if (depth === 16) opts = opCheckDeep(4499616);
188189

189190
const response = await client.sendUserOperation(
190191
acc.execute(
@@ -193,6 +194,7 @@ describe("Without Paymaster", () => {
193194
testGas.interface.encodeFunctionData("recursiveCall", [
194195
depth,
195196
0,
197+
0,
196198
depth,
197199
])
198200
),
@@ -205,12 +207,36 @@ describe("Without Paymaster", () => {
205207
});
206208
});
207209

210+
describe("With gas discount", () => {
211+
const depth = 3;
212+
[15000, 20000, 25000, 30000, 35000].forEach((discount) => {
213+
test(`Sender can make contract interactions with ${discount} gas discount to recursive calls`, async () => {
214+
const response = await client.sendUserOperation(
215+
acc.execute(
216+
config.testGas,
217+
0,
218+
testGas.interface.encodeFunctionData("recursiveCall", [
219+
depth,
220+
0,
221+
discount,
222+
depth,
223+
])
224+
),
225+
opChecks(provider)
226+
);
227+
const event = await response.wait();
228+
229+
expect(event?.args.success).toBe(true);
230+
});
231+
});
232+
});
233+
208234
describe("With multiple stacks per depth", () => {
209235
[0, 1, 2, 3].forEach((depth) => {
210236
test(`Sender can make contract interactions with ${depth} recursive calls`, async () => {
211237
let opts = opChecks(provider);
212-
if (depth === 2) opts = opCheckDeep(865684);
213-
if (depth === 3) opts = opCheckDeep(7925084);
238+
if (depth === 2) opts = opCheckDeep(866332);
239+
if (depth === 3) opts = opCheckDeep(7929055);
214240

215241
const width = depth;
216242
const response = await client.sendUserOperation(
@@ -220,6 +246,7 @@ describe("Without Paymaster", () => {
220246
testGas.interface.encodeFunctionData("recursiveCall", [
221247
depth,
222248
width,
249+
0,
223250
depth,
224251
])
225252
),

pkg/gas/estimate.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import (
1212
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
1313
)
1414

15+
var (
16+
fallBackBinarySearchCutoff = int64(30000)
17+
)
18+
1519
func isPrefundNotPaid(err error) bool {
1620
return strings.HasPrefix(err.Error(), "AA21") || strings.HasPrefix(err.Error(), "AA31")
1721
}
@@ -24,6 +28,10 @@ func isExecutionOOG(err error) bool {
2428
return strings.Contains(err.Error(), "execution OOG")
2529
}
2630

31+
func isExecutionReverted(err error) bool {
32+
return strings.Contains(err.Error(), "execution reverted")
33+
}
34+
2735
type EstimateInput struct {
2836
Rpc *rpc.Client
2937
EntryPoint common.Address
@@ -145,6 +153,49 @@ func EstimateGas(in *EstimateInput) (verificationGas uint64, callGas uint64, err
145153
ChainID: in.ChainID,
146154
})
147155
if err != nil {
156+
// Execution is successful but one shot tracing has failed. Fallback to binary search with an
157+
// efficient range. Hitting this point could mean a contract is passing manual gas limits with a
158+
// static discount, e.g. sub(gas(), STATIC_DISCOUNT). This is not yet accounted for in the tracer.
159+
if isExecutionOOG(err) {
160+
l := cgl.Int64()
161+
r := (l * 150) / 100 // Set upper bound to +50%
162+
f := int64(0)
163+
simErr := err
164+
for r-l >= fallBackBinarySearchCutoff {
165+
m := (l + r) / 2
166+
167+
data["callGasLimit"] = hexutil.EncodeBig(big.NewInt(int64(m)))
168+
simOp, err := userop.New(data)
169+
if err != nil {
170+
return 0, 0, err
171+
}
172+
_, err = execution.TraceSimulateHandleOp(&execution.TraceInput{
173+
Rpc: in.Rpc,
174+
EntryPoint: in.EntryPoint,
175+
Op: simOp,
176+
ChainID: in.ChainID,
177+
})
178+
simErr = err
179+
if err != nil && (isExecutionOOG(err) || isExecutionReverted(err)) {
180+
// CGL too low, go higher.
181+
l = m + 1
182+
continue
183+
} else if err == nil {
184+
// CGL too high, go lower.
185+
r = m - 1
186+
// Set final.
187+
f = m
188+
continue
189+
} else {
190+
// Unexpected error.
191+
return 0, 0, err
192+
}
193+
}
194+
if f == 0 {
195+
return 0, 0, simErr
196+
}
197+
return simOp.VerificationGasLimit.Uint64(), big.NewInt(f).Uint64(), nil
198+
}
148199
return 0, 0, err
149200
}
150201
return simOp.VerificationGasLimit.Uint64(), simOp.CallGasLimit.Uint64(), nil

0 commit comments

Comments
 (0)