From f199d4fe9cbd2bd88d613a4a89ddd62eac4dd4ee Mon Sep 17 00:00:00 2001 From: marie-fourier Date: Wed, 30 Aug 2023 13:20:12 +0500 Subject: [PATCH 1/4] binary search gas limits --- packages/executor/src/modules/eth.ts | 23 +- .../executor/src/services/BundlingService.ts | 4 + .../src/services/UserOpValidation/service.ts | 22 ++ .../src/services/UserOpValidation/utils.ts | 20 +- .../UserOpValidation/validators/estimation.ts | 211 ++++++++++++++++-- .../UserOpValidation/validators/safe.ts | 22 +- 6 files changed, 264 insertions(+), 38 deletions(-) diff --git a/packages/executor/src/modules/eth.ts b/packages/executor/src/modules/eth.ts index 52d7654f..1b0b549b 100644 --- a/packages/executor/src/modules/eth.ts +++ b/packages/executor/src/modules/eth.ts @@ -159,12 +159,27 @@ export class Eth { }); //> - const userOpToEstimate: UserOperationStruct = { + // Binary search gas limits + let userOpToEstimate: UserOperationStruct = { ...userOpComplemented, preVerificationGas, verificationGasLimit, callGasLimit, }; + + // binary search vgl and cgl + try { + userOpToEstimate = await this.userOpValidationService.binarySearchVGL( + userOpToEstimate, + entryPoint + ); + userOpToEstimate = await this.userOpValidationService.binarySearchCGL( + userOpToEstimate, + entryPoint + ); + // eslint-disable-next-line no-empty + } catch (err) {} + const gasFee = await getGasFee( this.networkName, this.provider, @@ -177,11 +192,11 @@ export class Eth { return { preVerificationGas, - verificationGasLimit, - verificationGas: verificationGasLimit, + verificationGasLimit: userOpToEstimate.verificationGasLimit, + verificationGas: userOpToEstimate.verificationGasLimit, validAfter: BigNumber.from(validAfter), validUntil: BigNumber.from(validUntil), - callGasLimit, + callGasLimit: userOpToEstimate.callGasLimit, maxFeePerGas: gasFee.maxFeePerGas, maxPriorityFeePerGas: gasFee.maxPriorityFeePerGas, }; diff --git a/packages/executor/src/services/BundlingService.ts b/packages/executor/src/services/BundlingService.ts index 92831d50..aca5ad5a 100644 --- a/packages/executor/src/services/BundlingService.ts +++ b/packages/executor/src/services/BundlingService.ts @@ -52,6 +52,10 @@ export class BundlingService { async sendNextBundle(): Promise { return await this.mutex.runExclusive(async () => { + const entries = await this.mempoolService.getSortedOps(); + if (!entries.length) { + return null; + } this.logger.debug("sendNextBundle"); const gasFee = await getGasFee( this.network, diff --git a/packages/executor/src/services/UserOpValidation/service.ts b/packages/executor/src/services/UserOpValidation/service.ts index 84a2964f..8bc804b9 100644 --- a/packages/executor/src/services/UserOpValidation/service.ts +++ b/packages/executor/src/services/UserOpValidation/service.ts @@ -111,4 +111,26 @@ export class UserOpValidationService { return true; } + + async binarySearchVGL( + userOp: UserOperationStruct, + entryPoint: string + ): Promise { + if (this.config.unsafeMode) { + // TODO: handle Nethermind + return this.estimationService.binarySearchVGL(userOp, entryPoint); + } + return this.estimationService.binarySearchVGLSafe(userOp, entryPoint); + } + + async binarySearchCGL( + userOp: UserOperationStruct, + entryPoint: string + ): Promise { + if (this.config.unsafeMode) { + // TODO: handle Nethermind + return userOp; // CGL search not supported in unsafeMode + } + return this.estimationService.binarySearchCGLSafe(userOp, entryPoint); + } } diff --git a/packages/executor/src/services/UserOpValidation/utils.ts b/packages/executor/src/services/UserOpValidation/utils.ts index 9e98a52a..7448cb2e 100644 --- a/packages/executor/src/services/UserOpValidation/utils.ts +++ b/packages/executor/src/services/UserOpValidation/utils.ts @@ -23,7 +23,7 @@ export function nethermindErrorHandler( ): any { try { let { error } = errorResult; - if (error.error) { + if (error && error.error) { error = error.error; } if (error && error.code == -32015 && error.data.startsWith("Reverted ")) { @@ -311,3 +311,21 @@ export function isSlotAssociatedWith( } return false; } + +export function parseValidationResult( + entryPointContract: IEntryPoint, + userOp: UserOperationStruct, + data: string +): UserOpValidationResult { + const { name: errorName, args: errorArgs } = + entryPointContract.interface.parseError(data); + const errFullName = `${errorName}(${errorArgs.toString()})`; + const errResult = parseErrorResult(userOp, { + errorName, + errorArgs, + }); + if (!errorName.includes("Result")) { + throw new Error(errFullName); + } + return errResult; +} diff --git a/packages/executor/src/services/UserOpValidation/validators/estimation.ts b/packages/executor/src/services/UserOpValidation/validators/estimation.ts index edc431d5..35475219 100644 --- a/packages/executor/src/services/UserOpValidation/validators/estimation.ts +++ b/packages/executor/src/services/UserOpValidation/validators/estimation.ts @@ -2,13 +2,31 @@ import { AddressZero, BytesZero } from "params/lib"; import RpcError from "types/lib/api/errors/rpc-error"; import { IEntryPoint__factory } from "types/lib/executor/contracts"; import { UserOperationStruct } from "types/lib/executor/contracts/EntryPoint"; +import { BundlerCollectorReturn, ExitInfo } from "types/lib/executor"; import * as RpcErrorCodes from "types/lib/api/errors/rpc-error-codes"; -import { providers } from "ethers"; -import { nethermindErrorHandler } from "../utils"; +import { BigNumber, providers } from "ethers"; +import { nethermindErrorHandler, parseValidationResult } from "../utils"; import { ExecutionResult, Logger } from "../../../interfaces"; +import { GethTracer } from "../GethTracer"; + +const isVGLLow = (err: Error): boolean => { + const { message } = err; + if (!message) return false; + return ( + message.indexOf("OOG") > -1 || + message.indexOf("AA40") > -1 || + message.indexOf("ogg.validation") > -1 + ); +}; export class EstimationService { - constructor(private provider: providers.Provider, private logger: Logger) {} + private gethTracer: GethTracer; + + constructor(private provider: providers.Provider, private logger: Logger) { + this.gethTracer = new GethTracer( + this.provider as providers.JsonRpcProvider + ); + } async estimateUserOp( userOp: UserOperationStruct, @@ -19,20 +37,11 @@ export class EstimationService { this.provider ); - const tx = { - to: entryPoint, - data: entryPointContract.interface.encodeFunctionData( - "simulateHandleOp", - [userOp, AddressZero, BytesZero] - ), - }; - const errorResult = await entryPointContract.callStatic .simulateHandleOp(userOp, AddressZero, BytesZero) .catch((e: any) => nethermindErrorHandler(entryPointContract, e)); if (errorResult.errorName === "FailedOp") { - this.logger.debug(tx); throw new RpcError( errorResult.errorArgs.at(-1), RpcErrorCodes.VALIDATION_FAILED @@ -40,10 +49,186 @@ export class EstimationService { } if (errorResult.errorName !== "ExecutionResult") { - this.logger.debug(tx); throw errorResult; } return errorResult.errorArgs; } + + // Binary search verificationGasLimit + async binarySearchVGL( + userOp: UserOperationStruct, + entryPoint: string + ): Promise { + const { verificationGasLimit } = userOp; + let [left, right] = [ + BigNumber.from(verificationGasLimit).div(2), // the estimated VGL doesn't differ that much from the actual VGL, so we can add some markup here + BigNumber.from(verificationGasLimit), + ]; + let lastOptimalVGL: BigNumber | undefined; + while (left.lt(right)) { + const mid = left.add(right).div(2); + userOp.verificationGasLimit = mid; + try { + await this.estimateUserOp({ ...userOp, callGasLimit: 0 }, entryPoint); + lastOptimalVGL = mid; + break; + } catch (err) { + if (isVGLLow(err as Error)) { + left = mid.add(1); + } else { + right = mid.sub(1); + } + } + } + + userOp.verificationGasLimit = lastOptimalVGL || userOp.verificationGasLimit; + return userOp; + } + + async binarySearchVGLSafe( + userOp: UserOperationStruct, + entryPoint: string + ): Promise { + const { verificationGasLimit } = userOp; + let [left, right] = [ + BigNumber.from(verificationGasLimit).div(2), + BigNumber.from(verificationGasLimit), + ]; + let lastOptimalVGL: BigNumber | undefined; + while (left.lt(right)) { + const mid = left.add(right).div(2); + userOp.verificationGasLimit = mid; + this.logger.debug(`mid: ${mid}`); + try { + await this.checkForOOG({ ...userOp, callGasLimit: 0 }, entryPoint); + lastOptimalVGL = mid; + break; + } catch (err) { + this.logger.debug(err); + if (isVGLLow(err as Error)) { + left = mid.add(1); + } else { + right = mid.sub(1); + } + } + } + + userOp.verificationGasLimit = lastOptimalVGL || userOp.verificationGasLimit; + return userOp; + } + + async binarySearchCGLSafe( + userOp: UserOperationStruct, + entryPoint: string + ): Promise { + const { callGasLimit } = userOp; + let [left, right] = [ + BigNumber.from(callGasLimit).div(5), + BigNumber.from(callGasLimit), + ]; + let lastOptimalCGL: BigNumber | undefined; + while (left.lt(right)) { + const mid = left.add(right).div(2); + lastOptimalCGL = mid; + try { + await this.checkForOOG(userOp, entryPoint); + lastOptimalCGL = mid; + } catch (err) { + this.logger.debug(err); + if (isVGLLow(err as Error)) { + left = mid.add(1); + } else { + right = mid.sub(1); + } + } + } + + userOp.callGasLimit = lastOptimalCGL || userOp.callGasLimit; + return userOp; + } + + // Binary search callGasLimit + async binarySearchCGL( + userOp: UserOperationStruct, + entryPoint: string + ): Promise { + const { callGasLimit } = userOp; + let [left, right] = [ + BigNumber.from(callGasLimit).div(5), // the estimated CGL doesn't differ that much from the actual CGL, so we can add some markup here + BigNumber.from(callGasLimit), + ]; + let lastOptimalCGL: BigNumber | undefined; + let retries = 2; // keep trying to find the most optimal value + while (left.lt(right)) { + const mid = left.add(right).div(2); + userOp.callGasLimit = mid; + try { + await this.estimateUserOp(userOp, entryPoint); + lastOptimalCGL = mid; + right = mid.sub(1); + } catch (err) { + if (isVGLLow(err as Error)) { + left = mid.add(1); + } else { + right = mid.sub(1); + } + if (lastOptimalCGL !== undefined && retries == 0) break; + if (lastOptimalCGL !== undefined) { + retries--; + } + } + } + + userOp.callGasLimit = lastOptimalCGL || userOp.callGasLimit; + return userOp; + } + + async checkForOOG( + userOp: UserOperationStruct, + entryPoint: string + ): Promise { + const entryPointContract = IEntryPoint__factory.connect( + entryPoint, + this.provider + ); + + const tx = { + data: entryPointContract.interface.encodeFunctionData( + "simulateHandleOp", + [userOp, AddressZero, BytesZero] + ), + to: entryPoint, + }; + + const traceCall: BundlerCollectorReturn = + await this.gethTracer.debug_traceCall(tx); + const lastResult = traceCall.calls.at(-1) as ExitInfo; + if (lastResult.type !== "REVERT") { + throw new RpcError( + "Invalid response. simulateCall must revert", + RpcErrorCodes.VALIDATION_FAILED + ); + } + const data = (lastResult as ExitInfo).data; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const validationResult = parseValidationResult( + entryPointContract, + userOp, + data + ); + + traceCall.callsFromEntryPoint.forEach((currentLevel, index) => { + if (currentLevel.oog) { + if (index >= 1 && index < 3) { + throw new Error("oog.validation"); + } + if (index == 3) { + throw new Error("oog.execution"); + } + } + }); + + return ""; + } } diff --git a/packages/executor/src/services/UserOpValidation/validators/safe.ts b/packages/executor/src/services/UserOpValidation/validators/safe.ts index d8a81322..0282f580 100644 --- a/packages/executor/src/services/UserOpValidation/validators/safe.ts +++ b/packages/executor/src/services/UserOpValidation/validators/safe.ts @@ -24,7 +24,7 @@ import { isSlotAssociatedWith, parseCallStack, parseEntitySlots, - parseErrorResult, + parseValidationResult, } from "../utils"; import { ReputationService } from "../../ReputationService"; @@ -215,7 +215,7 @@ export class SafeValidationService { ); } const data = (lastResult as ExitInfo).data; - const validationResult = this.parseValidationResult( + const validationResult = parseValidationResult( entryPointContract, userOp, data @@ -405,22 +405,4 @@ export class SafeValidationService { return validationResult; } - - private parseValidationResult( - entryPointContract: IEntryPoint, - userOp: UserOperationStruct, - data: string - ): UserOpValidationResult { - const { name: errorName, args: errorArgs } = - entryPointContract.interface.parseError(data); - const errFullName = `${errorName}(${errorArgs.toString()})`; - const errResult = parseErrorResult(userOp, { - errorName, - errorArgs, - }); - if (!errorName.includes("Result")) { - throw new Error(errFullName); - } - return errResult; - } } From f64484ed86ab75800aec73bb568b20cd4f0e758d Mon Sep 17 00:00:00 2001 From: marie-fourier Date: Mon, 4 Sep 2023 12:51:33 +0500 Subject: [PATCH 2/4] fix binary search & adjust to new test cases --- packages/executor/src/modules/eth.ts | 4 - .../src/services/UserOpValidation/service.ts | 2 - .../UserOpValidation/validators/estimation.ts | 76 ++-- .../UserOpValidation/validators/safe.ts | 31 +- packages/executor/tracer.js | 332 ++++++++++-------- .../types/src/executor/validation/index.ts | 15 +- 6 files changed, 238 insertions(+), 222 deletions(-) diff --git a/packages/executor/src/modules/eth.ts b/packages/executor/src/modules/eth.ts index 1b0b549b..ff02147d 100644 --- a/packages/executor/src/modules/eth.ts +++ b/packages/executor/src/modules/eth.ts @@ -173,10 +173,6 @@ export class Eth { userOpToEstimate, entryPoint ); - userOpToEstimate = await this.userOpValidationService.binarySearchCGL( - userOpToEstimate, - entryPoint - ); // eslint-disable-next-line no-empty } catch (err) {} diff --git a/packages/executor/src/services/UserOpValidation/service.ts b/packages/executor/src/services/UserOpValidation/service.ts index 8bc804b9..e1053b33 100644 --- a/packages/executor/src/services/UserOpValidation/service.ts +++ b/packages/executor/src/services/UserOpValidation/service.ts @@ -117,7 +117,6 @@ export class UserOpValidationService { entryPoint: string ): Promise { if (this.config.unsafeMode) { - // TODO: handle Nethermind return this.estimationService.binarySearchVGL(userOp, entryPoint); } return this.estimationService.binarySearchVGLSafe(userOp, entryPoint); @@ -128,7 +127,6 @@ export class UserOpValidationService { entryPoint: string ): Promise { if (this.config.unsafeMode) { - // TODO: handle Nethermind return userOp; // CGL search not supported in unsafeMode } return this.estimationService.binarySearchCGLSafe(userOp, entryPoint); diff --git a/packages/executor/src/services/UserOpValidation/validators/estimation.ts b/packages/executor/src/services/UserOpValidation/validators/estimation.ts index 35475219..3b4ceb61 100644 --- a/packages/executor/src/services/UserOpValidation/validators/estimation.ts +++ b/packages/executor/src/services/UserOpValidation/validators/estimation.ts @@ -5,7 +5,7 @@ import { UserOperationStruct } from "types/lib/executor/contracts/EntryPoint"; import { BundlerCollectorReturn, ExitInfo } from "types/lib/executor"; import * as RpcErrorCodes from "types/lib/api/errors/rpc-error-codes"; import { BigNumber, providers } from "ethers"; -import { nethermindErrorHandler, parseValidationResult } from "../utils"; +import { nethermindErrorHandler } from "../utils"; import { ExecutionResult, Logger } from "../../../interfaces"; import { GethTracer } from "../GethTracer"; @@ -19,6 +19,16 @@ const isVGLLow = (err: Error): boolean => { ); }; +const isCGLLow = (err: Error): boolean => { + const { message } = err; + if (!message) return false; + return ( + message.indexOf("OOG") > -1 || + message.indexOf("AA40") > -1 || + message.indexOf("ogg.execution") > -1 + ); +}; + export class EstimationService { private gethTracer: GethTracer; @@ -68,9 +78,11 @@ export class EstimationService { let lastOptimalVGL: BigNumber | undefined; while (left.lt(right)) { const mid = left.add(right).div(2); - userOp.verificationGasLimit = mid; try { - await this.estimateUserOp({ ...userOp, callGasLimit: 0 }, entryPoint); + await this.estimateUserOp( + { ...userOp, verificationGasLimit: mid }, + entryPoint + ); lastOptimalVGL = mid; break; } catch (err) { @@ -98,14 +110,14 @@ export class EstimationService { let lastOptimalVGL: BigNumber | undefined; while (left.lt(right)) { const mid = left.add(right).div(2); - userOp.verificationGasLimit = mid; - this.logger.debug(`mid: ${mid}`); try { - await this.checkForOOG({ ...userOp, callGasLimit: 0 }, entryPoint); + await this.checkForOOG( + { ...userOp, verificationGasLimit: mid }, + entryPoint + ); lastOptimalVGL = mid; break; } catch (err) { - this.logger.debug(err); if (isVGLLow(err as Error)) { left = mid.add(1); } else { @@ -118,38 +130,9 @@ export class EstimationService { return userOp; } - async binarySearchCGLSafe( - userOp: UserOperationStruct, - entryPoint: string - ): Promise { - const { callGasLimit } = userOp; - let [left, right] = [ - BigNumber.from(callGasLimit).div(5), - BigNumber.from(callGasLimit), - ]; - let lastOptimalCGL: BigNumber | undefined; - while (left.lt(right)) { - const mid = left.add(right).div(2); - lastOptimalCGL = mid; - try { - await this.checkForOOG(userOp, entryPoint); - lastOptimalCGL = mid; - } catch (err) { - this.logger.debug(err); - if (isVGLLow(err as Error)) { - left = mid.add(1); - } else { - right = mid.sub(1); - } - } - } - - userOp.callGasLimit = lastOptimalCGL || userOp.callGasLimit; - return userOp; - } - // Binary search callGasLimit - async binarySearchCGL( + // Only available in safe mode + async binarySearchCGLSafe( userOp: UserOperationStruct, entryPoint: string ): Promise { @@ -164,11 +147,11 @@ export class EstimationService { const mid = left.add(right).div(2); userOp.callGasLimit = mid; try { - await this.estimateUserOp(userOp, entryPoint); + await this.checkForOOG(userOp, entryPoint); lastOptimalCGL = mid; right = mid.sub(1); } catch (err) { - if (isVGLLow(err as Error)) { + if (isCGLLow(err as Error)) { left = mid.add(1); } else { right = mid.sub(1); @@ -212,11 +195,12 @@ export class EstimationService { } const data = (lastResult as ExitInfo).data; // eslint-disable-next-line @typescript-eslint/no-unused-vars - const validationResult = parseValidationResult( - entryPointContract, - userOp, - data - ); + const { name: errorName, args: errorArgs } = + entryPointContract.interface.parseError(data); + const errFullName = `${errorName}(${errorArgs.toString()})`; + if (!errorName?.startsWith("ExecutionResult")) { + throw new Error(errFullName); + } traceCall.callsFromEntryPoint.forEach((currentLevel, index) => { if (currentLevel.oog) { @@ -229,6 +213,6 @@ export class EstimationService { } }); - return ""; + return ""; // successful validation } } diff --git a/packages/executor/src/services/UserOpValidation/validators/safe.ts b/packages/executor/src/services/UserOpValidation/validators/safe.ts index 0282f580..a4258973 100644 --- a/packages/executor/src/services/UserOpValidation/validators/safe.ts +++ b/packages/executor/src/services/UserOpValidation/validators/safe.ts @@ -389,17 +389,26 @@ export class SafeValidationService { } } - if ( - Object.keys(currentNumLevel.contractSize).find( - (addr) => addr !== sender && currentNumLevel.contractSize[addr] <= 2 - ) != null - ) { - throw new RpcError( - `${entityTitle} accesses un-deployed contract ${JSON.stringify( - currentNumLevel.contractSize - )}`, - RpcErrorCodes.INVALID_OPCODE - ); + for (const addr of Object.keys(currentNumLevel.contractSize)) { + if ( + addr !== sender && + currentNumLevel.contractSize[addr].contractSize <= 2 + ) { + const { opcode } = currentNumLevel.contractSize[addr]; + throw new RpcError( + `${entityTitle} accesses un-deployed contract address ${addr} with opcode ${opcode}`, + RpcErrorCodes.INVALID_OPCODE + ); + } + } + + for (const addr of Object.keys(currentNumLevel.extCodeAccessInfo)) { + if (addr === entryPoint) { + throw new RpcError( + `${entityTitle} accesses EntryPoint contract address ${addr} with opcode ${currentNumLevel.extCodeAccessInfo[addr]}`, + RpcErrorCodes.INVALID_OPCODE + ); + } } } diff --git a/packages/executor/tracer.js b/packages/executor/tracer.js index 839d46b1..666cb835 100644 --- a/packages/executor/tracer.js +++ b/packages/executor/tracer.js @@ -1,160 +1,190 @@ function bundlerCollectorTracer() { return { - callsFromEntryPoint: [], - currentLevel: null, - keccak: [], - calls: [], - logs: [], - debug: [], - lastOp: '', - stopCollectingTopic: 'bb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972', - stopCollecting: false, - topLevelCallCounter: 0, - fault: function fault(log, db) { - this.debug.push('fault depth=', log.getDepth(), ' gas=', log.getGas(), ' cost=', log.getCost(), ' err=', log.getError()); - }, - result: function result(ctx, db) { - return { - callsFromEntryPoint: this.callsFromEntryPoint, - keccak: this.keccak, - logs: this.logs, - calls: this.calls, - debug: this.debug, - }; - }, - enter: function enter(frame) { - if (this.stopCollecting) { - return; - } - this.calls.push({ - type: frame.getType(), - from: toHex(frame.getFrom()), - to: toHex(frame.getTo()), - method: toHex(frame.getInput()).slice(0, 10), - gas: frame.getGas(), - value: frame.getValue(), - }); - }, - exit: function exit(frame) { - if (this.stopCollecting) { - return; - } - this.calls.push({ - type: frame.getError() != null ? 'REVERT' : 'RETURN', - gasUsed: frame.getGasUsed(), - data: toHex(frame.getOutput()).slice(0, 4000), - }); - }, - countSlot: function countSlot(list, key) { - var _a; - list[key] = ((_a = list[key]) !== null && _a !== void 0 ? _a : 0) + 1; - }, - step: function step(log, db) { - var _a; - if (this.stopCollecting) { - return; - } - var opcode = log.op.toString(); - if (log.getGas() < log.getCost()) { - this.currentLevel.oog = true; - } - if (opcode === 'REVERT' || opcode === 'RETURN') { - if (log.getDepth() === 1) { - var ofs = parseInt(log.stack.peek(0).toString()); - var len = parseInt(log.stack.peek(1).toString()); - var data = toHex(log.memory.slice(ofs, ofs + len)).slice(0, 4000); + callsFromEntryPoint: [], + currentLevel: null, + keccak: [], + calls: [], + logs: [], + debug: [], + lastOp: '', + lastThreeOpcodes: [], + stopCollectingTopic: 'bb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972', + stopCollecting: false, + topLevelCallCounter: 0, + fault: function (log, db) { + this.debug.push('fault depth=', log.getDepth(), ' gas=', log.getGas(), ' cost=', log.getCost(), ' err=', log.getError()); + }, + result: function (ctx, db) { + return { + callsFromEntryPoint: this.callsFromEntryPoint, + keccak: this.keccak, + logs: this.logs, + calls: this.calls, + debug: this.debug + }; + }, + enter: function (frame) { + if (this.stopCollecting) { + return; + } this.calls.push({ - type: opcode, - gasUsed: 0, - data: data, + type: frame.getType(), + from: toHex(frame.getFrom()), + to: toHex(frame.getTo()), + method: toHex(frame.getInput()).slice(0, 10), + gas: frame.getGas(), + value: frame.getValue() }); - } - } - if (log.getDepth() === 1) { - if (opcode === 'CALL' || opcode === 'STATICCALL') { - var addr = toAddress(log.stack.peek(1).toString(16)); - var topLevelTargetAddress = toHex(addr); - var ofs = parseInt(log.stack.peek(3).toString()); - var topLevelMethodSig = toHex(log.memory.slice(ofs, ofs + 4)); - this.currentLevel = this.callsFromEntryPoint[ - this.topLevelCallCounter - ] = { - topLevelMethodSig: topLevelMethodSig, - topLevelTargetAddress: topLevelTargetAddress, - access: {}, - opcodes: {}, - contractSize: {}, - }; - this.topLevelCallCounter++; - } else if (opcode === 'LOG1') { - var topic = log.stack.peek(2).toString(16); - if (topic === this.stopCollectingTopic) { - this.stopCollecting = true; + }, + exit: function (frame) { + if (this.stopCollecting) { + return; } - } - this.lastOp = ''; - return; - } - if (opcode.match(/^(EXT.*|CALL|CALLCODE|DELEGATECALL|STATICCALL)$/) != null) { - var idx = opcode.startsWith('EXT') ? 0 : 1; - var addr = toAddress(log.stack.peek(idx).toString(16)); - var addrHex = toHex(addr); - if (((_a = this.currentLevel.contractSize[addrHex]) !== null && _a !== void 0 ? _a : 0) === 0 && !isPrecompiled(addr)) { - this.currentLevel.contractSize[addrHex] = db.getCode(addr).length; - } - } - if (this.lastOp === 'GAS' && !opcode.includes('CALL')) { - this.countSlot(this.currentLevel.opcodes, 'GAS'); - } - if (opcode !== 'GAS') { - if ( - opcode.match(/^(DUP\d+|PUSH\d+|SWAP\d+|POP|ADD|SUB|MUL|DIV|EQ|LTE?|S?GTE?|SLT|SH[LR]|AND|OR|NOT|ISZERO)$/) == null - ) { - this.countSlot(this.currentLevel.opcodes, opcode); - } - } - this.lastOp = opcode; - if (opcode === 'SLOAD' || opcode === 'SSTORE') { - var slot = toWord(log.stack.peek(0).toString(16)); - var slotHex = toHex(slot); - var addr = log.contract.getAddress(); - var addrHex = toHex(addr); - var access = this.currentLevel.access[addrHex]; - if (access == null) { - access = { - reads: {}, - writes: {}, + this.calls.push({ + type: frame.getError() != null ? 'REVERT' : 'RETURN', + gasUsed: frame.getGasUsed(), + data: toHex(frame.getOutput()).slice(0, 4000) + }); + }, + countSlot: function (list, key) { + var _a; + list[key] = ((_a = list[key]) !== null && _a !== void 0 ? _a : 0) + 1; + }, + step: function (log, db) { + var _a; + if (this.stopCollecting) { + return; + } + var opcode = log.op.toString(); + var stackSize = log.stack.length(); + var stackTop3 = []; + for (var i = 0; i < 3 && i < stackSize; i++) { + stackTop3.push(log.stack.peek(i)); + } + this.lastThreeOpcodes.push({ opcode: opcode, stackTop3: stackTop3 }); + if (this.lastThreeOpcodes.length > 3) { + this.lastThreeOpcodes.shift(); + } + if (log.getGas() < log.getCost()) { + this.currentLevel.oog = true; + } + if (opcode === 'REVERT' || opcode === 'RETURN') { + if (log.getDepth() === 1) { + var ofs = parseInt(log.stack.peek(0).toString()); + var len = parseInt(log.stack.peek(1).toString()); + var data = toHex(log.memory.slice(ofs, ofs + len)).slice(0, 4000); + this.calls.push({ + type: opcode, + gasUsed: 0, + data: data + }); + } + this.lastThreeOpcodes = []; + } + if (log.getDepth() === 1) { + if (opcode === 'CALL' || opcode === 'STATICCALL') { + var addr = toAddress(log.stack.peek(1).toString(16)); + var topLevelTargetAddress = toHex(addr); + var ofs = parseInt(log.stack.peek(3).toString()); + var topLevelMethodSig = toHex(log.memory.slice(ofs, ofs + 4)); + this.currentLevel = this.callsFromEntryPoint[this.topLevelCallCounter] = { + topLevelMethodSig: topLevelMethodSig, + topLevelTargetAddress: topLevelTargetAddress, + access: {}, + opcodes: {}, + extCodeAccessInfo: {}, + contractSize: {} + }; + this.topLevelCallCounter++; + } + else if (opcode === 'LOG1') { + var topic = log.stack.peek(2).toString(16); + if (topic === this.stopCollectingTopic) { + this.stopCollecting = true; + } + } + this.lastOp = ''; + return; + } + var lastOpInfo = this.lastThreeOpcodes[this.lastThreeOpcodes.length - 2]; + if (((_a = lastOpInfo === null || lastOpInfo === void 0 ? void 0 : lastOpInfo.opcode) === null || _a === void 0 ? void 0 : _a.match(/^(EXT.*)$/)) != null) { + var addr = toAddress(lastOpInfo.stackTop3[0].toString(16)); + var addrHex = toHex(addr); + var last3opcodesString = this.lastThreeOpcodes.map(function (x) { return x.opcode; }).join(' '); + if (last3opcodesString.match(/^(\w+) EXTCODESIZE ISZERO$/) == null) { + this.currentLevel.extCodeAccessInfo[addrHex] = opcode; + } + else { + } + } + var isAllowedPrecompiled = function (address) { + var addrHex = toHex(address); + var addressInt = parseInt(addrHex); + return addressInt > 0 && addressInt < 10; }; - this.currentLevel.access[addrHex] = access; - } - if (opcode === 'SLOAD') { - if (access.reads[slotHex] == null && access.writes[slotHex] == null) { - access.reads[slotHex] = toHex(db.getState(addr, slot)); + if (opcode.match(/^(EXT.*|CALL|CALLCODE|DELEGATECALL|STATICCALL)$/) != null) { + var idx = opcode.startsWith('EXT') ? 0 : 1; + var addr = toAddress(log.stack.peek(idx).toString(16)); + var addrHex = toHex(addr); + if (this.currentLevel.contractSize[addrHex] == null && !isAllowedPrecompiled(addr)) { + this.currentLevel.contractSize[addrHex] = { + contractSize: db.getCode(addr).length, + opcode: opcode + }; + } + } + if (this.lastOp === 'GAS' && !opcode.includes('CALL')) { + this.countSlot(this.currentLevel.opcodes, 'GAS'); + } + if (opcode !== 'GAS') { + if (opcode.match(/^(DUP\d+|PUSH\d+|SWAP\d+|POP|ADD|SUB|MUL|DIV|EQ|LTE?|S?GTE?|SLT|SH[LR]|AND|OR|NOT|ISZERO)$/) == null) { + this.countSlot(this.currentLevel.opcodes, opcode); + } + } + this.lastOp = opcode; + if (opcode === 'SLOAD' || opcode === 'SSTORE') { + var slot = toWord(log.stack.peek(0).toString(16)); + var slotHex = toHex(slot); + var addr = log.contract.getAddress(); + var addrHex = toHex(addr); + var access = this.currentLevel.access[addrHex]; + if (access == null) { + access = { + reads: {}, + writes: {} + }; + this.currentLevel.access[addrHex] = access; + } + if (opcode === 'SLOAD') { + if (access.reads[slotHex] == null && access.writes[slotHex] == null) { + access.reads[slotHex] = toHex(db.getState(addr, slot)); + } + } + else { + this.countSlot(access.writes, slotHex); + } + } + if (opcode === 'KECCAK256') { + var ofs = parseInt(log.stack.peek(0).toString()); + var len = parseInt(log.stack.peek(1).toString()); + if (len > 20 && len < 512) { + this.keccak.push(toHex(log.memory.slice(ofs, ofs + len))); + } + } + else if (opcode.startsWith('LOG')) { + var count = parseInt(opcode.substring(3)); + var ofs = parseInt(log.stack.peek(0).toString()); + var len = parseInt(log.stack.peek(1).toString()); + var topics = []; + for (var i = 0; i < count; i++) { + topics.push('0x' + log.stack.peek(2 + i).toString(16)); + } + var data = toHex(log.memory.slice(ofs, ofs + len)); + this.logs.push({ + topics: topics, + data: data + }); } - } else { - this.countSlot(access.writes, slotHex); - } - } - if (opcode === 'KECCAK256') { - var ofs = parseInt(log.stack.peek(0).toString()); - var len = parseInt(log.stack.peek(1).toString()); - if (len > 20 && len < 512) { - this.keccak.push(toHex(log.memory.slice(ofs, ofs + len))); - } - } else if (opcode.startsWith('LOG')) { - var count = parseInt(opcode.substring(3)); - var ofs = parseInt(log.stack.peek(0).toString()); - var len = parseInt(log.stack.peek(1).toString()); - var topics = []; - for (var i = 0; i < count; i++) { - topics.push('0x' + log.stack.peek(2 + i).toString(16)); - } - var data = toHex(log.memory.slice(ofs, ofs + len)); - this.logs.push({ - topics: topics, - data: data, - }); } - }, }; -} +} \ No newline at end of file diff --git a/packages/types/src/executor/validation/index.ts b/packages/types/src/executor/validation/index.ts index 1ea2250e..bb586d80 100644 --- a/packages/types/src/executor/validation/index.ts +++ b/packages/types/src/executor/validation/index.ts @@ -1,14 +1,7 @@ import { BigNumberish } from "ethers"; export interface BundlerCollectorReturn { - /** - * storage and opcode info, collected on top-level calls from EntryPoint - */ callsFromEntryPoint: TopLevelCallInfo[]; - - /** - * values passed into KECCAK opcode - */ keccak: string[]; calls: Array; logs: LogInfo[]; @@ -35,10 +28,16 @@ export interface TopLevelCallInfo { topLevelTargetAddress: string; opcodes: { [opcode: string]: number }; access: { [address: string]: AccessInfo }; - contractSize: { [addr: string]: number }; + contractSize: { [addr: string]: ContractSizeInfo }; + extCodeAccessInfo: { [addr: string]: string }; oog?: boolean; } +export interface ContractSizeInfo { + opcode: string; + contractSize: number; +} + export interface AccessInfo { // slot value, just prior this operation reads: { [slot: string]: string }; From f8ad722441252dd705dc4bcaf0eb391982e0ba6b Mon Sep 17 00:00:00 2001 From: marie-fourier Date: Mon, 4 Sep 2023 13:04:26 +0500 Subject: [PATCH 3/4] chore(release): v0.0.40 --- lerna.json | 2 +- package.json | 2 +- packages/api/package.json | 6 +++--- packages/cli/package.json | 10 +++++----- packages/db/package.json | 4 ++-- packages/executor/package.json | 6 +++--- packages/params/package.json | 4 ++-- packages/types/package.json | 2 +- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lerna.json b/lerna.json index 90ee01e3..c1b46b8a 100644 --- a/lerna.json +++ b/lerna.json @@ -4,7 +4,7 @@ ], "npmClient": "yarn", "useWorkspaces": true, - "version": "0.0.39", + "version": "0.0.40", "stream": "true", "command": { "version": { diff --git a/package.json b/package.json index 4d71e5a8..0e4fc745 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "root", "private": true, - "version": "0.0.39", + "version": "0.0.40", "engines": { "node": ">=18.0.0" }, diff --git a/packages/api/package.json b/packages/api/package.json index 048677ff..2291f037 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "api", - "version": "0.0.39", + "version": "0.0.40", "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.39", + "executor": "^0.0.40", "fastify": "4.14.1", "pino": "8.11.0", "pino-pretty": "10.0.0", "reflect-metadata": "0.1.13", - "types": "^0.0.39" + "types": "^0.0.40" }, "devDependencies": { "@types/connect": "3.4.35" diff --git a/packages/cli/package.json b/packages/cli/package.json index 2b2e9567..c900f1f3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "cli", - "version": "0.0.39", + "version": "0.0.40", "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.39", - "db": "^0.0.39", - "executor": "^0.0.39", + "api": "^0.0.40", + "db": "^0.0.40", + "executor": "^0.0.40", "find-up": "5.0.0", "got": "12.5.3", "js-yaml": "4.1.0", - "types": "^0.0.39", + "types": "^0.0.40", "yargs": "17.6.2" }, "devDependencies": { diff --git a/packages/db/package.json b/packages/db/package.json index 0fba3cab..6776d034 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "db", - "version": "0.0.39", + "version": "0.0.40", "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.39" + "types": "^0.0.40" } } diff --git a/packages/executor/package.json b/packages/executor/package.json index 705b0da7..61242140 100644 --- a/packages/executor/package.json +++ b/packages/executor/package.json @@ -1,6 +1,6 @@ { "name": "executor", - "version": "0.0.39", + "version": "0.0.40", "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.39", - "types": "^0.0.39" + "params": "^0.0.40", + "types": "^0.0.40" } } diff --git a/packages/params/package.json b/packages/params/package.json index eb0300fd..108988ca 100644 --- a/packages/params/package.json +++ b/packages/params/package.json @@ -1,6 +1,6 @@ { "name": "params", - "version": "0.0.39", + "version": "0.0.40", "description": "Various bundler parameters", "author": "Etherspot", "homepage": "https://github.com/etherspot/skandha#readme", @@ -25,7 +25,7 @@ "@eth-optimism/sdk": "3.0.0", "@mantleio/sdk": "0.2.1", "ethers": "5.7.2", - "types": "^0.0.39" + "types": "^0.0.40" }, "scripts": { "clean": "rm -rf lib && rm -f *.tsbuildinfo", diff --git a/packages/types/package.json b/packages/types/package.json index 6a7b9ba1..f17460aa 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "types", - "version": "0.0.39", + "version": "0.0.40", "description": "The types of Etherspot bundler client", "author": "Etherspot", "homepage": "https://https://github.com/etherspot/skandha#readme", From 3760cd09f7a5f190cb1c099158947cfe7c62941a Mon Sep 17 00:00:00 2001 From: marie-fourier Date: Fri, 8 Sep 2023 16:43:09 +0500 Subject: [PATCH 4/4] chore(release): v0.0.42 --- lerna.json | 2 +- package.json | 2 +- packages/api/package.json | 6 +++--- packages/cli/package.json | 10 +++++----- packages/db/package.json | 4 ++-- packages/executor/package.json | 6 +++--- packages/params/package.json | 4 ++-- packages/types/package.json | 2 +- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lerna.json b/lerna.json index 34841a43..4d241454 100644 --- a/lerna.json +++ b/lerna.json @@ -4,7 +4,7 @@ ], "npmClient": "yarn", "useWorkspaces": true, - "version": "0.0.41", + "version": "0.0.42", "stream": "true", "command": { "version": { diff --git a/package.json b/package.json index 2a18af30..16b953bf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "root", "private": true, - "version": "0.0.41", + "version": "0.0.42", "engines": { "node": ">=18.0.0" }, diff --git a/packages/api/package.json b/packages/api/package.json index f9159315..f788cead 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "api", - "version": "0.0.41", + "version": "0.0.42", "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.41", + "executor": "^0.0.42", "fastify": "4.14.1", "pino": "8.11.0", "pino-pretty": "10.0.0", "reflect-metadata": "0.1.13", - "types": "^0.0.41" + "types": "^0.0.42" }, "devDependencies": { "@types/connect": "3.4.35" diff --git a/packages/cli/package.json b/packages/cli/package.json index f57af94d..a50168df 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "cli", - "version": "0.0.41", + "version": "0.0.42", "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.41", - "db": "^0.0.41", - "executor": "^0.0.41", + "api": "^0.0.42", + "db": "^0.0.42", + "executor": "^0.0.42", "find-up": "5.0.0", "got": "12.5.3", "js-yaml": "4.1.0", - "types": "^0.0.41", + "types": "^0.0.42", "yargs": "17.6.2" }, "devDependencies": { diff --git a/packages/db/package.json b/packages/db/package.json index 05a23fb1..b3dcc526 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "db", - "version": "0.0.41", + "version": "0.0.42", "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.41" + "types": "^0.0.42" } } diff --git a/packages/executor/package.json b/packages/executor/package.json index 147a84da..84bed5c1 100644 --- a/packages/executor/package.json +++ b/packages/executor/package.json @@ -1,6 +1,6 @@ { "name": "executor", - "version": "0.0.41", + "version": "0.0.42", "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.41", - "types": "^0.0.41" + "params": "^0.0.42", + "types": "^0.0.42" } } diff --git a/packages/params/package.json b/packages/params/package.json index d70359b9..f24aaf9f 100644 --- a/packages/params/package.json +++ b/packages/params/package.json @@ -1,6 +1,6 @@ { "name": "params", - "version": "0.0.41", + "version": "0.0.42", "description": "Various bundler parameters", "author": "Etherspot", "homepage": "https://github.com/etherspot/skandha#readme", @@ -25,7 +25,7 @@ "@eth-optimism/sdk": "3.0.0", "@mantleio/sdk": "0.2.1", "ethers": "5.7.2", - "types": "^0.0.41" + "types": "^0.0.42" }, "scripts": { "clean": "rm -rf lib && rm -f *.tsbuildinfo", diff --git a/packages/types/package.json b/packages/types/package.json index b8dfae8d..3abbc6e4 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "types", - "version": "0.0.41", + "version": "0.0.42", "description": "The types of Etherspot bundler client", "author": "Etherspot", "homepage": "https://https://github.com/etherspot/skandha#readme",