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

Commit

Permalink
Catch gas estimation errors from postOp OOG (#344)
Browse files Browse the repository at this point in the history
  • Loading branch information
hazim-j authored Nov 29, 2023
1 parent 6ad110d commit 376d4c5
Show file tree
Hide file tree
Showing 9 changed files with 302 additions and 5 deletions.
2 changes: 2 additions & 0 deletions e2e/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ interface IConfig {
testERC20Token: string;
testGas: string;
testAccount: string;
testPaymaster: string;
}

const config: IConfig = {
Expand All @@ -18,6 +19,7 @@ const config: IConfig = {
testERC20Token: "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B",
testGas: "0x450d8479B0ceF1e6933DED809e12845aF413A50D",
testAccount: "0x6D7d359cE9e60dDa36EE712cE9B5947B4C72F862",
testPaymaster: "0xa9C7F67D5Be8A805dC80f06E49BDe939384E300b",
};

export default config;
10 changes: 8 additions & 2 deletions e2e/setup.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ethers } from "ethers";
import { Presets } from "userop";
import { erc20ABI } from "./src/abi";
import { erc20ABI, testPaymasterABI } from "./src/abi";
import { fundIfRequired } from "./src/helpers";
import config from "./config";

Expand All @@ -12,12 +12,18 @@ export default async function () {
erc20ABI,
provider
);
const testPaymaster = new ethers.Contract(
config.testPaymaster,
testPaymasterABI,
provider
);
const acc = await Presets.Builder.SimpleAccount.init(signer, config.nodeUrl);
await fundIfRequired(
provider,
testToken,
await signer.getAddress(),
acc.getSender(),
config.testAccount
config.testAccount,
testPaymaster
);
}
230 changes: 230 additions & 0 deletions e2e/src/abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,3 +367,233 @@ export const testAccountABI = [
type: "receive",
},
];

export const testPaymasterABI = [
{
stateMutability: "payable",
type: "fallback",
},
{
inputs: [
{
internalType: "address",
name: "entryPoint",
type: "address",
},
],
name: "addStake",
outputs: [],
stateMutability: "payable",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "entryPoint",
type: "address",
},
],
name: "deposit",
outputs: [],
stateMutability: "payable",
type: "function",
},
{
inputs: [],
name: "offset",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
internalType: "enum IPaymaster.PostOpMode",
name: "mode",
type: "uint8",
},
{
internalType: "bytes",
name: "context",
type: "bytes",
},
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
name: "postOp",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{
internalType: "uint256",
name: "depth",
type: "uint256",
},
{
internalType: "uint256",
name: "width",
type: "uint256",
},
{
internalType: "uint256",
name: "discount",
type: "uint256",
},
{
internalType: "uint256",
name: "count",
type: "uint256",
},
],
name: "recursiveCall",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "payable",
type: "function",
},
{
inputs: [
{
internalType: "uint256",
name: "key",
type: "uint256",
},
],
name: "store",
outputs: [
{
internalType: "uint256",
name: "value",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
components: [
{
internalType: "address",
name: "sender",
type: "address",
},
{
internalType: "uint256",
name: "nonce",
type: "uint256",
},
{
internalType: "bytes",
name: "initCode",
type: "bytes",
},
{
internalType: "bytes",
name: "callData",
type: "bytes",
},
{
internalType: "uint256",
name: "callGasLimit",
type: "uint256",
},
{
internalType: "uint256",
name: "verificationGasLimit",
type: "uint256",
},
{
internalType: "uint256",
name: "preVerificationGas",
type: "uint256",
},
{
internalType: "uint256",
name: "maxFeePerGas",
type: "uint256",
},
{
internalType: "uint256",
name: "maxPriorityFeePerGas",
type: "uint256",
},
{
internalType: "bytes",
name: "paymasterAndData",
type: "bytes",
},
{
internalType: "bytes",
name: "signature",
type: "bytes",
},
],
internalType: "struct UserOperation",
name: "userOp",
type: "tuple",
},
{
internalType: "bytes32",
name: "",
type: "bytes32",
},
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
name: "validatePaymasterUserOp",
outputs: [
{
internalType: "bytes",
name: "context",
type: "bytes",
},
{
internalType: "uint256",
name: "validationData",
type: "uint256",
},
],
stateMutability: "pure",
type: "function",
},
{
inputs: [
{
internalType: "uint256",
name: "times",
type: "uint256",
},
],
name: "wasteGas",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
stateMutability: "payable",
type: "receive",
},
];
34 changes: 33 additions & 1 deletion e2e/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
import { ethers } from "ethers";
import { Constants } from "userop";
import { EntryPoint__factory } from "userop/dist/typechain";

export const fundIfRequired = async (
provider: ethers.providers.JsonRpcProvider,
token: ethers.Contract,
bundler: string,
account: string,
testAccount: string
testAccount: string,
testPaymaster: ethers.Contract
) => {
const signer = provider.getSigner(0);
const ep = EntryPoint__factory.connect(
Constants.ERC4337.EntryPoint,
provider
);
const [
bundlerBalance,
accountBalance,
testAccountBalance,
accountTokenBalance,
testPaymasterDepositInfo,
] = await Promise.all([
provider.getBalance(bundler),
provider.getBalance(account),
provider.getBalance(testAccount),
token.balanceOf(account) as ethers.BigNumber,
ep.getDepositInfo(testPaymaster.address),
]);

if (bundlerBalance.eq(0)) {
Expand Down Expand Up @@ -60,6 +68,30 @@ export const fundIfRequired = async (
await response.wait();
console.log("Minted 10 Test Tokens for Account...");
}

if (testPaymasterDepositInfo.stake.eq(0)) {
const response = await signer.sendTransaction({
to: testPaymaster.address,
value: ethers.constants.WeiPerEther.mul(2),
data: testPaymaster.interface.encodeFunctionData("addStake", [
Constants.ERC4337.EntryPoint,
]),
});
await response.wait();
console.log("Staked Test Paymaster with 2 ETH...");
}

if (testPaymasterDepositInfo.deposit.eq(0)) {
const response = await signer.sendTransaction({
to: testPaymaster.address,
value: ethers.constants.WeiPerEther.mul(2),
data: testPaymaster.interface.encodeFunctionData("deposit", [
Constants.ERC4337.EntryPoint,
]),
});
await response.wait();
console.log("Funded Test Paymaster with 2 ETH...");
}
};

export const getCallGasLimitBenchmark = async (
Expand Down
10 changes: 10 additions & 0 deletions e2e/src/testAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from "userop";
import { EntryPoint, EntryPoint__factory } from "userop/dist/typechain";
import { testAccountABI } from "./abi";
import config from "../config";

const RECURSIVE_CALL_MODE = "0x0001";
const FORCE_VALIDATION_OOG_MODE = "0x0002";
Expand Down Expand Up @@ -71,4 +72,13 @@ export class TestAccount extends UserOperationBuilder {
ethers.utils.defaultAbiCoder.encode(["uint256"], [wasteGasMultiplier])
).setSignature(FORCE_VALIDATION_OOG_MODE);
}

forcePostOpValidationOOG(wasteGasMultiplier: number) {
return this.setPaymasterAndData(
ethers.utils.hexConcat([
config.testPaymaster,
ethers.utils.defaultAbiCoder.encode(["uint256"], [wasteGasMultiplier]),
])
);
}
}
15 changes: 14 additions & 1 deletion e2e/test/verification.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe("During the verification phase", () => {
});
});

describe("With dependency on callGasLimit", () => {
describe("With sender dependency on callGasLimit", () => {
[0, 1, 2, 3, 4, 5].forEach((times) => {
test(`Sender can run validation with non-simulated code that uses ${times} storage writes`, async () => {
const response = await client.sendUserOperation(
Expand All @@ -43,4 +43,17 @@ describe("During the verification phase", () => {
});
});
});

describe("With paymaster dependency on callGasLimit", () => {
[0, 1, 2, 3, 4, 5].forEach((times) => {
test(`Paymaster can run postOp with non-simulated code that uses ${times} storage writes`, async () => {
const response = await client.sendUserOperation(
acc.forcePostOpValidationOOG(times)
);
const event = await response.wait();

expect(event?.args.success).toBe(true);
});
});
});
});
Loading

0 comments on commit 376d4c5

Please sign in to comment.