Skip to content

Commit

Permalink
Merge pull request #329 from bcnmy/refactor/estimation-of-userop
Browse files Browse the repository at this point in the history
Refactor/estimation of userop
  • Loading branch information
livingrockrises authored Nov 15, 2023
2 parents df6294e + 950e4d6 commit f74b117
Show file tree
Hide file tree
Showing 11 changed files with 112 additions and 93 deletions.
50 changes: 27 additions & 23 deletions packages/account/src/BaseSmartAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import { IBundler, UserOpResponse } from "@biconomy/bundler";
import { IPaymaster, PaymasterAndDataResponse } from "@biconomy/paymaster";
import { SendUserOpParams } from "@biconomy/modules";
import { SponsorUserOperationDto, BiconomyPaymaster, PaymasterMode, IHybridPaymaster } from "@biconomy/paymaster";
import { BaseSmartAccountConfig, Overrides, TransactionDetailsForUserOp } from "./utils/Types";
import { BaseSmartAccountConfig, EstimateUserOpGasParams, TransactionDetailsForUserOp } from "./utils/Types";
import { GasOverheads } from "./utils/Preverificaiton";
import { EntryPoint, EntryPoint__factory } from "@account-abstraction/contracts";
import { DEFAULT_ENTRYPOINT_ADDRESS } from "./utils/Constants";
import { DEFAULT_ENTRYPOINT_ADDRESS, DefaultGasLimit } from "./utils/Constants";
import { LRUCache } from "lru-cache";

type UserOperationKey = keyof UserOperation;
Expand Down Expand Up @@ -201,7 +201,7 @@ export abstract class BaseSmartAccount implements IBaseSmartAccount {
Logger.log("userOp validated");
if (!this.bundler) throw new Error("Bundler is not provided");
Logger.log("userOp being sent to the bundler", userOp);
const bundlerResponse = await this.bundler.sendUserOp(userOp, params);
const bundlerResponse = await this.bundler.sendUserOp(userOp, params?.simulationType);
return bundlerResponse;
}

Expand Down Expand Up @@ -237,13 +237,9 @@ export abstract class BaseSmartAccount implements IBaseSmartAccount {
return userOp;
}

// TODO // Should make this a Dto
async estimateUserOpGas(
userOp: Partial<UserOperation>,
overrides?: Overrides,
skipBundlerGasEstimation?: boolean,
paymasterServiceData?: SponsorUserOperationDto,
): Promise<Partial<UserOperation>> {
async estimateUserOpGas(params: EstimateUserOpGasParams): Promise<Partial<UserOperation>> {
let userOp = params.userOp;
const { overrides, skipBundlerGasEstimation, paymasterServiceData } = params;
const requiredFields: UserOperationKey[] = ["sender", "nonce", "initCode", "callData"];
this.validateUserOp(userOp, requiredFields);

Expand All @@ -257,26 +253,34 @@ export abstract class BaseSmartAccount implements IBaseSmartAccount {
Logger.log("userOp in estimation", userOp);

if (skipBundlerCall) {
if (this.paymaster && this.paymaster instanceof BiconomyPaymaster && paymasterServiceData?.mode === PaymasterMode.SPONSORED) {
if (this.paymaster && this.paymaster instanceof BiconomyPaymaster) {
if (!userOp.maxFeePerGas && !userOp.maxPriorityFeePerGas) {
throw new Error("maxFeePerGas and maxPriorityFeePerGas are required for skipBundlerCall mode");
}
// Making call to paymaster to get gas estimations for userOp
const { callGasLimit, verificationGasLimit, preVerificationGas, paymasterAndData } = await (
this.paymaster as IHybridPaymaster<SponsorUserOperationDto>
).getPaymasterAndData(userOp, paymasterServiceData);
finalUserOp.verificationGasLimit = verificationGasLimit ?? userOp.verificationGasLimit;
finalUserOp.callGasLimit = callGasLimit ?? userOp.callGasLimit;
finalUserOp.preVerificationGas = preVerificationGas ?? userOp.preVerificationGas;
finalUserOp.paymasterAndData = paymasterAndData ?? userOp.paymasterAndData;
if (paymasterServiceData?.mode === PaymasterMode.SPONSORED) {
// Making call to paymaster to get gas estimations for userOp
const { callGasLimit, verificationGasLimit, preVerificationGas, paymasterAndData } = await (
this.paymaster as IHybridPaymaster<SponsorUserOperationDto>
).getPaymasterAndData(userOp, paymasterServiceData);
finalUserOp.verificationGasLimit = verificationGasLimit ?? userOp.verificationGasLimit;
finalUserOp.callGasLimit = callGasLimit ?? userOp.callGasLimit;
finalUserOp.preVerificationGas = preVerificationGas ?? userOp.preVerificationGas;
finalUserOp.paymasterAndData = paymasterAndData ?? userOp.paymasterAndData;
} else {
// use dummy values for gas limits as fee quote call will ignore this later.
finalUserOp.callGasLimit = DefaultGasLimit.callGasLimit;
finalUserOp.verificationGasLimit = DefaultGasLimit.verificationGasLimit;
finalUserOp.preVerificationGas = DefaultGasLimit.preVerificationGas;
}
} else {
Logger.warn("Skipped paymaster call. If you are using paymasterAndData, generate data externally");
finalUserOp = await this.calculateUserOpGasValues(userOp);
finalUserOp.paymasterAndData = "0x";
{
Logger.warn("Skipped paymaster call. If you are using paymasterAndData, generate data externally");
finalUserOp = await this.calculateUserOpGasValues(userOp);
finalUserOp.paymasterAndData = "0x";
}
}
} else {
if (!this.bundler) throw new Error("Bundler is not provided");
// TODO: is this still needed to delete?
delete userOp.maxFeePerGas;
delete userOp.maxPriorityFeePerGas;
// Making call to bundler to get gas estimations for userOp
Expand Down
3 changes: 2 additions & 1 deletion packages/account/src/BiconomySmartAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,8 @@ export class BiconomySmartAccount extends SmartAccount implements IBiconomySmart
};

// Note: Can change the default behaviour of calling estimations using bundler/local
userOp = await this.estimateUserOpGas(userOp, overrides, skipBundlerGasEstimation, paymasterServiceData);
userOp = await this.estimateUserOpGas({ userOp, overrides, skipBundlerGasEstimation, paymasterServiceData });
userOp.paymasterAndData = userOp.paymasterAndData ?? "0x";
Logger.log("UserOp after estimation ", userOp);

return userOp;
Expand Down
11 changes: 6 additions & 5 deletions packages/account/src/BiconomySmartAccountV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,12 +467,13 @@ export class BiconomySmartAccountV2 extends BaseSmartAccount {
userOp.signature = signature;

// Note: Can change the default behaviour of calling estimations using bundler/local
userOp = await this.estimateUserOpGas(
userOp = await this.estimateUserOpGas({
userOp,
buildUseropDto?.overrides,
buildUseropDto?.skipBundlerGasEstimation,
buildUseropDto?.paymasterServiceData,
);
overrides: buildUseropDto?.overrides,
skipBundlerGasEstimation: buildUseropDto?.skipBundlerGasEstimation,
paymasterServiceData: buildUseropDto?.paymasterServiceData,
});
userOp.paymasterAndData = userOp.paymasterAndData ?? "0x";
Logger.log("UserOp after estimation ", userOp);

return userOp;
Expand Down
47 changes: 29 additions & 18 deletions packages/account/src/SmartAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { IPaymaster, PaymasterAndDataResponse } from "@biconomy/paymaster";
import { Logger } from "@biconomy/common";
import { IEntryPoint } from "@account-abstraction/contracts";
import { SponsorUserOperationDto, BiconomyPaymaster, IHybridPaymaster, PaymasterMode } from "@biconomy/paymaster";
import { SmartAccountConfig, Overrides, SendUserOpDto } from "./utils/Types";
import { SmartAccountConfig, SendUserOpDto, EstimateUserOpGasParams } from "./utils/Types";
import { DefaultGasLimit } from "./utils/Constants";

type UserOperationKey = keyof UserOperation;

Expand Down Expand Up @@ -99,12 +100,9 @@ export abstract class SmartAccount implements ISmartAccount {
return userOp;
}

async estimateUserOpGas(
userOp: Partial<UserOperation>,
overrides?: Overrides,
skipBundlerGasEstimation?: boolean,
paymasterServiceData?: SponsorUserOperationDto,
): Promise<Partial<UserOperation>> {
async estimateUserOpGas(params: EstimateUserOpGasParams): Promise<Partial<UserOperation>> {
let userOp = params.userOp;
const { overrides, skipBundlerGasEstimation, paymasterServiceData } = params;
const requiredFields: UserOperationKey[] = ["sender", "nonce", "initCode", "callData"];
this.validateUserOp(userOp, requiredFields);

Expand All @@ -118,26 +116,39 @@ export abstract class SmartAccount implements ISmartAccount {
Logger.log("userOp in estimation", userOp);

if (skipBundlerCall) {
if (this.paymaster && this.paymaster instanceof BiconomyPaymaster && paymasterServiceData?.mode === PaymasterMode.SPONSORED) {
if (this.paymaster && this.paymaster instanceof BiconomyPaymaster) {
if (!userOp.maxFeePerGas && !userOp.maxPriorityFeePerGas) {
throw new Error("maxFeePerGas and maxPriorityFeePerGas are required for skipBundlerCall mode");
}
// Making call to paymaster to get gas estimations for userOp
const { callGasLimit, verificationGasLimit, preVerificationGas, paymasterAndData } = await (
this.paymaster as IHybridPaymaster<SponsorUserOperationDto>
).getPaymasterAndData(userOp, paymasterServiceData);
finalUserOp.verificationGasLimit = verificationGasLimit ?? userOp.verificationGasLimit;
finalUserOp.callGasLimit = callGasLimit ?? userOp.callGasLimit;
finalUserOp.preVerificationGas = preVerificationGas ?? userOp.preVerificationGas;
finalUserOp.paymasterAndData = paymasterAndData ?? userOp.paymasterAndData;
if (paymasterServiceData?.mode === PaymasterMode.SPONSORED) {
const v1BiconomyInfo = {
name: "BICONOMY",
version: "1.0.0",
};
const smartAccountInfo = paymasterServiceData?.smartAccountInfo ?? v1BiconomyInfo;
paymasterServiceData.smartAccountInfo = smartAccountInfo;

// Making call to paymaster to get gas estimations for userOp
const { callGasLimit, verificationGasLimit, preVerificationGas, paymasterAndData } = await (
this.paymaster as IHybridPaymaster<SponsorUserOperationDto>
).getPaymasterAndData(userOp, paymasterServiceData);
finalUserOp.verificationGasLimit = verificationGasLimit ?? userOp.verificationGasLimit;
finalUserOp.callGasLimit = callGasLimit ?? userOp.callGasLimit;
finalUserOp.preVerificationGas = preVerificationGas ?? userOp.preVerificationGas;
finalUserOp.paymasterAndData = paymasterAndData ?? userOp.paymasterAndData;
} else {
// use dummy values for gas limits as fee quote call will ignore this later.
finalUserOp.callGasLimit = DefaultGasLimit.callGasLimit;
finalUserOp.verificationGasLimit = DefaultGasLimit.verificationGasLimit;
finalUserOp.preVerificationGas = DefaultGasLimit.preVerificationGas;
}
} else {
Logger.warn("Skipped paymaster call. If you are using paymasterAndData, generate data externally");
finalUserOp = await this.calculateUserOpGasValues(userOp);
finalUserOp.paymasterAndData = "0x";
}
} else {
if (!this.bundler) throw new Error("Bundler is not provided");
// TODO: is this still needed to delete?
delete userOp.maxFeePerGas;
delete userOp.maxPriorityFeePerGas;
// Making call to bundler to get gas estimations for userOp
Expand Down Expand Up @@ -290,7 +301,7 @@ export abstract class SmartAccount implements ISmartAccount {
Logger.log("userOp validated");
if (!this.bundler) throw new Error("Bundler is not provided");
Logger.log("userOp being sent to the bundler", userOp);
const bundlerResponse = await this.bundler.sendUserOp(userOp, params);
const bundlerResponse = await this.bundler.sendUserOp(userOp, params?.simulationType);
return bundlerResponse;
}
}
6 changes: 6 additions & 0 deletions packages/account/src/utils/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,9 @@ export const PROXY_CREATION_CODE =
"0x6080346100aa57601f61012038819003918201601f19168301916001600160401b038311848410176100af578084926020946040528339810103126100aa57516001600160a01b0381168082036100aa5715610065573055604051605a90816100c68239f35b60405162461bcd60e51b815260206004820152601e60248201527f496e76616c696420696d706c656d656e746174696f6e206164647265737300006044820152606490fd5b600080fd5b634e487b7160e01b600052604160045260246000fdfe608060405230546000808092368280378136915af43d82803e156020573d90f35b3d90fdfea2646970667358221220a03b18dce0be0b4c9afe58a9eb85c35205e2cf087da098bbf1d23945bf89496064736f6c63430008110033";

export const ADDRESS_RESOLVER_ADDRESS = "0x00000E81673606e07fC79CE5F1b3B26957844468";

export const DefaultGasLimit = {
callGasLimit: 800000,
verificationGasLimit: 1000000,
preVerificationGas: 100000,
};
9 changes: 8 additions & 1 deletion packages/account/src/utils/Types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Signer } from "ethers";
import { ChainId } from "@biconomy/core-types";
import { BigNumberish, BigNumber } from "ethers";
import { IBundler } from "@biconomy/bundler";
import { IPaymaster, PaymasterFeeQuote, SponsorUserOperationDto } from "@biconomy/paymaster";
import { BaseValidationModule, ModuleInfo } from "@biconomy/modules";
import { Provider } from "@ethersproject/providers";
import { GasOverheads } from "./Preverificaiton";
import { UserOperation, ChainId } from "@biconomy/core-types";

export type EntryPointAddresses = {
[address: string]: string;
Expand Down Expand Up @@ -123,6 +123,13 @@ export type InitializeV2Data = {
accountIndex?: number;
};

export type EstimateUserOpGasParams = {
userOp: Partial<UserOperation>;
overrides?: Overrides;
skipBundlerGasEstimation?: boolean;
paymasterServiceData?: SponsorUserOperationDto;
};

export interface TransactionDetailsForUserOp {
target: string;
data: string;
Expand Down
6 changes: 3 additions & 3 deletions packages/bundler/src/Bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import {
SendUserOpResponse,
UserOpGasResponse,
UserOpByHashResponse,
SendUserOpOptions,
GetGasFeeValuesResponse,
GasFeeValues,
UserOpStatus,
GetUserOperationStatusResponse,
SimulationType,
} from "./utils/Types";
import { resolveProperties } from "ethers/lib/utils";
import { deepHexlify, sendRequest, getTimestampInSeconds, HttpMethod, Logger, RPC_PROVIDER_URLS } from "@biconomy/common";
Expand Down Expand Up @@ -109,13 +109,13 @@ export class Bundler implements IBundler {
* @description This function will send signed userOp to bundler to get mined on chain
* @returns Promise<UserOpResponse>
*/
async sendUserOp(userOp: UserOperation, simulationParam?: SendUserOpOptions): Promise<UserOpResponse> {
async sendUserOp(userOp: UserOperation, simulationType?: SimulationType): Promise<UserOpResponse> {
const chainId = this.bundlerConfig.chainId;
// transformUserOP will convert all bigNumber values to string
userOp = transformUserOP(userOp);
const hexifiedUserOp = deepHexlify(await resolveProperties(userOp));
const simType = {
simulation_type: simulationParam?.simulationType || "validation",
simulation_type: simulationType || "validation",
};
const params = [hexifiedUserOp, this.bundlerConfig.entryPointAddress, simType];
const bundlerUrl = this.getBundlerUrl();
Expand Down
13 changes: 3 additions & 10 deletions packages/bundler/src/interfaces/IBundler.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import {
UserOpResponse,
UserOpGasResponse,
UserOpReceipt,
UserOpByHashResponse,
SendUserOpOptions,
GasFeeValues,
UserOpStatus,
} from "../utils/Types";
import { UserOpResponse, UserOpGasResponse, UserOpReceipt, UserOpByHashResponse, GasFeeValues, UserOpStatus, SimulationType } from "../utils/Types";
import { UserOperation } from "@biconomy/core-types";

export interface IBundler {
estimateUserOpGas(_userOp: Partial<UserOperation>): Promise<UserOpGasResponse>;
sendUserOp(_userOp: UserOperation, _simulationParam?: SendUserOpOptions): Promise<UserOpResponse>;
// could have second arg object called options
sendUserOp(_userOp: UserOperation, _simulationType?: SimulationType): Promise<UserOpResponse>;
getUserOpReceipt(_userOpHash: string): Promise<UserOpReceipt>;
getUserOpByHash(_userOpHash: string): Promise<UserOpByHashResponse>;
getGasFeeValues(): Promise<GasFeeValues>;
Expand Down
52 changes: 26 additions & 26 deletions packages/bundler/src/utils/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,33 +66,33 @@ export const UserOpWaitForTxHashIntervals: { [key in ChainId]?: number } = {

export const UserOpReceiptMaxDurationIntervals: { [key in ChainId]?: number } = {

Check warning on line 67 in packages/bundler/src/utils/Constants.ts

View workflow job for this annotation

GitHub Actions / Lint sources (18.x)

'key' is defined but never used

Check warning on line 67 in packages/bundler/src/utils/Constants.ts

View workflow job for this annotation

GitHub Actions / Lint sources (18.x)

'key' is defined but never used
[ChainId.MAINNET]: 300000,
[ChainId.GOERLI]: 30000,
[ChainId.POLYGON_MUMBAI]: 30000,
[ChainId.POLYGON_MAINNET]: 30000,
[ChainId.BSC_TESTNET]: 30000,
[ChainId.BSC_MAINNET]: 30000,
[ChainId.POLYGON_ZKEVM_TESTNET]: 30000,
[ChainId.POLYGON_ZKEVM_MAINNET]: 30000,
[ChainId.ARBITRUM_GOERLI_TESTNET]: 30000,
[ChainId.ARBITRUM_ONE_MAINNET]: 30000,
[ChainId.GOERLI]: 50000,
[ChainId.POLYGON_MUMBAI]: 50000,
[ChainId.POLYGON_MAINNET]: 60000,
[ChainId.BSC_TESTNET]: 50000,
[ChainId.BSC_MAINNET]: 50000,
[ChainId.POLYGON_ZKEVM_TESTNET]: 40000,
[ChainId.POLYGON_ZKEVM_MAINNET]: 40000,
[ChainId.ARBITRUM_GOERLI_TESTNET]: 50000,
[ChainId.ARBITRUM_ONE_MAINNET]: 50000,
[ChainId.ARBITRUM_NOVA_MAINNET]: 30000,
[ChainId.OPTIMISM_MAINNET]: 30000,
[ChainId.OPTIMISM_GOERLI_TESTNET]: 30000,
[ChainId.AVALANCHE_MAINNET]: 30000,
[ChainId.AVALANCHE_TESTNET]: 30000,
[ChainId.MOONBEAM_MAINNET]: 30000,
[ChainId.BASE_GOERLI_TESTNET]: 30000,
[ChainId.BASE_MAINNET]: 30000,
[ChainId.LINEA_TESTNET]: 30000,
[ChainId.LINEA_MAINNET]: 30000,
[ChainId.MANTLE_MAINNET]: 30000,
[ChainId.MANTLE_TESTNET]: 30000,
[ChainId.OPBNB_MAINNET]: 30000,
[ChainId.OPBNB_TESTNET]: 30000,
[ChainId.ASTAR_MAINNET]: 30000,
[ChainId.ASTAR_TESTNET]: 30000,
[ChainId.CHILIZ_MAINNET]: 30000,
[ChainId.CHILIZ_TESTNET]: 30000,
[ChainId.OPTIMISM_MAINNET]: 40000,
[ChainId.OPTIMISM_GOERLI_TESTNET]: 40000,
[ChainId.AVALANCHE_MAINNET]: 40000,
[ChainId.AVALANCHE_TESTNET]: 40000,
[ChainId.MOONBEAM_MAINNET]: 40000,
[ChainId.BASE_GOERLI_TESTNET]: 40000,
[ChainId.BASE_MAINNET]: 40000,
[ChainId.LINEA_TESTNET]: 50000,
[ChainId.LINEA_MAINNET]: 50000,
[ChainId.MANTLE_MAINNET]: 40000,
[ChainId.MANTLE_TESTNET]: 40000,
[ChainId.OPBNB_MAINNET]: 40000,
[ChainId.OPBNB_TESTNET]: 40000,
[ChainId.ASTAR_MAINNET]: 40000,
[ChainId.ASTAR_TESTNET]: 40000,
[ChainId.CHILIZ_MAINNET]: 40000,
[ChainId.CHILIZ_TESTNET]: 40000,
};

export const UserOpWaitForTxHashMaxDurationIntervals: { [key in ChainId]?: number } = {

Check warning on line 98 in packages/bundler/src/utils/Constants.ts

View workflow job for this annotation

GitHub Actions / Lint sources (18.x)

'key' is defined but never used

Check warning on line 98 in packages/bundler/src/utils/Constants.ts

View workflow job for this annotation

GitHub Actions / Lint sources (18.x)

'key' is defined but never used
Expand Down
4 changes: 0 additions & 4 deletions packages/bundler/src/utils/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ export type UserOpStatus = {
userOperationReceipt?: UserOpReceipt;
};

export type SendUserOpOptions = {
simulationType?: SimulationType;
};

export type SimulationType = "validation" | "validation_and_execution";

// Converted to JsonRpcResponse with strict type
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { defaultAbiCoder } from "ethers/lib/utils";
import { ISessionValidationModule } from "interfaces/ISessionValidationModule";
import { ERC20SessionKeyData, SessionValidationModuleConfig } from "utils/Types";
import { ISessionValidationModule } from "../interfaces/ISessionValidationModule";
import { ERC20SessionKeyData, SessionValidationModuleConfig } from "../utils/Types";

/**
* Session validation module for ERC20 token transfers.
Expand Down

0 comments on commit f74b117

Please sign in to comment.