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

Commit ec87d31

Browse files
authored
Optimise binary search for verificationGasLimit (#270)
1 parent f268ce1 commit ec87d31

File tree

16 files changed

+402
-81
lines changed

16 files changed

+402
-81
lines changed

e2e/config.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ interface IConfig {
44
bundlerUrl: string;
55
testERC20Token: string;
66
testGas: string;
7+
testAccount: string;
78
}
89

910
const config: IConfig = {
@@ -12,9 +13,11 @@ const config: IConfig = {
1213
"c6cbc5ffad570fdad0544d1b5358a36edeb98d163b6567912ac4754e144d4edb",
1314
nodeUrl: "http://localhost:8545",
1415
bundlerUrl: "http://localhost:4337",
16+
17+
// https://github.com/stackup-wallet/contracts/blob/main/contracts/test
1518
testERC20Token: "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B",
16-
// https://github.com/stackup-wallet/contracts/blob/main/contracts/test/TestGas.sol
1719
testGas: "0xc2e76Ee793a194Dd930C18c4cDeC93E7C75d567C",
20+
testAccount: "0x3dFD39F2c17625b301ae0EF72B411D1de5211325",
1821
};
1922

2023
export default config;

e2e/jest.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const config: Config = {
44
verbose: true,
55
preset: "ts-jest",
66
testEnvironment: "node",
7+
globalSetup: "./setup.ts",
78
};
89

910
export default config;

e2e/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
"lint:fix": "eslint . --fix",
1010
"prettier": "prettier --check --ignore-path ./.eslintignore '**'",
1111
"prettier:fix": "prettier --write --ignore-path ./.eslintignore '**'",
12-
"test": "jest"
12+
"test": "jest --runInBand"
1313
},
1414
"dependencies": {
1515
"ethers": "^5.7.2",
16-
"userop": "^0.3.0"
16+
"userop": "^0.3.3"
1717
},
1818
"devDependencies": {
1919
"@types/jest": "^29.5.2",

e2e/setup.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ethers } from "ethers";
2+
import { Presets } from "userop";
3+
import { erc20ABI } from "./src/abi";
4+
import { fundIfRequired } from "./src/helpers";
5+
import config from "./config";
6+
7+
export default async function () {
8+
const provider = new ethers.providers.JsonRpcProvider(config.nodeUrl);
9+
const signer = new ethers.Wallet(config.signingKey);
10+
const testToken = new ethers.Contract(
11+
config.testERC20Token,
12+
erc20ABI,
13+
provider
14+
);
15+
const acc = await Presets.Builder.SimpleAccount.init(signer, config.nodeUrl);
16+
await fundIfRequired(
17+
provider,
18+
testToken,
19+
await signer.getAddress(),
20+
acc.getSender(),
21+
config.testAccount
22+
);
23+
}

e2e/src/abi.ts

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,166 @@ export const testGasABI = [
184184
type: "function",
185185
},
186186
];
187+
188+
export const testAccountABI = [
189+
{
190+
stateMutability: "payable",
191+
type: "fallback",
192+
},
193+
{
194+
inputs: [],
195+
name: "offset",
196+
outputs: [
197+
{
198+
internalType: "uint256",
199+
name: "",
200+
type: "uint256",
201+
},
202+
],
203+
stateMutability: "view",
204+
type: "function",
205+
},
206+
{
207+
inputs: [
208+
{
209+
internalType: "uint256",
210+
name: "depth",
211+
type: "uint256",
212+
},
213+
{
214+
internalType: "uint256",
215+
name: "width",
216+
type: "uint256",
217+
},
218+
{
219+
internalType: "uint256",
220+
name: "discount",
221+
type: "uint256",
222+
},
223+
{
224+
internalType: "uint256",
225+
name: "count",
226+
type: "uint256",
227+
},
228+
],
229+
name: "recursiveCall",
230+
outputs: [
231+
{
232+
internalType: "uint256",
233+
name: "",
234+
type: "uint256",
235+
},
236+
],
237+
stateMutability: "payable",
238+
type: "function",
239+
},
240+
{
241+
inputs: [
242+
{
243+
internalType: "uint256",
244+
name: "",
245+
type: "uint256",
246+
},
247+
],
248+
name: "store",
249+
outputs: [
250+
{
251+
internalType: "uint256",
252+
name: "",
253+
type: "uint256",
254+
},
255+
],
256+
stateMutability: "view",
257+
type: "function",
258+
},
259+
{
260+
inputs: [
261+
{
262+
components: [
263+
{
264+
internalType: "address",
265+
name: "sender",
266+
type: "address",
267+
},
268+
{
269+
internalType: "uint256",
270+
name: "nonce",
271+
type: "uint256",
272+
},
273+
{
274+
internalType: "bytes",
275+
name: "initCode",
276+
type: "bytes",
277+
},
278+
{
279+
internalType: "bytes",
280+
name: "callData",
281+
type: "bytes",
282+
},
283+
{
284+
internalType: "uint256",
285+
name: "callGasLimit",
286+
type: "uint256",
287+
},
288+
{
289+
internalType: "uint256",
290+
name: "verificationGasLimit",
291+
type: "uint256",
292+
},
293+
{
294+
internalType: "uint256",
295+
name: "preVerificationGas",
296+
type: "uint256",
297+
},
298+
{
299+
internalType: "uint256",
300+
name: "maxFeePerGas",
301+
type: "uint256",
302+
},
303+
{
304+
internalType: "uint256",
305+
name: "maxPriorityFeePerGas",
306+
type: "uint256",
307+
},
308+
{
309+
internalType: "bytes",
310+
name: "paymasterAndData",
311+
type: "bytes",
312+
},
313+
{
314+
internalType: "bytes",
315+
name: "signature",
316+
type: "bytes",
317+
},
318+
],
319+
internalType: "struct UserOperation",
320+
name: "userOp",
321+
type: "tuple",
322+
},
323+
{
324+
internalType: "bytes32",
325+
name: "",
326+
type: "bytes32",
327+
},
328+
{
329+
internalType: "uint256",
330+
name: "missingAccountFunds",
331+
type: "uint256",
332+
},
333+
],
334+
name: "validateUserOp",
335+
outputs: [
336+
{
337+
internalType: "uint256",
338+
name: "validationData",
339+
type: "uint256",
340+
},
341+
],
342+
stateMutability: "nonpayable",
343+
type: "function",
344+
},
345+
{
346+
stateMutability: "payable",
347+
type: "receive",
348+
},
349+
];

e2e/src/helpers.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,21 @@ export const fundIfRequired = async (
55
provider: ethers.providers.JsonRpcProvider,
66
token: ethers.Contract,
77
bundler: string,
8-
account: string
8+
account: string,
9+
testAccount: string
910
) => {
1011
const signer = provider.getSigner(0);
11-
const [bundlerBalance, accountBalance, accountTokenBalance] =
12-
await Promise.all([
13-
provider.getBalance(bundler),
14-
provider.getBalance(account),
15-
token.balanceOf(account) as ethers.BigNumber,
16-
]);
12+
const [
13+
bundlerBalance,
14+
accountBalance,
15+
testAccountBalance,
16+
accountTokenBalance,
17+
] = await Promise.all([
18+
provider.getBalance(bundler),
19+
provider.getBalance(account),
20+
provider.getBalance(testAccount),
21+
token.balanceOf(account) as ethers.BigNumber,
22+
]);
1723

1824
if (bundlerBalance.eq(0)) {
1925
const response = await signer.sendTransaction({
@@ -33,6 +39,15 @@ export const fundIfRequired = async (
3339
console.log("Funded Account with 2 ETH...");
3440
}
3541

42+
if (testAccountBalance.eq(0)) {
43+
const response = await signer.sendTransaction({
44+
to: testAccount,
45+
value: ethers.constants.WeiPerEther.mul(2),
46+
});
47+
await response.wait();
48+
console.log("Funded Test Account with 2 ETH...");
49+
}
50+
3651
if (accountTokenBalance.eq(0)) {
3752
const response = await signer.sendTransaction({
3853
to: token.address,

e2e/src/testAccount.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { ethers } from "ethers";
2+
import {
3+
Constants,
4+
Presets,
5+
UserOperationBuilder,
6+
BundlerJsonRpcProvider,
7+
IPresetBuilderOpts,
8+
UserOperationMiddlewareFn,
9+
} from "userop";
10+
import { EntryPoint, EntryPoint__factory } from "userop/dist/typechain";
11+
import { testAccountABI } from "./abi";
12+
13+
const RECURSIVE_CALL_MODE = "0x0001";
14+
15+
export class TestAccount extends UserOperationBuilder {
16+
private provider: ethers.providers.JsonRpcProvider;
17+
private entryPoint: EntryPoint;
18+
private account: ethers.Contract;
19+
20+
private constructor(
21+
address: string,
22+
rpcUrl: string,
23+
opts?: IPresetBuilderOpts
24+
) {
25+
super();
26+
this.provider = new BundlerJsonRpcProvider(rpcUrl).setBundlerRpc(
27+
opts?.overrideBundlerRpc
28+
);
29+
this.entryPoint = EntryPoint__factory.connect(
30+
opts?.entryPoint || Constants.ERC4337.EntryPoint,
31+
this.provider
32+
);
33+
this.account = new ethers.Contract(address, testAccountABI, this.provider);
34+
}
35+
36+
private resolveAccount: UserOperationMiddlewareFn = async (ctx) => {
37+
ctx.op.nonce = await this.entryPoint.getNonce(ctx.op.sender, 0);
38+
};
39+
40+
public static async init(
41+
address: string,
42+
rpcUrl: string,
43+
opts?: IPresetBuilderOpts
44+
): Promise<TestAccount> {
45+
const instance = new TestAccount(address, rpcUrl, opts);
46+
47+
const base = instance
48+
.useDefaults({ sender: instance.account.address })
49+
.useMiddleware(instance.resolveAccount)
50+
.useMiddleware(Presets.Middleware.getGasPrice(instance.provider));
51+
52+
return opts?.paymasterMiddleware
53+
? base.useMiddleware(opts.paymasterMiddleware)
54+
: base.useMiddleware(
55+
Presets.Middleware.estimateUserOperationGas(instance.provider)
56+
);
57+
}
58+
59+
recursiveCall(depth: number, width: number, discount: number) {
60+
return this.setCallData(
61+
ethers.utils.defaultAbiCoder.encode(
62+
["uint256", "uint256", "uint256"],
63+
[depth, width, discount]
64+
)
65+
).setSignature(RECURSIVE_CALL_MODE);
66+
}
67+
}

e2e/test/withoutPaymaster.test.ts renamed to e2e/test/execution.test.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ethers } from "ethers";
22
import { Presets, Client, ISendUserOperationOpts } from "userop";
3-
import { fundIfRequired, getCallGasLimitBenchmark } from "../src/helpers";
3+
import { getCallGasLimitBenchmark } from "../src/helpers";
44
import { erc20ABI, testGasABI } from "../src/abi";
55
import { errorCodes } from "../src/errors";
66
import config from "../config";
@@ -28,7 +28,7 @@ const opCheckDeep = (benchmark: number): ISendUserOperationOpts => ({
2828
},
2929
});
3030

31-
describe("Without Paymaster", () => {
31+
describe("During the execution phase", () => {
3232
const provider = new ethers.providers.JsonRpcProvider(config.nodeUrl);
3333
const signer = new ethers.Wallet(config.signingKey);
3434
const testToken = new ethers.Contract(
@@ -48,12 +48,6 @@ describe("Without Paymaster", () => {
4848
acc = await Presets.Builder.SimpleAccount.init(signer, config.nodeUrl, {
4949
overrideBundlerRpc: config.bundlerUrl,
5050
});
51-
await fundIfRequired(
52-
provider,
53-
testToken,
54-
await signer.getAddress(),
55-
acc.getSender()
56-
);
5751
});
5852

5953
test("Sender can transfer 0 ETH", async () => {

0 commit comments

Comments
 (0)