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

Commit 376d4c5

Browse files
authored
Catch gas estimation errors from postOp OOG (#344)
1 parent 6ad110d commit 376d4c5

File tree

9 files changed

+302
-5
lines changed

9 files changed

+302
-5
lines changed

e2e/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ interface IConfig {
55
testERC20Token: string;
66
testGas: string;
77
testAccount: string;
8+
testPaymaster: string;
89
}
910

1011
const config: IConfig = {
@@ -18,6 +19,7 @@ const config: IConfig = {
1819
testERC20Token: "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B",
1920
testGas: "0x450d8479B0ceF1e6933DED809e12845aF413A50D",
2021
testAccount: "0x6D7d359cE9e60dDa36EE712cE9B5947B4C72F862",
22+
testPaymaster: "0xa9C7F67D5Be8A805dC80f06E49BDe939384E300b",
2123
};
2224

2325
export default config;

e2e/setup.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ethers } from "ethers";
22
import { Presets } from "userop";
3-
import { erc20ABI } from "./src/abi";
3+
import { erc20ABI, testPaymasterABI } from "./src/abi";
44
import { fundIfRequired } from "./src/helpers";
55
import config from "./config";
66

@@ -12,12 +12,18 @@ export default async function () {
1212
erc20ABI,
1313
provider
1414
);
15+
const testPaymaster = new ethers.Contract(
16+
config.testPaymaster,
17+
testPaymasterABI,
18+
provider
19+
);
1520
const acc = await Presets.Builder.SimpleAccount.init(signer, config.nodeUrl);
1621
await fundIfRequired(
1722
provider,
1823
testToken,
1924
await signer.getAddress(),
2025
acc.getSender(),
21-
config.testAccount
26+
config.testAccount,
27+
testPaymaster
2228
);
2329
}

e2e/src/abi.ts

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,3 +367,233 @@ export const testAccountABI = [
367367
type: "receive",
368368
},
369369
];
370+
371+
export const testPaymasterABI = [
372+
{
373+
stateMutability: "payable",
374+
type: "fallback",
375+
},
376+
{
377+
inputs: [
378+
{
379+
internalType: "address",
380+
name: "entryPoint",
381+
type: "address",
382+
},
383+
],
384+
name: "addStake",
385+
outputs: [],
386+
stateMutability: "payable",
387+
type: "function",
388+
},
389+
{
390+
inputs: [
391+
{
392+
internalType: "address",
393+
name: "entryPoint",
394+
type: "address",
395+
},
396+
],
397+
name: "deposit",
398+
outputs: [],
399+
stateMutability: "payable",
400+
type: "function",
401+
},
402+
{
403+
inputs: [],
404+
name: "offset",
405+
outputs: [
406+
{
407+
internalType: "uint256",
408+
name: "",
409+
type: "uint256",
410+
},
411+
],
412+
stateMutability: "view",
413+
type: "function",
414+
},
415+
{
416+
inputs: [
417+
{
418+
internalType: "enum IPaymaster.PostOpMode",
419+
name: "mode",
420+
type: "uint8",
421+
},
422+
{
423+
internalType: "bytes",
424+
name: "context",
425+
type: "bytes",
426+
},
427+
{
428+
internalType: "uint256",
429+
name: "",
430+
type: "uint256",
431+
},
432+
],
433+
name: "postOp",
434+
outputs: [],
435+
stateMutability: "nonpayable",
436+
type: "function",
437+
},
438+
{
439+
inputs: [
440+
{
441+
internalType: "uint256",
442+
name: "depth",
443+
type: "uint256",
444+
},
445+
{
446+
internalType: "uint256",
447+
name: "width",
448+
type: "uint256",
449+
},
450+
{
451+
internalType: "uint256",
452+
name: "discount",
453+
type: "uint256",
454+
},
455+
{
456+
internalType: "uint256",
457+
name: "count",
458+
type: "uint256",
459+
},
460+
],
461+
name: "recursiveCall",
462+
outputs: [
463+
{
464+
internalType: "uint256",
465+
name: "",
466+
type: "uint256",
467+
},
468+
],
469+
stateMutability: "payable",
470+
type: "function",
471+
},
472+
{
473+
inputs: [
474+
{
475+
internalType: "uint256",
476+
name: "key",
477+
type: "uint256",
478+
},
479+
],
480+
name: "store",
481+
outputs: [
482+
{
483+
internalType: "uint256",
484+
name: "value",
485+
type: "uint256",
486+
},
487+
],
488+
stateMutability: "view",
489+
type: "function",
490+
},
491+
{
492+
inputs: [
493+
{
494+
components: [
495+
{
496+
internalType: "address",
497+
name: "sender",
498+
type: "address",
499+
},
500+
{
501+
internalType: "uint256",
502+
name: "nonce",
503+
type: "uint256",
504+
},
505+
{
506+
internalType: "bytes",
507+
name: "initCode",
508+
type: "bytes",
509+
},
510+
{
511+
internalType: "bytes",
512+
name: "callData",
513+
type: "bytes",
514+
},
515+
{
516+
internalType: "uint256",
517+
name: "callGasLimit",
518+
type: "uint256",
519+
},
520+
{
521+
internalType: "uint256",
522+
name: "verificationGasLimit",
523+
type: "uint256",
524+
},
525+
{
526+
internalType: "uint256",
527+
name: "preVerificationGas",
528+
type: "uint256",
529+
},
530+
{
531+
internalType: "uint256",
532+
name: "maxFeePerGas",
533+
type: "uint256",
534+
},
535+
{
536+
internalType: "uint256",
537+
name: "maxPriorityFeePerGas",
538+
type: "uint256",
539+
},
540+
{
541+
internalType: "bytes",
542+
name: "paymasterAndData",
543+
type: "bytes",
544+
},
545+
{
546+
internalType: "bytes",
547+
name: "signature",
548+
type: "bytes",
549+
},
550+
],
551+
internalType: "struct UserOperation",
552+
name: "userOp",
553+
type: "tuple",
554+
},
555+
{
556+
internalType: "bytes32",
557+
name: "",
558+
type: "bytes32",
559+
},
560+
{
561+
internalType: "uint256",
562+
name: "",
563+
type: "uint256",
564+
},
565+
],
566+
name: "validatePaymasterUserOp",
567+
outputs: [
568+
{
569+
internalType: "bytes",
570+
name: "context",
571+
type: "bytes",
572+
},
573+
{
574+
internalType: "uint256",
575+
name: "validationData",
576+
type: "uint256",
577+
},
578+
],
579+
stateMutability: "pure",
580+
type: "function",
581+
},
582+
{
583+
inputs: [
584+
{
585+
internalType: "uint256",
586+
name: "times",
587+
type: "uint256",
588+
},
589+
],
590+
name: "wasteGas",
591+
outputs: [],
592+
stateMutability: "nonpayable",
593+
type: "function",
594+
},
595+
{
596+
stateMutability: "payable",
597+
type: "receive",
598+
},
599+
];

e2e/src/helpers.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,32 @@
11
import { ethers } from "ethers";
22
import { Constants } from "userop";
3+
import { EntryPoint__factory } from "userop/dist/typechain";
34

45
export const fundIfRequired = async (
56
provider: ethers.providers.JsonRpcProvider,
67
token: ethers.Contract,
78
bundler: string,
89
account: string,
9-
testAccount: string
10+
testAccount: string,
11+
testPaymaster: ethers.Contract
1012
) => {
1113
const signer = provider.getSigner(0);
14+
const ep = EntryPoint__factory.connect(
15+
Constants.ERC4337.EntryPoint,
16+
provider
17+
);
1218
const [
1319
bundlerBalance,
1420
accountBalance,
1521
testAccountBalance,
1622
accountTokenBalance,
23+
testPaymasterDepositInfo,
1724
] = await Promise.all([
1825
provider.getBalance(bundler),
1926
provider.getBalance(account),
2027
provider.getBalance(testAccount),
2128
token.balanceOf(account) as ethers.BigNumber,
29+
ep.getDepositInfo(testPaymaster.address),
2230
]);
2331

2432
if (bundlerBalance.eq(0)) {
@@ -60,6 +68,30 @@ export const fundIfRequired = async (
6068
await response.wait();
6169
console.log("Minted 10 Test Tokens for Account...");
6270
}
71+
72+
if (testPaymasterDepositInfo.stake.eq(0)) {
73+
const response = await signer.sendTransaction({
74+
to: testPaymaster.address,
75+
value: ethers.constants.WeiPerEther.mul(2),
76+
data: testPaymaster.interface.encodeFunctionData("addStake", [
77+
Constants.ERC4337.EntryPoint,
78+
]),
79+
});
80+
await response.wait();
81+
console.log("Staked Test Paymaster with 2 ETH...");
82+
}
83+
84+
if (testPaymasterDepositInfo.deposit.eq(0)) {
85+
const response = await signer.sendTransaction({
86+
to: testPaymaster.address,
87+
value: ethers.constants.WeiPerEther.mul(2),
88+
data: testPaymaster.interface.encodeFunctionData("deposit", [
89+
Constants.ERC4337.EntryPoint,
90+
]),
91+
});
92+
await response.wait();
93+
console.log("Funded Test Paymaster with 2 ETH...");
94+
}
6395
};
6496

6597
export const getCallGasLimitBenchmark = async (

e2e/src/testAccount.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from "userop";
1010
import { EntryPoint, EntryPoint__factory } from "userop/dist/typechain";
1111
import { testAccountABI } from "./abi";
12+
import config from "../config";
1213

1314
const RECURSIVE_CALL_MODE = "0x0001";
1415
const FORCE_VALIDATION_OOG_MODE = "0x0002";
@@ -71,4 +72,13 @@ export class TestAccount extends UserOperationBuilder {
7172
ethers.utils.defaultAbiCoder.encode(["uint256"], [wasteGasMultiplier])
7273
).setSignature(FORCE_VALIDATION_OOG_MODE);
7374
}
75+
76+
forcePostOpValidationOOG(wasteGasMultiplier: number) {
77+
return this.setPaymasterAndData(
78+
ethers.utils.hexConcat([
79+
config.testPaymaster,
80+
ethers.utils.defaultAbiCoder.encode(["uint256"], [wasteGasMultiplier]),
81+
])
82+
);
83+
}
7484
}

e2e/test/verification.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe("During the verification phase", () => {
3131
});
3232
});
3333

34-
describe("With dependency on callGasLimit", () => {
34+
describe("With sender dependency on callGasLimit", () => {
3535
[0, 1, 2, 3, 4, 5].forEach((times) => {
3636
test(`Sender can run validation with non-simulated code that uses ${times} storage writes`, async () => {
3737
const response = await client.sendUserOperation(
@@ -43,4 +43,17 @@ describe("During the verification phase", () => {
4343
});
4444
});
4545
});
46+
47+
describe("With paymaster dependency on callGasLimit", () => {
48+
[0, 1, 2, 3, 4, 5].forEach((times) => {
49+
test(`Paymaster can run postOp with non-simulated code that uses ${times} storage writes`, async () => {
50+
const response = await client.sendUserOperation(
51+
acc.forcePostOpValidationOOG(times)
52+
);
53+
const event = await response.wait();
54+
55+
expect(event?.args.success).toBe(true);
56+
});
57+
});
58+
});
4659
});

0 commit comments

Comments
 (0)