diff --git a/src/createRollup.integration.test.ts b/src/createRollup.integration.test.ts index 426bbfb4..58ce600a 100644 --- a/src/createRollup.integration.test.ts +++ b/src/createRollup.integration.test.ts @@ -9,10 +9,9 @@ import { type PrivateKeyAccountWithPrivateKey, } from './testHelpers'; import { createRollupFetchTransactionHash } from './createRollupFetchTransactionHash'; -import { getInitializedAnvilTestStackEnv } from './integrationTestHelpers/anvilHarness'; -import { isAnvilIntegrationTestMode } from './integrationTestHelpers/injectedMode'; +import { getAnvilTestStack, isAnvilTestMode } from './integrationTestHelpers/injectedMode'; -const env = isAnvilIntegrationTestMode() ? getInitializedAnvilTestStackEnv() : undefined; +const env = isAnvilTestMode() ? getAnvilTestStack() : undefined; const parentChainPublicClient = createPublicClient({ chain: env ? env.l2.chain : nitroTestnodeL2, diff --git a/src/decorators/arbAggregatorActions.integration.test.ts b/src/decorators/arbAggregatorActions.integration.test.ts index 5e39563f..c36def75 100644 --- a/src/decorators/arbAggregatorActions.integration.test.ts +++ b/src/decorators/arbAggregatorActions.integration.test.ts @@ -5,26 +5,30 @@ import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; import { nitroTestnodeL2 } from '../chains'; import { arbAggregatorActions } from './arbAggregatorActions'; import { getNitroTestnodePrivateKeyAccounts } from '../testHelpers'; +import { getAnvilTestStack, isAnvilTestMode } from '../integrationTestHelpers/injectedMode'; -const testnodeAccounts = getNitroTestnodePrivateKeyAccounts(); -const l2RollupOwner = testnodeAccounts.l2RollupOwner; -const randomAccount = privateKeyToAccount(generatePrivateKey()); +const env = isAnvilTestMode() ? getAnvilTestStack() : undefined; -const nitroTestnodeL2Client = createPublicClient({ - chain: nitroTestnodeL2, +const l2Client = createPublicClient({ + chain: env ? env.l2.chain : nitroTestnodeL2, transport: http(), }).extend(arbAggregatorActions); +const l2RollupOwner = env + ? env.l2.accounts.deployer + : getNitroTestnodePrivateKeyAccounts().l2RollupOwner; +const randomAccount = privateKeyToAccount(generatePrivateKey()); + describe('ArgAggregator decorator tests', () => { it('successfully fetches the batch posters and the fee collectors', async () => { - const batchPosters = await nitroTestnodeL2Client.arbAggregatorReadContract({ + const batchPosters = await l2Client.arbAggregatorReadContract({ functionName: 'getBatchPosters', }); expect(batchPosters).toHaveLength(2); expect(batchPosters[0]).toEqual('0xA4b000000000000000000073657175656e636572'); - const batchPosterFeeCollector = await nitroTestnodeL2Client.arbAggregatorReadContract({ + const batchPosterFeeCollector = await l2Client.arbAggregatorReadContract({ functionName: 'getFeeCollector', args: [batchPosters[0]], }); @@ -34,25 +38,26 @@ describe('ArgAggregator decorator tests', () => { it('succesfully updates the fee collector of a batch poster', async () => { // Get the batch posters - const batchPosters = await nitroTestnodeL2Client.arbAggregatorReadContract({ + const batchPosters = await l2Client.arbAggregatorReadContract({ functionName: 'getBatchPosters', }); // Set the fee collector of the batch poster to the random address - const setFeeCollectorTransactionRequest = - await nitroTestnodeL2Client.arbAggregatorPrepareTransactionRequest({ + const setFeeCollectorTransactionRequest = await l2Client.arbAggregatorPrepareTransactionRequest( + { functionName: 'setFeeCollector', args: [batchPosters[1], randomAccount.address], upgradeExecutor: false, account: l2RollupOwner.address, - }); - const txHash = await nitroTestnodeL2Client.sendRawTransaction({ + }, + ); + const txHash = await l2Client.sendRawTransaction({ serializedTransaction: await l2RollupOwner.signTransaction(setFeeCollectorTransactionRequest), }); - await nitroTestnodeL2Client.waitForTransactionReceipt({ hash: txHash }); + await l2Client.waitForTransactionReceipt({ hash: txHash }); // Check the fee collector has changed - const batchPosterFeeCollector = await nitroTestnodeL2Client.arbAggregatorReadContract({ + const batchPosterFeeCollector = await l2Client.arbAggregatorReadContract({ functionName: 'getFeeCollector', args: [batchPosters[1]], }); diff --git a/src/integrationTestHelpers/anvilHarness.ts b/src/integrationTestHelpers/anvilHarness.ts index 9717444f..10fafab6 100644 --- a/src/integrationTestHelpers/anvilHarness.ts +++ b/src/integrationTestHelpers/anvilHarness.ts @@ -8,6 +8,7 @@ import { createPublicClient, createWalletClient, defineChain, + Hex, http, parseEther, parseGwei, @@ -49,24 +50,40 @@ import { import { type RpcCachingProxy, startRpcCachingProxy } from './rpcCachingProxy'; import type { CustomTimingParams, PrivateKeyAccountWithPrivateKey } from '../testHelpers'; -export type AnvilTestStack = { +type RegisteredParentChain = Parameters[0]; + +type BaseStack = { l2: { rpcUrl: string; chain: Chain; - accounts: { - deployer: PrivateKeyAccountWithPrivateKey; - }; + accounts: L2Accounts; timingParams: CustomTimingParams; rollupCreatorVersion: RollupCreatorSupportedVersion; }; l3: { - accounts: { - tokenBridgeDeployer: PrivateKeyAccountWithPrivateKey; - }; + accounts: L3Accounts; nativeToken: Address; }; }; +export type AnvilTestStack = BaseStack< + { + deployer: PrivateKeyAccountWithPrivateKey; + }, + { + tokenBridgeDeployer: PrivateKeyAccountWithPrivateKey; + } +>; + +export type InjectedAnvilTestStack = BaseStack< + { + deployerPrivateKey: Hex; + }, + { + tokenBridgeDeployerPrivateKey: Hex; + } +>; + let envPromise: Promise | undefined; let initializedEnv: AnvilTestStack | undefined; let runtimeDir: string | undefined; @@ -83,6 +100,46 @@ function prepareNitroRuntimeDir(runtimeDir: string) { chmodSync(join(runtimeDir, 'nitro-data'), 0o777); } +export function dehydrateAnvilTestStack(env: AnvilTestStack): InjectedAnvilTestStack { + return { + ...env, + l2: { + ...env.l2, + accounts: { + deployerPrivateKey: env.l2.accounts.deployer.privateKey, + }, + }, + l3: { + ...env.l3, + accounts: { + tokenBridgeDeployerPrivateKey: env.l3.accounts.tokenBridgeDeployer.privateKey, + }, + }, + }; +} + +export function hydrateAnvilTestStack(env: InjectedAnvilTestStack): AnvilTestStack { + const l2Chain = defineChain(env.l2.chain) as RegisteredParentChain; + registerCustomParentChain(l2Chain); + + return { + ...env, + l2: { + ...env.l2, + chain: l2Chain, + accounts: { + deployer: createAccount(env.l2.accounts.deployerPrivateKey), + }, + }, + l3: { + ...env.l3, + accounts: { + tokenBridgeDeployer: createAccount(env.l3.accounts.tokenBridgeDeployerPrivateKey), + }, + }, + }; +} + export async function setupAnvilTestStack(): Promise { if (envPromise) { return envPromise; @@ -222,11 +279,19 @@ export async function setupAnvilTestStack(): Promise { parentChainBeaconRpcUrl: sepoliaBeaconRpc, }); - if (l2ChainConfig.arbitrum.DataAvailabilityCommittee) { - delete l2NodeConfig.node?.['data-availability']?.['rpc-aggregator']; + if (l2NodeConfig.node === undefined || l2NodeConfig.node['batch-poster'] === undefined) { + throw new Error('L2 node config batch poster is undefined'); } - l2NodeConfig.node!['batch-poster']!.enable = false; + l2NodeConfig.node['batch-poster'].enable = true; + l2NodeConfig.node['batch-poster']['max-delay'] = '1s'; + l2NodeConfig.node['batch-poster']['poll-interval'] = '1s'; + l2NodeConfig.node['batch-poster']['error-delay'] = '1s'; + l2NodeConfig.node['batch-poster']['l1-block-bound'] = 'ignore'; + l2NodeConfig.node['batch-poster']['data-poster'] = { + 'wait-for-l1-finality': false, + }; + l2NodeConfig.node!.staker!.enable = false; const nodeConfigPath = join(runtimeDir, 'l2-node-config.json'); writeFileSync(nodeConfigPath, JSON.stringify(l2NodeConfig, null, 2), { mode: 0o644 }); diff --git a/src/integrationTestHelpers/globalSetup.mjs b/src/integrationTestHelpers/globalSetup.mjs index cd6a1830..38a11d95 100644 --- a/src/integrationTestHelpers/globalSetup.mjs +++ b/src/integrationTestHelpers/globalSetup.mjs @@ -1,9 +1,14 @@ -import { afterAll } from 'vitest'; +import { + dehydrateAnvilTestStack, + setupAnvilTestStack, + teardownAnvilTestStack, +} from './anvilHarness.ts'; -import { setupAnvilTestStack, teardownAnvilTestStack } from './anvilHarness.ts'; +export async function setup(project) { + const env = await setupAnvilTestStack(); + project.provide('anvilTestStack', structuredClone(dehydrateAnvilTestStack(env))); -await setupAnvilTestStack(); - -afterAll(() => { - teardownAnvilTestStack(); -}); + return async () => { + teardownAnvilTestStack(); + }; +} diff --git a/src/integrationTestHelpers/injectedMode.ts b/src/integrationTestHelpers/injectedMode.ts index 60740c87..292f25d7 100644 --- a/src/integrationTestHelpers/injectedMode.ts +++ b/src/integrationTestHelpers/injectedMode.ts @@ -1,16 +1,22 @@ import { inject } from 'vitest'; -export const INTEGRATION_TEST_MODE = 'integrationTestMode' as const; +import { + type AnvilTestStack, + type InjectedAnvilTestStack, + hydrateAnvilTestStack, +} from './anvilHarness'; + export type IntegrationTestMode = 'testnode' | 'anvil'; declare module 'vitest' { export interface ProvidedContext { integrationTestMode: IntegrationTestMode; + anvilTestStack?: InjectedAnvilTestStack; } } -export function isAnvilIntegrationTestMode(): boolean { - const value = inject(INTEGRATION_TEST_MODE); +export function isAnvilTestMode(): boolean { + const value = inject('integrationTestMode'); if (value === 'anvil') { return true; @@ -18,3 +24,13 @@ export function isAnvilIntegrationTestMode(): boolean { return false; } + +export function getAnvilTestStack(): AnvilTestStack { + const value = inject('anvilTestStack'); + + if (value === undefined) { + throw new Error('Injected Anvil test stack is unavailable.'); + } + + return hydrateAnvilTestStack(value); +} diff --git a/vitest.integration.anvil.config.ts b/vitest.integration.anvil.config.ts index f360d02b..0b105cd3 100644 --- a/vitest.integration.anvil.config.ts +++ b/vitest.integration.anvil.config.ts @@ -9,10 +9,15 @@ export default mergeConfig( integrationTestMode: 'anvil', }, // The Anvil stack boots a forked L1 and dockerized Nitro L2 in-process. + // Use worker threads so the provided Anvil env can cross the worker boundary with structured clone. + pool: 'threads', testTimeout: 45 * 60 * 1000, - setupFiles: ['./src/integrationTestHelpers/globalSetup.mjs'], + globalSetup: ['./src/integrationTestHelpers/globalSetup.mjs'], exclude: [...configDefaults.exclude], - include: ['./src/createRollup.integration.test.ts'], + include: [ + './src/createRollup.integration.test.ts', + './src/decorators/arbAggregatorActions.integration.test.ts', + ], fileParallelism: false, }, }),