diff --git a/.github/workflows/bundler-spec-tests.yml b/.github/workflows/bundler-spec-tests.yml index 9f9451e9..ae7480de 100644 --- a/.github/workflows/bundler-spec-tests.yml +++ b/.github/workflows/bundler-spec-tests.yml @@ -13,9 +13,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - with: - repository: etherspot/skandha - ref: some-fixes - name: Setup PDM run: curl -sSL https://raw.githubusercontent.com/pdm-project/pdm/main/install-pdm.py | python3 - @@ -61,8 +58,11 @@ jobs: - name: Deploy ERC-4337 contracts working-directory: ./bundler-spec-tests + # checkout to 0.6.0 commit https://github.com/eth-infinitism/account-abstraction/commits/abff2aca61a8f0934e533d0d352978055fddbd96 + # change this command when using new EP run: | cd @account-abstraction && \ + git checkout abff2aca61a8f0934e533d0d352978055fddbd96 && \ yarn deploy --network localhost - name: Fund bundler @@ -85,6 +85,7 @@ jobs: - name: Send Slack notification uses: ravsamhq/notify-slack-action@v2 if: always() + continue-on-error: true with: status: ${{ job.status }} notification_title: "{workflow} has {status_message}" diff --git a/.github/workflows/check-package-version.yml b/.github/workflows/check-package-version.yml index 084654f6..fc9fb981 100644 --- a/.github/workflows/check-package-version.yml +++ b/.github/workflows/check-package-version.yml @@ -10,15 +10,26 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 + + - name: Setup NodeJS + uses: "actions/setup-node@v3" + with: + node-version: 18.15 + - name: Check if version has been updated id: check uses: EndBug/version-check@v2.1.1 with: token: ${{ secrets.GITHUB_TOKEN }} file-name: ./package.json + - name: Log when changed if: steps.check.outputs.changed == 'true' run: 'echo "Yayy!! Version change found in commit ${{ steps.check.outputs.commit }}! New version: ${{ steps.check.outputs.version }} (${{ steps.check.outputs.type }})"' + + - name: Dry run Skandha build + run: yarn install && yarn build && yarn run bootstrap + - name: Log when unchanged if: steps.check.outputs.changed == 'false' run: echo "No version change :/ Please update version in package.json!" && exit 1 diff --git a/README.md b/README.md index 270d2a15..d8e0c163 100644 --- a/README.md +++ b/README.md @@ -1,80 +1,89 @@ -
-

Skandha

-
- - - -
- -

- - A modular, developer-friendly Typescript Bundler for Ethereum EIP-4337 Account Abstraction - -

-
- -### Warning! This repo/software is under active development - -## βš™οΈ How to run (from Source code) - -Run with one-liner: - -```sh -curl -fsSL https://skandha.run | bash -``` -or follow steps below: - -1. install all dependencies by running `yarn` -2. build `yarn build && yarn bootstrap` -3. `cp config.json.default config.json` -4. edit `config.json` -5. (optional) run local geth-node from `test/geth-dev` -6. run `./skandha` -7. Skandha will run for all chains available in `config.json` -8. Networks will be available at `http://localhost:14337/{chainId}/` (e.g. for dev `http://localhost:14337/1337/`) - -## 🐳 How to run (a Docker image) - -1. `cp config.json.default config.json` -2. edit `config.json` -3. `docker build -t etherspot/skandha .` -4. `docker run --mount type=bind,source="$(pwd)"/config.json,target=/usr/app/config.json,readonly -dp 14337:14337 etherspot/skandha start` - - -## πŸ“œ Additional features -- [x] Unsafe mode - bypass opcode & stake validation -- [x] Redirect RPC - Redirect ETH rpc calls to the underlying execution client. This is needed if you use UserOp.js -- [x] P2P - Exchange of UserOps between all the nodes in the network. Heavily inspired by the Lodestar's implementation of p2p (https://github.com/ChainSafe/lodestar/) - -### ⚑️ CLI Options -- `--unsafeMode` - enables unsafeMode -- `--redirectRpc` - enables redirecting eth rpc calls - -## πŸ”‘ Relayer Configuration - -#### config.json - -```json -{ - "networks": { - "dev": { # network Id (check packages/types/src/networks/networks.ts) - "entryPoints": [ # supported entry points - "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" - ], - "relayer": "0xprivateKey", # relayer private key, can access from here or via environment variables (SKANDHA_MUMBAI_RELAYER | SKANDHA_DEV_RELAYER | etc.) - "beneficiary": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", # fee collector, avaiable via env var (SKANDHA_MUMBAI_BENEFICIARY | etc) - "rpcEndpoint": "http://localhost:8545", # rpc provider, also available via env variable (SKANDHA_MUMBAI_RPC | etc) - "minInclusionDenominator": 10, # optional, see EIP-4337 - "throttlingSlack": 10, # optional, see EIP-4337 - "banSlack": 10 # optional, see EIP-4337 - "minSignerBalance": 1, # optional, default is 0.1 ETH. If the relayer's balance drops lower than this, it will be selected as a fee collector - "multicall": "0x", # optional, address of multicall3 contract, default is 0xcA11bde05977b3631167028862bE2a173976CA11 (see https://github.com/mds1/multicall#multicall3-contract-addresses) - } - } -} -``` - -#### Mempool_ID of the canonical mempool on various networks - -- Sepolia | QmdDwVFoEEcgv5qnaTB8ncnXGMnqrhnA5nYpRr4ouWe4AT | https://ipfs.io/ipfs/QmdDwVFoEEcgv5qnaTB8ncnXGMnqrhnA5nYpRr4ouWe4AT?filename=sepolia_canonical_mempool.yaml +
+

Skandha

+
+ + + +
+ +

+ + A modular, developer-friendly Typescript Bundler for Ethereum EIP-4337 Account Abstraction + +

+
+ +### Warning! This repo/software is under active development + +## βš™οΈ How to run (from Source code) + +Run with one-liner: + +```sh +curl -fsSL https://skandha.run | bash +``` +or follow steps below: + +1. install all dependencies by running `yarn` +2. build `yarn build && yarn bootstrap` +3. `cp config.json.default config.json` +4. edit `config.json` +5. (optional) run local geth-node from `test/geth-dev` +6. run `./skandha` +7. Skandha will run for all chains available in `config.json` +8. Networks will be available at `http://localhost:14337/{chainId}/` (e.g. for dev `http://localhost:14337/1337/`) + +## 🐳 How to run (a Docker image) + +1. `cp config.json.default config.json` +2. edit `config.json` +3. `docker build -t etherspot/skandha .` +4. `docker run --mount type=bind,source="$(pwd)"/config.json,target=/usr/app/config.json,readonly -dp 14337:14337 etherspot/skandha start` + + +## πŸ“œ Additional features +- [x] Unsafe mode - bypass opcode & stake validation +- [x] Redirect RPC - Redirect ETH rpc calls to the underlying execution client. This is needed if you use UserOp.js +- [x] P2P - Exchange of UserOps between all the nodes in the network. Heavily inspired by the Lodestar's implementation of p2p (https://github.com/ChainSafe/lodestar/) + +### ⚑️ CLI Options +- `--unsafeMode` - enables unsafeMode +- `--redirectRpc` - enables redirecting eth rpc calls + +## πŸ”‘ Relayer Configuration + +#### config.json + +```yaml +{ + "networks": { + "dev": { # network Id (check packages/types/src/networks/networks.ts) + "entryPoints": [ # supported entry points + "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" + ], + "relayer": "0xprivateKey", # relayer private key, can access from here or via environment variables (SKANDHA_MUMBAI_RELAYER | SKANDHA_DEV_RELAYER | etc.) + "beneficiary": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", # fee collector, avaiable via env var (SKANDHA_MUMBAI_BENEFICIARY | etc) + "rpcEndpoint": "http://localhost:8545", # rpc provider, also available via env variable (SKANDHA_MUMBAI_RPC | etc) + "minInclusionDenominator": 10, # optional, see EIP-4337 + "throttlingSlack": 10, # optional, see EIP-4337 + "banSlack": 10 # optional, see EIP-4337 + "minSignerBalance": 1, # optional, default is 0.1 ETH. If the relayer's balance drops lower than this, it will be selected as a fee collector + "multicall": "0xcA11bde05977b3631167028862bE2a173976CA11", # optional, multicall3 contract (see https://github.com/mds1/multicall#multicall3-contract-addresses) + "estimationStaticBuffer": 21000, # adds certain amount of gas to callGasLimit on estimation + "validationGasLimit": 10e6, # gas limit during simulateHandleOps and simulateValidation calls + "receiptLookupRange": 1024, # limits the block range of getUserOperationByHash and getUserOperationReceipt + "etherscanApiKey": "", # etherscan api is used to fetch gas prices + "conditionalTransactions": false, # enable conditional transactions + "rpcEndpointSubmit": "", # rpc endpoint that is used only during submission of a bundle + "gasPriceMarkup": 0, # adds % markup on reported gas price via skandha_getGasPrice, 10000 = 100.00%, 500 = 5% + "enforceGasPrice": false, # do not bundle userops with low gas prices + "enforceGasPriceThreshold": 1000, # gas price threshold in bps. If set to 500, userops' gas price is allowed to be 5% lower than the network's gas price + } + } +} +``` + +#### Mempool_ID of the canonical mempool on various networks + +- Sepolia | QmdDwVFoEEcgv5qnaTB8ncnXGMnqrhnA5nYpRr4ouWe4AT | https://ipfs.io/ipfs/QmdDwVFoEEcgv5qnaTB8ncnXGMnqrhnA5nYpRr4ouWe4AT?filename=sepolia_canonical_mempool.yaml - Mumbai | QmQfRyE9iVTBqZ17hPSP4tuMzaez83Y5wD874ymyRtj9VE | https://ipfs.io/ipfs/QmQfRyE9iVTBqZ17hPSP4tuMzaez83Y5wD874ymyRtj9VE?filename=mumbai_canonical_mempool.yaml \ No newline at end of file diff --git a/lerna.json b/lerna.json index 79a03998..c09428df 100644 --- a/lerna.json +++ b/lerna.json @@ -4,7 +4,7 @@ ], "npmClient": "yarn", "useWorkspaces": true, - "version": "0.0.24", + "version": "0.0.44", "stream": "true", "command": { "version": { diff --git a/package.json b/package.json index b5f26655..bc8ceb5b 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "root", "private": true, - "version": "0.0.24", + "version": "0.0.44", "engines": { - "node": ">=18.0.0" - }, - "scripts": { + "node": ">=18.0.0" + }, + "scripts": { "clean": "rm -rf ./packages/*/lib ./packages/*/*.tsbuildinfo", "bootstrap": "lerna bootstrap & lerna link", "prebuild": "yarn workspace types run build", diff --git a/packages/api/package.json b/packages/api/package.json index 502a7d4e..49afe62f 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "api", - "version": "0.0.24", + "version": "0.0.44", "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.24", + "executor": "^0.0.44", "fastify": "4.14.1", "pino": "8.11.0", "pino-pretty": "10.0.0", "reflect-metadata": "0.1.13", - "types": "^0.0.24" + "types": "^0.0.44" }, "devDependencies": { "@types/connect": "3.4.35" diff --git a/packages/api/src/app.ts b/packages/api/src/app.ts index 5a79f813..92141c8a 100644 --- a/packages/api/src/app.ts +++ b/packages/api/src/app.ts @@ -10,6 +10,7 @@ import logger from "./logger"; import { BundlerRPCMethods, CustomRPCMethods, + HttpStatus, RedirectedRPCMethods, } from "./constants"; import { EthAPI, DebugAPI, Web3API, RedirectAPI } from "./modules"; @@ -131,11 +132,10 @@ export class ApiApp { if (this.redirectRpc && method in RedirectedRPCMethods) { const body = await redirectApi.redirect(method, params); - return res.status(200).send({ - jsonrpc, - id, - ...body, - }); + if (body.error) { + return res.status(HttpStatus.OK).send({ ...body, id }); + } + return res.status(HttpStatus.OK).send({ jsonrpc, id, ...body }); } if (result === undefined) { @@ -152,12 +152,20 @@ export class ApiApp { entryPoint: params[1], }); break; - case BundlerRPCMethods.eth_estimateUserOperationGas: - result = await ethApi.estimateUserOperationGas({ - userOp: params[0], - entryPoint: params[1], - }); - break; + case BundlerRPCMethods.eth_estimateUserOperationGas: { + if (this.testingMode) { + result = await ethApi.estimateUserOpGasAndValidateSignature({ + userOp: params[0], + entryPoint: params[1], + }); + } else { + result = await ethApi.estimateUserOperationGas({ + userOp: params[0], + entryPoint: params[1], + }); + } + break; + } case BundlerRPCMethods.eth_getUserOperationReceipt: result = await ethApi.getUserOperationReceipt(params[0]); break; @@ -176,6 +184,21 @@ export class ApiApp { case CustomRPCMethods.skandha_getGasPrice: result = await skandhaApi.getGasPrice(); break; + case CustomRPCMethods.skandha_feeHistory: + result = await skandhaApi.getFeeHistory({ + entryPoint: params[0], + blockCount: params[1], + newestBlock: params[2], + }); + break; + case CustomRPCMethods.skandha_config: + result = await skandhaApi.getConfig(); + // skip hexlify for this particular rpc + return res.status(HttpStatus.OK).send({ + jsonrpc, + id, + result, + }); default: throw new RpcError( `Method ${method} is not supported`, @@ -185,7 +208,7 @@ export class ApiApp { } result = deepHexlify(result); - return res.status(200).send({ + return res.status(HttpStatus.OK).send({ jsonrpc, id, result, diff --git a/packages/api/src/constants.ts b/packages/api/src/constants.ts index 9651022c..e4f87ece 100644 --- a/packages/api/src/constants.ts +++ b/packages/api/src/constants.ts @@ -1,6 +1,8 @@ export const CustomRPCMethods = { skandha_validateUserOperation: "skandha_validateUserOperation", skandha_getGasPrice: "skandha_getGasPrice", + skandha_config: "skandha_config", + skandha_feeHistory: "skandha_feeHistory", }; export const BundlerRPCMethods = { @@ -59,3 +61,8 @@ export const RedirectedRPCMethods = { eth_maxPriorityFeePerGas: "eth_maxPriorityFeePerGas", eth_sendRawTransaction: "eth_sendRawTransaction", }; + +export enum HttpStatus { + OK = 200, + INTERNAL_SERVER_ERROR = 500, +} diff --git a/packages/api/src/dto/FeeHistory.dto.ts b/packages/api/src/dto/FeeHistory.dto.ts new file mode 100644 index 00000000..606f75ea --- /dev/null +++ b/packages/api/src/dto/FeeHistory.dto.ts @@ -0,0 +1,15 @@ +import { IsEthereumAddress, ValidateIf } from "class-validator"; +import { BigNumberish } from "ethers"; +import { IsBigNumber } from "../utils/is-bignumber"; + +export class FeeHistoryArgs { + @IsEthereumAddress() + entryPoint!: string; + + @IsBigNumber() + blockCount!: BigNumberish; + + @ValidateIf((o) => o.newestBlock != "latest") + @IsBigNumber() + newestBlock!: BigNumberish | string; +} diff --git a/packages/api/src/modules/eth.ts b/packages/api/src/modules/eth.ts index 7ae6f650..c896f990 100644 --- a/packages/api/src/modules/eth.ts +++ b/packages/api/src/modules/eth.ts @@ -21,6 +21,15 @@ export class EthAPI { return await this.ethModule.sendUserOperation(args); } + /** + * @params args sama as in sendUserOperation + */ + async estimateUserOpGasAndValidateSignature( + args: SendUserOperationGasArgs + ): Promise { + return await this.ethModule.estimateUserOperationGasWithSignature(args); + } + /** * Estimate the gas values for a UserOperation. Given UserOperation optionally without gas limits and gas prices, return the needed gas limits. * The signature field is ignored by the wallet, so that the operation will not require user’s approval. diff --git a/packages/api/src/modules/redirect.ts b/packages/api/src/modules/redirect.ts index 622f95c3..6cf33c8d 100644 --- a/packages/api/src/modules/redirect.ts +++ b/packages/api/src/modules/redirect.ts @@ -22,8 +22,9 @@ export class RedirectAPI { /** NETHERMIND ERROR PARSING */ if ( - body.error.data.startsWith("Reverted ") && - body.error.code == -32015 + body.error.data && + body.error.code == -32015 && + body.error.data.startsWith("Reverted ") ) { body.error.code = 3; body.error.message = "execution reverted"; diff --git a/packages/api/src/modules/skandha.ts b/packages/api/src/modules/skandha.ts index fec09a54..979b475f 100644 --- a/packages/api/src/modules/skandha.ts +++ b/packages/api/src/modules/skandha.ts @@ -1,8 +1,15 @@ import { Eth } from "executor/lib/modules/eth"; -import { GetGasPriceResponse } from "types/lib/api/interfaces"; +import { + GetConfigResponse, + GetFeeHistoryResponse, + GetGasPriceResponse, +} from "types/lib/api/interfaces"; import { Skandha } from "executor/lib/modules"; +import RpcError from "types/lib/api/errors/rpc-error"; +import * as RpcErrorCodes from "types/lib/api/errors/rpc-error-codes"; import { RpcMethodValidator } from "../utils/RpcMethodValidator"; import { SendUserOperationGasArgs } from "../dto/SendUserOperation.dto"; +import { FeeHistoryArgs } from "../dto/FeeHistory.dto"; export class SkandhaAPI { constructor(private ethModule: Eth, private skandhaModule: Skandha) {} @@ -19,7 +26,29 @@ export class SkandhaAPI { return await this.ethModule.validateUserOp(args); } + /** + * @param entryPoint Entry Point + * @param useropCount Number of blocks in the requested range + * @param newestBlock Highest number block of the requested range, or "latest" + * @returns + */ + @RpcMethodValidator(FeeHistoryArgs) + async getFeeHistory(args: FeeHistoryArgs): Promise { + if (!this.ethModule.validateEntryPoint(args.entryPoint)) { + throw new RpcError("Invalid Entrypoint", RpcErrorCodes.INVALID_REQUEST); + } + return await this.skandhaModule.getFeeHistory( + args.entryPoint, + args.blockCount, + args.newestBlock + ); + } + async getGasPrice(): Promise { return await this.skandhaModule.getGasPrice(); } + + async getConfig(): Promise { + return await this.skandhaModule.getConfig(); + } } diff --git a/packages/api/src/server.ts b/packages/api/src/server.ts index 2bef36f0..dea6c8ff 100644 --- a/packages/api/src/server.ts +++ b/packages/api/src/server.ts @@ -3,6 +3,7 @@ import cors from "@fastify/cors"; import RpcError from "types/lib/api/errors/rpc-error"; import { ServerConfig } from "types/lib/api/interfaces"; import logger from "./logger"; +import { HttpStatus } from "./constants"; export class Server { constructor(private app: FastifyInstance, private config: ServerConfig) { @@ -70,16 +71,18 @@ export class Server { data: err.data, code: err.code, }; - return res.status(200).send({ + return res.status(HttpStatus.OK).send({ jsonrpc: body.jsonrpc, id: body.id, error, }); } - return res.status(err.statusCode ?? 500).send({ - error: "Unexpected behaviour", - }); + return res + .status(err.statusCode ?? HttpStatus.INTERNAL_SERVER_ERROR) + .send({ + error: "Unexpected behaviour", + }); }); await this.app.listen({ diff --git a/packages/cli/package.json b/packages/cli/package.json index 40aa0a69..db9fe8c8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "cli", - "version": "0.0.24", + "version": "0.0.44", "description": "> TODO: description", "author": "zincoshine ", "homepage": "https://https://github.com/etherspot/skandha#readme", @@ -31,14 +31,13 @@ "url": "https://https://github.com/etherspot/skandha/issues" }, "dependencies": { - "api": "^0.0.24", - "db": "^0.0.24", - "executor": "^0.0.24", - "node": "*", + "api": "^0.0.44", + "db": "^0.0.44", + "executor": "^0.0.44", "find-up": "5.0.0", "got": "12.5.3", "js-yaml": "4.1.0", - "types": "^0.0.24", + "types": "^0.0.44", "yargs": "17.6.2" }, "devDependencies": { diff --git a/packages/cli/src/cmds/start/handler.ts b/packages/cli/src/cmds/start/handler.ts new file mode 100644 index 00000000..dc263e37 --- /dev/null +++ b/packages/cli/src/cmds/start/handler.ts @@ -0,0 +1,86 @@ +/* eslint-disable no-console */ +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, + getNamespaceByValue, + RocksDbController, + LocalDbController, +} from "db/lib"; +import { ConfigOptions } from "executor/lib/interfaces"; +import { IDbController } from "types/lib"; +import { mkdir, readFile } from "../../util"; +import { IGlobalArgs } from "../../options"; +import { IBundlerArgs } from "./index"; + +export async function bundlerHandler( + args: IBundlerArgs & IGlobalArgs +): Promise { + const { dataDir, networksFile, testingMode, unsafeMode, redirectRpc } = args; + + let config: Config; + try { + const configPath = path.resolve(dataDir, "..", networksFile); + const configOptions = readFile(configPath) as ConfigOptions; + config = new Config({ + networks: configOptions.networks, + testingMode, + unsafeMode, + redirectRpc, + }); + } catch (err) { + logger.debug("Config file not found. Proceeding with env vars..."); + config = new Config({ + networks: {}, + testingMode, + unsafeMode, + redirectRpc, + }); + } + + if (unsafeMode) { + logger.warn( + "WARNING: Running in unsafe mode, skips opcode check and stake check" + ); + } + if (redirectRpc) { + logger.warn( + "WARNING: RPC redirecting is enabled, redirects RPC whitelisted calls to RPC" + ); + } + + let db: IDbController; + + if (testingMode) { + db = new LocalDbController(getNamespaceByValue(Namespace.userOps)); + } else { + const dbPath = resolve(dataDir); + mkdir(dbPath); + + db = new RocksDbController( + resolve(dataDir), + getNamespaceByValue(Namespace.userOps) + ); + await db.start(); + } + + const server = await Server.init({ + enableRequestLogging: args["api.enableRequestLogging"], + port: args["api.port"], + host: args["api.address"], + cors: args["api.cors"], + }); + + new ApiApp({ + server: server.application, + config: config, + db, + testingMode, + redirectRpc, + }); + + await server.listen(); +} diff --git a/packages/db/package.json b/packages/db/package.json index a5e7be2e..cedaffa7 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "db", - "version": "0.0.24", + "version": "0.0.44", "description": "The DB module of Etherspot bundler client", "author": "Etherspot", "homepage": "https://github.com/etherspot/etherspot-bundler#readme", @@ -38,6 +38,6 @@ "devDependencies": { "@types/rocksdb": "3.0.1", "prettier": "^2.8.4", - "types": "^0.0.24" + "types": "^0.0.44" } } diff --git a/packages/executor/customTracer.js b/packages/executor/customTracer.js deleted file mode 100644 index 0ef0cb79..00000000 --- a/packages/executor/customTracer.js +++ /dev/null @@ -1,171 +0,0 @@ -function tracer() { - return { - addrs: ['0xffffffffffffffffffffffffffffffffffffffff'], - output: { - '0xffffffffffffffffffffffffffffffffffffffff': { - storage: {}, - number: 0, - violation: {} - } - }, - prevOp: { op: '', data: '' }, - calls: [], - numberCounter: 0, - - fault: function fault(log, db) {}, - - result: function result(ctx, db) { - return { - trace: this.output, - calls: this.calls - }; - }, - - step: function step(log, db) { - var opcode = log.op.toString(); - - if (log.getGas() < log.getCost()) { } - - if (this.prevOp.op == 'KECCAK256') { - this.pKeccakAfter(log); - } - - if ( - opcode.match(/^(EXT.*|CALL|CALLCODE|DELEGATECALL|STATICCALL|CREATE2)$/) != - null - ) { - var idx = opcode.startsWith("EXT") ? 0 : 1; - var addr = toAddress(log.stack.peek(idx).toString(16)); - var hex = toHex(addr); - if (this.output[hex] && this.output[hex].contractSize == null) { - this.output[hex].contractSize = db.getCode(addr).length; - } - } - - if (log.getDepth() > 1) { - if (this.prevOp.op === 'GAS' && !opcode.includes('CALL')) { - const to = this.addrs[log.getDepth() - 1]; - if (!this.output[to].violation) { - this.output[to].violation = {}; - } - this.output[to].violation['GAS'] = true; - } - } - - this.prevOp.op = opcode; - switch (opcode) { - case 'SLOAD': - case 'SSTORE': - if (log.getDepth() !== 1) { - this.pSloadStore(log); - } - break; - case 'REVERT': - case '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, 1000); - this.calls.push({ - type: opcode, - gasUsed: 0, - data: data, - }); - this.addrs.pop(); - } - break; - case 'KECCAK256': - if (log.getDepth() !== 1) { - this.pKeccak(log); - } - break; - case 'NUMBER': - if (log.getDepth() == 1) { - this.numberCounter += 1; - break; - } - case 'GASPRICE': - case 'GASLIMIT': - case 'DIFFICULTY': - case 'TIMESTAMP': - case 'BASEFEE': - case 'BLOCKHASH': - case 'NUMBER': - case 'SELFBALANCE': - case 'BALANCE': - case 'ORIGIN': - case 'COINBASE': - case 'SELFDESTRUCT': - case 'RANDOM': - case 'PREVRANDAO': - case 'CREATE': - case 'CREATE2': { - const to = this.addrs[log.getDepth() - 1]; - if (!this.output[to].violation[opcode]) { - this.output[to].violation[opcode] = 0; - } - this.output[to].violation[opcode] += 1; - break; - } - default: - break; - } - }, - - pSloadStore: function(log) { - var key = log.stack.peek(0).toString(16); - const addr = log.contract.getAddress(); - const to = toHex(addr); - if (!this.output[to].storage[key]) { - this.output[to].storage[key] = 0; - } - this.output[to].storage[key] += 1; - }, - - pKeccak: function(log) { - var _ofs = log.stack.peek(0); - var _len = log.stack.peek(1); - this.prevOp.data = toHex(log.memory.slice(_ofs, _ofs + _len)); - }, - - pKeccakAfter: function(log) { - var input = this.prevOp.data; - var hash = log.stack.peek(0).toString(16); - var to = this.addrs[log.getDepth() - 1]; - this.prevOp.op = ''; - if (!this.output[to].keccak) { - this.output[to].keccak = {}; - } - this.output[to].keccak[input] = hash; - }, - - enter: function enter(frame) { - var to = toHex(frame.getTo()); - this.calls.push({ - type: frame.getType(), - from: toHex(frame.getFrom()), - to: to, - method: toHex(frame.getInput()).slice(0, 10), - gas: frame.getGas(), - value: frame.getValue(), - }); - this.addrs.push(to); - this.initStorage(to); - }, - - exit: function exit(frame) { - this.calls.push({ - type: frame.getError() != null ? "REVERT" : "RETURN", - gasUsed: frame.getGasUsed(), - data: toHex(frame.getOutput()).slice(0, 1000), - }); - this.addrs.pop(); - }, - - initStorage: function(to) { - if (!this.output[to]) { - this.output[to] = { storage: {}, number: this.numberCounter, violation: {} }; - } - }, - } -} \ No newline at end of file diff --git a/packages/executor/package.json b/packages/executor/package.json index 14a5dc82..bba214f5 100644 --- a/packages/executor/package.json +++ b/packages/executor/package.json @@ -1,6 +1,6 @@ { "name": "executor", - "version": "0.0.24", + "version": "0.0.44", "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.24", - "types": "^0.0.24" + "params": "^0.0.44", + "types": "^0.0.44" } } diff --git a/packages/executor/src/config.ts b/packages/executor/src/config.ts index 2abb3626..9fcf9da0 100644 --- a/packages/executor/src/config.ts +++ b/packages/executor/src/config.ts @@ -13,12 +13,14 @@ export class Config { networks: Networks; testingMode: boolean; unsafeMode: boolean; + redirectRpc: boolean; constructor(private config: ConfigOptions) { - this.supportedNetworks = this.parseSupportedNetworks(); - this.networks = this.parseNetworkConfigs(); this.testingMode = config.testingMode ?? false; this.unsafeMode = config.unsafeMode ?? false; + this.redirectRpc = config.redirectRpc ?? false; + this.supportedNetworks = this.parseSupportedNetworks(); + this.networks = this.parseNetworkConfigs(); } getNetworkProvider(network: NetworkName): providers.JsonRpcProvider | null { @@ -88,6 +90,9 @@ export class Config { } private parseSupportedNetworks(): NetworkName[] { + if (this.testingMode) { + return ["dev"]; + } const envNetworks = NETWORKS_ENV(); if (envNetworks) { return envNetworks.map((key) => key as NetworkName); @@ -131,8 +136,43 @@ export class Config { conf.receiptLookupRange || bundlerDefaultConfigs.receiptLookupRange ) ); + conf.conditionalTransactions = Boolean( + fromEnvVar( + network, + "CONDITIONAL_TRANSACTIONS", + conf.conditionalTransactions || + bundlerDefaultConfigs.conditionalTransactions + ) + ); + conf.rpcEndpointSubmit = fromEnvVar( + network, + "RPC_SUBMIT", + conf.rpcEndpointSubmit || bundlerDefaultConfigs.rpcEndpointSubmit + ); + conf.gasPriceMarkup = Number( + fromEnvVar( + network, + "GAS_PRICE_MARKUP", + conf.gasPriceMarkup || bundlerDefaultConfigs.gasPriceMarkup + ) + ); + conf.enforceGasPrice = Boolean( + fromEnvVar( + network, + "ENFORCE_GAS_PRICE", + conf.enforceGasPrice || bundlerDefaultConfigs.enforceGasPrice + ) + ); + conf.enforceGasPriceThreshold = Number( + fromEnvVar( + network, + "ENFORCE_GAS_PRICE_THRESHOLD", + conf.enforceGasPriceThreshold || + bundlerDefaultConfigs.enforceGasPriceThreshold + ) + ); - return Object.assign(bundlerDefaultConfigs, conf); + return Object.assign({}, bundlerDefaultConfigs, conf); } } @@ -142,11 +182,15 @@ const bundlerDefaultConfigs: BundlerConfig = { banSlack: 10, minSignerBalance: utils.parseEther("0.1"), multicall: "0xcA11bde05977b3631167028862bE2a173976CA11", // default multicall address - estimationBaseFeeDivisor: 25, estimationStaticBuffer: 21000, validationGasLimit: 10e6, receiptLookupRange: 1024, etherscanApiKey: "", + conditionalTransactions: false, + rpcEndpointSubmit: "", + gasPriceMarkup: 0, + enforceGasPrice: false, + enforceGasPriceThreshold: 1000, }; const NETWORKS_ENV = (): string[] | undefined => { diff --git a/packages/executor/src/executor.ts b/packages/executor/src/executor.ts index 9b2f74b8..137328bb 100644 --- a/packages/executor/src/executor.ts +++ b/packages/executor/src/executor.ts @@ -118,7 +118,7 @@ export class Executor { this.skandha = new Skandha( this.network, this.provider, - this.networkConfig, + this.config, this.logger ); @@ -126,5 +126,19 @@ export class Executor { this.bundlingService.setBundlingMode("manual"); this.logger.info(`${this.network}: set to manual bundling mode`); } + + if (this.networkConfig.conditionalTransactions) { + this.logger.info(`${this.network}: [x] CONDITIONAL TRANSACTIONS`); + } + + if (this.networkConfig.rpcEndpointSubmit) { + this.logger.info( + `${this.network}: [x] SEPARATE RPC FOR SUBMITTING BUNDLES` + ); + } + + if (this.networkConfig.enforceGasPrice) { + this.logger.info(`${this.network}: [x] ENFORCING GAS PRICES`); + } } } diff --git a/packages/executor/src/interfaces.ts b/packages/executor/src/interfaces.ts index 7cb3faee..746a6a2d 100644 --- a/packages/executor/src/interfaces.ts +++ b/packages/executor/src/interfaces.ts @@ -1,6 +1,7 @@ -import { BigNumberish, BytesLike } from "ethers"; +import { BigNumber, BigNumberish, BytesLike } from "ethers"; import { NetworkName } from "types/lib"; import { Executor } from "./executor"; +import { MempoolEntry } from "./entities/MempoolEntry"; export interface Log { blockNumber: number; @@ -29,7 +30,7 @@ export interface TracerTracer { contractSize?: number; number?: number; storage?: { - [slot: string]: number; + [slot: string]: number | string; }; keccak?: { [slot: string]: any; @@ -99,10 +100,6 @@ export interface NetworkConfig { banSlack: number; minSignerBalance: BigNumberish; multicall: string; - // reduces baseFee by a given number in % before dividing paid gas - // use this as a buffer to callGasLimit - // 25% by default - estimationBaseFeeDivisor: number; // adds certain amount of gas to callGasLimit // 21000 by default estimationStaticBuffer: number; @@ -116,6 +113,21 @@ export interface NetworkConfig { // etherscan api is used to fetch gas prices // default = "" (empty string) etherscanApiKey: string; + // enables contidional rpc + conditionalTransactions: boolean; + // rpc endpoint that is used only during submission of a bundle + rpcEndpointSubmit: string; + // adds % markup on reported gas price via skandha_getGasPrice + // 10000 = 100.00% + // 500 = 5% + gasPriceMarkup: number; + // do not bundle userops with low gas prices + enforceGasPrice: boolean; + // gas price threshold in bps + // 10000 = 100.00%, 500 = 5% + // if set to 500, then the userop's gas price is allowed to be + // 5% lower than the networks gas prices + enforceGasPriceThreshold: number; } export type BundlerConfig = Omit< @@ -131,4 +143,57 @@ export interface ConfigOptions { networks: Networks; testingMode?: boolean; unsafeMode: boolean; + redirectRpc: boolean; +} + +export interface SlotMap { + [slot: string]: string; +} + +export interface StorageMap { + [address: string]: string | SlotMap; +} + +export interface ReferencedCodeHashes { + // addresses accessed during this user operation + addresses: string[]; + // keccak over the code of all referenced addresses + hash: string; +} + +export interface UserOpValidationResult { + returnInfo: { + preOpGas: BigNumberish; + prefund: BigNumberish; + sigFailed: boolean; + validAfter: number; + validUntil: number; + }; + + senderInfo: StakeInfo; + factoryInfo?: StakeInfo; + paymasterInfo?: StakeInfo; + aggregatorInfo?: StakeInfo; + referencedContracts?: ReferencedCodeHashes; + storageMap?: StorageMap; +} + +export interface ExecutionResult { + preOpGas: BigNumber; + paid: number; + validAfter: number; + validUntil: number; + targetSuccess: boolean; + targetResult: string; +} + +export interface StakeInfo { + addr: string; + stake: BigNumberish; + unstakeDelaySec: BigNumberish; +} + +export interface Bundle { + entries: MempoolEntry[]; + storageMap: StorageMap; } diff --git a/packages/executor/src/modules/eth.ts b/packages/executor/src/modules/eth.ts index b3a0601e..dc504377 100644 --- a/packages/executor/src/modules/eth.ts +++ b/packages/executor/src/modules/eth.ts @@ -3,7 +3,7 @@ import { arrayify, hexlify } from "ethers/lib/utils"; import RpcError from "types/lib/api/errors/rpc-error"; import * as RpcErrorCodes from "types/lib/api/errors/rpc-error-codes"; import { - EntryPoint, + IEntryPoint, UserOperationEventEvent, UserOperationStruct, } from "types/lib/executor/contracts/EntryPoint"; @@ -12,11 +12,12 @@ import { UserOperationByHashResponse, UserOperationReceipt, } from "types/lib/api/interfaces"; -import { EntryPoint__factory } from "types/lib/executor/contracts/factories"; +import { IEntryPoint__factory } from "types/lib/executor/contracts/factories"; import { NetworkName } from "types/lib"; import { INodeAPI } from "types/lib/node"; import { IPVGEstimator } from "params/lib/types/IPVGEstimator"; import { estimateOptimismPVG, estimateArbitrumPVG } from "params/lib"; +import { getGasFee } from "params/lib"; import { NetworkConfig } from "../interfaces"; import { deepHexlify, getUserOpHash, packUserOp } from "../utils"; import { UserOpValidationService, MempoolService } from "../services"; @@ -39,9 +40,7 @@ export class Eth { private logger: Logger, private nodeApi?: INodeAPI ) { - if ( - ["arbitrum", "arbitrumNitro", "arbitrumNova"].includes(this.networkName) - ) { + if (["arbitrum", "arbitrumNova"].includes(this.networkName)) { this.pvgEstimator = estimateArbitrumPVG(this.provider); } @@ -73,12 +72,13 @@ export class Eth { } this.logger.debug("Validating user op before sending to mempool..."); + await this.userOpValidationService.validateGasFee(userOp); const validationResult = await this.userOpValidationService.simulateValidation(userOp, entryPoint); // TODO: fetch aggregator this.logger.debug("Validation successful. Saving in mempool..."); - const entryPointContract = EntryPoint__factory.connect( + const entryPointContract = IEntryPoint__factory.connect( entryPoint, this.provider ); @@ -129,35 +129,33 @@ export class Eth { const userOpComplemented: UserOperationStruct = { paymasterAndData: userOp.paymasterAndData ?? "0x", - verificationGasLimit: 10e6, - maxFeePerGas: 0, - maxPriorityFeePerGas: 0, - preVerificationGas: 0, ...userOp, + callGasLimit: BigNumber.from(10e6), + preVerificationGas: BigNumber.from(1e6), + verificationGasLimit: BigNumber.from(10e6), + maxFeePerGas: 1, + maxPriorityFeePerGas: 1, }; - if (BigNumber.from(userOpComplemented.callGasLimit).eq(0)) { - userOpComplemented.callGasLimit = BigNumber.from(10e6); - } - if (BigNumber.from(userOpComplemented.preVerificationGas).eq(0)) { - userOpComplemented.preVerificationGas = BigNumber.from(1e6); - } - if (BigNumber.from(userOpComplemented.verificationGasLimit).eq(0)) { - userOpComplemented.verificationGasLimit = BigNumber.from(10e6); - } - - if (userOpComplemented.signature === "0x") { - userOpComplemented.signature = await this.getDummySignature({ - userOp: userOpComplemented, - entryPoint: args.entryPoint, - }); - } + userOpComplemented.signature = await this.getDummySignature({ + userOp: userOpComplemented, + entryPoint: args.entryPoint, + }); const returnInfo = await this.userOpValidationService.validateForEstimation( userOpComplemented, entryPoint ); + // eslint-disable-next-line prefer-const + let { preOpGas, validAfter, validUntil, paid } = returnInfo; + + const verificationGasLimit = BigNumber.from(preOpGas) + .sub(userOpComplemented.preVerificationGas) + .mul(130) + .div(100) // 130% markup + .toNumber(); + let preVerificationGas: BigNumberish = this.calcPreVerificationGas(userOp); userOpComplemented.preVerificationGas = preVerificationGas; if (this.pvgEstimator) { @@ -168,35 +166,19 @@ export class Eth { ); } - // eslint-disable-next-line prefer-const - let { preOpGas, validAfter, validUntil, paid } = returnInfo; - let callGasLimit: BigNumber = BigNumber.from(0); // calculate callGasLimit based on paid fee - const block = await this.provider.getBlock("latest"); - const { estimationBaseFeeDivisor, estimationStaticBuffer } = this.config; - const estimatedBaseFee = block.baseFeePerGas - ?.mul(100) - .div(100 + (estimationBaseFeeDivisor || 0)); - - if (!estimatedBaseFee) { - callGasLimit = BigNumber.from(paid).div(userOpComplemented.maxFeePerGas); - } else { - const lhs = BigNumber.from(userOpComplemented.maxFeePerGas); - const rhs = estimatedBaseFee.add(userOpComplemented.maxPriorityFeePerGas); - const divisor = lhs.lt(rhs) ? lhs : rhs; // min(maxFeePerGas, base + priorityFee) - callGasLimit = BigNumber.from(paid).div(divisor); - } + const { estimationStaticBuffer } = this.config; + callGasLimit = BigNumber.from(paid).div(userOpComplemented.maxFeePerGas); callGasLimit = callGasLimit.sub(preOpGas).add(estimationStaticBuffer || 0); if (callGasLimit.lt(0)) { callGasLimit = BigNumber.from(estimationStaticBuffer || 0); } - // } //< checking for execution revert - const estimatedCallGasLimit = await this.provider + await this.provider .estimateGas({ from: entryPoint, to: userOp.sender, @@ -209,27 +191,75 @@ export class Eth { }); //> - // if calculation on paid fee failed - // fallback to estimateGas - if (callGasLimit.eq(0)) { - callGasLimit = estimatedCallGasLimit; - } + // Binary search gas limits + const userOpToEstimate: UserOperationStruct = { + ...userOpComplemented, + preVerificationGas, + verificationGasLimit, + callGasLimit, + }; - const verificationGas = BigNumber.from(preOpGas).toNumber(); - validAfter = BigNumber.from(validAfter); - validUntil = BigNumber.from(validUntil); - if (validUntil === BigNumber.from(0)) { - validUntil = undefined; - } - if (validAfter === BigNumber.from(0)) { - validAfter = undefined; - } + const gasFee = await getGasFee( + this.networkName, + this.provider, + this.config.etherscanApiKey, + { + entryPoint, + userOp: userOpToEstimate, + } + ); return { preVerificationGas, - verificationGas, - validAfter, - validUntil, + verificationGasLimit: userOpToEstimate.verificationGasLimit, + verificationGas: userOpToEstimate.verificationGasLimit, + validAfter: validAfter ? BigNumber.from(validAfter) : undefined, + validUntil: validUntil ? BigNumber.from(validUntil) : undefined, + callGasLimit: userOpToEstimate.callGasLimit, + maxFeePerGas: gasFee.maxFeePerGas, + maxPriorityFeePerGas: gasFee.maxPriorityFeePerGas, + }; + } + + /** + * Estimates userop gas and validates the signature + * @param args same as in sendUserOperation + */ + async estimateUserOperationGasWithSignature( + args: SendUserOperationGasArgs + ): Promise { + const { userOp, entryPoint } = args; + if (!this.validateEntryPoint(entryPoint)) { + throw new RpcError("Invalid Entrypoint", RpcErrorCodes.INVALID_REQUEST); + } + + const { returnInfo } = + await this.userOpValidationService.validateForEstimationWithSignature( + userOp, + entryPoint + ); + const { preOpGas, validAfter, validUntil } = returnInfo; + const callGasLimit = await this.provider + .estimateGas({ + from: entryPoint, + to: userOp.sender, + data: userOp.callData, + }) + .then((b) => b.toNumber()) + .catch((err) => { + const message = + err.message.match(/reason="(.*?)"/)?.at(1) ?? "execution reverted"; + throw new RpcError(message, RpcErrorCodes.EXECUTION_REVERTED); + }); + // const preVerificationGas = this.calcPreVerificationGas(userOp); + const verificationGasLimit = BigNumber.from(preOpGas).toNumber(); + + return { + preVerificationGas: this.calcPreVerificationGas(userOp), + verificationGasLimit, + verificationGas: verificationGasLimit, + validAfter: BigNumber.from(validAfter), + validUntil: BigNumber.from(validUntil), callGasLimit, }; } @@ -392,7 +422,7 @@ export class Eth { ); } - private validateEntryPoint(entryPoint: string): boolean { + validateEntryPoint(entryPoint: string): boolean { return ( this.config.entryPoints.findIndex( (ep) => ep.toLowerCase() === entryPoint.toLowerCase() @@ -445,10 +475,10 @@ export class Eth { private async getUserOperationEvent( userOpHash: string - ): Promise<[EntryPoint | null, UserOperationEventEvent | null]> { + ): Promise<[IEntryPoint | null, UserOperationEventEvent | null]> { let event: UserOperationEventEvent[] = []; for (const addr of await this.getSupportedEntryPoints()) { - const contract = EntryPoint__factory.connect(addr, this.provider); + const contract = IEntryPoint__factory.connect(addr, this.provider); try { const blockNumber = await this.provider.getBlockNumber(); let fromBlockNumber = blockNumber - this.config.receiptLookupRange; diff --git a/packages/executor/src/modules/skandha.ts b/packages/executor/src/modules/skandha.ts index 0c7979c3..dcfb51f9 100644 --- a/packages/executor/src/modules/skandha.ts +++ b/packages/executor/src/modules/skandha.ts @@ -1,25 +1,42 @@ -import { ethers } from "ethers"; +import { BigNumber, BigNumberish, ethers } from "ethers"; import { NetworkName } from "types/lib"; -import { GetGasPriceResponse } from "types/lib/api/interfaces"; +import { + GetConfigResponse, + GetFeeHistoryResponse, + GetGasPriceResponse, +} from "types/lib/api/interfaces"; import RpcError from "types/lib/api/errors/rpc-error"; import * as RpcErrorCodes from "types/lib/api/errors/rpc-error-codes"; +import { GasPriceMarkupOne } from "params/lib"; +import { getGasFee } from "params/lib"; +import { IEntryPoint__factory } from "types/lib/executor/contracts"; +import { UserOperationStruct } from "types/lib/executor/contracts/EntryPoint"; import { Logger, NetworkConfig } from "../interfaces"; -import { getGasFee } from "../utils/getGasFee"; +import { Config } from "../config"; // custom features of Skandha export class Skandha { + networkConfig: NetworkConfig; + constructor( private networkName: NetworkName, private provider: ethers.providers.JsonRpcProvider, - private config: NetworkConfig, + private config: Config, private logger: Logger - ) {} + ) { + const networkConfig = this.config.getNetworkConfig(this.networkName); + if (!networkConfig) { + throw new Error("No network config"); + } + this.networkConfig = networkConfig; + } async getGasPrice(): Promise { + const multiplier = this.networkConfig.gasPriceMarkup; const gasFee = await getGasFee( this.networkName, this.provider, - this.config.etherscanApiKey + this.networkConfig.etherscanApiKey ); let { maxPriorityFeePerGas, maxFeePerGas } = gasFee; @@ -36,9 +53,118 @@ export class Skandha { } } + if (multiplier && !BigNumber.from(multiplier).eq(0)) { + const bnMultiplier = GasPriceMarkupOne.add(multiplier); + maxFeePerGas = bnMultiplier.mul(maxFeePerGas).div(GasPriceMarkupOne); + maxPriorityFeePerGas = bnMultiplier + .mul(maxPriorityFeePerGas) + .div(GasPriceMarkupOne); + } + return { maxPriorityFeePerGas, maxFeePerGas, }; } + + async getConfig(): Promise { + const wallet = this.config.getRelayer(this.networkName); + const hasEtherscanApiKey = Boolean(this.networkConfig.etherscanApiKey); + const hasExecutionRpc = Boolean(this.networkConfig.rpcEndpointSubmit); + return { + flags: { + unsafeMode: this.config.unsafeMode, + testingMode: this.config.testingMode, + redirectRpc: this.config.redirectRpc, + }, + entryPoints: this.networkConfig.entryPoints, + beneficiary: this.networkConfig.beneficiary, + relayer: wallet ? await wallet.getAddress() : "", + minInclusionDenominator: BigNumber.from( + this.networkConfig.minInclusionDenominator + ).toNumber(), + throttlingSlack: BigNumber.from( + this.networkConfig.throttlingSlack + ).toNumber(), + banSlack: BigNumber.from(this.networkConfig.banSlack).toNumber(), + minSignerBalance: `${ethers.utils.formatEther( + this.networkConfig.minSignerBalance + )} eth`, + multicall: this.networkConfig.multicall, + estimationStaticBuffer: BigNumber.from( + this.networkConfig.estimationStaticBuffer + ).toNumber(), + validationGasLimit: BigNumber.from( + this.networkConfig.validationGasLimit + ).toNumber(), + receiptLookupRange: BigNumber.from( + this.networkConfig.receiptLookupRange + ).toNumber(), + etherscanApiKey: hasEtherscanApiKey, + conditionalTransactions: this.networkConfig.conditionalTransactions, + rpcEndpointSubmit: hasExecutionRpc, + gasPriceMarkup: BigNumber.from( + this.networkConfig.gasPriceMarkup + ).toNumber(), + enforceGasPrice: this.networkConfig.enforceGasPrice, + enforceGasPriceThreshold: BigNumber.from( + this.networkConfig.enforceGasPriceThreshold + ).toNumber(), + }; + } + + /** + * see eth_feeHistory + * @param entryPoint Entry Point contract + * @param blockCount Number of blocks in the requested range + * @param newestBlock Highest number block of the requested range, or "latest" + */ + async getFeeHistory( + entryPoint: string, + blockCount: BigNumberish, + newestBlock: BigNumberish | string + ): Promise { + const toBlockInfo = await this.provider.getBlock(newestBlock.toString()); + const fromBlockNumber = BigNumber.from(toBlockInfo.number) + .sub(blockCount) + .toNumber(); + const contract = IEntryPoint__factory.connect(entryPoint, this.provider); + const events = await contract.queryFilter( + contract.filters.UserOperationEvent(), + fromBlockNumber, + toBlockInfo.number + ); + const txReceipts = await Promise.all( + events.map((event) => event.getTransaction()) + ); + const txDecoded = txReceipts + .map((receipt) => { + try { + return contract.interface.decodeFunctionData( + "handleOps", + receipt.data + ); + } catch (err) { + this.logger.error(err); + return null; + } + }) + .filter((el) => el !== null); + + const actualGasPrice = events.map((event) => + BigNumber.from(event.args.actualGasCost).div(event.args.actualGasUsed) + ); + const userops = txDecoded + .map((handleOps) => handleOps!.ops as UserOperationStruct[]) + .reduce((p, c) => { + return p.concat(c); + }, []); + return { + actualGasPrice, + maxFeePerGas: userops.map((userop) => userop.maxFeePerGas), + maxPriorityFeePerGas: userops.map( + (userop) => userop.maxPriorityFeePerGas + ), + }; + } } diff --git a/packages/executor/src/services/BundlingService.ts b/packages/executor/src/services/BundlingService.ts index d662388a..768c848b 100644 --- a/packages/executor/src/services/BundlingService.ts +++ b/packages/executor/src/services/BundlingService.ts @@ -1,23 +1,28 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import { BigNumber, BigNumberish, ethers, providers } from "ethers"; +import { BigNumber, ethers, providers } from "ethers"; import { NetworkName } from "types/lib"; -import { EntryPoint__factory } from "types/lib/executor/contracts/factories"; -import { EntryPoint } from "types/lib/executor/contracts/EntryPoint"; +import { IEntryPoint__factory } from "types/lib/executor/contracts/factories"; import { Mutex } from "async-mutex"; import { SendBundleReturn } from "types/lib/executor"; import { IMulticall3__factory } from "types/lib/executor/contracts/factories/IMulticall3__factory"; -import { chainsWithoutEIP1559 } from "params/lib"; +import { GasPriceMarkupOne, chainsWithoutEIP1559 } from "params/lib"; +import { IEntryPoint } from "types/lib/executor/contracts"; +import { getGasFee } from "params/lib"; +import { IGetGasFeeResult } from "params/lib/gas-price-oracles/oracles"; import { getAddr } from "../utils"; import { MempoolEntry } from "../entities/MempoolEntry"; import { ReputationStatus } from "../entities/interfaces"; import { Config } from "../config"; -import { BundlingMode, Logger, NetworkConfig } from "../interfaces"; -import { getGasFee } from "../utils/getGasFee"; -import { ReputationService } from "./ReputationService"; import { + Bundle, + BundlingMode, + Logger, + NetworkConfig, UserOpValidationResult, - UserOpValidationService, -} from "./UserOpValidation"; +} from "../interfaces"; +import { mergeStorageMap } from "../utils/mergeStorageMap"; +import { ReputationService } from "./ReputationService"; +import { UserOpValidationService } from "./UserOpValidation"; import { MempoolService } from "./MempoolService"; export class BundlingService { @@ -47,74 +52,131 @@ 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 bundle = await this.createBundle(); - if (bundle.length == 0) { + const gasFee = await getGasFee( + this.network, + this.provider, + this.networkConfig.etherscanApiKey + ); + if ( + !gasFee.gasPrice && + !gasFee.maxFeePerGas && + !gasFee.maxPriorityFeePerGas + ) { + this.logger.debug("Could not fetch gas prices..."); + return null; + } + const bundle = await this.createBundle(gasFee); + if (bundle.entries.length == 0) { this.logger.debug("sendNextBundle - no bundle"); return null; } - return await this.sendBundle(bundle); + return await this.sendBundle(bundle, gasFee); }); } - async sendBundle(bundle: MempoolEntry[]): Promise { - if (!bundle.length) { + async sendBundle( + bundle: Bundle, + gasFee: IGetGasFeeResult + ): Promise { + const { entries, storageMap } = bundle; + if (!bundle.entries.length) { return null; } - const entryPoint = bundle[0]!.entryPoint; - const entryPointContract = EntryPoint__factory.connect( + const entryPoint = entries[0]!.entryPoint; + const entryPointContract = IEntryPoint__factory.connect( entryPoint, this.provider ); const wallet = this.config.getRelayer(this.network)!; const beneficiary = await this.selectBeneficiary(); try { - const gasFee = await getGasFee( - this.network, - this.provider, - this.networkConfig.etherscanApiKey - ); const txRequest = entryPointContract.interface.encodeFunctionData( "handleOps", - [bundle.map((entry) => entry.userOp), beneficiary] + [entries.map((entry) => entry.userOp), beneficiary] ); - const transaction = { + const transaction: ethers.providers.TransactionRequest = { to: entryPoint, data: txRequest, type: 2, maxPriorityFeePerGas: gasFee.maxPriorityFeePerGas, maxFeePerGas: gasFee.maxFeePerGas, - gasPrice: undefined as BigNumberish | undefined, }; if (chainsWithoutEIP1559.some((network) => network === this.network)) { - transaction.type = 1; transaction.gasPrice = gasFee.gasPrice; delete transaction.maxPriorityFeePerGas; delete transaction.maxFeePerGas; + delete transaction.type; } - this.logger.debug(JSON.stringify(transaction, undefined, 2)); - - const gasLimit = await this.estimateBundleGas(bundle); - const tx = await wallet.sendTransaction({ + const gasLimit = await this.estimateBundleGas(entries); + const tx = { ...transaction, gasLimit, - }); + chainId: this.provider._network.chainId, + nonce: await wallet.getTransactionCount(), + }; + + let txHash: string; + // geth-dev doesn't support signTransaction + if (!this.config.testingMode) { + // check for execution revert + try { + await wallet.estimateGas(tx); + } catch (err) { + this.logger.error(err); + for (const entry of entries) { + await this.mempoolService.remove(entry); + } + return null; + } + const signedRawTx = await wallet.signTransaction(tx); + + const method = !this.networkConfig.conditionalTransactions + ? "eth_sendRawTransaction" + : "eth_sendRawTransactionConditional"; + const params = !this.networkConfig.conditionalTransactions + ? [signedRawTx] + : [signedRawTx, { knownAccounts: storageMap }]; - this.logger.debug(`Sent new bundle ${tx.hash}`); + this.logger.debug({ + method, + ...tx, + params, + }); + + if (this.networkConfig.rpcEndpointSubmit) { + this.logger.debug("Sending to a separate rpc"); + const provider = new ethers.providers.JsonRpcProvider( + this.networkConfig.rpcEndpointSubmit + ); + txHash = await provider.send(method, params); + } else { + txHash = await this.provider.send(method, params); + } + + this.logger.debug(`Sent new bundle ${txHash}`); + } else { + const resp = await wallet.sendTransaction(tx); + txHash = resp.hash; + } - for (const entry of bundle) { + for (const entry of entries) { await this.mempoolService.remove(entry); } const userOpHashes = await this.getUserOpHashes( entryPointContract, - bundle + entries ); this.logger.debug(`User op hashes ${userOpHashes}`); return { - transactionHash: tx.hash, + transactionHash: txHash, userOpHashes: userOpHashes, }; } catch (err: any) { @@ -123,7 +185,7 @@ export class BundlingService { return null; } const { index, paymaster, reason } = err.errorArgs; - const entry = bundle[index]; + const entry = entries[index]; if (paymaster !== ethers.constants.AddressZero) { await this.reputationService.crashedHandleOps(paymaster); } else if (typeof reason === "string" && reason.startsWith("AA1")) { @@ -142,19 +204,63 @@ export class BundlingService { } } - async createBundle(): Promise { + async createBundle(gasFee: IGetGasFeeResult): Promise { // TODO: support multiple entry points // filter bundles by entry points const entries = await this.mempoolService.getSortedOps(); - const bundle: MempoolEntry[] = []; + const bundle: Bundle = { + storageMap: {}, + entries: [], + }; const paymasterDeposit: { [key: string]: BigNumber } = {}; const stakedEntityCount: { [key: string]: number } = {}; const senders = new Set(); + const knownSenders = entries.map((it) => { + return it.userOp.sender.toLowerCase(); + }); + for (const entry of entries) { + // validate gas prices if enabled + if (this.networkConfig.enforceGasPrice) { + let { maxPriorityFeePerGas, maxFeePerGas } = gasFee; + const { enforceGasPriceThreshold } = this.networkConfig; + if (chainsWithoutEIP1559.some((network) => network === this.network)) { + maxFeePerGas = maxPriorityFeePerGas = gasFee.gasPrice; + } + // userop max fee per gas = userop.maxFee * (100 + threshold) / 100; + const userOpMaxFeePerGas = BigNumber.from(entry.userOp.maxFeePerGas) + .mul(GasPriceMarkupOne.add(enforceGasPriceThreshold)) + .div(GasPriceMarkupOne); + // userop priority fee per gas = userop.priorityFee * (100 + threshold) / 100; + const userOpmaxPriorityFeePerGas = BigNumber.from( + entry.userOp.maxPriorityFeePerGas + ) + .mul(GasPriceMarkupOne.add(enforceGasPriceThreshold)) + .div(GasPriceMarkupOne); + if ( + userOpMaxFeePerGas.lt(maxFeePerGas!) || + userOpmaxPriorityFeePerGas.lt(maxPriorityFeePerGas!) + ) { + this.logger.debug( + { + sender: entry.userOp.sender, + nonce: entry.userOp.nonce.toString(), + userOpMaxFeePerGas: userOpMaxFeePerGas.toString(), + userOpmaxPriorityFeePerGas: userOpmaxPriorityFeePerGas.toString(), + maxPriorityFeePerGas: maxPriorityFeePerGas!.toString(), + maxFeePerGas: maxFeePerGas!.toString(), + }, + "Skipping user op with low gas price" + ); + continue; + } + } + const paymaster = getAddr(entry.userOp.paymasterAndData); const factory = getAddr(entry.userOp.initCode); + // validate Paymaster if (paymaster) { const paymasterStatus = await this.reputationService.getStatus( paymaster @@ -166,17 +272,19 @@ export class BundlingService { paymasterStatus === ReputationStatus.THROTTLED || (stakedEntityCount[paymaster] ?? 0) > 1 ) { - this.logger.debug("skipping throttled paymaster", { - metadata: { + this.logger.debug( + { sender: entry.userOp.sender, nonce: entry.userOp.nonce, paymaster, }, - }); + "skipping throttled paymaster" + ); continue; } } + // validate Factory if (factory) { const deployerStatus = await this.reputationService.getStatus(factory); if (deployerStatus === ReputationStatus.BANNED) { @@ -186,24 +294,23 @@ export class BundlingService { deployerStatus === ReputationStatus.THROTTLED || (stakedEntityCount[factory] ?? 0) > 1 ) { - this.logger.debug("skipping throttled factory", { - metadata: { + this.logger.debug( + { sender: entry.userOp.sender, nonce: entry.userOp.nonce, factory, }, - }); + "skipping throttled factory" + ); continue; } } if (senders.has(entry.userOp.sender)) { - this.logger.debug("skipping already included sender", { - metadata: { - sender: entry.userOp.sender, - nonce: entry.userOp.nonce, - }, - }); + this.logger.debug( + { sender: entry.userOp.sender, nonce: entry.userOp.nonce }, + "skipping already included sender" + ); continue; } @@ -222,8 +329,24 @@ export class BundlingService { continue; } + // Check if userOp is trying to access storage of another userop + if (validationResult.storageMap) { + const sender = entry.userOp.sender.toLowerCase(); + const conflictingSender = Object.keys(validationResult.storageMap) + .map((address) => address.toLowerCase()) + .find((address) => { + return address !== sender && knownSenders.includes(address); + }); + if (conflictingSender) { + this.logger.debug( + `UserOperation from ${entry.userOp.sender} sender accessed a storage of another known sender ${conflictingSender}` + ); + continue; + } + } + // TODO: add total gas cap - const entryPointContract = EntryPoint__factory.connect( + const entryPointContract = IEntryPoint__factory.connect( entry.entryPoint, this.provider ); @@ -252,7 +375,21 @@ export class BundlingService { } senders.add(entry.userOp.sender); - bundle.push(entry); + if ( + this.networkConfig.conditionalTransactions && + validationResult.storageMap + ) { + if (BigNumber.from(entry.userOp.nonce).gt(0)) { + const { storageHash } = await this.provider.send("eth_getProof", [ + entry.userOp.sender, + [], + "latest", + ]); + bundle.storageMap[entry.userOp.sender.toLowerCase()] = storageHash; + } + mergeStorageMap(bundle.storageMap, validationResult.storageMap); + } + bundle.entries.push(entry); } return bundle; } @@ -318,7 +455,7 @@ export class BundlingService { } async getUserOpHashes( - entryPoint: EntryPoint, + entryPoint: IEntryPoint, userOps: MempoolEntry[] ): Promise { try { diff --git a/packages/executor/src/services/EventsService.ts b/packages/executor/src/services/EventsService.ts index 759efe77..8be009e3 100644 --- a/packages/executor/src/services/EventsService.ts +++ b/packages/executor/src/services/EventsService.ts @@ -1,7 +1,7 @@ import { providers } from "ethers"; import { IDbController } from "types/lib"; -import { EntryPoint } from "types/lib/executor/contracts"; -import { EntryPoint__factory } from "types/lib/executor/contracts/factories"; +import { IEntryPoint } from "types/lib/executor/contracts"; +import { IEntryPoint__factory } from "types/lib/executor/contracts/factories"; import { AccountDeployedEvent, SignatureAggregatorChangedEvent, @@ -11,7 +11,7 @@ import { TypedEvent } from "types/lib/executor/contracts/common"; import { ReputationService } from "./ReputationService"; export class EventsService { - private entryPoints: EntryPoint[] = []; + private entryPoints: IEntryPoint[] = []; private lastBlockPerEntryPoint: { [address: string]: number; } = {}; @@ -26,7 +26,7 @@ export class EventsService { ) { this.LAST_BLOCK_KEY = `${this.chainId}:LAST_BLOCK_PER_ENTRY_POINTS`; for (const entryPoint of this.entryPointAddrs) { - const contract = EntryPoint__factory.connect(entryPoint, this.provider); + const contract = IEntryPoint__factory.connect(entryPoint, this.provider); this.entryPoints.push(contract); } } diff --git a/packages/executor/src/services/MempoolService.ts b/packages/executor/src/services/MempoolService.ts index 884c96f5..0465efbe 100644 --- a/packages/executor/src/services/MempoolService.ts +++ b/packages/executor/src/services/MempoolService.ts @@ -6,8 +6,8 @@ import { UserOperationStruct } from "types/lib/executor/contracts/EntryPoint"; import { getAddr, now } from "../utils"; import { MempoolEntry } from "../entities/MempoolEntry"; import { IMempoolEntry, MempoolEntrySerialized } from "../entities/interfaces"; +import { StakeInfo } from "../interfaces"; import { ReputationService } from "./ReputationService"; -import { StakeInfo } from "./UserOpValidation"; export class MempoolService { private MAX_MEMPOOL_USEROPS_PER_SENDER = 4; diff --git a/packages/executor/src/services/ReputationService.ts b/packages/executor/src/services/ReputationService.ts index 91e92968..0c183409 100644 --- a/packages/executor/src/services/ReputationService.ts +++ b/packages/executor/src/services/ReputationService.ts @@ -6,7 +6,7 @@ import { ReputationEntrySerialized, ReputationStatus, } from "../entities/interfaces"; -import { StakeInfo } from "./UserOpValidation"; +import { StakeInfo } from "../interfaces"; export class ReputationService { private REP_COLL_KEY: string; // prefix in rocksdb @@ -142,7 +142,10 @@ export class ReputationService { * @param info StakeInfo * @returns null on success otherwise error */ - async checkStake(info: StakeInfo): Promise { + async checkStake(info: StakeInfo | undefined): Promise { + if (!info) { + return "unstaked"; + } if (!info.addr || (await this.isWhitelisted(info.addr))) { return null; } diff --git a/packages/executor/src/services/GethTracer.ts b/packages/executor/src/services/UserOpValidation/GethTracer.ts similarity index 59% rename from packages/executor/src/services/GethTracer.ts rename to packages/executor/src/services/UserOpValidation/GethTracer.ts index 3dd54200..acb650ec 100644 --- a/packages/executor/src/services/GethTracer.ts +++ b/packages/executor/src/services/UserOpValidation/GethTracer.ts @@ -1,9 +1,11 @@ import { readFileSync } from "node:fs"; import { resolve } from "node:path"; -import { providers } from "ethers"; -import { TracerPrestateResponse, TracerResult } from "../interfaces"; +import { BigNumber, providers } from "ethers"; +import { BundlerCollectorReturn } from "types/lib/executor"; +import { TracerPrestateResponse } from "../../interfaces"; + const tracer = readFileSync( - resolve(process.cwd(), "packages", "executor", "customTracer.js") + resolve(process.cwd(), "packages", "executor", "tracer.js") ).toString(); if (tracer == null) { throw new Error("Tracer not found"); @@ -16,10 +18,7 @@ const stringifiedTracer = tracer.match(regexp)![1]; // console.log( // JSON.stringify( // { -// tracer: stringifiedTracer.replace( -// /0xffffffffffffffffffffffffffffffffffffffff/g, -// "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789" -// ), +// tracer: stringifiedTracer, // }, // undefined, // 2 @@ -31,19 +30,23 @@ export class GethTracer { async debug_traceCall( tx: providers.TransactionRequest - ): Promise { + ): Promise { + const { gasLimit, ...txWithoutGasLimit } = tx; + const gas = `0x${BigNumber.from(gasLimit ?? 10e6) + .toNumber() + .toString(16)}`; // we're not using toHexString() of BigNumber, because it adds a leading zero which is not accepted by the nodes const ret: any = await this.provider.send("debug_traceCall", [ - tx, + { + ...txWithoutGasLimit, + gas, + }, "latest", { - tracer: stringifiedTracer.replace( - /0xffffffffffffffffffffffffffffffffffffffff/g, - tx.to!.toLowerCase() - ), + tracer: stringifiedTracer, }, ]); - return ret as TracerResult; + return ret as BundlerCollectorReturn; } async debug_traceCallPrestate( diff --git a/packages/executor/src/services/UserOpValidation/index.ts b/packages/executor/src/services/UserOpValidation/index.ts new file mode 100644 index 00000000..6261f896 --- /dev/null +++ b/packages/executor/src/services/UserOpValidation/index.ts @@ -0,0 +1 @@ +export * from "./service"; diff --git a/packages/executor/src/services/UserOpValidation/service.ts b/packages/executor/src/services/UserOpValidation/service.ts new file mode 100644 index 00000000..e1053b33 --- /dev/null +++ b/packages/executor/src/services/UserOpValidation/service.ts @@ -0,0 +1,134 @@ +import { BigNumber, providers } from "ethers"; +import { NetworkName } from "types/lib"; +import { UserOperationStruct } from "types/lib/executor/contracts/EntryPoint"; +import RpcError from "types/lib/api/errors/rpc-error"; +import * as RpcErrorCodes from "types/lib/api/errors/rpc-error-codes"; +import { Config } from "../../config"; +import { + ExecutionResult, + Logger, + NetworkConfig, + UserOpValidationResult, +} from "../../interfaces"; +import { ReputationService } from "../ReputationService"; +import { + EstimationService, + SafeValidationService, + UnsafeValidationService, +} from "./validators"; + +export class UserOpValidationService { + private networkConfig: NetworkConfig; + + private estimationService: EstimationService; + private safeValidationService: SafeValidationService; + private unsafeValidationService: UnsafeValidationService; + + constructor( + private provider: providers.Provider, + private reputationService: ReputationService, + private network: NetworkName, + private config: Config, + private logger: Logger + ) { + const networkConfig = config.getNetworkConfig(network); + if (!networkConfig) { + throw new Error(`No config found for ${network}`); + } + this.networkConfig = networkConfig; + + this.estimationService = new EstimationService(this.provider, this.logger); + this.safeValidationService = new SafeValidationService( + this.provider, + this.reputationService, + this.network, + this.logger + ); + this.unsafeValidationService = new UnsafeValidationService( + this.provider, + this.networkConfig, + this.logger + ); + } + + async validateForEstimation( + userOp: UserOperationStruct, + entryPoint: string + ): Promise { + return await this.estimationService.estimateUserOp(userOp, entryPoint); + } + + async validateForEstimationWithSignature( + userOp: UserOperationStruct, + entryPoint: string + ): Promise { + return await this.unsafeValidationService.validateUnsafely( + userOp, + entryPoint + ); + } + + async simulateValidation( + userOp: UserOperationStruct, + entryPoint: string, + codehash?: string + ): Promise { + if (this.config.unsafeMode) { + return await this.unsafeValidationService.validateUnsafely( + userOp, + entryPoint + ); + } + return await this.safeValidationService.validateSafely( + userOp, + entryPoint, + codehash + ); + } + + async validateGasFee(userOp: UserOperationStruct): Promise { + const block = await this.provider.getBlock("latest"); + const { baseFeePerGas } = block; + let { maxFeePerGas, maxPriorityFeePerGas } = userOp; + maxFeePerGas = BigNumber.from(maxFeePerGas); + maxPriorityFeePerGas = BigNumber.from(maxPriorityFeePerGas); + if (!baseFeePerGas) { + if (!maxFeePerGas.eq(maxPriorityFeePerGas)) { + throw new RpcError( + "maxFeePerGas must be equal to maxPriorityFeePerGas", + RpcErrorCodes.INVALID_USEROP + ); + } + return true; + } + + if (maxFeePerGas.lt(baseFeePerGas)) { + throw new RpcError( + "maxFeePerGas must be greater or equal to baseFee", + RpcErrorCodes.INVALID_USEROP + ); + } + + return true; + } + + async binarySearchVGL( + userOp: UserOperationStruct, + entryPoint: string + ): Promise { + if (this.config.unsafeMode) { + return this.estimationService.binarySearchVGL(userOp, entryPoint); + } + return this.estimationService.binarySearchVGLSafe(userOp, entryPoint); + } + + async binarySearchCGL( + userOp: UserOperationStruct, + entryPoint: string + ): Promise { + if (this.config.unsafeMode) { + 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 new file mode 100644 index 00000000..7448cb2e --- /dev/null +++ b/packages/executor/src/services/UserOpValidation/utils.ts @@ -0,0 +1,331 @@ +import { BigNumber, BytesLike } from "ethers"; +import { AddressZero } from "params/lib"; +import RpcError from "types/lib/api/errors/rpc-error"; +import { + IEntryPoint, + IEntryPoint__factory, + IAccount__factory, + IAggregatedAccount__factory, + IAggregator__factory, + IPaymaster__factory, + SenderCreator__factory, +} from "types/lib/executor/contracts"; +import { UserOperationStruct } from "types/lib/executor/contracts/EntryPoint"; +import * as RpcErrorCodes from "types/lib/api/errors/rpc-error-codes"; +import { Interface, hexZeroPad, hexlify, keccak256 } from "ethers/lib/utils"; +import { BundlerCollectorReturn, CallEntry } from "types/lib/executor"; +import { UserOpValidationResult, StakeInfo } from "../../interfaces"; +import { getAddr } from "../../utils"; + +export function nethermindErrorHandler( + epContract: IEntryPoint, + errorResult: any +): any { + try { + let { error } = errorResult; + if (error && 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; +} + +export function parseErrorResult( + userOp: UserOperationStruct, + errorResult: { errorName: string; errorArgs: any } +): UserOpValidationResult { + if (!errorResult?.errorName?.startsWith("ValidationResult")) { + // parse it as FailedOp + // if its FailedOp, then we have the paymaster param... otherwise its an Error(string) + let paymaster = errorResult.errorArgs?.paymaster; + if (paymaster === AddressZero) { + paymaster = undefined; + } + // eslint-disable-next-line + const msg: string = + errorResult.errorArgs?.reason ?? errorResult.toString(); + + if (paymaster == null) { + throw new RpcError(msg, RpcErrorCodes.VALIDATION_FAILED); + } else { + throw new RpcError(msg, RpcErrorCodes.REJECTED_BY_PAYMASTER, { + paymaster, + }); + } + } + + const { + returnInfo, + senderInfo, + factoryInfo, + paymasterInfo, + aggregatorInfo, // may be missing (exists only SimulationResultWithAggregator + } = errorResult.errorArgs; + + // extract address from "data" (first 20 bytes) + // add it as "addr" member to the "stakeinfo" struct + // if no address, then return "undefined" instead of struct. + function fillEntity(data: BytesLike, info: StakeInfo): StakeInfo | undefined { + const addr = getAddr(data); + return addr == null + ? undefined + : { + ...info, + addr, + }; + } + + return { + returnInfo, + senderInfo: { + ...senderInfo, + addr: userOp.sender, + }, + factoryInfo: fillEntity(userOp.initCode, factoryInfo), + paymasterInfo: fillEntity(userOp.paymasterAndData, paymasterInfo), + aggregatorInfo: fillEntity( + aggregatorInfo?.actualAggregator, + aggregatorInfo?.stakeInfo + ), + }; +} + +export function compareBytecode( + artifactBytecode: string, + contractBytecode: string +): number { + if (artifactBytecode.length <= 2 || contractBytecode.length <= 2) return 0; + + if (typeof artifactBytecode === "string") + artifactBytecode = artifactBytecode + // eslint-disable-next-line no-useless-escape + .replace(/\_\_\$/g, "000") + // eslint-disable-next-line no-useless-escape + .replace(/\$\_\_/g, "000"); + + let matchedBytes = 0; + for (let i = 0; i < artifactBytecode.length; i++) { + if (artifactBytecode[i] === contractBytecode[i]) matchedBytes++; + } + if (isNaN(matchedBytes / artifactBytecode.length)) { + return 0; + } + + return matchedBytes / artifactBytecode.length; +} + +export function toBytes32(b: BytesLike | number): string { + return hexZeroPad(hexlify(b).toLowerCase(), 32); +} + +export function requireCond( + cond: boolean, + msg: string, + code?: number, + data: any = undefined +): void { + if (!cond) { + throw new RpcError(msg, code, data); + } +} + +/** + * parse all call operation in the trace. + * notes: + * - entries are ordered by the return (so nested call appears before its outer call + * - last entry is top-level return from "simulateValidation". it as ret and rettype, but no type or address + * @param tracerResults + */ +export function parseCallStack( + tracerResults: BundlerCollectorReturn +): CallEntry[] { + const abi = Object.values( + [ + ...IEntryPoint__factory.abi, + ...IAccount__factory.abi, + ...IAggregatedAccount__factory.abi, + ...IAggregator__factory.abi, + ...IPaymaster__factory.abi, + ].reduce((set, entry: any) => { + const key = `${entry.name}(${entry?.inputs + ?.map((i: any) => i.type) + .join(",")})`; + return { + ...set, + [key]: entry, + }; + }, {}) + ) as any; + + const xfaces = new Interface(abi); + + function callCatch(x: () => T, def: T1): T | T1 { + try { + return x(); + } catch { + return def; + } + } + + const out: CallEntry[] = []; + const stack: any[] = []; + tracerResults.calls + .filter((x) => !x.type.startsWith("depth")) + .forEach((c) => { + if (c.type.match(/REVERT|RETURN/) != null) { + const top = stack.splice(-1)[0] ?? { + type: "top", + method: "validateUserOp", + }; + const returnData: string = (c as any).data; + if (top.type.match(/CREATE/) != null) { + out.push({ + to: top.to, + from: top.from, + type: top.type, + method: "", + return: `len=${returnData.length}`, + }); + } else { + const method = callCatch( + () => xfaces.getFunction(top.method), + top.method + ); + if (c.type === "REVERT") { + const parsedError = callCatch( + () => xfaces.parseError(returnData), + returnData + ); + out.push({ + to: top.to, + from: top.from, + type: top.type, + method: method.name, + value: top.value, + revert: parsedError, + }); + } else { + const ret = callCatch( + () => xfaces.decodeFunctionResult(method, returnData), + returnData + ); + out.push({ + to: top.to, + from: top.from, + type: top.type, + value: top.value, + method: method.name ?? method, + return: ret, + }); + } + } + } else { + stack.push(c); + } + }); + + // TODO: verify that stack is empty at the end. + + return out; +} + +/** + * slots associated with each entity. + * keccak( A || ...) is associated with "A" + * removed rule: keccak( ... || ASSOC ) (for a previously associated hash) is also associated with "A" + * + * @param stakeInfoEntities stake info for (factory, account, paymaster). factory and paymaster can be null. + * @param keccak array of buffers that were given to keccak in the transaction + */ +export function parseEntitySlots( + stakeInfoEntities: { [addr: string]: StakeInfo | undefined }, + keccak: string[] +): { [addr: string]: Set } { + // for each entity (sender, factory, paymaster), hold the valid slot addresses + // valid: the slot was generated by keccak(entity || ...) + const entitySlots: { [addr: string]: Set } = {}; + + keccak.forEach((k) => { + Object.values(stakeInfoEntities).forEach((info) => { + const addr = info?.addr?.toLowerCase(); + if (addr == null) return; + const addrPadded = toBytes32(addr); + if (entitySlots[addr] == null) { + entitySlots[addr] = new Set(); + } + + const currentEntitySlots = entitySlots[addr]; + + if (k.startsWith(addrPadded)) { + currentEntitySlots.add(keccak256(k)); + } + }); + }); + + return entitySlots; +} + +export const callsFromEntryPointMethodSigs: { [key: string]: string } = { + factory: SenderCreator__factory.createInterface().getSighash("createSender"), + account: IAccount__factory.createInterface().getSighash("validateUserOp"), + paymaster: IPaymaster__factory.createInterface().getSighash( + "validatePaymasterUserOp" + ), +}; + +// return true if the given slot is associated with the given address, given the known keccak operations: +// @param slot the SLOAD/SSTORE slot address we're testing +// @param addr - the address we try to check for association with +// @param reverseKeccak - a mapping we built for keccak values that contained the address +export function isSlotAssociatedWith( + slot: string, + addr: string, + entitySlots: { [addr: string]: Set } +): boolean { + const addrPadded = hexZeroPad(addr, 32).toLowerCase(); + if (slot === addrPadded) { + return true; + } + const k = entitySlots[addr]; + if (k == null) { + return false; + } + const slotN = BigNumber.from(slot); + // scan all slot entries to check of the given slot is within a structure, starting at that offset. + // assume a maximum size on a (static) structure size. + for (const k1 of k.keys()) { + const kn = BigNumber.from(k1); + if (slotN.gte(kn) && slotN.lt(kn.add(128))) { + return true; + } + } + 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 new file mode 100644 index 00000000..3b4ceb61 --- /dev/null +++ b/packages/executor/src/services/UserOpValidation/validators/estimation.ts @@ -0,0 +1,218 @@ +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 { BigNumber, providers } from "ethers"; +import { nethermindErrorHandler } 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 + ); +}; + +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; + + constructor(private provider: providers.Provider, private logger: Logger) { + this.gethTracer = new GethTracer( + this.provider as providers.JsonRpcProvider + ); + } + + async estimateUserOp( + userOp: UserOperationStruct, + entryPoint: string + ): Promise { + const entryPointContract = IEntryPoint__factory.connect( + entryPoint, + this.provider + ); + + const errorResult = await entryPointContract.callStatic + .simulateHandleOp(userOp, AddressZero, BytesZero) + .catch((e: any) => nethermindErrorHandler(entryPointContract, e)); + + if (errorResult.errorName === "FailedOp") { + throw new RpcError( + errorResult.errorArgs.at(-1), + RpcErrorCodes.VALIDATION_FAILED + ); + } + + if (errorResult.errorName !== "ExecutionResult") { + 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); + try { + await this.estimateUserOp( + { ...userOp, verificationGasLimit: mid }, + 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); + try { + await this.checkForOOG( + { ...userOp, verificationGasLimit: mid }, + 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; + } + + // Binary search callGasLimit + // Only available in safe mode + async binarySearchCGLSafe( + 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.checkForOOG(userOp, entryPoint); + lastOptimalCGL = mid; + right = mid.sub(1); + } catch (err) { + if (isCGLLow(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 { 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) { + if (index >= 1 && index < 3) { + throw new Error("oog.validation"); + } + if (index == 3) { + throw new Error("oog.execution"); + } + } + }); + + return ""; // successful validation + } +} diff --git a/packages/executor/src/services/UserOpValidation/validators/index.ts b/packages/executor/src/services/UserOpValidation/validators/index.ts new file mode 100644 index 00000000..cd89c7e6 --- /dev/null +++ b/packages/executor/src/services/UserOpValidation/validators/index.ts @@ -0,0 +1,3 @@ +export * from "./estimation"; +export * from "./safe"; +export * from "./unsafe"; diff --git a/packages/executor/src/services/UserOpValidation/validators/safe.ts b/packages/executor/src/services/UserOpValidation/validators/safe.ts new file mode 100644 index 00000000..2774f794 --- /dev/null +++ b/packages/executor/src/services/UserOpValidation/validators/safe.ts @@ -0,0 +1,417 @@ +import { IEntryPoint__factory } from "types/lib/executor/contracts"; +import { + IEntryPoint, + UserOperationStruct, +} from "types/lib/executor/contracts/EntryPoint"; +import { BigNumber, ethers, providers } from "ethers"; +import { BundlerCollectorReturn, ExitInfo } from "types/lib/executor"; +import RpcError from "types/lib/api/errors/rpc-error"; +import * as RpcErrorCodes from "types/lib/api/errors/rpc-error-codes"; +import { WhitelistedEntities } from "params/lib/whitelisted-entities"; +import { NetworkName } from "types/lib"; +import { + IWhitelistedEntities, + IWhitelistedEntity, +} from "params/lib/types/IWhitelistedEntities"; +import { + Logger, + StorageMap, + UserOpValidationResult, +} from "../../../interfaces"; +import { GethTracer } from "../GethTracer"; +import { + callsFromEntryPointMethodSigs, + isSlotAssociatedWith, + parseCallStack, + parseEntitySlots, + parseValidationResult, +} from "../utils"; +import { ReputationService } from "../../ReputationService"; + +/** + * Some opcodes like: + * - CREATE2 + * are not included here because they are handled elsewhere. + * Do not include them in this list!!! + */ +const bannedOpCodes = new Set([ + "GASPRICE", + "GASLIMIT", + "DIFFICULTY", + "TIMESTAMP", + "BASEFEE", + "BLOCKHASH", + "NUMBER", + "SELFBALANCE", + "BALANCE", + "ORIGIN", + "GAS", + "CREATE", + "COINBASE", + "SELFDESTRUCT", + "RANDOM", + "PREVRANDAO", +]); + +// REF: https://github.com/eth-infinitism/bundler/blob/main/packages/bundler/src/modules/ValidationManager.ts +export class SafeValidationService { + private gethTracer: GethTracer; + + constructor( + private provider: providers.Provider, + private reputationService: ReputationService, + private network: NetworkName, + private logger: Logger + ) { + this.gethTracer = new GethTracer( + this.provider as providers.JsonRpcProvider + ); + } + + async validateSafely( + userOp: UserOperationStruct, + entryPoint: string, + codehash?: string + ): Promise { + const entryPointContract = IEntryPoint__factory.connect( + entryPoint, + this.provider + ); + const simulationGas = BigNumber.from(userOp.preVerificationGas) + .add(userOp.verificationGasLimit) + .add(userOp.callGasLimit); + + const tx: providers.TransactionRequest = { + to: entryPoint, + data: entryPointContract.interface.encodeFunctionData( + "simulateValidation", + [userOp] + ), + gasLimit: simulationGas, + }; + + const traceCall: BundlerCollectorReturn = + await this.gethTracer.debug_traceCall(tx); + const validationResult = await this.validateOpcodesAndStake( + traceCall, + entryPointContract, + userOp + ); + + const { returnInfo } = validationResult; + if (returnInfo.sigFailed) { + throw new RpcError( + "Invalid UserOp signature or paymaster signature", + RpcErrorCodes.INVALID_SIGNATURE + ); + } + + const now = Math.floor(Date.now() / 1000); + if (returnInfo.validUntil != null && returnInfo.validUntil < now) { + throw new RpcError("already expired", RpcErrorCodes.USEROP_EXPIRED); + } + + if (returnInfo.validAfter != null && returnInfo.validAfter > now + 30) { + throw new RpcError("expires too soon", RpcErrorCodes.USEROP_EXPIRED); + } + + if (validationResult.aggregatorInfo != null) { + const stakeErr = await this.reputationService.checkStake( + validationResult.aggregatorInfo + ); + if (stakeErr) { + throw new RpcError(stakeErr, RpcErrorCodes.VALIDATION_FAILED); + } + } + + const prestateTrace = await this.gethTracer.debug_traceCallPrestate(tx); + const addresses = traceCall.callsFromEntryPoint.flatMap((level) => + Object.keys(level.contractSize) + ); + const code = addresses.map((addr) => prestateTrace[addr]?.code).join(";"); + const hash = ethers.utils.keccak256( + ethers.utils.hexlify(ethers.utils.toUtf8Bytes(code)) + ); + + if (codehash && codehash !== hash) { + throw new RpcError( + "modified code after first validation", + RpcErrorCodes.INVALID_OPCODE + ); + } + + const storageMap: StorageMap = {}; + traceCall.callsFromEntryPoint.forEach((level) => { + Object.keys(level.access).forEach((addr) => { + storageMap[addr] = storageMap[addr] ?? level.access[addr].reads; + }); + }); + + return { + ...validationResult, + referencedContracts: { + addresses, + hash, + }, + storageMap, + }; + } + + private async validateOpcodesAndStake( + traceCall: BundlerCollectorReturn, + entryPointContract: IEntryPoint, + userOp: UserOperationStruct + ): Promise { + const entryPoint = entryPointContract.address.toLowerCase(); + if (traceCall == null || traceCall.callsFromEntryPoint == undefined) { + throw new Error( + "Could not validate transaction. Tracing is not available" + ); + } + + if (Object.values(traceCall.callsFromEntryPoint).length < 1) { + throw new RpcError( + "Unexpected traceCall result: no calls from entrypoint.", + RpcErrorCodes.INTERNAL_ERROR + ); + } + + const callStack = parseCallStack(traceCall); + + const callInfoEntryPoint = callStack.find( + (call) => + call.to === entryPoint && + call.from !== entryPoint && + call.method !== "0x" && + call.method !== "depositTo" + ); + + if (callInfoEntryPoint != null) { + throw new RpcError( + `illegal call into EntryPoint during validation ${callInfoEntryPoint?.method}`, + RpcErrorCodes.INVALID_OPCODE + ); + } + + if ( + callStack.some( + ({ to, value }) => to !== entryPoint && BigNumber.from(value ?? 0).gt(0) + ) + ) { + throw new RpcError( + "May not may CALL with value", + RpcErrorCodes.INVALID_OPCODE + ); + } + + const sender = userOp.sender.toLowerCase(); + + // Parse error result from the last call + 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; + const validationResult = parseValidationResult( + entryPointContract, + userOp, + data + ); + + const stakeInfoEntities = { + factory: validationResult.factoryInfo, + account: validationResult.senderInfo, + paymaster: validationResult.paymasterInfo, + }; + + const entitySlots: { [addr: string]: Set } = parseEntitySlots( + stakeInfoEntities, + traceCall.keccak + ); + + for (const [entityTitle, entStakes] of Object.entries(stakeInfoEntities)) { + const entityAddr = (entStakes?.addr || "").toLowerCase(); + const currentNumLevel = traceCall.callsFromEntryPoint.find( + (info) => + info.topLevelMethodSig === callsFromEntryPointMethodSigs[entityTitle] + ); + if (currentNumLevel == null) { + if (entityTitle === "account") { + throw new RpcError( + "missing trace into validateUserOp", + RpcErrorCodes.EXECUTION_REVERTED + ); + } + continue; + } + const opcodes = currentNumLevel.opcodes; + const access = currentNumLevel.access; + + if (currentNumLevel.oog) { + throw new RpcError( + `${entityTitle} internally reverts on oog`, + RpcErrorCodes.INVALID_OPCODE + ); + } + + const whitelist: IWhitelistedEntity | undefined = + WhitelistedEntities[entityTitle as keyof IWhitelistedEntities]; + if ( + entityAddr && + whitelist != null && + whitelist[this.network] && + whitelist[this.network]!.some( + (addr) => addr === ethers.utils.getAddress(entityAddr) + ) + ) { + this.logger.debug( + `${entityTitle} is in whitelist. Skipping opcode validation...` + ); + continue; + } + + Object.keys(opcodes).forEach((opcode) => { + if (bannedOpCodes.has(opcode)) { + throw new RpcError( + `${entityTitle} uses banned opcode: ${opcode}`, + RpcErrorCodes.INVALID_OPCODE + ); + } + }); + + // Special case for CREATE2 + if (entityTitle === "factory") { + if (opcodes.CREATE2 > 1) { + throw new RpcError( + `${entityTitle} with too many CREATE2`, + RpcErrorCodes.INVALID_OPCODE + ); + } + } else { + if (opcodes.CREATE2 > 0) { + throw new RpcError( + `${entityTitle} uses banned opcode: CREATE2`, + RpcErrorCodes.INVALID_OPCODE + ); + } + } + + for (const [addr, { reads, writes }] of Object.entries(access)) { + if (addr === sender) { + continue; + } + + if (addr === entryPoint) { + continue; + } + + // eslint-disable-next-line no-inner-declarations + function nameAddr(addr: string, _currentEntity: string): string { + const [title] = + Object.entries(stakeInfoEntities).find( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + ([title, info]) => info?.addr.toLowerCase() === addr.toLowerCase() + ) ?? []; + + return title ?? addr; + } + + let requireStakeSlot: string | undefined; + for (const slot of [...Object.keys(writes), ...Object.keys(reads)]) { + if (isSlotAssociatedWith(slot, sender, entitySlots)) { + if ( + userOp.initCode.length > 2 && + !( + entityAddr === sender && + (await this.reputationService.checkStake( + stakeInfoEntities.factory + )) === null + ) + ) { + requireStakeSlot = slot; + } + } else if (isSlotAssociatedWith(slot, entityAddr, entitySlots)) { + requireStakeSlot = slot; + } else if (addr === entityAddr) { + requireStakeSlot = slot; + } else { + const readWrite = Object.keys(writes).includes(addr) + ? "write to" + : "read from"; + throw new RpcError( + // eslint-disable-next-line prettier/prettier + `${entityTitle} has forbidden ${readWrite} ${nameAddr(addr, entityTitle)} slot ${slot}`, + RpcErrorCodes.INVALID_OPCODE, + { + [entityTitle]: entStakes?.addr, + } + ); + } + } + + if (requireStakeSlot != null) { + const stake = await this.reputationService.checkStake(entStakes); + if (stake != null) { + throw new RpcError( + `unstaked ${entityTitle} accessed ${nameAddr( + addr, + entityTitle + )} slot ${requireStakeSlot}`, + RpcErrorCodes.INVALID_OPCODE, + { + [entityTitle]: entStakes?.addr, + } + ); + } + } + } + + if (entityTitle === "paymaster") { + const validatePaymasterUserOp = callStack.find( + (call) => + call.method === "validatePaymasterUserOp" && call.to === entityAddr + ); + const context = validatePaymasterUserOp?.return?.context; + if (context != null && context !== "0x") { + const stake = await this.reputationService.checkStake(entStakes); + if (stake != null) { + throw new RpcError( + "unstaked paymaster must not return context", + RpcErrorCodes.INVALID_OPCODE, + { + [entityTitle]: entStakes?.addr, + } + ); + } + } + } + + 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 + ); + } + } + } + + return validationResult; + } +} diff --git a/packages/executor/src/services/UserOpValidation/validators/unsafe.ts b/packages/executor/src/services/UserOpValidation/validators/unsafe.ts new file mode 100644 index 00000000..64df3447 --- /dev/null +++ b/packages/executor/src/services/UserOpValidation/validators/unsafe.ts @@ -0,0 +1,34 @@ +import { IEntryPoint__factory } from "types/lib/executor/contracts"; +import { UserOperationStruct } from "types/lib/executor/contracts/EntryPoint"; +import { providers } from "ethers"; +import { + Logger, + NetworkConfig, + UserOpValidationResult, +} from "../../../interfaces"; +import { nethermindErrorHandler, parseErrorResult } from "../utils"; + +export class UnsafeValidationService { + constructor( + private provider: providers.Provider, + private networkConfig: NetworkConfig, + private logger: Logger + ) {} + + async validateUnsafely( + userOp: UserOperationStruct, + entryPoint: string + ): Promise { + const { validationGasLimit } = this.networkConfig; + const entryPointContract = IEntryPoint__factory.connect( + entryPoint, + this.provider + ); + const errorResult = await entryPointContract.callStatic + .simulateValidation(userOp, { + gasLimit: validationGasLimit, + }) + .catch((e: any) => nethermindErrorHandler(entryPointContract, e)); + return parseErrorResult(userOp, errorResult); + } +} diff --git a/packages/executor/src/tracer/utils.ts b/packages/executor/src/tracer/utils.ts deleted file mode 100644 index 5985bbdb..00000000 --- a/packages/executor/src/tracer/utils.ts +++ /dev/null @@ -1,21 +0,0 @@ -export function compareBytecode( - artifactBytecode: string, - contractBytecode: string -): number { - if (artifactBytecode.length <= 2 || contractBytecode.length <= 2) return 0; - - if (typeof artifactBytecode === "string") - artifactBytecode = artifactBytecode - .replace(/\_\_\$/g, "000") - .replace(/\$\_\_/g, "000"); - - let matchedBytes = 0; - for (let i = 0; i < artifactBytecode.length; i++) { - if (artifactBytecode[i] === contractBytecode[i]) matchedBytes++; - } - if (isNaN(matchedBytes / artifactBytecode.length)) { - return 0; - } - - return matchedBytes / artifactBytecode.length; -} diff --git a/packages/executor/src/utils/gas-oracles/interfaces.ts b/packages/executor/src/utils/gas-oracles/interfaces.ts deleted file mode 100644 index d619ae87..00000000 --- a/packages/executor/src/utils/gas-oracles/interfaces.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { BigNumberish } from "ethers"; - -export type IGetGasFeeResult = { - maxPriorityFeePerGas: BigNumberish | undefined; - maxFeePerGas: BigNumberish | undefined; - gasPrice: BigNumberish | undefined; -}; - -export type IOracle = (apiKey: string) => Promise; diff --git a/packages/executor/src/utils/index.ts b/packages/executor/src/utils/index.ts index 287aa60f..e89aa3e8 100644 --- a/packages/executor/src/utils/index.ts +++ b/packages/executor/src/utils/index.ts @@ -4,11 +4,11 @@ import { hexlify, keccak256, } from "ethers/lib/utils"; -import { EntryPoint__factory } from "types/lib/executor/contracts/factories/EntryPoint__factory"; +import { IEntryPoint__factory } from "types/lib/executor/contracts/factories"; import { UserOperationStruct } from "types/lib/executor/contracts/EntryPoint"; const UserOpType = ( - EntryPoint__factory.abi.find( + IEntryPoint__factory.abi.find( (entry: any) => entry.name === "simulateValidation" ) as any ).inputs?.[0]; @@ -105,8 +105,6 @@ export function packUserOp( name: "userOp", type: "tuple", }; - // console.log('hard-coded userOpType', userOpType) - // console.log('from ABI userOpType', UserOpType) let encoded = defaultAbiCoder.encode( [userOpType as any], [ diff --git a/packages/executor/src/utils/mergeStorageMap.ts b/packages/executor/src/utils/mergeStorageMap.ts new file mode 100644 index 00000000..b6c1f7fb --- /dev/null +++ b/packages/executor/src/utils/mergeStorageMap.ts @@ -0,0 +1,37 @@ +import { SlotMap, StorageMap } from "../interfaces"; + +// REF: https://github.com/eth-infinitism/bundler/blob/ba29f67567410787d8ccb4828fa5abb65118010e/packages/bundler/src/modules/moduleUtils.ts#L20-L50 +/** +/ * merge all validationStorageMap objects into merged map + * - entry with "root" (string) is always preferred over entry with slot-map + * - merge slot entries + * NOTE: slot values are supposed to be the value before the transaction started. + * so same address/slot in different validations should carry the same value + * @param mergedStorageMap + * @param validationStorageMap + */ +export function mergeStorageMap( + mergedStorageMap: StorageMap, + validationStorageMap: StorageMap +): StorageMap { + Object.entries(validationStorageMap).forEach(([addr, validationEntry]) => { + if (typeof validationEntry === "string") { + // it's a root. override specific slots, if any + mergedStorageMap[addr] = validationEntry; + } else if (typeof mergedStorageMap[addr] === "string") { + // merged address already contains a root. ignore specific slot values + } else { + let slots: SlotMap; + if (mergedStorageMap[addr] == null) { + slots = mergedStorageMap[addr] = {}; + } else { + slots = mergedStorageMap[addr] as SlotMap; + } + + Object.entries(validationEntry).forEach(([slot, val]) => { + slots[slot] = val; + }); + } + }); + return mergedStorageMap; +} diff --git a/packages/executor/tracer.js b/packages/executor/tracer.js new file mode 100644 index 00000000..666cb835 --- /dev/null +++ b/packages/executor/tracer.js @@ -0,0 +1,190 @@ +function bundlerCollectorTracer() { + return { + 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: 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 (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 (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; + }; + 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 + }); + } + } + }; +} \ No newline at end of file diff --git a/packages/params/package.json b/packages/params/package.json index 307d40a9..26329a4b 100644 --- a/packages/params/package.json +++ b/packages/params/package.json @@ -1,6 +1,6 @@ { "name": "params", - "version": "0.0.24", + "version": "0.0.44", "description": "Various bundler parameters", "author": "Etherspot", "homepage": "https://github.com/etherspot/skandha#readme", @@ -23,10 +23,11 @@ "dependencies": { "@arbitrum/sdk": "3.1.4", "@eth-optimism/sdk": "3.0.0", + "@mantleio/sdk": "0.2.1", "ethers": "5.7.2", - "types": "^0.0.24", "utils": "*", - "@chainsafe/ssz": "0.10.1" + "@chainsafe/ssz": "0.10.1", + "types": "^0.0.44" }, "scripts": { "clean": "rm -rf lib && rm -f *.tsbuildinfo", diff --git a/packages/params/src/constants.ts b/packages/params/src/constants.ts index 944673e9..a6b61615 100644 --- a/packages/params/src/constants.ts +++ b/packages/params/src/constants.ts @@ -1,4 +1,6 @@ -import { constants } from "ethers"; +import { BigNumber, constants } from "ethers"; export const AddressZero = constants.AddressZero; export const BytesZero = "0x"; + +export const GasPriceMarkupOne = BigNumber.from(10000); // 100.00% diff --git a/packages/params/src/eip1559.ts b/packages/params/src/eip1559.ts index 1b9ffe1f..cd26bdbd 100644 --- a/packages/params/src/eip1559.ts +++ b/packages/params/src/eip1559.ts @@ -8,4 +8,7 @@ export const chainsWithoutEIP1559: NetworkName[] = [ "polygonzkevm", "mantle", "mantleTestnet", + "scroll", + "scrollAlpha", + "scrollSepolia", ]; diff --git a/packages/params/src/gas-estimation/arbitrum.ts b/packages/params/src/gas-estimation/arbitrum.ts index 87642480..e88b1150 100644 --- a/packages/params/src/gas-estimation/arbitrum.ts +++ b/packages/params/src/gas-estimation/arbitrum.ts @@ -2,7 +2,7 @@ import { NodeInterface__factory } from "@arbitrum/sdk/dist/lib/abi/factories/Nod import { NODE_INTERFACE_ADDRESS } from "@arbitrum/sdk/dist/lib/dataEntities/constants"; import { UserOperationStruct } from "types/lib/executor/contracts/EntryPoint"; import { BigNumber, BigNumberish, ethers } from "ethers"; -import { EntryPoint__factory } from "types/lib/executor/contracts"; +import { IEntryPoint__factory } from "types/lib/executor/contracts"; import { IPVGEstimator, IPVGEstimatorWrapper } from "../types/IPVGEstimator"; export const estimateArbitrumPVG: IPVGEstimatorWrapper = ( @@ -18,7 +18,7 @@ export const estimateArbitrumPVG: IPVGEstimatorWrapper = ( userOp: UserOperationStruct, initial: BigNumberish ): Promise => { - const entryPoint = EntryPoint__factory.connect(entryPointAddr, provider); + const entryPoint = IEntryPoint__factory.connect(entryPointAddr, provider); const handleOpsData = entryPoint.interface.encodeFunctionData("handleOps", [ [userOp], dummyWallet.address, diff --git a/packages/params/src/gas-estimation/mantle.ts b/packages/params/src/gas-estimation/mantle.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/params/src/gas-estimation/optimism.ts b/packages/params/src/gas-estimation/optimism.ts index 744428db..fb165da6 100644 --- a/packages/params/src/gas-estimation/optimism.ts +++ b/packages/params/src/gas-estimation/optimism.ts @@ -1,6 +1,6 @@ import { UserOperationStruct } from "types/lib/executor/contracts/EntryPoint"; import { BigNumber, BigNumberish, ethers } from "ethers"; -import { EntryPoint__factory } from "types/lib/executor/contracts"; +import { IEntryPoint__factory } from "types/lib/executor/contracts"; import { estimateL1Gas } from "@eth-optimism/sdk"; import { IPVGEstimator, IPVGEstimatorWrapper } from "../types/IPVGEstimator"; @@ -13,7 +13,7 @@ export const estimateOptimismPVG: IPVGEstimatorWrapper = ( userOp: UserOperationStruct, initial: BigNumberish ): Promise => { - const entryPoint = EntryPoint__factory.connect(entryPointAddr, provider); + const entryPoint = IEntryPoint__factory.connect(entryPointAddr, provider); const handleOpsData = entryPoint.interface.encodeFunctionData("handleOps", [ [userOp], dummyWallet.address, diff --git a/packages/executor/src/utils/getGasFee.ts b/packages/params/src/gas-price-oracles/getGasFee.ts similarity index 82% rename from packages/executor/src/utils/getGasFee.ts rename to packages/params/src/gas-price-oracles/getGasFee.ts index efdda72a..aae0bdc2 100644 --- a/packages/executor/src/utils/getGasFee.ts +++ b/packages/params/src/gas-price-oracles/getGasFee.ts @@ -1,15 +1,16 @@ import { providers } from "ethers"; import { NetworkName } from "types/lib"; -import { IGetGasFeeResult, oracles } from "./gas-oracles"; +import { IGetGasFeeResult, IOracleOptions, oracles } from "./oracles"; export const getGasFee = async ( network: NetworkName, provider: providers.JsonRpcProvider, - apiKey = "" + apiKey = "", + options?: IOracleOptions ): Promise => { if (oracles[network]) { try { - return await oracles[network]!(apiKey); + return await oracles[network]!(apiKey, provider, options); } catch (err) { // eslint-disable-next-line no-console console.error(`Couldn't fetch fee data for ${network}: ${err}`); diff --git a/packages/params/src/gas-price-oracles/index.ts b/packages/params/src/gas-price-oracles/index.ts new file mode 100644 index 00000000..1d0095d9 --- /dev/null +++ b/packages/params/src/gas-price-oracles/index.ts @@ -0,0 +1 @@ +export * from "./getGasFee"; diff --git a/packages/executor/src/utils/gas-oracles/arbitrum.ts b/packages/params/src/gas-price-oracles/oracles/arbitrum.ts similarity index 100% rename from packages/executor/src/utils/gas-oracles/arbitrum.ts rename to packages/params/src/gas-price-oracles/oracles/arbitrum.ts diff --git a/packages/executor/src/utils/gas-oracles/index.ts b/packages/params/src/gas-price-oracles/oracles/index.ts similarity index 82% rename from packages/executor/src/utils/gas-oracles/index.ts rename to packages/params/src/gas-price-oracles/oracles/index.ts index 4ba70b9c..0901c5ed 100644 --- a/packages/executor/src/utils/gas-oracles/index.ts +++ b/packages/params/src/gas-price-oracles/oracles/index.ts @@ -7,6 +7,7 @@ import { getMaticGasFee } from "./matic"; import { getMumbaiGasFee } from "./mumbai"; import { getOptimismGasFee } from "./optimism"; import { IOracle } from "./interfaces"; +import { getMantleGasFee } from "./mantle"; export const oracles: { [key in NetworkName]?: IOracle; @@ -15,4 +16,6 @@ export const oracles: { mumbai: getMumbaiGasFee, optimism: getOptimismGasFee, arbitrum: getArbitrumGasFee, + mantle: getMantleGasFee, + mantleTestnet: getMantleGasFee, }; diff --git a/packages/params/src/gas-price-oracles/oracles/interfaces.ts b/packages/params/src/gas-price-oracles/oracles/interfaces.ts new file mode 100644 index 00000000..fbdccbac --- /dev/null +++ b/packages/params/src/gas-price-oracles/oracles/interfaces.ts @@ -0,0 +1,19 @@ +import { BigNumberish, ethers } from "ethers"; +import { UserOperationStruct } from "types/lib/executor/contracts/EntryPoint"; + +export type IGetGasFeeResult = { + maxPriorityFeePerGas: BigNumberish | undefined; + maxFeePerGas: BigNumberish | undefined; + gasPrice: BigNumberish | undefined; +}; + +export type IOracle = ( + apiKey: string, + provider?: ethers.providers.JsonRpcProvider, + options?: IOracleOptions +) => Promise; + +export type IOracleOptions = { + entryPoint: string; + userOp: UserOperationStruct; +}; diff --git a/packages/params/src/gas-price-oracles/oracles/mantle.ts b/packages/params/src/gas-price-oracles/oracles/mantle.ts new file mode 100644 index 00000000..cfbeb045 --- /dev/null +++ b/packages/params/src/gas-price-oracles/oracles/mantle.ts @@ -0,0 +1,55 @@ +import { BigNumber, ethers } from "ethers"; +import { MantleGasOracleABI } from "types/lib/executor/abis"; +import mantleSDK from "@mantleio/sdk"; +import { IGetGasFeeResult, IOracle } from "./interfaces"; + +const oracleAddress = "0x420000000000000000000000000000000000000F"; +const minGasPrice = 50000000; + +export const getMantleGasFee: IOracle = async ( + apiKey, + provider, + options +): Promise => { + if (!provider) throw new Error("No provider"); + + const oracle = new ethers.Contract( + oracleAddress, + MantleGasOracleABI, + provider + ); + + let gasPrice = await oracle.callStatic.gasPrice(); + if (gasPrice && BigNumber.from(gasPrice).lt(minGasPrice)) { + gasPrice = BigNumber.from(minGasPrice); + } + + if (options) { + const tx = { + from: options.entryPoint, + to: options.userOp.sender, + data: options.userOp.callData, + }; + try { + const mantleProvider = mantleSDK.asL2Provider(provider); + const L1Price = await mantleProvider.getL1GasPrice(); + const L1Cost = L1Price.mul(2562); // constant l1 gas used, set by Mantle + const L2Cost = await mantleProvider.estimateL2GasCost(tx); + const { callGasLimit, preVerificationGas, verificationGasLimit } = + options.userOp; + const totalGasLimit = BigNumber.from(callGasLimit) + .add(preVerificationGas) + .add(verificationGasLimit); + gasPrice = L1Cost.add(L2Cost).div(totalGasLimit); + } catch (err) { + // eslint-disable-next-line no-console + console.log("Error during estimating total gas cost on Mantle", err); + } + } + + return { + maxPriorityFeePerGas: gasPrice, + gasPrice: gasPrice, + maxFeePerGas: gasPrice, + }; +}; diff --git a/packages/executor/src/utils/gas-oracles/matic.ts b/packages/params/src/gas-price-oracles/oracles/matic.ts similarity index 100% rename from packages/executor/src/utils/gas-oracles/matic.ts rename to packages/params/src/gas-price-oracles/oracles/matic.ts diff --git a/packages/executor/src/utils/gas-oracles/mumbai.ts b/packages/params/src/gas-price-oracles/oracles/mumbai.ts similarity index 100% rename from packages/executor/src/utils/gas-oracles/mumbai.ts rename to packages/params/src/gas-price-oracles/oracles/mumbai.ts diff --git a/packages/executor/src/utils/gas-oracles/optimism.ts b/packages/params/src/gas-price-oracles/oracles/optimism.ts similarity index 100% rename from packages/executor/src/utils/gas-oracles/optimism.ts rename to packages/params/src/gas-price-oracles/oracles/optimism.ts diff --git a/packages/executor/src/utils/gas-oracles/utils.ts b/packages/params/src/gas-price-oracles/oracles/utils.ts similarity index 100% rename from packages/executor/src/utils/gas-oracles/utils.ts rename to packages/params/src/gas-price-oracles/oracles/utils.ts diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index a6294878..23a62ffe 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -5,3 +5,4 @@ export * from "./mempools"; export * from "./eip1559"; export * from "./gas-estimation"; export * from "./constants"; +export * from "./gas-price-oracles"; diff --git a/packages/params/src/whitelisted-entities/factories.ts b/packages/params/src/whitelisted-entities/factories.ts index 72ea2c13..4f238c82 100644 --- a/packages/params/src/whitelisted-entities/factories.ts +++ b/packages/params/src/whitelisted-entities/factories.ts @@ -1,3 +1,36 @@ +import { getAddress } from "ethers/lib/utils"; import { IWhitelistedEntity } from "../types/IWhitelistedEntities"; -export const WhitelistedFactories: IWhitelistedEntity = {}; +export const WhitelistedFactories: IWhitelistedEntity = { + // Etherspot Paymasters + // ref: https://github.com/etherspot/etherspot-prime-contracts/blob/master/DEPLOYMENTS.md + mainnet: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + arbitrum: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + optimism: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + matic: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + fuse: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + xdai: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + mantle: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + avalanche: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + bsc: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + base: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + linea: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + goerli: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + sepolia: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + arbitrumNitro: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + optimismGoerli: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + mumbai: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + fuseSparknet: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + baseGoerli: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + chiado: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + fuji: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + bscTest: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + lineaTestnet: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + scrollSepolia: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + mantleTestnet: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + flare: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + flareCoston: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + flareCoston2: [getAddress("0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E")], + bifrost: [getAddress("0x527bAb8bDC50A809d7c35D0129173BBed55C5EAE")], + bifrostTestnet: [getAddress("0x527bAb8bDC50A809d7c35D0129173BBed55C5EAE")], +}; diff --git a/packages/types/package.json b/packages/types/package.json index de8b9c53..d19c365f 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "types", - "version": "0.0.24", + "version": "0.0.44", "description": "The types of Etherspot bundler client", "author": "Etherspot", "homepage": "https://https://github.com/etherspot/skandha#readme", diff --git a/packages/types/src/api/interfaces.ts b/packages/types/src/api/interfaces.ts index 4dbfefa3..aff9fc3c 100644 --- a/packages/types/src/api/interfaces.ts +++ b/packages/types/src/api/interfaces.ts @@ -1,14 +1,16 @@ import { BigNumberish, providers } from "ethers"; import { UserOperationStruct } from "../executor/contracts/EntryPoint"; -export type EstimatedUserOperationGas = { - preVerificationGas: BigNumberish; - verificationGas: BigNumberish; - deadline?: BigNumberish; - callGasLimit: BigNumberish; - validAfter?: BigNumberish; - validUntil?: BigNumberish; -}; +export type EstimatedUserOperationGas = + | { + preVerificationGas: BigNumberish; + verificationGas: BigNumberish; + verificationGasLimit: BigNumberish; + callGasLimit: BigNumberish; + validAfter?: BigNumberish; + validUntil?: BigNumberish; + } + | GetGasPriceResponse; export type UserOperationByHashResponse = { userOperation: UserOperationStruct; @@ -23,6 +25,12 @@ export type GetGasPriceResponse = { maxPriorityFeePerGas: BigNumberish; }; +export type GetFeeHistoryResponse = { + actualGasPrice: BigNumberish[]; + maxFeePerGas: BigNumberish[]; + maxPriorityFeePerGas: BigNumberish[]; +}; + export type UserOperationReceipt = { userOpHash: string; sender: string; @@ -36,6 +44,31 @@ export type UserOperationReceipt = { receipt: providers.TransactionReceipt; }; +export type GetConfigResponse = { + flags: { + redirectRpc: boolean; + testingMode: boolean; + unsafeMode: boolean; + }; + entryPoints: string[]; + beneficiary: string; + relayer: string; + minInclusionDenominator: number; + throttlingSlack: number; + banSlack: number; + minSignerBalance: string; + multicall: string; + estimationStaticBuffer: number; + validationGasLimit: number; + receiptLookupRange: number; + etherscanApiKey: boolean; // true if set + conditionalTransactions: boolean; + rpcEndpointSubmit: boolean; // true if not empty string + gasPriceMarkup: number; + enforceGasPrice: boolean; + enforceGasPriceThreshold: number; +}; + export type SupportedEntryPoints = string[]; export type EthChainIdResponse = { chainId: number }; diff --git a/packages/types/src/executor/abis/MantleGasOracle.ts b/packages/types/src/executor/abis/MantleGasOracle.ts new file mode 100644 index 00000000..f3083d3a --- /dev/null +++ b/packages/types/src/executor/abis/MantleGasOracle.ts @@ -0,0 +1,237 @@ +export const MantleGasOracleABI = [ + { + type: "constructor", + inputs: [{ type: "address", name: "_owner", internalType: "address" }], + }, + { + type: "function", + stateMutability: "view", + outputs: [{ type: "uint256", name: "", internalType: "uint256" }], + name: "IsBurning", + inputs: [], + }, + { + type: "function", + stateMutability: "view", + outputs: [{ type: "uint256", name: "", internalType: "uint256" }], + name: "charge", + inputs: [], + }, + { + type: "function", + stateMutability: "view", + outputs: [{ type: "uint256", name: "", internalType: "uint256" }], + name: "daGasPrice", + inputs: [], + }, + { + type: "function", + stateMutability: "view", + outputs: [{ type: "uint256", name: "", internalType: "uint256" }], + name: "daSwitch", + inputs: [], + }, + { + type: "function", + stateMutability: "view", + outputs: [{ type: "uint256", name: "", internalType: "uint256" }], + name: "decimals", + inputs: [], + }, + { + type: "function", + stateMutability: "view", + outputs: [{ type: "uint256", name: "", internalType: "uint256" }], + name: "gasPrice", + inputs: [], + }, + { + type: "function", + stateMutability: "view", + outputs: [{ type: "uint256", name: "", internalType: "uint256" }], + name: "getL1Fee", + inputs: [{ type: "bytes", name: "_data", internalType: "bytes" }], + }, + { + type: "function", + stateMutability: "view", + outputs: [{ type: "uint256", name: "", internalType: "uint256" }], + name: "getL1GasUsed", + inputs: [{ type: "bytes", name: "_data", internalType: "bytes" }], + }, + { + type: "function", + stateMutability: "view", + outputs: [{ type: "uint256", name: "", internalType: "uint256" }], + name: "l1BaseFee", + inputs: [], + }, + { + type: "function", + stateMutability: "view", + outputs: [{ type: "uint256", name: "", internalType: "uint256" }], + name: "overhead", + inputs: [], + }, + { + type: "function", + stateMutability: "view", + outputs: [{ type: "address", name: "", internalType: "address" }], + name: "owner", + inputs: [], + }, + { + type: "function", + stateMutability: "nonpayable", + outputs: [], + name: "renounceOwnership", + inputs: [], + }, + { + type: "function", + stateMutability: "view", + outputs: [{ type: "uint256", name: "", internalType: "uint256" }], + name: "scalar", + inputs: [], + }, + { + type: "function", + stateMutability: "view", + outputs: [{ type: "address", name: "", internalType: "address" }], + name: "sccAddress", + inputs: [], + }, + { + type: "function", + stateMutability: "nonpayable", + outputs: [], + name: "setCharge", + inputs: [{ type: "uint256", name: "_charge", internalType: "uint256" }], + }, + { + type: "function", + stateMutability: "nonpayable", + outputs: [], + name: "setDAGasPrice", + inputs: [{ type: "uint256", name: "_daGasPrice", internalType: "uint256" }], + }, + { + type: "function", + stateMutability: "nonpayable", + outputs: [], + name: "setDaSwitch", + inputs: [{ type: "uint256", name: "_daSwitch", internalType: "uint256" }], + }, + { + type: "function", + stateMutability: "nonpayable", + outputs: [], + name: "setDecimals", + inputs: [{ type: "uint256", name: "_decimals", internalType: "uint256" }], + }, + { + type: "function", + stateMutability: "nonpayable", + outputs: [], + name: "setGasPrice", + inputs: [{ type: "uint256", name: "_gasPrice", internalType: "uint256" }], + }, + { + type: "function", + stateMutability: "nonpayable", + outputs: [], + name: "setIsBurning", + inputs: [{ type: "uint256", name: "_isBurning", internalType: "uint256" }], + }, + { + type: "function", + stateMutability: "nonpayable", + outputs: [], + name: "setL1BaseFee", + inputs: [{ type: "uint256", name: "_baseFee", internalType: "uint256" }], + }, + { + type: "function", + stateMutability: "nonpayable", + outputs: [], + name: "setOverhead", + inputs: [{ type: "uint256", name: "_overhead", internalType: "uint256" }], + }, + { + type: "function", + stateMutability: "nonpayable", + outputs: [], + name: "setScalar", + inputs: [{ type: "uint256", name: "_scalar", internalType: "uint256" }], + }, + { + type: "function", + stateMutability: "nonpayable", + outputs: [], + name: "transferOwnership", + inputs: [{ type: "address", name: "newOwner", internalType: "address" }], + }, + { + type: "event", + name: "ChargeUpdated", + inputs: [{ type: "uint256", name: "", indexed: false }], + anonymous: false, + }, + { + type: "event", + name: "DAGasPriceUpdated", + inputs: [{ type: "uint256", name: "", indexed: false }], + anonymous: false, + }, + { + type: "event", + name: "DASwitchUpdated", + inputs: [{ type: "uint256", name: "", indexed: false }], + anonymous: false, + }, + { + type: "event", + name: "DecimalsUpdated", + inputs: [{ type: "uint256", name: "", indexed: false }], + anonymous: false, + }, + { + type: "event", + name: "GasPriceUpdated", + inputs: [{ type: "uint256", name: "", indexed: false }], + anonymous: false, + }, + { + type: "event", + name: "IsBurningUpdated", + inputs: [{ type: "uint256", name: "", indexed: false }], + anonymous: false, + }, + { + type: "event", + name: "L1BaseFeeUpdated", + inputs: [{ type: "uint256", name: "", indexed: false }], + anonymous: false, + }, + { + type: "event", + name: "OverheadUpdated", + inputs: [{ type: "uint256", name: "", indexed: false }], + anonymous: false, + }, + { + type: "event", + name: "OwnershipTransferred", + inputs: [ + { type: "address", name: "previousOwner", indexed: true }, + { type: "address", name: "newOwner", indexed: true }, + ], + anonymous: false, + }, + { + type: "event", + name: "ScalarUpdated", + inputs: [{ type: "uint256", name: "", indexed: false }], + anonymous: false, + }, +]; diff --git a/packages/types/src/executor/abis/index.ts b/packages/types/src/executor/abis/index.ts new file mode 100644 index 00000000..270e24d6 --- /dev/null +++ b/packages/types/src/executor/abis/index.ts @@ -0,0 +1 @@ +export * from "./MantleGasOracle"; diff --git a/packages/types/src/executor/contracts/EntryPoint.ts b/packages/types/src/executor/contracts/EntryPoint.ts index f92bc232..61588898 100644 --- a/packages/types/src/executor/contracts/EntryPoint.ts +++ b/packages/types/src/executor/contracts/EntryPoint.ts @@ -109,69 +109,11 @@ export declare namespace IEntryPoint { }; } -export declare namespace EntryPoint { - export type MemoryUserOpStruct = { - sender: string; - nonce: BigNumberish; - callGasLimit: BigNumberish; - verificationGasLimit: BigNumberish; - preVerificationGas: BigNumberish; - paymaster: string; - maxFeePerGas: BigNumberish; - maxPriorityFeePerGas: BigNumberish; - }; - - export type MemoryUserOpStructOutput = [ - string, - BigNumber, - BigNumber, - BigNumber, - BigNumber, - string, - BigNumber, - BigNumber - ] & { - sender: string; - nonce: BigNumber; - callGasLimit: BigNumber; - verificationGasLimit: BigNumber; - preVerificationGas: BigNumber; - paymaster: string; - maxFeePerGas: BigNumber; - maxPriorityFeePerGas: BigNumber; - }; - - export type UserOpInfoStruct = { - mUserOp: EntryPoint.MemoryUserOpStruct; - userOpHash: BytesLike; - prefund: BigNumberish; - contextOffset: BigNumberish; - preOpGas: BigNumberish; - }; - - export type UserOpInfoStructOutput = [ - EntryPoint.MemoryUserOpStructOutput, - string, - BigNumber, - BigNumber, - BigNumber - ] & { - mUserOp: EntryPoint.MemoryUserOpStructOutput; - userOpHash: string; - prefund: BigNumber; - contextOffset: BigNumber; - preOpGas: BigNumber; - }; -} - -export interface EntryPointInterface extends utils.Interface { +export interface IEntryPointInterface extends utils.Interface { functions: { - "SIG_VALIDATION_FAILED()": FunctionFragment; - "_validateSenderAndPaymaster(bytes,address,bytes)": FunctionFragment; "addStake(uint32)": FunctionFragment; "balanceOf(address)": FunctionFragment; "depositTo(address)": FunctionFragment; - "deposits(address)": FunctionFragment; "getDepositInfo(address)": FunctionFragment; "getNonce(address,uint192)": FunctionFragment; "getSenderAddress(bytes)": FunctionFragment; @@ -179,8 +121,6 @@ export interface EntryPointInterface extends utils.Interface { "handleAggregatedOps(((address,uint256,bytes,bytes,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],address,bytes)[],address)": FunctionFragment; "handleOps((address,uint256,bytes,bytes,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],address)": FunctionFragment; "incrementNonce(uint192)": FunctionFragment; - "innerHandleOp(bytes,((address,uint256,uint256,uint256,uint256,address,uint256,uint256),bytes32,uint256,uint256,uint256),bytes)": FunctionFragment; - "nonceSequenceNumber(address,uint192)": FunctionFragment; "simulateHandleOp((address,uint256,bytes,bytes,uint256,uint256,uint256,uint256,uint256,bytes,bytes),address,bytes)": FunctionFragment; "simulateValidation((address,uint256,bytes,bytes,uint256,uint256,uint256,uint256,uint256,bytes,bytes))": FunctionFragment; "unlockStake()": FunctionFragment; @@ -190,12 +130,9 @@ export interface EntryPointInterface extends utils.Interface { getFunction( nameOrSignatureOrTopic: - | "SIG_VALIDATION_FAILED" - | "_validateSenderAndPaymaster" | "addStake" | "balanceOf" | "depositTo" - | "deposits" | "getDepositInfo" | "getNonce" | "getSenderAddress" @@ -203,8 +140,6 @@ export interface EntryPointInterface extends utils.Interface { | "handleAggregatedOps" | "handleOps" | "incrementNonce" - | "innerHandleOp" - | "nonceSequenceNumber" | "simulateHandleOp" | "simulateValidation" | "unlockStake" @@ -212,18 +147,6 @@ export interface EntryPointInterface extends utils.Interface { | "withdrawTo" ): FunctionFragment; - encodeFunctionData( - functionFragment: "SIG_VALIDATION_FAILED", - values?: undefined - ): string; - encodeFunctionData( - functionFragment: "_validateSenderAndPaymaster", - values: [ - BytesLike, - string, - BytesLike - ] - ): string; encodeFunctionData( functionFragment: "addStake", values: [BigNumberish] @@ -236,10 +159,6 @@ export interface EntryPointInterface extends utils.Interface { functionFragment: "depositTo", values: [string] ): string; - encodeFunctionData( - functionFragment: "deposits", - values: [string] - ): string; encodeFunctionData( functionFragment: "getDepositInfo", values: [string] @@ -268,18 +187,6 @@ export interface EntryPointInterface extends utils.Interface { functionFragment: "incrementNonce", values: [BigNumberish] ): string; - encodeFunctionData( - functionFragment: "innerHandleOp", - values: [ - BytesLike, - EntryPoint.UserOpInfoStruct, - BytesLike - ] - ): string; - encodeFunctionData( - functionFragment: "nonceSequenceNumber", - values: [string, BigNumberish] - ): string; encodeFunctionData( functionFragment: "simulateHandleOp", values: [ @@ -305,18 +212,9 @@ export interface EntryPointInterface extends utils.Interface { values: [string, BigNumberish] ): string; - decodeFunctionResult( - functionFragment: "SIG_VALIDATION_FAILED", - data: BytesLike - ): Result; - decodeFunctionResult( - functionFragment: "_validateSenderAndPaymaster", - data: BytesLike - ): Result; decodeFunctionResult(functionFragment: "addStake", data: BytesLike): Result; decodeFunctionResult(functionFragment: "balanceOf", data: BytesLike): Result; decodeFunctionResult(functionFragment: "depositTo", data: BytesLike): Result; - decodeFunctionResult(functionFragment: "deposits", data: BytesLike): Result; decodeFunctionResult( functionFragment: "getDepositInfo", data: BytesLike @@ -339,14 +237,6 @@ export interface EntryPointInterface extends utils.Interface { functionFragment: "incrementNonce", data: BytesLike ): Result; - decodeFunctionResult( - functionFragment: "innerHandleOp", - data: BytesLike - ): Result; - decodeFunctionResult( - functionFragment: "nonceSequenceNumber", - data: BytesLike - ): Result; decodeFunctionResult( functionFragment: "simulateHandleOp", data: BytesLike @@ -508,12 +398,12 @@ export type WithdrawnEvent = TypedEvent< export type WithdrawnEventFilter = TypedEventFilter; -export interface EntryPoint extends BaseContract { +export interface IEntryPoint extends BaseContract { connect(signerOrProvider: Signer | Provider | string): this; attach(addressOrName: string): this; deployed(): Promise; - interface: EntryPointInterface; + interface: IEntryPointInterface; queryFilter( event: TypedEventFilter, @@ -535,17 +425,8 @@ export interface EntryPoint extends BaseContract { removeListener: OnEvent; functions: { - SIG_VALIDATION_FAILED(overrides?: CallOverrides): Promise<[BigNumber]>; - - _validateSenderAndPaymaster( - initCode: BytesLike, - sender: string, - paymasterAndData: BytesLike, - overrides?: CallOverrides - ): Promise<[void]>; - addStake( - unstakeDelaySec: BigNumberish, + _unstakeDelaySec: BigNumberish, overrides?: PayableOverrides & { from?: string } ): Promise; @@ -559,19 +440,6 @@ export interface EntryPoint extends BaseContract { overrides?: PayableOverrides & { from?: string } ): Promise; - deposits( - arg0: string, - overrides?: CallOverrides - ): Promise< - [BigNumber, boolean, BigNumber, number, number] & { - deposit: BigNumber; - staked: boolean; - stake: BigNumber; - unstakeDelaySec: number; - withdrawTime: number; - } - >; - getDepositInfo( account: string, overrides?: CallOverrides @@ -614,19 +482,6 @@ export interface EntryPoint extends BaseContract { overrides?: Overrides & { from?: string } ): Promise; - innerHandleOp( - callData: BytesLike, - opInfo: EntryPoint.UserOpInfoStruct, - context: BytesLike, - overrides?: Overrides & { from?: string } - ): Promise; - - nonceSequenceNumber( - arg0: string, - arg1: BigNumberish, - overrides?: CallOverrides - ): Promise<[BigNumber]>; - simulateHandleOp( op: UserOperationStruct, target: string, @@ -655,17 +510,8 @@ export interface EntryPoint extends BaseContract { ): Promise; }; - SIG_VALIDATION_FAILED(overrides?: CallOverrides): Promise; - - _validateSenderAndPaymaster( - initCode: BytesLike, - sender: string, - paymasterAndData: BytesLike, - overrides?: CallOverrides - ): Promise; - addStake( - unstakeDelaySec: BigNumberish, + _unstakeDelaySec: BigNumberish, overrides?: PayableOverrides & { from?: string } ): Promise; @@ -679,19 +525,6 @@ export interface EntryPoint extends BaseContract { overrides?: PayableOverrides & { from?: string } ): Promise; - deposits( - arg0: string, - overrides?: CallOverrides - ): Promise< - [BigNumber, boolean, BigNumber, number, number] & { - deposit: BigNumber; - staked: boolean; - stake: BigNumber; - unstakeDelaySec: number; - withdrawTime: number; - } - >; - getDepositInfo( account: string, overrides?: CallOverrides @@ -730,19 +563,6 @@ export interface EntryPoint extends BaseContract { overrides?: Overrides & { from?: string } ): Promise; - innerHandleOp( - callData: BytesLike, - opInfo: EntryPoint.UserOpInfoStruct, - context: BytesLike, - overrides?: Overrides & { from?: string } - ): Promise; - - nonceSequenceNumber( - arg0: string, - arg1: BigNumberish, - overrides?: CallOverrides - ): Promise; - simulateHandleOp( op: UserOperationStruct, target: string, @@ -771,17 +591,8 @@ export interface EntryPoint extends BaseContract { ): Promise; callStatic: { - SIG_VALIDATION_FAILED(overrides?: CallOverrides): Promise; - - _validateSenderAndPaymaster( - initCode: BytesLike, - sender: string, - paymasterAndData: BytesLike, - overrides?: CallOverrides - ): Promise; - addStake( - unstakeDelaySec: BigNumberish, + _unstakeDelaySec: BigNumberish, overrides?: CallOverrides ): Promise; @@ -795,19 +606,6 @@ export interface EntryPoint extends BaseContract { overrides?: CallOverrides ): Promise; - deposits( - arg0: string, - overrides?: CallOverrides - ): Promise< - [BigNumber, boolean, BigNumber, number, number] & { - deposit: BigNumber; - staked: boolean; - stake: BigNumber; - unstakeDelaySec: number; - withdrawTime: number; - } - >; - getDepositInfo( account: string, overrides?: CallOverrides @@ -846,19 +644,6 @@ export interface EntryPoint extends BaseContract { overrides?: CallOverrides ): Promise; - innerHandleOp( - callData: BytesLike, - opInfo: EntryPoint.UserOpInfoStruct, - context: BytesLike, - overrides?: CallOverrides - ): Promise; - - nonceSequenceNumber( - arg0: string, - arg1: BigNumberish, - overrides?: CallOverrides - ): Promise; - simulateHandleOp( op: UserOperationStruct, target: string, @@ -994,17 +779,8 @@ export interface EntryPoint extends BaseContract { }; estimateGas: { - SIG_VALIDATION_FAILED(overrides?: CallOverrides): Promise; - - _validateSenderAndPaymaster( - initCode: BytesLike, - sender: string, - paymasterAndData: BytesLike, - overrides?: CallOverrides - ): Promise; - addStake( - unstakeDelaySec: BigNumberish, + _unstakeDelaySec: BigNumberish, overrides?: PayableOverrides & { from?: string } ): Promise; @@ -1018,11 +794,6 @@ export interface EntryPoint extends BaseContract { overrides?: PayableOverrides & { from?: string } ): Promise; - deposits( - arg0: string, - overrides?: CallOverrides - ): Promise; - getDepositInfo( account: string, overrides?: CallOverrides @@ -1061,19 +832,6 @@ export interface EntryPoint extends BaseContract { overrides?: Overrides & { from?: string } ): Promise; - innerHandleOp( - callData: BytesLike, - opInfo: EntryPoint.UserOpInfoStruct, - context: BytesLike, - overrides?: Overrides & { from?: string } - ): Promise; - - nonceSequenceNumber( - arg0: string, - arg1: BigNumberish, - overrides?: CallOverrides - ): Promise; - simulateHandleOp( op: UserOperationStruct, target: string, @@ -1103,19 +861,8 @@ export interface EntryPoint extends BaseContract { }; populateTransaction: { - SIG_VALIDATION_FAILED( - overrides?: CallOverrides - ): Promise; - - _validateSenderAndPaymaster( - initCode: BytesLike, - sender: string, - paymasterAndData: BytesLike, - overrides?: CallOverrides - ): Promise; - addStake( - unstakeDelaySec: BigNumberish, + _unstakeDelaySec: BigNumberish, overrides?: PayableOverrides & { from?: string } ): Promise; @@ -1129,11 +876,6 @@ export interface EntryPoint extends BaseContract { overrides?: PayableOverrides & { from?: string } ): Promise; - deposits( - arg0: string, - overrides?: CallOverrides - ): Promise; - getDepositInfo( account: string, overrides?: CallOverrides @@ -1172,19 +914,6 @@ export interface EntryPoint extends BaseContract { overrides?: Overrides & { from?: string } ): Promise; - innerHandleOp( - callData: BytesLike, - opInfo: EntryPoint.UserOpInfoStruct, - context: BytesLike, - overrides?: Overrides & { from?: string } - ): Promise; - - nonceSequenceNumber( - arg0: string, - arg1: BigNumberish, - overrides?: CallOverrides - ): Promise; - simulateHandleOp( op: UserOperationStruct, target: string, diff --git a/packages/types/src/executor/contracts/IAccount.ts b/packages/types/src/executor/contracts/IAccount.ts index 385ca1d9..67b79066 100644 --- a/packages/types/src/executor/contracts/IAccount.ts +++ b/packages/types/src/executor/contracts/IAccount.ts @@ -1,7 +1,7 @@ /* Autogenerated file. Do not edit manually. */ /* tslint:disable */ /* eslint-disable */ -import { +import type { BaseContract, BigNumber, BigNumberish, @@ -13,9 +13,14 @@ import { Signer, utils, } from "ethers"; -import { FunctionFragment, Result } from "@ethersproject/abi"; -import { Listener, Provider } from "@ethersproject/providers"; -import { TypedEventFilter, TypedEvent, TypedListener, OnEvent } from "./common"; +import type { FunctionFragment, Result } from "@ethersproject/abi"; +import type { Listener, Provider } from "@ethersproject/providers"; +import type { + TypedEventFilter, + TypedEvent, + TypedListener, + OnEvent +} from "./common"; export type UserOperationStruct = { sender: string; @@ -59,12 +64,18 @@ export type UserOperationStructOutput = [ export interface IAccountInterface extends utils.Interface { functions: { - "validateUserOp((address,uint256,bytes,bytes,uint256,uint256,uint256,uint256,uint256,bytes,bytes),bytes32,address,uint256)": FunctionFragment; + "validateUserOp((address,uint256,bytes,bytes,uint256,uint256,uint256,uint256,uint256,bytes,bytes),bytes32,uint256)": FunctionFragment; }; + getFunction(nameOrSignatureOrTopic: "validateUserOp"): FunctionFragment; + encodeFunctionData( functionFragment: "validateUserOp", - values: [UserOperationStruct, BytesLike, string, BigNumberish] + values: [ + UserOperationStruct, + BytesLike, + BigNumberish + ] ): string; decodeFunctionResult( @@ -105,25 +116,22 @@ export interface IAccount extends BaseContract { validateUserOp( userOp: UserOperationStruct, userOpHash: BytesLike, - aggregator: string, missingAccountFunds: BigNumberish, - overrides?: Overrides & { from?: string | Promise } + overrides?: Overrides & { from?: string } ): Promise; }; validateUserOp( userOp: UserOperationStruct, userOpHash: BytesLike, - aggregator: string, missingAccountFunds: BigNumberish, - overrides?: Overrides & { from?: string | Promise } + overrides?: Overrides & { from?: string } ): Promise; callStatic: { validateUserOp( userOp: UserOperationStruct, userOpHash: BytesLike, - aggregator: string, missingAccountFunds: BigNumberish, overrides?: CallOverrides ): Promise; @@ -135,9 +143,8 @@ export interface IAccount extends BaseContract { validateUserOp( userOp: UserOperationStruct, userOpHash: BytesLike, - aggregator: string, missingAccountFunds: BigNumberish, - overrides?: Overrides & { from?: string | Promise } + overrides?: Overrides & { from?: string } ): Promise; }; @@ -145,9 +152,8 @@ export interface IAccount extends BaseContract { validateUserOp( userOp: UserOperationStruct, userOpHash: BytesLike, - aggregator: string, missingAccountFunds: BigNumberish, - overrides?: Overrides & { from?: string | Promise } + overrides?: Overrides & { from?: string } ): Promise; }; } diff --git a/packages/types/src/executor/contracts/SenderCreator.ts b/packages/types/src/executor/contracts/SenderCreator.ts new file mode 100644 index 00000000..74ebdbf8 --- /dev/null +++ b/packages/types/src/executor/contracts/SenderCreator.ts @@ -0,0 +1,104 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + BaseContract, + BigNumber, + BytesLike, + CallOverrides, + ContractTransaction, + Overrides, + PopulatedTransaction, + Signer, + utils, +} from "ethers"; +import type { FunctionFragment, Result } from "@ethersproject/abi"; +import type { Listener, Provider } from "@ethersproject/providers"; +import type { + TypedEventFilter, + TypedEvent, + TypedListener, + OnEvent +} from "./common"; + +export interface SenderCreatorInterface extends utils.Interface { + functions: { + "createSender(bytes)": FunctionFragment; + }; + + getFunction(nameOrSignatureOrTopic: "createSender"): FunctionFragment; + + encodeFunctionData( + functionFragment: "createSender", + values: [BytesLike] + ): string; + + decodeFunctionResult( + functionFragment: "createSender", + data: BytesLike + ): Result; + + events: {}; +} + +export interface SenderCreator extends BaseContract { + connect(signerOrProvider: Signer | Provider | string): this; + attach(addressOrName: string): this; + deployed(): Promise; + + interface: SenderCreatorInterface; + + queryFilter( + event: TypedEventFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>; + + listeners( + eventFilter?: TypedEventFilter + ): Array>; + listeners(eventName?: string): Array; + removeAllListeners( + eventFilter: TypedEventFilter + ): this; + removeAllListeners(eventName?: string): this; + off: OnEvent; + on: OnEvent; + once: OnEvent; + removeListener: OnEvent; + + functions: { + createSender( + initCode: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + }; + + createSender( + initCode: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + callStatic: { + createSender( + initCode: BytesLike, + overrides?: CallOverrides + ): Promise; + }; + + filters: {}; + + estimateGas: { + createSender( + initCode: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + }; + + populateTransaction: { + createSender( + initCode: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + }; +} diff --git a/packages/types/src/executor/contracts/factories/EntryPoint__factory.ts b/packages/types/src/executor/contracts/factories/EntryPoint__factory.ts index d2f4b61c..e9597bec 100644 --- a/packages/types/src/executor/contracts/factories/EntryPoint__factory.ts +++ b/packages/types/src/executor/contracts/factories/EntryPoint__factory.ts @@ -1,11 +1,12 @@ /* Autogenerated file. Do not edit manually. */ /* tslint:disable */ /* eslint-disable */ -import { Signer, utils, Contract, ContractFactory, Overrides } from "ethers"; -import type { Provider, TransactionRequest } from "@ethersproject/providers"; + +import { Contract, Signer, utils } from "ethers"; +import type { Provider } from "@ethersproject/providers"; import type { - EntryPoint, - EntryPointInterface, + IEntryPoint, + IEntryPointInterface, } from "../EntryPoint"; const _abi = [ @@ -543,47 +544,11 @@ const _abi = [ name: "Withdrawn", type: "event", }, - { - inputs: [], - name: "SIG_VALIDATION_FAILED", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes", - name: "initCode", - type: "bytes", - }, - { - internalType: "address", - name: "sender", - type: "address", - }, - { - internalType: "bytes", - name: "paymasterAndData", - type: "bytes", - }, - ], - name: "_validateSenderAndPaymaster", - outputs: [], - stateMutability: "view", - type: "function", - }, { inputs: [ { internalType: "uint32", - name: "unstakeDelaySec", + name: "_unstakeDelaySec", type: "uint32", }, ], @@ -624,45 +589,6 @@ const _abi = [ stateMutability: "payable", type: "function", }, - { - inputs: [ - { - internalType: "address", - name: "", - type: "address", - }, - ], - name: "deposits", - outputs: [ - { - internalType: "uint112", - name: "deposit", - type: "uint112", - }, - { - internalType: "bool", - name: "staked", - type: "bool", - }, - { - internalType: "uint112", - name: "stake", - type: "uint112", - }, - { - internalType: "uint32", - name: "unstakeDelaySec", - type: "uint32", - }, - { - internalType: "uint48", - name: "withdrawTime", - type: "uint48", - }, - ], - stateMutability: "view", - type: "function", - }, { inputs: [ { @@ -1002,128 +928,6 @@ const _abi = [ stateMutability: "nonpayable", type: "function", }, - { - inputs: [ - { - internalType: "bytes", - name: "callData", - type: "bytes", - }, - { - components: [ - { - components: [ - { - internalType: "address", - name: "sender", - type: "address", - }, - { - internalType: "uint256", - name: "nonce", - type: "uint256", - }, - { - internalType: "uint256", - name: "callGasLimit", - type: "uint256", - }, - { - internalType: "uint256", - name: "verificationGasLimit", - type: "uint256", - }, - { - internalType: "uint256", - name: "preVerificationGas", - type: "uint256", - }, - { - internalType: "address", - name: "paymaster", - type: "address", - }, - { - internalType: "uint256", - name: "maxFeePerGas", - type: "uint256", - }, - { - internalType: "uint256", - name: "maxPriorityFeePerGas", - type: "uint256", - }, - ], - internalType: "struct EntryPoint.MemoryUserOp", - name: "mUserOp", - type: "tuple", - }, - { - internalType: "bytes32", - name: "userOpHash", - type: "bytes32", - }, - { - internalType: "uint256", - name: "prefund", - type: "uint256", - }, - { - internalType: "uint256", - name: "contextOffset", - type: "uint256", - }, - { - internalType: "uint256", - name: "preOpGas", - type: "uint256", - }, - ], - internalType: "struct EntryPoint.UserOpInfo", - name: "opInfo", - type: "tuple", - }, - { - internalType: "bytes", - name: "context", - type: "bytes", - }, - ], - name: "innerHandleOp", - outputs: [ - { - internalType: "uint256", - name: "actualGasCost", - type: "uint256", - }, - ], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "", - type: "address", - }, - { - internalType: "uint192", - name: "", - type: "uint192", - }, - ], - name: "nonceSequenceNumber", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - stateMutability: "view", - type: "function", - }, { inputs: [ { @@ -1312,58 +1116,17 @@ const _abi = [ stateMutability: "nonpayable", type: "function", }, - { - stateMutability: "payable", - type: "receive", - }, ] as const; -const _bytecode = - "0x60a080604052346200008957600160025561022c8181016001600160401b038111838210176200007357829162005d18833903906000f080156200006757608052604051615c8990816200008f82396080518181816113df01528181613e9501526141b60152f35b6040513d6000823e3d90fd5b634e487b7160e01b600052604160045260246000fd5b600080fdfe60806040526004361015610023575b361561001957600080fd5b610021615531565b005b60003560e01c80630396cb60146101b35780630bd28e3b146101aa5780631b2e01b8146101a15780631d732756146101985780631fad948c1461018f578063205c28781461018657806335567e1a1461017d5780634b1d7cf5146101745780635287ce121461016b57806370a08231146101625780638f41ec5a14610159578063957122ab146101505780639b249f6914610147578063a61935311461013e578063b760faf914610135578063bb9fe6bf1461012c578063c23a5cea14610123578063d6383f941461011a578063ee219423146101115763fc7e286d0361000e5761010c611bcd565b61000e565b5061010c6119b5565b5061010c61184d565b5061010c6116b4565b5061010c611536565b5061010c6114f7565b5061010c6114d6565b5061010c611337565b5061010c611164565b5061010c611129565b5061010c6110a4565b5061010c610f54565b5061010c610bf8565b5061010c610b33565b5061010c610994565b5061010c6108ba565b5061010c6106e7565b5061010c610467565b5061010c610385565b5060207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595760043563ffffffff8116808203610359576103547fa5ae833d0bb1dcd632d98a8b70973e8516812898e19bf27b70071ebc8dc52c01916102716102413373ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b9161024d811515615697565b61026a610261600185015463ffffffff1690565b63ffffffff1690565b11156156fc565b54926103366dffffffffffffffffffffffffffff946102f461029834888460781c166121d5565b966102a4881515615761565b6102b0818911156157c6565b6102d4816102bc6105ec565b941684906dffffffffffffffffffffffffffff169052565b6001602084015287166dffffffffffffffffffffffffffff166040830152565b63ffffffff83166060820152600060808201526103313373ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b61582b565b6040805194855263ffffffff90911660208501523393918291820190565b0390a2005b600080fd5b6024359077ffffffffffffffffffffffffffffffffffffffffffffffff8216820361035957565b50346103595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595760043577ffffffffffffffffffffffffffffffffffffffffffffffff81168103610359576104149033600052600160205260406000209077ffffffffffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b61041e8154612491565b9055005b73ffffffffffffffffffffffffffffffffffffffff81160361035957565b6024359061044d82610422565b565b60c4359061044d82610422565b359061044d82610422565b50346103595760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595760206104fc6004356104a881610422565b73ffffffffffffffffffffffffffffffffffffffff6104c561035e565b91166000526001835260406000209077ffffffffffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b54604051908152f35b507f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60a0810190811067ffffffffffffffff82111761055157604052565b610559610505565b604052565b610100810190811067ffffffffffffffff82111761055157604052565b67ffffffffffffffff811161055157604052565b6060810190811067ffffffffffffffff82111761055157604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761055157604052565b6040519061044d82610535565b6040519060c0820182811067ffffffffffffffff82111761055157604052565b604051906040820182811067ffffffffffffffff82111761055157604052565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f60209267ffffffffffffffff8111610675575b01160190565b61067d610505565b61066f565b92919261068e82610639565b9161069c60405193846105ab565b829481845281830111610359578281602093846000960137010152565b9181601f840112156103595782359167ffffffffffffffff8311610359576020838186019501011161035957565b5034610359576101c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595767ffffffffffffffff60043581811161035957366023820112156103595761074a903690602481600401359101610682565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc36016101808112610359576101006040519161078783610535565b12610359576040516107988161055e565b6107a0610440565b815260443560208201526064356040820152608435606082015260a43560808201526107ca61044f565b60a082015260e43560c08201526101043560e082015281526101243560208201526101443560408201526101643560608201526101843560808201526101a4359182116103595761083e9261082661082e9336906004016106b9565b9290916128b1565b6040519081529081906020820190565b0390f35b9060407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8301126103595760043567ffffffffffffffff9283821161035957806023830112156103595781600401359384116103595760248460051b830101116103595760240191906024356108b781610422565b90565b5034610359576108c936610842565b6108d4929192611e3a565b6108dd83611d2d565b60005b84811061095d57506000927fbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f9728480a183915b85831061092d576109238585611ed7565b6100216001600255565b909193600190610953610941878987611dec565b61094b8886611dca565b51908861233f565b0194019190610912565b8061098b610984610972600194869896611dca565b5161097e848a88611dec565b84613448565b9083612f30565b019290926108e0565b50346103595760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610359576004356109d081610422565b6024359060009133835282602052604083206dffffffffffffffffffffffffffff81541692838311610ad557848373ffffffffffffffffffffffffffffffffffffffff829593610a788496610a3f610a2c8798610ad29c6121c0565b6dffffffffffffffffffffffffffff1690565b6dffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffff0000000000000000000000000000825416179055565b6040805173ffffffffffffffffffffffffffffffffffffffff831681526020810185905233917fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb91a2165af1610acc611ea7565b50615ba2565b80f35b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f576974686472617720616d6f756e7420746f6f206c61726765000000000000006044820152fd5b50346103595760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610359576020600435610b7181610422565b73ffffffffffffffffffffffffffffffffffffffff610b8e61035e565b911660005260018252610bc98160406000209077ffffffffffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000006040519260401b16178152f35b503461035957610c0736610842565b610c0f611e3a565b6000805b838210610df657610c249150611d2d565b7fbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972600080a16000805b848110610d5c57505060008093815b818110610c9357610923868660007f575ff3acadd5ab348fe1855e217e0f3678f8d767d7494c9f9fefbee2e17cca4d8180a2611ed7565b610cf7610ca182848a6124cb565b610ccc610cb3610cb36020840161256d565b73ffffffffffffffffffffffffffffffffffffffff1690565b7f575ff3acadd5ab348fe1855e217e0f3678f8d767d7494c9f9fefbee2e17cca4d600080a280612519565b906000915b808310610d1457505050610d0f90612491565b610c5c565b90919497610d4f610d49610d5592610d438c8b610d3c82610d368e8b8d611dec565b92611dca565b519161233f565b906121d5565b99612491565b95612491565b9190610cfc565b610d678186886124cb565b6020610d7f610d768380612519565b9290930161256d565b9173ffffffffffffffffffffffffffffffffffffffff60009316905b828410610db45750505050610daf90612491565b610c4d565b90919294610d4f81610de985610de2610dd0610dee968d611dca565b51610ddc8c8b8a611dec565b85613448565b908b613148565b612491565b929190610d9b565b610e018285876124cb565b90610e0c8280612519565b92610e1c610cb36020830161256d565b9173ffffffffffffffffffffffffffffffffffffffff8316610e416001821415612577565b610e62575b505050610e5c91610e56916121d5565b91612491565b90610c13565b909592610e7b6040999693999895989788810190611fc8565b92908a3b156103595789938b918a5193849283927fe3563a4f00000000000000000000000000000000000000000000000000000000845260049e8f850193610ec294612711565b03815a93600094fa9081610f3b575b50610f255786517f86a9f75000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8a16818a0190815281906020010390fd5b0390fd5b9497509295509093509181610e56610e5c610e46565b80610f48610f4e9261057b565b8061111e565b38610ed1565b50346103595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595761083e73ffffffffffffffffffffffffffffffffffffffff600435610fa881610422565b608060409283928351610fba81610535565b60009381858093528260208201528287820152826060820152015216815280602052209061104965ffffffffffff6001835194610ff686610535565b80546dffffffffffffffffffffffffffff8082168852607082901c60ff161515602089015260789190911c1685870152015463ffffffff8116606086015260201c16608084019065ffffffffffff169052565b5191829182919091608065ffffffffffff8160a08401956dffffffffffffffffffffffffffff808251168652602082015115156020870152604082015116604086015263ffffffff6060820151166060860152015116910152565b50346103595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595773ffffffffffffffffffffffffffffffffffffffff6004356110f581610422565b16600052600060205260206dffffffffffffffffffffffffffff60406000205416604051908152f35b600091031261035957565b50346103595760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261035957602060405160018152f35b50346103595760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261035957600467ffffffffffffffff8135818111610359576111b590369084016106b9565b9050602435916111c483610422565b604435908111610359576111db90369085016106b9565b92909115908161132d575b506112c6576014821015611236575b610f21836040519182917f08c379a0000000000000000000000000000000000000000000000000000000008352820160409060208152600060208201520190565b6112466112529261124c92612b88565b90612b96565b60601c90565b3b1561125f5738806111f5565b610f21906040519182917f08c379a0000000000000000000000000000000000000000000000000000000008352820160609060208152601b60208201527f41413330207061796d6173746572206e6f74206465706c6f796564000000000060408201520190565b610f21836040519182917f08c379a0000000000000000000000000000000000000000000000000000000008352820160609060208152601960208201527f41413230206163636f756e74206e6f74206465706c6f7965640000000000000060408201520190565b90503b15386111e6565b50346103595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595760043567ffffffffffffffff81116103595761138960249136906004016106b9565b906113bf6040519283927f570e1a3600000000000000000000000000000000000000000000000000000000845260048401612d2c565b0360208273ffffffffffffffffffffffffffffffffffffffff92816000857f0000000000000000000000000000000000000000000000000000000000000000165af1918215611471575b600092611441575b50604051917f6ca7b806000000000000000000000000000000000000000000000000000000008352166004820152fd5b61146391925060203d811161146a575b61145b81836105ab565b810190612d17565b9038611411565b503d611451565b611479612183565b611409565b90816101609103126103595790565b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc820112610359576004359067ffffffffffffffff8211610359576108b79160040161147e565b50346103595760206114ef6114ea3661148d565b612a0c565b604051908152f35b5060207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595761002160043561153181610422565b61562b565b5034610359576000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126116b1573381528060205260408120600181019063ffffffff825416908115611653576115f06115b5611618936115a76115a2855460ff9060701c1690565b61598f565b65ffffffffffff42166159f4565b84547fffffffffffffffffffffffffffffffffffffffffffff000000000000ffffffff16602082901b69ffffffffffff000000001617909455565b7fffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffff8154169055565b60405165ffffffffffff91909116815233907ffa9b3c14cc825c412c9ed81b3ba365a5b459439403f18829e572ed53a4180f0a90602090a280f35b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f6e6f74207374616b6564000000000000000000000000000000000000000000006044820152fd5b80fd5b50346103595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610359576004356116f081610422565b610ad273ffffffffffffffffffffffffffffffffffffffff6117323373ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b926117ea611755610a2c86546dffffffffffffffffffffffffffff9060781c1690565b94611761861515615a0e565b6117c26001820161179a65ffffffffffff611786835465ffffffffffff9060201c1690565b16611792811515615a73565b421015615ad8565b80547fffffffffffffffffffffffffffffffffffffffffffff00000000000000000000169055565b7fffffff0000000000000000000000000000ffffffffffffffffffffffffffffff8154169055565b6040805173ffffffffffffffffffffffffffffffffffffffff831681526020810186905233917fb7c918e0e249f999e965cafeb6c664271b3f4317d296461500e71da39f0cbda391a2600080809581948294165af1611847611ea7565b50615b3d565b50346103595760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595767ffffffffffffffff6004358181116103595761189e90369060040161147e565b602435916118ab83610422565b604435908111610359576118c6610f219136906004016106b9565b6118ce611caa565b6118d785612e2b565b6118ea6118e48287613240565b906153ba565b946118fa826000924384526121e2565b96438252819360609573ffffffffffffffffffffffffffffffffffffffff8316611981575b50505050608001519361194e6040611940602084015165ffffffffffff1690565b92015165ffffffffffff1690565b906040519687967f8b7ac980000000000000000000000000000000000000000000000000000000008852600488016127e1565b8395508394965061199b60409492939451809481936127d3565b03925af19060806119aa611ea7565b92919038808061191f565b5034610359576119c43661148d565b6119cc611caa565b6119d582612e2b565b6119df8183613240565b825160a00151919391611a0c9073ffffffffffffffffffffffffffffffffffffffff166154dc565b6154dc565b90611a30611a07855173ffffffffffffffffffffffffffffffffffffffff90511690565b94611a39612b50565b50611a68611a4c60409586810190611fc8565b90600060148310611bc55750611246611a079261124c92612b88565b91611a72916153ba565b805173ffffffffffffffffffffffffffffffffffffffff169073ffffffffffffffffffffffffffffffffffffffff821660018114916080880151978781015191886020820151611ac79065ffffffffffff1690565b91015165ffffffffffff16916060015192611ae06105f9565b9a8b5260208b0152841515898b015265ffffffffffff1660608a015265ffffffffffff16608089015260a088015215159081611bbc575b50611b515750610f2192519485947fe0cff05f00000000000000000000000000000000000000000000000000000000865260048601612cbd565b9190610f2193611b60846154dc565b611b87611b6b610619565b73ffffffffffffffffffffffffffffffffffffffff9096168652565b6020850152519586957ffaecb4e400000000000000000000000000000000000000000000000000000000875260048701612c2b565b90501538611b17565b9150506154dc565b50346103595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595773ffffffffffffffffffffffffffffffffffffffff600435611c1e81610422565b16600052600060205260a0604060002065ffffffffffff60018254920154604051926dffffffffffffffffffffffffffff90818116855260ff8160701c161515602086015260781c16604084015263ffffffff8116606084015260201c166080820152f35b60209067ffffffffffffffff8111611c9d575b60051b0190565b611ca5610505565b611c96565b60405190611cb782610535565b604051608083610100830167ffffffffffffffff811184821017611d20575b60405260009283815283602082015283604082015283606082015283838201528360a08201528360c08201528360e082015281528260208201528260408201528260608201520152565b611d28610505565b611cd6565b90611d3782611c83565b611d4460405191826105ab565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0611d728294611c83565b019060005b828110611d8357505050565b602090611d8e611caa565b82828501015201611d77565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6020918151811015611ddf575b60051b010190565b611de7611d9a565b611dd7565b9190811015611e2d575b60051b810135907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffea181360301821215610359570190565b611e35611d9a565b611df6565b6002805414611e495760028055565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152fd5b3d15611ed2573d90611eb882610639565b91611ec660405193846105ab565b82523d6000602084013e565b606090565b73ffffffffffffffffffffffffffffffffffffffff168015611f6a57600080809381935af1611f04611ea7565b5015611f0c57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f41413931206661696c65642073656e6420746f2062656e6566696369617279006044820152fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f4141393020696e76616c69642062656e656669636961727900000000000000006044820152fd5b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215610359570180359067ffffffffffffffff82116103595760200191813603831361035957565b90816020910312610359575190565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938186528686013760008582860101520116010190565b60005b83811061207a5750506000910152565b818101518382015260200161206a565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f6020936120c681518092818752878088019101612067565b0116010190565b906120e76080916108b796946101c0808652850191612028565b9360e0815173ffffffffffffffffffffffffffffffffffffffff80825116602087015260208201516040870152604082015160608701526060820151858701528482015160a087015260a08201511660c086015260c081015182860152015161010084015260208101516101208401526040810151610140840152606081015161016084015201516101808201526101a081840391015261208a565b506040513d6000823e3d90fd5b507f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b919082039182116121cd57565b61044d612190565b919082018092116121cd57565b905a918160206121fb6060830151936060810190611fc8565b906122348560405195869485947f1d732756000000000000000000000000000000000000000000000000000000008652600486016120cd565b03816000305af16000918161230f575b50612308575060206000803e7fdeaddead000000000000000000000000000000000000000000000000000000006000511461229b5761229561228a6108b7945a906121c0565b6080840151906121d5565b91614afc565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152600f60408201527f41413935206f7574206f6620676173000000000000000000000000000000000060608201520190565b9250505090565b61233191925060203d8111612338575b61232981836105ab565b810190612019565b9038612244565b503d61231f565b909291925a9380602061235b6060830151946060810190611fc8565b906123948660405195869485947f1d732756000000000000000000000000000000000000000000000000000000008652600486016120cd565b03816000305af160009181612471575b5061246a575060206000803e7fdeaddead00000000000000000000000000000000000000000000000000000000600051146123fc576123f66123eb6108b795965a906121c0565b6080830151906121d5565b92614ddf565b610f21836040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152600f60408201527f41413935206f7574206f6620676173000000000000000000000000000000000060608201520190565b9450505050565b61248a91925060203d81116123385761232981836105ab565b90386123a4565b6001907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146124bf570190565b6124c7612190565b0190565b919081101561250c575b60051b810135907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa181360301821215610359570190565b612514611d9a565b6124d5565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215610359570180359067ffffffffffffffff821161035957602001918160051b3603831361035957565b356108b781610422565b1561257e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4141393620696e76616c69642061676772656761746f720000000000000000006044820152fd5b90357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18236030181121561035957016020813591019167ffffffffffffffff821161035957813603831361035957565b6108b7916126578161263d8461045c565b73ffffffffffffffffffffffffffffffffffffffff169052565b602082013560208201526126f26126a361268861267760408601866125dc565b610160806040880152860191612028565b61269560608601866125dc565b908583036060870152612028565b6080840135608084015260a084013560a084015260c084013560c084015260e084013560e084015261010080850135908401526101206126e5818601866125dc565b9185840390860152612028565b9161270361014091828101906125dc565b929091818503910152612028565b949391929083604087016040885252606086019360608160051b8801019482600090815b848310612754575050505050508460206108b795968503910152612028565b9091929394977fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa08b820301855288357ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffea1843603018112156127cf57600191846127bd920161262c565b98602090810196950193019190612735565b8280fd5b908092918237016000815290565b9290936108b796959260c0958552602085015265ffffffffffff8092166040850152166060830152151560808201528160a0820152019061208a565b1561282457565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4141393220696e7465726e616c2063616c6c206f6e6c790000000000000000006044820152fd5b9060406108b79260008152816020820152019061208a565b6040906108b793928152816020820152019061208a565b909291925a936128c230331461281d565b8151946040860151955a6113886060830151890101116129e2576108b7966000958051612909575b50505090612903915a9003608084015101943691610682565b91615047565b612938916129349161292f855173ffffffffffffffffffffffffffffffffffffffff1690565b615c12565b1590565b612944575b80806128ea565b61290392919450612953615c24565b908151612967575b5050600193909161293d565b7f1c4fada7374c0a9ee8841fc38afe82932dc0f8e69012e927f061a8bae611a20173ffffffffffffffffffffffffffffffffffffffff6020870151926129d860206129c6835173ffffffffffffffffffffffffffffffffffffffff1690565b9201519560405193849316968361289a565b0390a3388061295b565b7fdeaddead0000000000000000000000000000000000000000000000000000000060005260206000fd5b612a22612a1c6040830183611fc8565b90615c07565b90612a33612a1c6060830183611fc8565b90612ae9612a48612a1c610120840184611fc8565b60405194859360208501956101008201359260e08301359260c08101359260a08201359260808301359273ffffffffffffffffffffffffffffffffffffffff60208201359135168c9693909a9998959261012098959273ffffffffffffffffffffffffffffffffffffffff6101408a019d168952602089015260408801526060870152608086015260a085015260c084015260e08301526101008201520152565b0391612b1b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938481018352826105ab565b51902060408051602081019283523091810191909152466060820152608092830181529091612b4a90826105ab565b51902090565b604051906040820182811067ffffffffffffffff821117612b7b575b60405260006020838281520152565b612b83610505565b612b6c565b906014116103595790601490565b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000009035818116939260148110612bcb57505050565b60140360031b82901b16169150565b9060c060a06108b793805184526020810151602085015260408101511515604085015265ffffffffffff80606083015116606086015260808201511660808501520151918160a0820152019061208a565b9294612c8c61044d95612c7a610100959998612c68612c54602097610140808c528b0190612bda565b9b878a019060208091805184520151910152565b80516060890152602001516080880152565b805160a08701526020015160c0860152565b73ffffffffffffffffffffffffffffffffffffffff81511660e0850152015191019060208091805184520151910152565b612d0661044d94612cf4612cdf60a0959998969960e0865260e0860190612bda565b98602085019060208091805184520151910152565b80516060840152602001516080830152565b019060208091805184520151910152565b9081602091031261035957516108b781610422565b9160206108b7938181520191612028565b90612d6c73ffffffffffffffffffffffffffffffffffffffff916108b797959694606085526060850191612028565b941660208201526040818503910152612028565b60009060033d11612d8d57565b905060046000803e60005160e01c90565b600060443d106108b7576040517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc91823d016004833e815167ffffffffffffffff918282113d602484011117612e1a57818401948551938411612e22573d85010160208487010111612e1a57506108b7929101602001906105ab565b949350505050565b50949350505050565b612e386040820182611fc8565b612e50612e448461256d565b93610120810190611fc8565b9290303b1561035957600093612e949160405196879586957f957122ab00000000000000000000000000000000000000000000000000000000875260048701612d3d565b0381305afa9081612f1d575b5061044d576001612eaf612d80565b6308c379a014612ec8575b612ec057565b61044d612183565b612ed0612d9e565b80612edc575b50612eba565b80516000925015612ed657610f21906040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301612882565b80610f48612f2a9261057b565b38612ea0565b9190612f3b9061317f565b73ffffffffffffffffffffffffffffffffffffffff929183166130da5761306c57612f659061317f565b9116612ffe57612f725750565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152602160448201527f41413332207061796d61737465722065787069726564206f72206e6f7420647560648201527f6500000000000000000000000000000000000000000000000000000000000000608482015260a490fd5b610f21826040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601460408201527f41413334207369676e6174757265206572726f7200000000000000000000000060608201520190565b610f21836040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601760408201527f414132322065787069726564206f72206e6f742064756500000000000000000060608201520190565b610f21846040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601460408201527f41413234207369676e6174757265206572726f7200000000000000000000000060608201520190565b9291906131549061317f565b909273ffffffffffffffffffffffffffffffffffffffff808095169116036130da5761306c57612f65905b80156131d25761318e9061535f565b73ffffffffffffffffffffffffffffffffffffffff65ffffffffffff8060408401511642119081156131c2575b5091511691565b90506020830151164210386131bb565b50600090600090565b156131e257565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f41413934206761732076616c756573206f766572666c6f7700000000000000006044820152fd5b916000915a9381519061325382826136b3565b61325c81612a0c565b602084015261329a6effffffffffffffffffffffffffffff60808401516060850151176040850151176101008401359060e0850135171711156131db565b6132a382613775565b6132ae818584613836565b97906132df6129346132d4875173ffffffffffffffffffffffffffffffffffffffff1690565b60208801519061546c565b6133db576132ec43600052565b73ffffffffffffffffffffffffffffffffffffffff61332460a0606097015173ffffffffffffffffffffffffffffffffffffffff1690565b166133c1575b505a810360a0840135106133545760809360c092604087015260608601525a900391013501910152565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601e60408201527f41413430206f76657220766572696669636174696f6e4761734c696d6974000060608201520190565b909350816133d2929750858461455c565b9590923861332a565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601a60408201527f4141323520696e76616c6964206163636f756e74206e6f6e636500000000000060608201520190565b9290916000925a825161345b81846136b3565b61346483612a0c565b60208501526134a26effffffffffffffffffffffffffffff60808301516060840151176040840151176101008601359060e0870135171711156131db565b6134ab81613775565b6134b78186868b613ba2565b98906134e86129346134dd865173ffffffffffffffffffffffffffffffffffffffff1690565b60208701519061546c565b6135e0576134f543600052565b73ffffffffffffffffffffffffffffffffffffffff61352d60a0606096015173ffffffffffffffffffffffffffffffffffffffff1690565b166135c5575b505a840360a08601351061355f5750604085015260608401526080919060c0905a900391013501910152565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152601e60448201527f41413430206f76657220766572696669636174696f6e4761734c696d697400006064820152608490fd5b909250816135d79298508686856147ef565b96909138613533565b610f21826040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601a60408201527f4141323520696e76616c6964206163636f756e74206e6f6e636500000000000060608201520190565b1561365557565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f4141393320696e76616c6964207061796d6173746572416e64446174610000006044820152fd5b613725906136dd6136c38261256d565b73ffffffffffffffffffffffffffffffffffffffff168452565b602081013560208401526080810135604084015260a0810135606084015260c0810135608084015260e081013560c084015261010081013560e0840152610120810190611fc8565b90811561376a5761374f61124c6112468460a09461374a601461044d9998101561364e565b612b88565b73ffffffffffffffffffffffffffffffffffffffff16910152565b505060a06000910152565b60a081015173ffffffffffffffffffffffffffffffffffffffff16156137b75760c060035b60ff60408401519116606084015102016080830151019101510290565b60c0600161379a565b6137d86040929594939560608352606083019061262c565b9460208201520152565b9061044d602f60405180947f414132332072657665727465643a20000000000000000000000000000000000060208301526138268151809260208686019101612067565b810103600f8101855201836105ab565b916000926000925a936139046020835193613865855173ffffffffffffffffffffffffffffffffffffffff1690565b9561387d6138766040830183611fc8565b9084613e0d565b60a086015173ffffffffffffffffffffffffffffffffffffffff16906138a243600052565b85809373ffffffffffffffffffffffffffffffffffffffff809416159889613b3a575b60600151908601516040517f3a871cdd0000000000000000000000000000000000000000000000000000000081529788968795869390600485016137c0565b03938a1690f1829181613b1a575b50613b115750600190613923612d80565b6308c379a014613abd575b50613a50575b613941575b50505a900391565b61396b9073ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b613986610a2c82546dffffffffffffffffffffffffffff1690565b8083116139e3576139dc926dffffffffffffffffffffffffffff9103166dffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffff0000000000000000000000000000825416179055565b3880613939565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601760408201527f41413231206469646e2774207061792070726566756e6400000000000000000060608201520190565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601660408201527f4141323320726576657274656420286f72204f4f47290000000000000000000060608201520190565b613ac5612d9e565b9081613ad1575061392e565b610f2191613adf91506137e2565b6040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301612882565b95506139349050565b613b3391925060203d81116123385761232981836105ab565b9038613912565b9450613b80610a2c613b6c8c73ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b546dffffffffffffffffffffffffffff1690565b8b811115613b975750856060835b969150506138c5565b606087918d03613b8e565b90926000936000935a94613beb6020835193613bd2855173ffffffffffffffffffffffffffffffffffffffff1690565b9561387d613be36040830183611fc8565b90848c61412b565b03938a1690f1829181613ded575b50613de45750600190613c0a612d80565b6308c379a014613d8e575b50613d20575b613c29575b5050505a900391565b613c539073ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b91613c6f610a2c84546dffffffffffffffffffffffffffff1690565b90818311613cba575082547fffffffffffffffffffffffffffffffffffff0000000000000000000000000000169190036dffffffffffffffffffffffffffff16179055388080613c20565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152601760448201527f41413231206469646e2774207061792070726566756e640000000000000000006064820152608490fd5b610f21846040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601660408201527f4141323320726576657274656420286f72204f4f47290000000000000000000060608201520190565b613d96612d9e565b9081613da25750613c15565b8691613dae91506137e2565b90610f216040519283927f220266b60000000000000000000000000000000000000000000000000000000084526004840161289a565b9650613c1b9050565b613e0691925060203d81116123385761232981836105ab565b9038613bf9565b909180613e1957505050565b81515173ffffffffffffffffffffffffffffffffffffffff1692833b6140be57606083510151604051907f570e1a3600000000000000000000000000000000000000000000000000000000825260208280613e78878760048401612d2c565b0381600073ffffffffffffffffffffffffffffffffffffffff95867f00000000000000000000000000000000000000000000000000000000000000001690f19182156140b1575b600092614091575b508082169586156140245716809503613fb7573b15613f4a5761124c6112467fd51a9c61267aa6196961883ecf5ff2da6619c37dac0fa92122513fb32c032d2d93613f1193612b88565b602083810151935160a001516040805173ffffffffffffffffffffffffffffffffffffffff9485168152939091169183019190915290a3565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152602060408201527f4141313520696e6974436f6465206d757374206372656174652073656e64657260608201520190565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152602060408201527f4141313420696e6974436f6465206d7573742072657475726e2073656e64657260608201520190565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601b60408201527f4141313320696e6974436f6465206661696c6564206f72204f4f47000000000060608201520190565b6140aa91925060203d811161146a5761145b81836105ab565b9038613ec7565b6140b9612183565b613ebf565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601f60408201527f414131302073656e64657220616c726561647920636f6e73747275637465640060608201520190565b9290918161413a575b50505050565b82515173ffffffffffffffffffffffffffffffffffffffff1693843b6143e257606084510151604051907f570e1a3600000000000000000000000000000000000000000000000000000000825260208280614199888860048401612d2c565b0381600073ffffffffffffffffffffffffffffffffffffffff95867f00000000000000000000000000000000000000000000000000000000000000001690f19182156143d5575b6000926143b5575b5080821696871561434757168096036142d9573b15614273575061124c6112467fd51a9c61267aa6196961883ecf5ff2da6619c37dac0fa92122513fb32c032d2d9361423393612b88565b602083810151935160a001516040805173ffffffffffffffffffffffffffffffffffffffff9485168152939091169183019190915290a338808080614134565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152602060448201527f4141313520696e6974436f6465206d757374206372656174652073656e6465726064820152608490fd5b610f21826040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152602060408201527f4141313420696e6974436f6465206d7573742072657475726e2073656e64657260608201520190565b610f21846040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601b60408201527f4141313320696e6974436f6465206661696c6564206f72204f4f47000000000060608201520190565b6143ce91925060203d811161146a5761145b81836105ab565b90386141e8565b6143dd612183565b6141e0565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152601f60448201527f414131302073656e64657220616c726561647920636f6e7374727563746564006064820152608490fd5b1561444f57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f4141343120746f6f206c6974746c6520766572696669636174696f6e476173006044820152fd5b919060408382031261035957825167ffffffffffffffff81116103595783019080601f83011215610359578151916144e483610639565b916144f260405193846105ab565b838352602084830101116103595760209261451291848085019101612067565b92015190565b9061044d602f60405180947f414133332072657665727465643a20000000000000000000000000000000000060208301526138268151809260208686019101612067565b93919260609460009460009380519261459b60a08a86015195614580888811614448565b015173ffffffffffffffffffffffffffffffffffffffff1690565b916145c68373ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b946145e2610a2c87546dffffffffffffffffffffffffffff1690565b968588106147825773ffffffffffffffffffffffffffffffffffffffff60208a98946146588a966dffffffffffffffffffffffffffff8b6146919e03166dffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffff0000000000000000000000000000825416179055565b015194604051998a98899788937ff465c77e000000000000000000000000000000000000000000000000000000008552600485016137c0565b0395169103f190818391849361475c575b506147555750506001906146b4612d80565b6308c379a014614733575b506146c657565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601660408201527f4141333320726576657274656420286f72204f4f47290000000000000000000060608201520190565b61473b612d9e565b908161474757506146bf565b610f2191613adf9150614518565b9450925050565b90925061477b91503d8085833e61477381836105ab565b8101906144ad565b91386146a2565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601e60408201527f41413331207061796d6173746572206465706f73697420746f6f206c6f77000060608201520190565b91949293909360609560009560009382519061481660a08b84015193614580848611614448565b936148418573ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b61485c610a2c82546dffffffffffffffffffffffffffff1690565b8781106149b7579273ffffffffffffffffffffffffffffffffffffffff60208a989693946146588a966dffffffffffffffffffffffffffff8d6148d69e9c9a03166dffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffff0000000000000000000000000000825416179055565b0395169103f1908183918493614999575b506149915750506001906148f9612d80565b6308c379a014614972575b5061490c5750565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152601660448201527f4141333320726576657274656420286f72204f4f4729000000000000000000006064820152608490fd5b61497a612d9e565b90816149865750614904565b613dae925050614518565b955093505050565b9092506149b091503d8085833e61477381836105ab565b91386148e7565b610f218a6040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601e60408201527f41413331207061796d6173746572206465706f73697420746f6f206c6f77000060608201520190565b60031115614a2f57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b929190614a7c6040916002865260606020870152606086019061208a565b930152565b939291906003811015614a2f57604091614a7c91865260606020870152606086019061208a565b9061044d603660405180947f4141353020706f73744f702072657665727465643a20000000000000000000006020830152614aec8151809260208686019101612067565b81010360168101855201836105ab565b929190925a93600091805191614b1183615318565b9260a0810195614b35875173ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff93908481169081614ca457505050614b76825173ffffffffffffffffffffffffffffffffffffffff1690565b985b5a90030193840297604084019089825110614c37577f49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f94614bc26020928c614c329551039061553a565b015194896020614c04614be9865173ffffffffffffffffffffffffffffffffffffffff1690565b9a5173ffffffffffffffffffffffffffffffffffffffff1690565b9401519785604051968796169a16988590949392606092608083019683521515602083015260408201520152565b0390a4565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152602060408201527f414135312070726566756e642062656c6f772061637475616c476173436f737460608201520190565b9a918051614cb4575b5050614b78565b6060850151600099509091803b15614ddb579189918983614d07956040518097819682957fa9a234090000000000000000000000000000000000000000000000000000000084528c029060048401614a5e565b0393f19081614dc8575b50614dc3576001614d20612d80565b6308c379a014614da4575b614d37575b3880614cad565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601260408201527f4141353020706f73744f7020726576657274000000000000000000000000000060608201520190565b614dac612d9e565b80614db75750614d2b565b613adf610f2191614aa8565b614d30565b80610f48614dd59261057b565b38614d11565b8980fd5b9392915a90600092805190614df382615318565b9360a0830196614e17885173ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff95908681169081614f0d57505050614e58845173ffffffffffffffffffffffffffffffffffffffff1690565b915b5a9003019485029860408301908a825110614ea757507f49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f949392614bc2614c32938c60209451039061553a565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152602060448201527f414135312070726566756e642062656c6f772061637475616c476173436f73746064820152608490fd5b93918051614f1d575b5050614e5a565b606087015160009a509091803b1561504357918a918a83614f70956040518097819682957fa9a234090000000000000000000000000000000000000000000000000000000084528c029060048401614a5e565b0393f19081615030575b5061502b576001614f89612d80565b6308c379a01461500e575b614fa0575b3880614f16565b610f218b6040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601260408201527f4141353020706f73744f7020726576657274000000000000000000000000000060608201520190565b615016612d9e565b806150215750614f94565b613dae8d91614aa8565b614f99565b80610f4861503d9261057b565b38614f7a565b8a80fd5b909392915a9480519161505983615318565b9260a081019561507d875173ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff938185169182615165575050506150bd825173ffffffffffffffffffffffffffffffffffffffff1690565b985b5a90030193840297604084019089825110614c37577f49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f946151096020928c614c329551039061553a565b61511288614a25565b015194896020615139614be9865173ffffffffffffffffffffffffffffffffffffffff1690565b940151604080519182529815602082015297880152606087015290821695909116939081906080820190565b9a918151615175575b50506150bf565b8784026151818a614a25565b60028a1461520c576060860151823b15610359576151d493600080948d604051978896879586937fa9a2340900000000000000000000000000000000000000000000000000000000855260048501614a81565b0393f180156151ff575b6151ec575b505b388061516e565b80610f486151f99261057b565b386151e3565b615207612183565b6151de565b6060860151823b156103595761525793600080948d604051978896879586937fa9a2340900000000000000000000000000000000000000000000000000000000855260048501614a81565b0393f19081615305575b50615300576001615270612d80565b6308c379a0146152ed575b156151e5576040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601260408201527f4141353020706f73744f7020726576657274000000000000000000000000000060608201520190565b6152f5612d9e565b80614db7575061527b565b6151e5565b80610f486153129261057b565b38615261565b60e060c082015191015180821461533c57480180821015615337575090565b905090565b5090565b6040519061534d8261058f565b60006040838281528260208201520152565b615367615340565b5065ffffffffffff808260a01c1680156153b3575b604051926153898461058f565b73ffffffffffffffffffffffffffffffffffffffff8116845260d01c602084015216604082015290565b508061537c565b6153cf6153d5916153c9615340565b5061535f565b9161535f565b9073ffffffffffffffffffffffffffffffffffffffff9182825116928315615461575b65ffffffffffff928391826040816020850151169301511693836040816020840151169201511690808410615459575b50808511615451575b506040519561543f8761058f565b16855216602084015216604082015290565b935038615431565b925038615428565b8151811693506153f8565b73ffffffffffffffffffffffffffffffffffffffff16600052600160205267ffffffffffffffff6154c88260401c60406000209077ffffffffffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b918254926154d584612491565b9055161490565b9073ffffffffffffffffffffffffffffffffffffffff6154fa612b50565b9216600052600060205263ffffffff600160406000206dffffffffffffffffffffffffffff815460781c1685520154166020830152565b61044d3361562b565b73ffffffffffffffffffffffffffffffffffffffff16600052600060205260406000206dffffffffffffffffffffffffffff8082541692830180931161561e575b8083116155c05761044d92166dffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffff0000000000000000000000000000825416179055565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f6465706f736974206f766572666c6f77000000000000000000000000000000006044820152fd5b615626612190565b61557b565b73ffffffffffffffffffffffffffffffffffffffff9061564b348261553a565b168060005260006020527f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c460206dffffffffffffffffffffffffffff60406000205416604051908152a2565b1561569e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f6d757374207370656369667920756e7374616b652064656c61790000000000006044820152fd5b1561570357565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f63616e6e6f7420646563726561736520756e7374616b652074696d65000000006044820152fd5b1561576857565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f6e6f207374616b652073706563696669656400000000000000000000000000006044820152fd5b156157cd57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f7374616b65206f766572666c6f770000000000000000000000000000000000006044820152fd5b9065ffffffffffff6080600161044d9461588b6dffffffffffffffffffffffffffff86511682906dffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffff0000000000000000000000000000825416179055565b602085015115156eff000000000000000000000000000082549160701b16807fffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffff83161783557fffffff000000000000000000000000000000ffffffffffffffffffffffffffff7cffffffffffffffffffffffffffff000000000000000000000000000000604089015160781b16921617178155019263ffffffff6060820151167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000008554161784550151167fffffffffffffffffffffffffffffffffffffffffffff000000000000ffffffff69ffffffffffff0000000083549260201b169116179055565b1561599657565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f616c726561647920756e7374616b696e670000000000000000000000000000006044820152fd5b91909165ffffffffffff808094169116019182116121cd57565b15615a1557565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f4e6f207374616b6520746f2077697468647261770000000000000000000000006044820152fd5b15615a7a57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f6d7573742063616c6c20756e6c6f636b5374616b6528292066697273740000006044820152fd5b15615adf57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f5374616b65207769746864726177616c206973206e6f742064756500000000006044820152fd5b15615b4457565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f6661696c656420746f207769746864726177207374616b6500000000000000006044820152fd5b15615ba957565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f6661696c656420746f20776974686472617700000000000000000000000000006044820152fd5b816040519182372090565b9060009283809360208451940192f190565b3d610800808211615c4b575b50604051906020818301016040528082526000602083013e90565b905038615c3056fea2646970667358221220a706d8b02d7086d80e9330811f5af84b2614abdc5e9a1f2260126070a31d7cee64736f6c634300081100336080806040523461001657610210908161001c8239f35b600080fdfe6080604052600436101561001257600080fd5b6000803560e01c63570e1a361461002857600080fd5b346100c95760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100c95760043567ffffffffffffffff918282116100c957366023830112156100c95781600401359283116100c95736602484840101116100c9576100c561009e84602485016100fc565b60405173ffffffffffffffffffffffffffffffffffffffff90911681529081906020820190565b0390f35b80fd5b507f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b90806014116101bb5767ffffffffffffffff917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec82018381116101cd575b604051937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f81600b8701160116850190858210908211176101c0575b604052808452602084019036848401116101bb576020946000600c819682946014880187378301015251923560601c5af19060005191156101b557565b60009150565b600080fd5b6101c86100cc565b610178565b6101d56100cc565b61013a56fea26469706673582212201927e80b76ab9b71c952137dd676621a9fdf520c25928815636594036eb1c40364736f6c63430008110033"; - -type EntryPointConstructorParams = - | [signer?: Signer] - | ConstructorParameters; - -const isSuperArgs = ( - xs: EntryPointConstructorParams -): xs is ConstructorParameters => xs.length > 1; - -export class EntryPoint__factory extends ContractFactory { - constructor(...args: EntryPointConstructorParams) { - if (isSuperArgs(args)) { - super(...args); - } else { - super(_abi, _bytecode, args[0]); - } - } - - override deploy( - overrides?: Overrides & { from?: string } - ): Promise { - return super.deploy(overrides || {}) as Promise; - } - override getDeployTransaction( - overrides?: Overrides & { from?: string } - ): TransactionRequest { - return super.getDeployTransaction(overrides || {}); - } - override attach(address: string): EntryPoint { - return super.attach(address) as EntryPoint; - } - override connect(signer: Signer): EntryPoint__factory { - return super.connect(signer) as EntryPoint__factory; - } - - static readonly bytecode = _bytecode; +export class IEntryPoint__factory { static readonly abi = _abi; - static createInterface(): EntryPointInterface { - return new utils.Interface(_abi) as EntryPointInterface; + static createInterface(): IEntryPointInterface { + return new utils.Interface(_abi) as IEntryPointInterface; } static connect( address: string, signerOrProvider: Signer | Provider - ): EntryPoint { - return new Contract(address, _abi, signerOrProvider) as EntryPoint; + ): IEntryPoint { + return new Contract(address, _abi, signerOrProvider) as IEntryPoint; } } diff --git a/packages/types/src/executor/contracts/factories/IAccount__factory.ts b/packages/types/src/executor/contracts/factories/IAccount__factory.ts index d039c550..2078b0db 100644 --- a/packages/types/src/executor/contracts/factories/IAccount__factory.ts +++ b/packages/types/src/executor/contracts/factories/IAccount__factory.ts @@ -3,8 +3,11 @@ /* eslint-disable */ import { Contract, Signer, utils } from "ethers"; -import { Provider } from "@ethersproject/providers"; -import type { IAccount, IAccountInterface } from "../IAccount"; +import type { Provider } from "@ethersproject/providers"; +import type { + IAccount, + IAccountInterface, +} from "../IAccount"; const _abi = [ { @@ -76,11 +79,6 @@ const _abi = [ name: "userOpHash", type: "bytes32", }, - { - internalType: "address", - name: "aggregator", - type: "address", - }, { internalType: "uint256", name: "missingAccountFunds", @@ -91,14 +89,14 @@ const _abi = [ outputs: [ { internalType: "uint256", - name: "sigTimeRange", + name: "validationData", type: "uint256", }, ], stateMutability: "nonpayable", type: "function", }, -]; +] as const; export class IAccount__factory { static readonly abi = _abi; diff --git a/packages/types/src/executor/contracts/factories/SenderCreator__factory.ts b/packages/types/src/executor/contracts/factories/SenderCreator__factory.ts new file mode 100644 index 00000000..2cc0cb37 --- /dev/null +++ b/packages/types/src/executor/contracts/factories/SenderCreator__factory.ts @@ -0,0 +1,81 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import { Signer, utils, Contract, ContractFactory, Overrides } from "ethers"; +import type { Provider, TransactionRequest } from "@ethersproject/providers"; +import type { + SenderCreator, + SenderCreatorInterface, +} from "../SenderCreator"; + +const _abi = [ + { + inputs: [ + { + internalType: "bytes", + name: "initCode", + type: "bytes", + }, + ], + name: "createSender", + outputs: [ + { + internalType: "address", + name: "sender", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, +] as const; + +const _bytecode = + "0x608060405234801561001057600080fd5b50610213806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063570e1a3614610030575b600080fd5b61004361003e3660046100f9565b61006c565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b60008061007c601482858761016b565b61008591610195565b60601c90506000610099846014818861016b565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092018290525084519495509360209350849250905082850182875af190506000519350806100f057600093505b50505092915050565b6000806020838503121561010c57600080fd5b823567ffffffffffffffff8082111561012457600080fd5b818501915085601f83011261013857600080fd5b81358181111561014757600080fd5b86602082850101111561015957600080fd5b60209290920196919550909350505050565b6000808585111561017b57600080fd5b8386111561018857600080fd5b5050820193919092039150565b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000081358181169160148510156101d55780818660140360031b1b83161692505b50509291505056fea2646970667358221220bb601edbce3d32fb4f7fc26d1b5dbc04e0f66210945754b8dfafdc97fa3d759e64736f6c634300080f0033"; + +type SenderCreatorConstructorParams = + | [signer?: Signer] + | ConstructorParameters; + +const isSuperArgs = ( + xs: SenderCreatorConstructorParams +): xs is ConstructorParameters => xs.length > 1; + +export class SenderCreator__factory extends ContractFactory { + constructor(...args: SenderCreatorConstructorParams) { + if (isSuperArgs(args)) { + super(...args); + } else { + super(_abi, _bytecode, args[0]); + } + } + + override deploy( + overrides?: Overrides & { from?: string } + ): Promise { + return super.deploy(overrides || {}) as Promise; + } + override getDeployTransaction( + overrides?: Overrides & { from?: string } + ): TransactionRequest { + return super.getDeployTransaction(overrides || {}); + } + override attach(address: string): SenderCreator { + return super.attach(address) as SenderCreator; + } + override connect(signer: Signer): SenderCreator__factory { + return super.connect(signer) as SenderCreator__factory; + } + + static readonly bytecode = _bytecode; + static readonly abi = _abi; + static createInterface(): SenderCreatorInterface { + return new utils.Interface(_abi) as SenderCreatorInterface; + } + static connect( + address: string, + signerOrProvider: Signer | Provider + ): SenderCreator { + return new Contract(address, _abi, signerOrProvider) as SenderCreator; + } +} diff --git a/packages/types/src/executor/contracts/factories/index.ts b/packages/types/src/executor/contracts/factories/index.ts index a8374ac4..2ab27c4b 100644 --- a/packages/types/src/executor/contracts/factories/index.ts +++ b/packages/types/src/executor/contracts/factories/index.ts @@ -1,7 +1,9 @@ /* eslint-disable camelcase */ -export { EntryPoint__factory } from "./EntryPoint__factory"; +export { IEntryPoint__factory } from "./EntryPoint__factory"; export { EtherspotAccountFactory__factory } from "./EtherspotAccountFactory__factory"; export { EtherspotAccount__factory } from "./EtherspotAccount__factory"; export { IAccount__factory } from "./IAccount__factory"; export { IAggregatedAccount__factory } from "./IAggregatedAccount__factory"; export { IAggregator__factory } from "./IAggregator__factory"; +export { IPaymaster__factory } from "./IPaymaster__factory"; +export { SenderCreator__factory } from "./SenderCreator__factory"; diff --git a/packages/types/src/executor/contracts/index.ts b/packages/types/src/executor/contracts/index.ts index c5884102..4ae72259 100644 --- a/packages/types/src/executor/contracts/index.ts +++ b/packages/types/src/executor/contracts/index.ts @@ -1,7 +1,8 @@ -export { EntryPoint } from "./EntryPoint"; +export { IEntryPoint } from "./EntryPoint"; export { EtherspotAccount } from "./EtherspotAccount"; export { IAccount } from "./IAccount"; export { IAggregatedAccount } from "./IAggregatedAccount"; export { IAggregator } from "./IAggregator"; export { IPaymaster } from "./IPaymaster"; +export { SenderCreator } from "./SenderCreator"; export * from "./factories"; diff --git a/packages/types/src/executor/index.ts b/packages/types/src/executor/index.ts index 0e6bb4d7..2041c8cf 100644 --- a/packages/types/src/executor/index.ts +++ b/packages/types/src/executor/index.ts @@ -8,3 +8,5 @@ export enum ReputationStatus { throttled = "throttled", banned = "banned", } + +export * from "./validation"; diff --git a/packages/types/src/executor/validation/index.ts b/packages/types/src/executor/validation/index.ts new file mode 100644 index 00000000..bb586d80 --- /dev/null +++ b/packages/types/src/executor/validation/index.ts @@ -0,0 +1,61 @@ +import { BigNumberish } from "ethers"; + +export interface BundlerCollectorReturn { + callsFromEntryPoint: TopLevelCallInfo[]; + keccak: string[]; + calls: Array; + logs: LogInfo[]; + debug: any[]; +} + +export interface MethodInfo { + type: string; + from: string; + to: string; + method: string; + value: any; + gas: number; +} + +export interface ExitInfo { + type: "REVERT" | "RETURN"; + gasUsed: number; + data: string; +} + +export interface TopLevelCallInfo { + topLevelMethodSig: string; + topLevelTargetAddress: string; + opcodes: { [opcode: string]: number }; + access: { [address: string]: AccessInfo }; + 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 }; + // count of writes. + writes: { [slot: string]: number }; +} + +export interface LogInfo { + topics: string[]; + data: string; +} + +export interface CallEntry { + to: string; + from: string; + type: string; // call opcode + method: string; // parsed method, or signash if unparsed + revert?: any; // parsed output from REVERT + return?: any; // parsed method output. + value?: BigNumberish; +} diff --git a/packages/types/src/networks/networks.ts b/packages/types/src/networks/networks.ts index d06ab560..50e2cde9 100644 --- a/packages/types/src/networks/networks.ts +++ b/packages/types/src/networks/networks.ts @@ -26,12 +26,23 @@ export type NetworkName = | "neonDevnet" | "optimismGoerli" | "dev" + | "base" | "baseGoerli" | "sepolia" | "chiado" | "polygonzkevm" | "mantle" - | "mantleTestnet"; + | "mantleTestnet" + | "linea" + | "lineaTestnet" + | "scroll" + | "scrollSepolia" + | "scrollAlpha" + | "flare" + | "flareCoston" + | "flareCoston2" + | "bifrost" + | "bifrostTestnet"; export const networkNames: NetworkName[] = [ "mainnet", @@ -61,12 +72,23 @@ export const networkNames: NetworkName[] = [ "neonDevnet", "optimismGoerli", "dev", + "base", "baseGoerli", "sepolia", "chiado", "polygonzkevm", "mantle", "mantleTestnet", + "linea", + "lineaTestnet", + "scroll", + "scrollSepolia", + "scrollAlpha", + "flare", + "flareCoston", + "flareCoston2", + "bifrost", + "bifrostTestnet", ]; export const NETWORK_NAME_TO_CHAIN_ID: { @@ -99,12 +121,23 @@ export const NETWORK_NAME_TO_CHAIN_ID: { neonDevnet: 245022926, optimismGoerli: 420, dev: 1337, + base: 8453, baseGoerli: 84531, sepolia: 11155111, chiado: 10200, polygonzkevm: 1442, mantle: 5000, mantleTestnet: 5001, + linea: 59144, + lineaTestnet: 59140, + scroll: 534352, + scrollSepolia: 534351, + scrollAlpha: 534353, + flare: 14, + flareCoston: 16, + flareCoston2: 114, + bifrost: 3068, + bifrostTestnet: 49088, }; export const CHAIN_ID_TO_NETWORK_NAME = Object.fromEntries( diff --git a/skandha b/skandha index 0f47a32e..579dc5b4 100755 --- a/skandha +++ b/skandha @@ -2,6 +2,6 @@ # Convenience script to run the etherspot binary from built source # -# ./skandha --network goerli +# ./skandha --redirectRpc node --experimental-specifier-resolution=node ./packages/cli/bin/skandha.js "$@" \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index ebb7ff7e..5664c671 100644 --- a/yarn.lock +++ b/yarn.lock @@ -628,7 +628,7 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/abstract-provider@5.7.0", "@ethersproject/abstract-provider@^5.7.0": +"@ethersproject/abstract-provider@5.7.0", "@ethersproject/abstract-provider@^5.6.1", "@ethersproject/abstract-provider@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz" integrity sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw== @@ -641,7 +641,7 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/web" "^5.7.0" -"@ethersproject/abstract-signer@5.7.0", "@ethersproject/abstract-signer@^5.7.0": +"@ethersproject/abstract-signer@5.7.0", "@ethersproject/abstract-signer@^5.6.2", "@ethersproject/abstract-signer@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz" integrity sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ== @@ -797,14 +797,14 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/sha2" "^5.7.0" -"@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.7.0": +"@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.6.0", "@ethersproject/properties@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz" integrity sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw== dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.0": +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.6.8", "@ethersproject/providers@^5.7.0": version "5.7.2" resolved "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz" integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== @@ -888,7 +888,7 @@ "@ethersproject/constants" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.7.0": +"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.6.2", "@ethersproject/transactions@^5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz" integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== @@ -933,7 +933,7 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/wordlists" "^5.7.0" -"@ethersproject/web@5.7.1", "@ethersproject/web@^5.7.0": +"@ethersproject/web@5.7.1", "@ethersproject/web@^5.6.1", "@ethersproject/web@^5.7.0": version "5.7.1" resolved "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz" integrity sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w== @@ -2476,6 +2476,40 @@ private-ip "^3.0.0" uint8arraylist "^2.3.2" +"@mantleio/contracts@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@mantleio/contracts/-/contracts-0.2.1.tgz#0a9810c28a63ae86e267bcced88ab07c13dce5fb" + integrity sha512-NqWUO8Vhu2OVA+pH+k761uWwssQmKsSBRA1vO9wrKUvlHsWN50DL/wrIDHZBksDwcjqOFGeIxWmt9QLwndhAjw== + dependencies: + "@ethersproject/abstract-provider" "^5.6.1" + "@ethersproject/abstract-signer" "^5.6.2" + "@mantleio/core-utils" "0.1.0" + +"@mantleio/core-utils@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@mantleio/core-utils/-/core-utils-0.1.0.tgz#8f6f93e930b364eddf201bfe9a5d648943e8b7f9" + integrity sha512-6v/CuKe8W3UVYVn6RUR6pbrMbKDcdrUoj24wCuwM4JFbPHzpTqUSnSXDHrDs3LJpWLxsptynNdIGw90wzCKpdg== + dependencies: + "@ethersproject/abstract-provider" "^5.6.1" + "@ethersproject/properties" "^5.6.0" + "@ethersproject/providers" "^5.6.8" + "@ethersproject/transactions" "^5.6.2" + "@ethersproject/web" "^5.6.1" + bufio "^1.0.7" + chai "^4.3.4" + ethers "^5.6.8" + +"@mantleio/sdk@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@mantleio/sdk/-/sdk-0.2.1.tgz#b1f1331cfc166ff5fbe4da565078952eb4611c52" + integrity sha512-bPC3eHEsRHSPAVv4EGC6lY43btiMaEK0e39yArz9LBtub8EkxyokDnD+XLdaeQrL8flucTH5DRcZAanqn9bq4g== + dependencies: + "@mantleio/contracts" "0.2.1" + "@mantleio/core-utils" "0.1.0" + lodash "^4.17.21" + merkletreejs "^0.2.27" + rlp "^2.2.7" + "@multiformats/mafmt@^11.0.2", "@multiformats/mafmt@^11.0.3": version "11.1.2" resolved "https://registry.npmjs.org/@multiformats/mafmt/-/mafmt-11.1.2.tgz" @@ -3365,7 +3399,7 @@ "@types/json5@^0.0.29": version "0.0.29" - resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== "@types/long@^4.0.1": @@ -5492,7 +5526,7 @@ ethereumjs-util@^7.1.0: ethereum-cryptography "^0.1.3" rlp "^2.2.4" -ethers@5.7.2, ethers@^5.1.0, ethers@^5.7.0: +ethers@5.7.2, ethers@^5.1.0, ethers@^5.6.8, ethers@^5.7.0: version "5.7.2" resolved "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==