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

Commit 817933b

Browse files
authored
Reduce the diff between tracing and binary search estimation (#226)
1 parent fe06f84 commit 817933b

File tree

3 files changed

+88
-16
lines changed

3 files changed

+88
-16
lines changed

e2e/src/helpers.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ethers } from "ethers";
2+
import { Constants } from "userop";
23

34
export const fundIfRequired = async (
45
provider: ethers.providers.JsonRpcProvider,
@@ -45,3 +46,15 @@ export const fundIfRequired = async (
4546
console.log("Minted 10 Test Tokens for Account...");
4647
}
4748
};
49+
50+
export const getCallGasLimitBenchmark = async (
51+
provider: ethers.providers.JsonRpcProvider,
52+
sender: string,
53+
callData: ethers.BytesLike
54+
) => {
55+
return provider.estimateGas({
56+
from: Constants.ERC4337.EntryPoint,
57+
to: sender,
58+
data: callData,
59+
});
60+
};

e2e/test/withoutPaymaster.test.ts

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,33 @@
11
import { ethers } from "ethers";
2-
import { Presets, Client } from "userop";
3-
import { fundIfRequired } from "../src/helpers";
2+
import { Presets, Client, ISendUserOperationOpts } from "userop";
3+
import { fundIfRequired, getCallGasLimitBenchmark } from "../src/helpers";
44
import { erc20ABI, testGasABI } from "../src/abi";
55
import { errorCodes } from "../src/errors";
66
import config from "../config";
77

8+
const opChecks = (
9+
provider: ethers.providers.JsonRpcProvider
10+
): ISendUserOperationOpts => ({
11+
onBuild: async (op) => {
12+
const cgl = ethers.BigNumber.from(op.callGasLimit).toNumber();
13+
const benchmark = await (
14+
await getCallGasLimitBenchmark(provider, op.sender, op.callData)
15+
).toNumber();
16+
17+
expect(cgl).toBeLessThanOrEqual(benchmark);
18+
},
19+
});
20+
21+
// TODO: Figure out why CGL is not LTE to benchmark at certain depths/widths.
22+
// Until then we use this check to prevent regression.
23+
const opCheckDeep = (benchmark: number): ISendUserOperationOpts => ({
24+
onBuild: async (op) => {
25+
expect(
26+
ethers.BigNumber.from(op.callGasLimit).toNumber()
27+
).toBeLessThanOrEqual(benchmark);
28+
},
29+
});
30+
831
describe("Without Paymaster", () => {
932
const provider = new ethers.providers.JsonRpcProvider(config.nodeUrl);
1033
const signer = new ethers.Wallet(config.signingKey);
@@ -35,7 +58,8 @@ describe("Without Paymaster", () => {
3558

3659
test("Sender can transfer 0 ETH", async () => {
3760
const response = await client.sendUserOperation(
38-
acc.execute(acc.getSender(), 0, "0x")
61+
acc.execute(acc.getSender(), 0, "0x"),
62+
{ ...opChecks(provider) }
3963
);
4064
const event = await response.wait();
4165

@@ -45,7 +69,8 @@ describe("Without Paymaster", () => {
4569
test("Sender can transfer half ETH balance", async () => {
4670
const balance = await provider.getBalance(acc.getSender());
4771
const response = await client.sendUserOperation(
48-
acc.execute(acc.getSender(), balance.div(2), "0x")
72+
acc.execute(acc.getSender(), balance.div(2), "0x"),
73+
{ ...opChecks(provider) }
4974
);
5075
const event = await response.wait();
5176

@@ -63,7 +88,8 @@ describe("Without Paymaster", () => {
6388
.add(ethers.BigNumber.from(op.verificationGasLimit).mul(3))
6489
);
6590
const response = await client.sendUserOperation(
66-
acc.execute(acc.getSender(), balance.sub(maxFee), "0x")
91+
acc.execute(acc.getSender(), balance.sub(maxFee), "0x"),
92+
{ ...opChecks(provider) }
6793
);
6894
const event = await response.wait();
6995

@@ -80,7 +106,8 @@ describe("Without Paymaster", () => {
80106
acc.getSender(),
81107
balance,
82108
])
83-
)
109+
),
110+
{ ...opChecks(provider) }
84111
);
85112
const event = await response.wait();
86113

@@ -100,7 +127,10 @@ describe("Without Paymaster", () => {
100127
])
101128
);
102129
}
103-
const response = await client.sendUserOperation(acc.executeBatch(to, data));
130+
const response = await client.sendUserOperation(
131+
acc.executeBatch(to, data),
132+
{ ...opChecks(provider) }
133+
);
104134
const event = await response.wait();
105135

106136
expect(event?.args.success).toBe(true);
@@ -114,7 +144,8 @@ describe("Without Paymaster", () => {
114144
config.testGas,
115145
0,
116146
testGas.interface.encodeFunctionData("recursiveCall", [32, 0, 32])
117-
)
147+
),
148+
{ ...opChecks(provider) }
118149
);
119150
} catch (error: any) {
120151
expect(error?.error.code).toBe(errorCodes.executionReverted);
@@ -125,6 +156,10 @@ describe("Without Paymaster", () => {
125156
describe("With zero value", () => {
126157
[0, 2, 4, 8, 16].forEach((depth) => {
127158
test(`Sender can make contract interactions with ${depth} recursive calls`, async () => {
159+
let opts = opChecks(provider);
160+
if (depth === 8) opts = opCheckDeep(1195400);
161+
if (depth === 16) opts = opCheckDeep(4364942);
162+
128163
const response = await client.sendUserOperation(
129164
acc.execute(
130165
config.testGas,
@@ -134,7 +169,8 @@ describe("Without Paymaster", () => {
134169
0,
135170
depth,
136171
])
137-
)
172+
),
173+
opts
138174
);
139175
const event = await response.wait();
140176

@@ -146,6 +182,10 @@ describe("Without Paymaster", () => {
146182
describe("With non-zero value", () => {
147183
[0, 2, 4, 8, 16].forEach((depth) => {
148184
test(`Sender can make contract interactions with ${depth} recursive calls`, async () => {
185+
let opts = opChecks(provider);
186+
if (depth === 8) opts = opCheckDeep(1261728);
187+
if (depth === 16) opts = opCheckDeep(4498665);
188+
149189
const response = await client.sendUserOperation(
150190
acc.execute(
151191
config.testGas,
@@ -155,7 +195,8 @@ describe("Without Paymaster", () => {
155195
0,
156196
depth,
157197
])
158-
)
198+
),
199+
opts
159200
);
160201
const event = await response.wait();
161202

@@ -167,6 +208,10 @@ describe("Without Paymaster", () => {
167208
describe("With multiple stacks per depth", () => {
168209
[0, 1, 2, 3].forEach((depth) => {
169210
test(`Sender can make contract interactions with ${depth} recursive calls`, async () => {
211+
let opts = opChecks(provider);
212+
if (depth === 2) opts = opCheckDeep(865684);
213+
if (depth === 3) opts = opCheckDeep(7925084);
214+
170215
const width = depth;
171216
const response = await client.sendUserOperation(
172217
acc.execute(
@@ -177,7 +222,8 @@ describe("Without Paymaster", () => {
177222
width,
178223
depth,
179224
])
180-
)
225+
),
226+
opts
181227
);
182228
const event = await response.wait();
183229

pkg/tracer/BundlerExecutionTracer.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,29 @@ var tracer = {
7070
}
7171

7272
if (this._depth >= 2) {
73-
var prev = Object.assign({}, this._defaultGasItem);
74-
if (this._executionGasStack[this._depth + 1] !== undefined)
75-
prev = this._executionGasStack[this._depth + 1];
73+
// Get the final gas item for the nested frame.
74+
var nested = Object.assign(
75+
{},
76+
this._executionGasStack[this._depth + 1] || this._defaultGasItem
77+
);
7678

79+
// Reset the nested gas item to prevent double counting on re-entry.
80+
this._executionGasStack[this._depth + 1] = Object.assign(
81+
{},
82+
this._defaultGasItem
83+
);
84+
85+
// Keep track of the total gas used by all frames at this depth.
86+
// This does not account for the gas required due to the 63/64 rule.
7787
var used = frame.getGasUsed();
78-
var required = used - prev.used + prev.required;
7988
this._executionGasStack[this._depth].used += used;
89+
90+
// Keep track of the total gas required by all frames at this depth.
91+
// This accounts for additional gas needed due to the 63/64 rule.
8092
this._executionGasStack[this._depth].required +=
81-
required + Math.ceil(required / 63);
93+
used - nested.used + Math.ceil((nested.required * 64) / 63);
8294

95+
// Keep track of the final gas limit.
8396
this.executionGasLimit = this._executionGasStack[this._depth].required;
8497
}
8598
}

0 commit comments

Comments
 (0)