-
Notifications
You must be signed in to change notification settings - Fork 144
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test(smart-contracts): migrate one test suite to use prool instance (#…
…858) * test: add prool instances setup * chore: update test script to use vitest * test(smart-contracts): migrate one test suite to use prool instance
- Loading branch information
Showing
15 changed files
with
495 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,3 +33,5 @@ site/**/*.js | |
**/userop.json | ||
|
||
vocs.config.tsx.timestamp-*.mjs | ||
|
||
bin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { rundlerBinaryPath } from "./src/constants"; | ||
import * as instances from "./src/instances"; | ||
import { | ||
cleanupRundler, | ||
downloadLatestRundlerRelease, | ||
isRundlerInstalled, | ||
} from "./src/rundler"; | ||
|
||
export default async function () { | ||
if (!(await isRundlerInstalled(rundlerBinaryPath))) { | ||
await downloadLatestRundlerRelease(rundlerBinaryPath); | ||
} | ||
|
||
const shutdown = await Promise.all( | ||
Object.values(instances).map((instance) => instance.start()) | ||
); | ||
|
||
return async () => { | ||
await Promise.all(shutdown.map((stop) => stop())); | ||
await cleanupRundler(rundlerBinaryPath); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"name": "local-vitest", | ||
"version": "0.0.0", | ||
"private": true, | ||
"type": "module", | ||
"devDependencies": { | ||
"prool": "^0.0.15", | ||
"typescript-template": "*" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,18 @@ | ||
import dotenv from "dotenv"; | ||
import fetch from "node-fetch"; | ||
import { setIntervalMining } from "viem/actions"; | ||
import { afterAll, beforeAll } from "vitest"; | ||
import * as instances from "./src/instances.js"; | ||
|
||
const client = instances.anvilArbSepolia.getClient(); | ||
|
||
dotenv.config(); | ||
|
||
// @ts-expect-error wut | ||
// @ts-expect-error this does exist but ts is not liking it | ||
global.fetch = fetch; | ||
|
||
beforeAll(async () => { | ||
await setIntervalMining(client, { interval: 0 }); | ||
}, 60_000); | ||
|
||
afterAll(async () => {}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { join } from "path"; | ||
|
||
export const rundlerBinaryPath = join(__dirname, "../bin/rundler"); | ||
|
||
export const poolId = Number(process.env.VITEST_POOL_ID ?? 1); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import getPort from "get-port"; | ||
import { createServer } from "prool"; | ||
import { anvil, rundler } from "prool/instances"; | ||
import { createClient, http, type Chain, type ClientConfig } from "viem"; | ||
import { arbitrumSepolia } from "viem/chains"; | ||
import { split } from "../../aa-sdk/core/src/transport/split"; | ||
import { poolId, rundlerBinaryPath } from "./constants"; | ||
|
||
export const anvilArbSepolia = defineInstance({ | ||
chain: arbitrumSepolia, | ||
forkUrl: "https://arbitrum-sepolia-rpc.publicnode.com", | ||
anvilPort: 8545, | ||
bundlerPort: 8645, | ||
}); | ||
|
||
type DefineInstanceParams = { | ||
chain: Chain; | ||
forkUrl: string; | ||
anvilPort: number; | ||
bundlerPort: number; | ||
}; | ||
|
||
const bundlerMethods = [ | ||
"eth_sendUserOperation", | ||
"eth_estimateUserOperationGas", | ||
"eth_getUserOperationReceipt", | ||
"eth_getUserOperationByHash", | ||
"eth_supportedEntryPoints", | ||
]; | ||
|
||
function defineInstance(params: DefineInstanceParams) { | ||
const { anvilPort, bundlerPort, forkUrl, chain: chain_ } = params; | ||
const rpcUrls = { | ||
bundler: `http://127.0.0.1:${bundlerPort}`, | ||
anvil: `http://127.0.0.1:${anvilPort}`, | ||
}; | ||
|
||
const chain = { | ||
...chain_, | ||
name: `${chain_.name} (Local)`, | ||
rpcUrls: { | ||
default: { | ||
http: [rpcUrls.anvil], | ||
}, | ||
}, | ||
} as const satisfies Chain; | ||
|
||
const clientConfig = { | ||
chain, | ||
transport(args) { | ||
const { | ||
config, | ||
request: request_, | ||
value, | ||
} = split({ | ||
overrides: [ | ||
{ | ||
methods: bundlerMethods, | ||
transport: http(rpcUrls.bundler + `/${poolId}`), | ||
}, | ||
], | ||
fallback: http(rpcUrls.anvil + `/${poolId}`), | ||
})(args); | ||
|
||
return { | ||
config, | ||
request(params) { | ||
// Here we can add further custom handling for certain methods, or we can do it above in the split transport config | ||
return request_(params); | ||
}, | ||
value, | ||
}; | ||
}, | ||
} as const satisfies ClientConfig; | ||
|
||
const anvilServer = createServer({ | ||
instance: anvil({ | ||
forkUrl: forkUrl, | ||
noMining: true, | ||
}), | ||
port: anvilPort, | ||
}); | ||
|
||
const bundlerServer = createServer({ | ||
instance: (key) => | ||
rundler({ | ||
binary: rundlerBinaryPath, | ||
nodeHttp: rpcUrls.anvil + `/${key}`, | ||
}), | ||
port: bundlerPort, | ||
}); | ||
|
||
return { | ||
chain, | ||
clientConfig, | ||
anvilServer, | ||
bundlerServer, | ||
getClient() { | ||
return createClient({ | ||
...clientConfig, | ||
chain, | ||
transport: clientConfig.transport, | ||
}).extend(() => ({ mode: "anvil" })); | ||
}, | ||
async restart() { | ||
await fetch(`${rpcUrls.anvil}/${poolId}/restart`); | ||
await bundlerServer.stop(); | ||
await bundlerServer.start(); | ||
}, | ||
async start() { | ||
// We do this because it's possible we're running all workspaces at the same time | ||
// and we don't want to start the servers multiple times | ||
// This still gives us isolation because each workspace should have its own pool id | ||
if ((await getPort({ port: anvilPort })) === anvilPort) { | ||
await anvilServer.start(); | ||
} | ||
if ((await getPort({ port: bundlerPort })) === bundlerPort) { | ||
await bundlerServer.start(); | ||
} | ||
|
||
return async () => { | ||
await bundlerServer.stop(); | ||
await anvilServer.stop(); | ||
}; | ||
}, | ||
async stop() { | ||
await anvilServer.stop(); | ||
await bundlerServer.stop(); | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import * as fs from "node:fs"; | ||
import * as path from "node:path"; | ||
import { pipeline } from "node:stream"; | ||
import { promisify } from "node:util"; | ||
import * as zlib from "node:zlib"; | ||
import * as tar from "tar"; | ||
|
||
const streamPipeline = promisify(pipeline); | ||
|
||
export async function isRundlerInstalled(rundlerPath: string) { | ||
try { | ||
await fs.promises.access(rundlerPath, fs.constants.F_OK); | ||
return true; | ||
} catch (e) { | ||
return false; | ||
} | ||
} | ||
|
||
export async function cleanupRundler(rundlerPath: string) { | ||
await fs.promises.rm(rundlerPath, { force: true }); | ||
} | ||
|
||
export async function downloadLatestRundlerRelease( | ||
filePath: string, | ||
version = "v0.2.2" | ||
) { | ||
const repoUrl = | ||
"https://api.github.com/repos/alchemyplatform/rundler/releases"; | ||
const { arch, platform } = process; | ||
|
||
try { | ||
// Get the list of releases from GitHub API | ||
const releasesResponse = await fetch(repoUrl); | ||
if (!releasesResponse.ok) { | ||
throw new Error( | ||
`Failed to fetch releases: ${releasesResponse.statusText}` | ||
); | ||
} | ||
const releases: any = await releasesResponse.json(); | ||
|
||
if (releases.length === 0) { | ||
return; | ||
} | ||
|
||
// Get the latest release | ||
const latestRelease = releases.find((x: any) => x.tag_name === version); | ||
if (!latestRelease) { | ||
throw new Error(`Failed to find release with tag ${version}`); | ||
} | ||
|
||
const asset = latestRelease.assets | ||
.filter((x: any) => (x.name as string).endsWith(".gz")) | ||
.find((x: any) => { | ||
return ( | ||
x.name.includes(platform) && | ||
(x.name.includes(arch) || | ||
(arch === "arm64" && | ||
platform === "darwin" && | ||
x.name.includes("aarch64"))) | ||
); | ||
}); | ||
|
||
if (!asset) { | ||
return; | ||
} | ||
|
||
const assetUrl = asset.browser_download_url; | ||
|
||
// Download the asset | ||
const assetResponse = await fetch(assetUrl); | ||
if (!assetResponse.ok) { | ||
throw new Error(`Failed to download asset: ${assetResponse.statusText}`); | ||
} | ||
|
||
if (!assetResponse.body) { | ||
throw new Error("Github request returned an empty body"); | ||
} | ||
|
||
// Save the downloaded file | ||
const extractPath = path.resolve(filePath, ".."); | ||
if (!(await isRundlerInstalled(extractPath))) { | ||
await fs.promises.mkdir(extractPath, { recursive: true }); | ||
} | ||
|
||
const gunzipStream = zlib.createGunzip(); | ||
const tarStream = tar.extract({ | ||
cwd: extractPath, | ||
}); | ||
await streamPipeline(assetResponse.body, gunzipStream, tarStream); | ||
} catch (error) { | ||
throw new Error(`Failed to download the latest release. ${error}`); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "typescript-template/base.json" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.