Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat paymaster call for buildUserOp #305

Merged
merged 26 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e55f2ae
add paymaster call for buildUserOp
AmanRaj1608 Sep 27, 2023
b0b0c49
code refactor
AmanRaj1608 Sep 28, 2023
320c4c3
minor fixes
AmanRaj1608 Sep 28, 2023
076ceba
chore: paymaster code changes
AmanRaj1608 Sep 28, 2023
9518cc7
Merge remote-tracking branch 'origin/development' into feat-paymaster…
AmanRaj1608 Sep 28, 2023
44ca56c
compute userOp locally
AmanRaj1608 Sep 28, 2023
2352218
fix test case for workflow
livingrockrises Sep 29, 2023
974e546
calcGasLimits check fix for undefined/true/false
livingrockrises Sep 30, 2023
6077bbc
dev notes
livingrockrises Sep 30, 2023
075afb3
refactor
livingrockrises Sep 30, 2023
c6e5570
add gasless flag + make 2.0.0 default + dont make provider feeData ca…
livingrockrises Sep 30, 2023
0300c08
Merge branch 'development' into feat-paymaster-changes
livingrockrises Oct 2, 2023
69b09d0
revert gasless flag from current feat branch
livingrockrises Oct 2, 2023
d6fc21a
lint fixes
livingrockrises Oct 2, 2023
af90122
add dependency
livingrockrises Oct 4, 2023
55e7577
add dependency
livingrockrises Oct 4, 2023
303e316
coverage script
livingrockrises Oct 4, 2023
a85490f
updated bundler getGasFeeValues
AmanRaj1608 Oct 4, 2023
321e087
update changes for v1 accounts
AmanRaj1608 Oct 9, 2023
9a816a5
flag logic update incase of undefined
AmanRaj1608 Oct 9, 2023
0d965b9
Sending better error message
tomarsachin2271 Oct 9, 2023
f1518d8
comments resolve
AmanRaj1608 Oct 9, 2023
17570fb
Merge branch 'feat-paymaster-changes' of https://github.com/bcnmy/bic…
AmanRaj1608 Oct 9, 2023
57a6600
test fix
AmanRaj1608 Oct 9, 2023
8ba5d9c
remove max fee values from paymaster response
AmanRaj1608 Oct 9, 2023
9c8997d
update error logs
AmanRaj1608 Oct 9, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"test": "concurrently -k --success first 'yarn start:ganache' 'yarn test:run'",
"start:ganache": "ganache -m 'direct buyer cliff train rice spirit census refuse glare expire innocent quote'",
"test:ci": "FORCE_COLOR=1 lerna run test:ci --stream --npm-client=yarn",
"test:coverage": "yarn jest --coverage",
"test:coverage": "concurrently -k --success first 'yarn start:ganache' 'yarn jest --runInBand --coverage'",
"diff": "lerna diff",
"release": "lerna version patch --no-git-tag-version --no-push --conventional-commits --yes"
},
Expand All @@ -53,10 +53,12 @@
]
},
"dependencies": {
"ganache": "^7.9.1",
"node-gyp": "^9.4.0",
"typescript": "^5.2.2"
},
"devDependencies": {
"@types/debug": "^4.1.9",
"@types/jest": "^29.5.4",
"@typescript-eslint/eslint-plugin": "^6.7.0",
"@typescript-eslint/parser": "^6.6.0",
Expand Down
59 changes: 47 additions & 12 deletions packages/account/src/BaseSmartAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import { calcPreVerificationGas, DefaultGasLimits } from "./utils/Preverificaito
import { NotPromise, packUserOp, Logger, RPC_PROVIDER_URLS } from "@biconomy/common";
import { IBundler, UserOpResponse } from "@biconomy/bundler";
import { IPaymaster, PaymasterAndDataResponse } from "@biconomy/paymaster";
import { SponsorUserOperationDto, BiconomyPaymaster, PaymasterMode, IHybridPaymaster } from "@biconomy/paymaster";
import { BaseSmartAccountConfig, Overrides, SendUserOpOptions, 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 { LRUCache } from 'lru-cache'
import { LRUCache } from "lru-cache";

type UserOperationKey = keyof UserOperation;

Expand Down Expand Up @@ -71,7 +72,7 @@ export abstract class BaseSmartAccount implements IBaseSmartAccount {
validateUserOp(userOp: Partial<UserOperation>, requiredFields: UserOperationKey[]): boolean {
for (const field of requiredFields) {
if (!userOp[field]) {
throw new Error(`${field} is missing`);
throw new Error(`${String(field)} is missing in the UserOp`);
}
}
return true;
Expand Down Expand Up @@ -205,10 +206,24 @@ export abstract class BaseSmartAccount implements IBaseSmartAccount {

async calculateUserOpGasValues(userOp: Partial<UserOperation>): Promise<Partial<UserOperation>> {
if (!this.provider) throw new Error("Provider is not present for making rpc calls");
const feeData = await this.provider.getFeeData();
userOp.maxFeePerGas = userOp.maxFeePerGas ?? feeData.maxFeePerGas ?? feeData.gasPrice ?? (await this.provider.getGasPrice());
userOp.maxPriorityFeePerGas =
userOp.maxPriorityFeePerGas ?? feeData.maxPriorityFeePerGas ?? feeData.gasPrice ?? (await this.provider.getGasPrice());
let feeData = null;

if (
userOp.maxFeePerGas === undefined ||
userOp.maxFeePerGas === null ||
userOp.maxPriorityFeePerGas === undefined ||
userOp.maxPriorityFeePerGas === null
) {
feeData = await this.provider.getFeeData();
}

if (userOp.maxFeePerGas === undefined || userOp.maxFeePerGas === null) {
userOp.maxFeePerGas = feeData?.maxFeePerGas ?? feeData?.gasPrice ?? (await this.provider.getGasPrice());
}

if (userOp.maxPriorityFeePerGas === undefined || userOp.maxPriorityFeePerGas === null) {
userOp.maxPriorityFeePerGas = feeData?.maxPriorityFeePerGas ?? feeData?.gasPrice ?? (await this.provider.getGasPrice());
}
if (userOp.initCode) userOp.verificationGasLimit = userOp.verificationGasLimit ?? (await this.getVerificationGasLimit(userOp.initCode));
userOp.callGasLimit =
userOp.callGasLimit ??
Expand All @@ -221,29 +236,48 @@ 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>> {
const requiredFields: UserOperationKey[] = ["sender", "nonce", "initCode", "callData"];
this.validateUserOp(userOp, requiredFields);

let finalUserOp = userOp;
const skipBundlerCall = skipBundlerGasEstimation ?? false;
const skipBundlerCall = skipBundlerGasEstimation ?? true;
// Override gas values in userOp if provided in overrides params
if (overrides) {
userOp = { ...userOp, ...overrides };
}

Logger.log("userOp in estimation", userOp);

if (!this.bundler || skipBundlerCall) {
if (!this.provider) throw new Error("Provider is not present for making rpc calls");
// if no bundler url is provided run offchain logic to assign following values of UserOp
// maxFeePerGas, maxPriorityFeePerGas, verificationGasLimit, callGasLimit, preVerificationGas
finalUserOp = await this.calculateUserOpGasValues(userOp);
if (skipBundlerCall) {
// Review: instead of checking mode it could be assumed or just pass gasless flag and use it
// make pmService data locally and pass the object with default values
if (this.paymaster && this.paymaster instanceof BiconomyPaymaster && paymasterServiceData?.mode === PaymasterMode.SPONSORED) {
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;
} 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 All @@ -261,6 +295,7 @@ export abstract class BaseSmartAccount implements IBaseSmartAccount {
finalUserOp.verificationGasLimit = verificationGasLimit ?? userOp.verificationGasLimit;
finalUserOp.callGasLimit = callGasLimit ?? userOp.callGasLimit;
finalUserOp.preVerificationGas = preVerificationGas ?? userOp.preVerificationGas;
finalUserOp.paymasterAndData = "0x";
}
return finalUserOp;
}
Expand Down
89 changes: 48 additions & 41 deletions packages/account/src/BiconomySmartAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,25 +256,59 @@ export class BiconomySmartAccount extends SmartAccount implements IBiconomySmart
return "0x";
}

async buildUserOp(transactions: Transaction[], overrides?: Overrides, skipBundlerGasEstimation?: boolean): Promise<Partial<UserOperation>> {
private async getNonce(): Promise<BigNumber> {
let nonce = BigNumber.from(0);
try {
nonce = await this.nonce();
} catch (error) {
// Not throwing this error as nonce would be 0 if this.nonce() throw exception, which is expected flow for undeployed account
AmanRaj1608 marked this conversation as resolved.
Show resolved Hide resolved
Logger.log("Error while getting nonce for the account. This is expected for undeployed accounts set nonce to 0");
}
return nonce;
}

private async getGasFeeValues(
overrides: Overrides | undefined,
skipBundlerGasEstimation: boolean | undefined,
): Promise<{ maxFeePerGas?: BigNumberish | undefined; maxPriorityFeePerGas?: BigNumberish | undefined }> {
const gasFeeValues = {
maxFeePerGas: overrides?.maxFeePerGas,
maxPriorityFeePerGas: overrides?.maxPriorityFeePerGas,
};
try {
if (this.bundler && !gasFeeValues.maxFeePerGas && !gasFeeValues.maxPriorityFeePerGas && (skipBundlerGasEstimation ?? true)) {
const gasFeeEstimation = await this.bundler.getGasFeeValues();
gasFeeValues.maxFeePerGas = gasFeeEstimation.maxFeePerGas;
gasFeeValues.maxPriorityFeePerGas = gasFeeEstimation.maxPriorityFeePerGas;
}
return gasFeeValues;
} catch (error: any) {
Logger.error("Error while getting gasFeeValues from bundler. Provided bundler might not have getGasFeeValues endpoint", error);
return gasFeeValues;
}
}

async buildUserOp(
transactions: Transaction[],
overrides?: Overrides,
skipBundlerGasEstimation?: boolean,
paymasterServiceData?: SponsorUserOperationDto,
): Promise<Partial<UserOperation>> {
this.isInitialized();
const to = transactions.map((element: Transaction) => element.to);
const data = transactions.map((element: Transaction) => element.data ?? "0x");
const value = transactions.map((element: Transaction) => element.value ?? BigNumber.from("0"));
this.isProxyDefined();

const [nonce, gasFeeValues] = await Promise.all([this.getNonce(), this.getGasFeeValues(overrides, skipBundlerGasEstimation)]);

let callData = "";
if (transactions.length === 1) {
callData = this.getExecuteCallData(to[0], value[0], data[0]);
} else {
callData = this.getExecuteBatchCallData(to, value, data);
}
let nonce = BigNumber.from(0);
try {
nonce = await this.nonce();
} catch (error) {
// Not throwing this error as nonce would be 0 if this.nonce() throw exception, which is expected flow for undeployed account
}

let isDeployed = true;

if (nonce.eq(0)) {
Expand All @@ -286,16 +320,14 @@ export class BiconomySmartAccount extends SmartAccount implements IBiconomySmart
nonce,
initCode: !isDeployed ? this.initCode : "0x",
callData: callData,
maxFeePerGas: gasFeeValues.maxFeePerGas || undefined,
maxPriorityFeePerGas: gasFeeValues.maxPriorityFeePerGas || undefined,
signature: this.getDummySignature(),
};

// for this Smart Account dummy ECDSA signature will be used to estimate gas
userOp.signature = this.getDummySignature();

userOp = await this.estimateUserOpGas(userOp, overrides, skipBundlerGasEstimation);
Logger.log("userOp after estimation ", userOp);

// Do not populate paymasterAndData as part of buildUserOp as it may not have all necessary details
userOp.paymasterAndData = "0x"; // await this.getPaymasterAndData(userOp)
// Note: Can change the default behaviour of calling estimations using bundler/local
userOp = await this.estimateUserOpGas(userOp, overrides, skipBundlerGasEstimation, paymasterServiceData);
Logger.log("UserOp after estimation ", userOp);

return userOp;
}
Expand Down Expand Up @@ -394,36 +426,11 @@ export class BiconomySmartAccount extends SmartAccount implements IBiconomySmart

newCallData = this.getExecuteBatchCallData(batchTo, batchValue, batchData);
}
let finalUserOp: Partial<UserOperation> = {
const finalUserOp: Partial<UserOperation> = {
...userOp,
callData: newCallData,
};

// Requesting to update gas limits again (especially callGasLimit needs to be re-calculated)
try {
delete finalUserOp.callGasLimit;
delete finalUserOp.verificationGasLimit;
delete finalUserOp.preVerificationGas;

// Maybe send paymasterAndData since we know it's for Token paymaster
/*finalUserOp.paymasterAndData =
'0x00000f7365ca6c59a2c93719ad53d567ed49c14c000000000000000000000000000000000000000000000000000000000064e3d3890000000000000000000000000000000000000000000000000000000064e3cc81000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000000000f7748595e46527413574a9327942e744e910000000000000000000000000000000000000000000000000000000063ac7f6c000000000000000000000000000000000000000000000000000000000010c8e07bf61410b71700f943499adfd23e50fb16040d587acb0a5e60ac8576cdbb4c8044f00579a1fc3f294e7dc4a5eb557a7193008343aa36225bddcfbd4fd15646031c'*/

// Review: and handle the case when mock pnd fails with AA31 during simulation.

finalUserOp = await this.estimateUserOpGas(finalUserOp);
const cgl = ethers.BigNumber.from(finalUserOp.callGasLimit);
if (finalUserOp.callGasLimit && cgl.lt(ethers.BigNumber.from("21000"))) {
return {
...userOp,
callData: newCallData,
};
}
Logger.log("userOp after estimation ", finalUserOp);
} catch (error) {
Logger.error("Failed to estimate gas for userOp with updated callData ", error);
Logger.log("sending updated userOp. calculateGasLimit flag should be sent to the paymaster to be able to update callGasLimit");
}
return finalUserOp;
}
} catch (error) {
Expand Down
Loading
Loading