diff --git a/lerna.json b/lerna.json index 87258710..301b0adc 100644 --- a/lerna.json +++ b/lerna.json @@ -4,7 +4,7 @@ ], "npmClient": "yarn", "useWorkspaces": true, - "version": "0.0.6", + "version": "0.0.7", "stream": "true", "command": { "version": { diff --git a/package.json b/package.json index d1199a1f..16f0b425 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "root", "private": true, - "version": "0.0.6", + "version": "0.0.7", "engines": { "node": ">=12.9.0" }, diff --git a/packages/api/package.json b/packages/api/package.json index b7b8aaf7..f4fd4723 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "api", - "version": "0.0.6", + "version": "0.0.7", "description": "The API module of Etherspot bundler client", "author": "Etherspot", "homepage": "https://https://github.com/etherspot/skandha#readme", @@ -35,12 +35,12 @@ "class-transformer": "0.5.1", "class-validator": "0.14.0", "ethers": "5.7.2", - "executor": "^0.0.6", + "executor": "^0.0.7", "fastify": "4.14.1", "pino": "8.11.0", "pino-pretty": "10.0.0", "reflect-metadata": "0.1.13", - "types": "^0.0.6" + "types": "^0.0.7" }, "devDependencies": { "@types/connect": "3.4.35" diff --git a/packages/api/src/logger.ts b/packages/api/src/logger.ts index 62ebf7bf..ff012ba8 100644 --- a/packages/api/src/logger.ts +++ b/packages/api/src/logger.ts @@ -2,6 +2,7 @@ import pino from "pino"; const logger = pino({ enabled: !process.env.BENCHMARK, + level: "trace", transport: { target: "pino-pretty", options: { diff --git a/packages/api/src/modules/redirect.ts b/packages/api/src/modules/redirect.ts index 63979539..622f95c3 100644 --- a/packages/api/src/modules/redirect.ts +++ b/packages/api/src/modules/redirect.ts @@ -19,6 +19,18 @@ export class RedirectAPI { if (err.body) { try { const body = JSON.parse(err.body); + + /** NETHERMIND ERROR PARSING */ + if ( + body.error.data.startsWith("Reverted ") && + body.error.code == -32015 + ) { + body.error.code = 3; + body.error.message = "execution reverted"; + body.error.data = body.error.data.slice(9); + } + /** */ + return body; // eslint-disable-next-line no-empty } catch (err) {} diff --git a/packages/cli/package.json b/packages/cli/package.json index 6c539e61..c9589fe6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "cli", - "version": "0.0.6", + "version": "0.0.7", "description": "> TODO: description", "author": "zincoshine ", "homepage": "https://https://github.com/etherspot/skandha#readme", @@ -31,13 +31,13 @@ "url": "https://https://github.com/etherspot/skandha/issues" }, "dependencies": { - "api": "^0.0.6", - "db": "^0.0.6", - "executor": "^0.0.6", + "api": "^0.0.7", + "db": "^0.0.7", + "executor": "^0.0.7", "find-up": "5.0.0", "got": "12.5.3", "js-yaml": "4.1.0", - "types": "^0.0.6", + "types": "^0.0.7", "yargs": "17.6.2" }, "devDependencies": { diff --git a/packages/cli/src/cmds/start/handler.ts b/packages/cli/src/cmds/start/handler.ts index 20321c83..cf4dd246 100644 --- a/packages/cli/src/cmds/start/handler.ts +++ b/packages/cli/src/cmds/start/handler.ts @@ -2,6 +2,7 @@ import path, { resolve } from "node:path"; import { Server } from "api/lib/server"; import { ApiApp } from "api/lib/app"; +import logger from "api/lib/logger"; import { Config } from "executor/lib/config"; import { Namespace, @@ -30,7 +31,7 @@ export async function bundlerHandler( unsafeMode, }); } catch (err) { - console.log("Config file not found. Proceeding with env vars..."); + logger.debug("Config file not found. Proceeding with env vars..."); config = new Config({ networks: {}, testingMode, @@ -38,6 +39,12 @@ export async function bundlerHandler( }); } + if (unsafeMode) { + logger.warn( + "WARNING: Running in unsafe mode, skips opcode check and stake check" + ); + } + let db: IDbController; if (testingMode) { diff --git a/packages/db/package.json b/packages/db/package.json index 0cf8774d..63b1b45a 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "db", - "version": "0.0.6", + "version": "0.0.7", "description": "The DB module of Etherspot bundler client", "author": "Etherspot", "homepage": "https://github.com/etherspot/etherspot-bundler#readme", @@ -37,6 +37,6 @@ "devDependencies": { "@types/rocksdb": "3.0.1", "prettier": "^2.8.4", - "types": "^0.0.6" + "types": "^0.0.7" } } diff --git a/packages/executor/package.json b/packages/executor/package.json index 0fcb9873..d5bf0ccf 100644 --- a/packages/executor/package.json +++ b/packages/executor/package.json @@ -1,6 +1,6 @@ { "name": "executor", - "version": "0.0.6", + "version": "0.0.7", "description": "The Relayer module of Etherspot bundler client", "author": "Etherspot", "homepage": "https://https://github.com/etherspot/skandha#readme", @@ -33,7 +33,7 @@ "dependencies": { "async-mutex": "0.4.0", "ethers": "5.7.2", - "params": "^0.0.6", - "types": "^0.0.6" + "params": "^0.0.7", + "types": "^0.0.7" } } diff --git a/packages/executor/src/modules/eth.ts b/packages/executor/src/modules/eth.ts index da9273ac..25fc3cfe 100644 --- a/packages/executor/src/modules/eth.ts +++ b/packages/executor/src/modules/eth.ts @@ -79,12 +79,12 @@ export class Eth { throw new RpcError("Invalid Entrypoint", RpcErrorCodes.INVALID_REQUEST); } const userOpComplemented: UserOperationStruct = { - ...userOp, paymasterAndData: userOp.paymasterAndData ?? "0x", maxFeePerGas: 0, maxPriorityFeePerGas: 0, preVerificationGas: 0, verificationGasLimit: 10e6, + ...userOp, }; const preVerificationGas = this.calcPreVerificationGas(userOp); userOpComplemented.preVerificationGas = preVerificationGas; @@ -151,7 +151,16 @@ export class Eth { RpcErrorCodes.INVALID_USEROP ); } - await this.userOpValidationService.simulateValidation(userOp, entryPoint); + this.logger.debug( + JSON.stringify( + await this.userOpValidationService.simulateValidation( + userOp, + entryPoint + ), + undefined, + 2 + ) + ); return true; } diff --git a/packages/executor/src/services/BundlingService.ts b/packages/executor/src/services/BundlingService.ts index b890529a..dd9c8d09 100644 --- a/packages/executor/src/services/BundlingService.ts +++ b/packages/executor/src/services/BundlingService.ts @@ -71,17 +71,32 @@ export class BundlingService { "handleOps", [bundle.map((entry) => entry.userOp), beneficiary] ); + this.logger.debug( + JSON.stringify( + { + to: entryPoint, + data: txRequest, + type: 2, + maxPriorityFeePerGas: gasFee.maxPriorityFeePerGas ?? 0, + maxFeePerGas: gasFee.maxFeePerGas ?? 0, + }, + undefined, + 2 + ) + ); + + const gasLimit = await this.estimateBundleGas(bundle); const tx = await wallet.sendTransaction({ to: entryPoint, data: txRequest, type: 2, maxPriorityFeePerGas: gasFee.maxPriorityFeePerGas ?? 0, maxFeePerGas: gasFee.maxFeePerGas ?? 0, + gasLimit, }); this.logger.debug(`Sent new bundle ${tx.hash}`); - // TODO: change to batched delete for (const entry of bundle) { await this.mempoolService.remove(entry); } @@ -319,4 +334,22 @@ export class BundlingService { return []; } } + + private async estimateBundleGas(bundle: MempoolEntry[]): Promise { + let gasLimit = BigNumber.from(0); + for (const { userOp } of bundle) { + gasLimit = BigNumber.from(userOp.verificationGasLimit) + .mul(3) + .add(userOp.preVerificationGas) + .add(userOp.callGasLimit) + .mul(11) + .div(10) + .add(gasLimit); + } + if (gasLimit.lt(1e5)) { + // gasLimit should at least be 1e5 to pass test in test-executor + gasLimit = BigNumber.from(1e5); + } + return gasLimit; + } } diff --git a/packages/executor/src/services/UserOpValidation.ts b/packages/executor/src/services/UserOpValidation.ts index b1640bef..76395755 100644 --- a/packages/executor/src/services/UserOpValidation.ts +++ b/packages/executor/src/services/UserOpValidation.ts @@ -9,11 +9,14 @@ import { IAggregator__factory, } from "types/lib/executor/contracts"; import { IPaymaster__factory } from "types/lib/executor/contracts/factories/IPaymaster__factory"; -import { UserOperationStruct } from "types/lib/executor/contracts/EntryPoint"; +import { + EntryPoint, + UserOperationStruct, +} from "types/lib/executor/contracts/EntryPoint"; import { BannedContracts } from "params/lib"; import { NetworkName } from "types/lib"; import { getAddr } from "../utils"; -import { TracerCall } from "../interfaces"; +import { TracerCall, TracerResult } from "../interfaces"; import { Config } from "../config"; import { ReputationService } from "./ReputationService"; import { GethTracer } from "./GethTracer"; @@ -70,7 +73,8 @@ export class UserOpValidationService { ); const errorResult = await entryPointContract.callStatic .simulateValidation(userOp, { gasLimit: 10e6 }) - .catch((e: any) => e); + .catch((e: any) => this.nethermindErrorHandler(entryPointContract, e)); + if (errorResult.errorName === "FailedOp") { throw new RpcError( errorResult.errorArgs.at(-1), @@ -105,7 +109,7 @@ export class UserOpValidationService { ); const errorResult = await entryPointContract.callStatic .simulateValidation(userOp, { gasLimit: 10e6 }) - .catch((e: any) => e); + .catch((e: any) => this.nethermindErrorHandler(entryPointContract, e)); return this.parseErrorResult(userOp, errorResult); } @@ -127,7 +131,12 @@ export class UserOpValidationService { ), gasLimit: 6e6, }; - const traceCall = await this.gethTracer.debug_traceCall(tx); + const traceCall: TracerResult = await this.gethTracer.debug_traceCall(tx); + if (traceCall == null || traceCall.calls == undefined) { + throw new Error( + "Could not validate transaction. Tracing is not available" + ); + } // TODO: restrict calling EntryPoint methods except fallback and depositTo if depth > 2 @@ -316,7 +325,7 @@ export class UserOpValidationService { const prestateTrace = await this.gethTracer.debug_traceCallPrestate(tx); const addresses = Object.keys(prestateTrace) .sort() - .filter((addr) => traceCall.trace[addr]); + .filter((addr) => traceCall!.trace[addr]); const code = addresses.map((addr) => prestateTrace[addr]?.code).join(";"); const hash = ethers.utils.keccak256( ethers.utils.hexlify(ethers.utils.toUtf8Bytes(code)) @@ -517,4 +526,24 @@ export class UserOpValidationService { return false; } } + + nethermindErrorHandler(epContract: EntryPoint, errorResult: any): any { + try { + let { error } = errorResult; + if (error.error) { + error = error.error; + } + if (error && error.code == -32015 && error.data.startsWith("Reverted ")) { + const parsed = epContract.interface.parseError(error.data.slice(9)); + errorResult = { + ...parsed, + errorName: parsed.name, + errorArgs: parsed.args, + }; + } + } catch (err) { + /* empty */ + } + return errorResult; + } } diff --git a/packages/params/package.json b/packages/params/package.json index d4472c24..0257c725 100644 --- a/packages/params/package.json +++ b/packages/params/package.json @@ -1,6 +1,6 @@ { "name": "params", - "version": "0.0.6", + "version": "0.0.7", "description": "Various bundler parameters", "author": "Etherspot", "homepage": "https://github.com/etherspot/skandha#readme", @@ -21,7 +21,7 @@ "url": "https://github.com/etherspot/skandha/issues" }, "dependencies": { - "types": "^0.0.6" + "types": "^0.0.7" }, "scripts": { "clean": "rm -rf lib && rm -f *.tsbuildinfo", diff --git a/packages/types/package.json b/packages/types/package.json index a85dd764..5e448efb 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "types", - "version": "0.0.6", + "version": "0.0.7", "description": "The types of Etherspot bundler client", "author": "Etherspot", "homepage": "https://https://github.com/etherspot/skandha#readme", diff --git a/packages/types/src/networks/networks.ts b/packages/types/src/networks/networks.ts index 6c7154d7..6dcc8ebd 100644 --- a/packages/types/src/networks/networks.ts +++ b/packages/types/src/networks/networks.ts @@ -27,7 +27,8 @@ export type NetworkName = | "optimismGoerli" | "dev" | "baseGoerli" - | "sepolia"; + | "sepolia" + | "chiado"; export const networkNames: NetworkName[] = [ "mainnet", @@ -59,6 +60,7 @@ export const networkNames: NetworkName[] = [ "dev", "baseGoerli", "sepolia", + "chiado", ]; export const NETWORK_NAME_TO_CHAIN_ID: { @@ -93,4 +95,5 @@ export const NETWORK_NAME_TO_CHAIN_ID: { dev: 1337, baseGoerli: 84531, sepolia: 11155111, + chiado: 10200, };