Skip to content

Instantly share code, notes, and snippets.

@arcticfloyd1984
Created June 13, 2023 12:02
Show Gist options
  • Save arcticfloyd1984/b96b2c1f2408056fce002490368cf347 to your computer and use it in GitHub Desktop.
Save arcticfloyd1984/b96b2c1f2408056fce002490368cf347 to your computer and use it in GitHub Desktop.
static async calcPreVerificationGas(
userOp: UserOperationType,
chainId: number,
entryPointContract: ethers.Contract,
overheads?: Partial<DefaultGasOverheadType>,
) {
const { defaultGasOverheads } = config;
const ov = { ...defaultGasOverheads, ...(overheads ?? {}) };
const p: UserOperationType = {
...userOp,
// dummy values, in case the UserOp is incomplete.
callGasLimit: BigNumber.from('0'),
maxFeePerGas: BigNumber.from('0'),
maxPriorityFeePerGas: BigNumber.from('0'),
verificationGasLimit: '0xF4240', // 1000000
preVerificationGas: 21000, // dummy value, just for calldata cost
signature: hexlify(Buffer.alloc(ov.sigSize, 1)), // dummy signature
} as any;
const packed = arrayify(packUserOp(p, false));
const callDataCost = packed
.map((x) => (x === 0 ? ov.zeroByte : ov.nonZeroByte))
.reduce((sum, x) => sum + x);
let ret = Math.round(
callDataCost
+ ov.fixed / ov.bundleSize
+ ov.perUserOp
+ ov.perUserOpWord * packed.length,
);
// calculate offset for Arbitrum
if (
chainId === BLOCKCHAINS.ARBITRUM_GOERLI_TESTNET
|| chainId === BLOCKCHAINS.ARBITRUM_NOVA_MAINNET
|| chainId === BLOCKCHAINS.ARBITRUM_ONE_MAINNET
) {
const data = await calcGasPrice(
entryPointContract.address,
userOp,
chainId,
);
ret += data;
}
return ret;
}
}
async estimateCreationGas(
entryPointAddress: string,
initCode?: string,
): Promise<number> {
if (initCode == null || initCode === '0x') return 0;
const deployerAddress = initCode.substring(0, 42);
const deployerCallData = `0x${initCode.substring(42)}`;
return this.networkService
.estimateCallGas(entryPointAddress, deployerAddress, deployerCallData)
.then((callGasLimitResponse) => callGasLimitResponse.toNumber())
.catch((error) => {
const message = error.message.match(/reason="(.*?)"/)?.at(1) ?? 'execution reverted';
log.info(`message: ${JSON.stringify(message)}`);
return 0;
});
}
async estimateUserOperationGas(
estimateUserOperationGasData: EstimateUserOperationGasDataType,
) {
const { userOp, entryPointContract, chainId } = estimateUserOperationGasData;
// 1. callGasLimit
let callGasLimit = 0;
if (userOp.callData === '0x') {
callGasLimit = 21000;
} else if (userOp.initCode !== '0x') {
// wallet not deployed yet and can't estimate on undeployed wallet
callGasLimit = 600000;
} else {
callGasLimit = await this.networkService
.estimateCallGas(
entryPointContract.address,
userOp.sender,
userOp.callData,
)
.then((callGasLimitResponse) => callGasLimitResponse.toNumber())
.catch((error) => {
const message = error.message.match(/reason="(.*?)"/)?.at(1) ?? 'execution reverted';
log.info(`message: ${JSON.stringify(message)}`);
return 0;
});
}
// 2. verificationGasLimit
const initGas = await this.estimateCreationGas(
entryPointContract.address,
userOp.initCode,
);
log.info(`initGas: ${initGas} on chainId: ${chainId}`);
const DefaultGasLimits = {
validateUserOpGas: 100000,
validatePaymasterUserOpGas: 100000,
postOpGas: 10877,
};
const validateUserOpGas = DefaultGasLimits.validatePaymasterUserOpGas
+ DefaultGasLimits.validateUserOpGas;
const { postOpGas } = DefaultGasLimits;
let verificationGasLimit = BigNumber.from(validateUserOpGas)
.add(initGas)
.toNumber();
if (BigNumber.from(postOpGas).gt(verificationGasLimit)) {
verificationGasLimit = postOpGas;
}
// validAfter, validUntil, deadline
const entryPointStatic = entryPointContract.connect(
this.networkService.ethersProvider.getSigner(config.zeroAddress),
);
const fullUserOp = {
...userOp,
// default values for missing fields.
signature:
'0x73c3ac716c487ca34bb858247b5ccf1dc354fbaabdd089af3b2ac8e78ba85a4959a2d76250325bd67c11771c31fccda87c33ceec17cc0de912690521bb95ffcb1b', // a valid signature
callGasLimit: BigNumber.from('0'),
maxFeePerGas: BigNumber.from('0'),
maxPriorityFeePerGas: BigNumber.from('0'),
preVerificationGas: BigNumber.from('0'),
verificationGasLimit,
};
let returnInfo;
try {
const simulationResult: any = await entryPointStatic.callStatic
.simulateValidation(fullUserOp)
.catch((e: any) => e);
log.info(`simulationResult: ${JSON.stringify(simulationResult)}`);
returnInfo = BundlerSimulationAndValidationService.parseUserOpSimulationResult(
userOp,
simulationResult,
).returnInfo;
} catch (error: any) {
log.info(`Bundler Simulation failed: ${parseError(error)}`);
return {
code: error.code,
message: parseError(error),
data: {
preVerificationGas: 0,
verificationGasLimit: 0,
callGasLimit: 0,
validAfter: 0,
validUntil: 0,
deadline: 0,
},
};
}
let { validAfter, validUntil } = returnInfo;
validAfter = BigNumber.from(validAfter);
validUntil = BigNumber.from(validUntil);
if (validUntil === BigNumber.from(0)) {
validUntil = undefined;
}
if (validAfter === BigNumber.from(0)) {
validAfter = undefined;
}
let deadline: any;
if (returnInfo.deadline) {
deadline = BigNumber.from(returnInfo.deadline);
}
const simulateUserOp = {
...userOp,
// default values for missing fields.
signature:
'0x73c3ac716c487ca34bb858247b5ccf1dc354fbaabdd089af3b2ac8e78ba85a4959a2d76250325bd67c11771c31fccda87c33ceec17cc0de912690521bb95ffcb1b', // a valid signature
callGasLimit: '0x00',
maxFeePerGas: '0x00',
maxPriorityFeePerGas: '0x00',
preVerificationGas: '0x00',
};
// 3. preVerificationGas
const preVerificationGas = await BundlerSimulationAndValidationService.calcPreVerificationGas(
simulateUserOp,
chainId,
entryPointContract,
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment