Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/createRollup.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
33 changes: 19 additions & 14 deletions src/decorators/arbAggregatorActions.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]],
});
Expand All @@ -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]],
});
Expand Down
85 changes: 75 additions & 10 deletions src/integrationTestHelpers/anvilHarness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
createPublicClient,
createWalletClient,
defineChain,
Hex,
http,
parseEther,
parseGwei,
Expand Down Expand Up @@ -49,24 +50,40 @@ import {
import { type RpcCachingProxy, startRpcCachingProxy } from './rpcCachingProxy';
import type { CustomTimingParams, PrivateKeyAccountWithPrivateKey } from '../testHelpers';

export type AnvilTestStack = {
type RegisteredParentChain = Parameters<typeof registerCustomParentChain>[0];

type BaseStack<L2Accounts, L3Accounts> = {
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<AnvilTestStack> | undefined;
let initializedEnv: AnvilTestStack | undefined;
let runtimeDir: string | undefined;
Expand All @@ -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<AnvilTestStack> {
if (envPromise) {
return envPromise;
Expand Down Expand Up @@ -222,11 +279,19 @@ export async function setupAnvilTestStack(): Promise<AnvilTestStack> {
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 });
Expand Down
19 changes: 12 additions & 7 deletions src/integrationTestHelpers/globalSetup.mjs
Original file line number Diff line number Diff line change
@@ -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();
};
}
22 changes: 19 additions & 3 deletions src/integrationTestHelpers/injectedMode.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
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;
}

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);
}
9 changes: 7 additions & 2 deletions vitest.integration.anvil.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}),
Expand Down
Loading