From f3ab2cd06e3a447df660b802a0f254b5dc3c82d1 Mon Sep 17 00:00:00 2001 From: Garvit Khatri Date: Wed, 5 Jun 2024 12:41:11 +0530 Subject: [PATCH] add bundler instance for prool --- .github/workflows/canary.yml | 1 - package.json | 2 +- alto => packages/alto/alto | 0 packages/alto/index.ts | 116 ++++++++++++++++++ packages/alto/package.json | 5 +- packages/tests-e2e/package.json | 1 + .../tests/eth_getUserOperationReceipt.test.ts | 21 +++- pnpm-lock.yaml | 6 + 8 files changed, 147 insertions(+), 5 deletions(-) rename alto => packages/alto/alto (100%) create mode 100644 packages/alto/index.ts diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index f6362fe0..6a357a84 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -1,7 +1,6 @@ name: Release (Canary) on: push: - branches: [main] workflow_dispatch: jobs: diff --git a/package.json b/package.json index 76204c86..f4291d18 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "clean": "rm -rf ./packages/alto/lib ./src/*.tsbuildinfo", "clean-modules": "rm -rf ./packages/alto/node_modules node_modules", "build": "pnpm -r run build", - "start": "node ./packages/alto/lib/cli/alto.js run", + "start": "pnpm run --filter @pimlico/alto start", "dev": "pnpm -r run dev", "test": "pnpm -r --workspace-concurrency 1 test --verbose=true", "test:spec": "./packages/spec-tests/run-spec-tests.sh", diff --git a/alto b/packages/alto/alto similarity index 100% rename from alto rename to packages/alto/alto diff --git a/packages/alto/index.ts b/packages/alto/index.ts new file mode 100644 index 00000000..3e649d87 --- /dev/null +++ b/packages/alto/index.ts @@ -0,0 +1,116 @@ +import { type ResultPromise, execa } from "execa" +import { defineInstance } from "prool" + +export type AltoParameters = { + rpcUrl: string + entrypoints: string[] + executorPrivateKey: string + host?: string + port?: number + path?: string +} + +export function toFlagCase(key: string) { + return `--${key.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)}` +} + +export function toArgs(options: { + [key: string]: + | Record + | string + | boolean + | number + | bigint + | undefined +}) { + return Object.entries(options).flatMap(([key, value]) => { + if (value === undefined) { + return [] + } + + if (typeof value === "object" && value !== null) { + return Object.entries(value).flatMap(([subKey, subValue]) => { + if (subValue === undefined) { + return [] + } + const flag = toFlagCase(key) + const value = `${subKey}: ${subValue}` + return [flag, value] + }) + } + + const flag = toFlagCase(key) + + if (value === false) { + return [] + } + if (value === true) { + return [flag] + } + + const stringified = value.toString() + if (stringified === "") { + return [flag] + } + + return [flag, stringified] + }) +} + +export const bundler = defineInstance((parameters: AltoParameters) => { + const { path = "./alto", ...args } = parameters + + let process: ResultPromise<{ cleanup: true; reject: false }> + + return { + _internal: {}, + host: args.host || "localhost", + name: "alto", + port: args.port ?? 4337, + start: async ({ emitter, port = args.port, status }) => { + const { promise, resolve, reject } = Promise.withResolvers() + + const commandArgs = toArgs({ + ...args, + port, + entrypoints: args.entrypoints.join(",") + }) + + process = execa(path, commandArgs, { + cleanup: true, + reject: false + }) + + process.stdout.on("data", (data) => { + const message = data.toString() + emitter.emit("message", message) + emitter.emit("stdout", message) + if (message.includes("Listening on")) { + emitter.emit("listening") + resolve() + } + }) + process.stderr.on("data", async (data) => { + const message = data.toString() + emitter.emit("message", message) + emitter.emit("stderr", message) + await stop() + reject(new Error(`Failed to start anvil: ${data.toString()}`)) + }) + process.on("close", () => process.removeAllListeners()) + process.on("exit", (code, signal) => { + emitter.emit("exit", code, signal) + + if (!code) { + process.removeAllListeners() + if (status === "starting") { + reject(new Error("Failed to start anvil: exited.")) + } + } + }) + + return promise + }, + stop: async () => {} + } +}) diff --git a/packages/alto/package.json b/packages/alto/package.json index 847e99d3..fe91df25 100644 --- a/packages/alto/package.json +++ b/packages/alto/package.json @@ -14,6 +14,7 @@ "lib/**/*.d.ts", "lib/**/*.js", "lib/**/*.js.map", + "./alto", "*.d.ts", "*.js" ], @@ -22,7 +23,8 @@ "dev": "nodemon --exec DOTENV_CONFIG_PATH=$(pwd)/../.env ts-node -r tsconfig-paths/register cli/alto.ts run", "test": "test -d test && mocha test/**/*.test.ts --exit || echo 'No test folder found. Skipping tests.'", "lint": "eslint ./**/*.ts", - "lint:fix": "eslint ./**/*.ts --fix" + "lint:fix": "eslint ./**/*.ts --fix", + "start": "node ./lib/cli/alto.js run" }, "exports": { ".": { @@ -48,6 +50,7 @@ "abitype": "^0.8.0", "async-mutex": "^0.4.0", "dotenv": "^16.0.3", + "execa": "^9.1.0", "fastify": "^4.25.2", "fastify-cors": "3.0.3", "opentelemetry-instrumentation-fetch-node": "^1.1.2", diff --git a/packages/tests-e2e/package.json b/packages/tests-e2e/package.json index 7968c6df..c9e6107e 100644 --- a/packages/tests-e2e/package.json +++ b/packages/tests-e2e/package.json @@ -9,6 +9,7 @@ "dependencies": { "@types/node": "^18.16.3", "permissionless": "^0.1.25", + "prool": "^0.0.4", "ts-node": "^10.9.2", "viem": "^2.9.5", "vitest": "^1.6.0", diff --git a/packages/tests-e2e/tests/eth_getUserOperationReceipt.test.ts b/packages/tests-e2e/tests/eth_getUserOperationReceipt.test.ts index fa0f4d06..22c629ef 100644 --- a/packages/tests-e2e/tests/eth_getUserOperationReceipt.test.ts +++ b/packages/tests-e2e/tests/eth_getUserOperationReceipt.test.ts @@ -1,7 +1,7 @@ import { test, describe, expect, beforeAll, beforeEach } from "vitest" import { ENTRYPOINT_ADDRESS_V06, - BundlerClient, + type BundlerClient, ENTRYPOINT_ADDRESS_V07 } from "permissionless" import { @@ -9,13 +9,15 @@ import { getBundlerClient, getSmartAccountClient } from "../src/utils" -import { Address, Hex } from "viem" +import type { Address, Hex } from "viem" import { deployRevertingContract, decodeRevert, getRevertCall } from "../src/revertingContract" import { deployPaymaster } from "../src/testPaymaster" +import { bundler } from "@pimlico/alto" +import { createServer } from "prool" describe.each([ { entryPoint: ENTRYPOINT_ADDRESS_V06, version: "v0.6" }, @@ -29,6 +31,21 @@ describe.each([ revertingContract = await deployRevertingContract() paymaster = await deployPaymaster(entryPoint) bundlerClient = getBundlerClient(entryPoint) + + const bundlerInstance = bundler({ + rpcUrl: "http://127.0.0.1:8545", + entrypoints: [ENTRYPOINT_ADDRESS_V06, ENTRYPOINT_ADDRESS_V07], + executorPrivateKey: + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + }) + + bundlerInstance.on("message", console.log) + + const bundlerServer = createServer({ + instance: bundlerInstance + }) + + await bundlerServer.start() }) beforeEach(async () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7fb6df9..427df704 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -101,6 +101,9 @@ importers: dotenv: specifier: ^16.0.3 version: 16.4.5 + execa: + specifier: ^9.1.0 + version: 9.1.0 fastify: specifier: ^4.25.2 version: 4.27.0 @@ -168,6 +171,9 @@ importers: permissionless: specifier: ^0.1.25 version: 0.1.30(viem@2.13.5) + prool: + specifier: ^0.0.4 + version: 0.0.4 ts-node: specifier: ^10.9.2 version: 10.9.2(@swc/core@1.3.105)(@types/node@18.16.3)(typescript@5.3.3)