Skip to content

Commit 2470706

Browse files
authored
feat: merge-train/spartan-v5 (#24438)
BEGIN_COMMIT_OVERRIDE test(e2e): skip token bridge deploy and pre-deploy cross-chain L1 contracts (#24408) END_COMMIT_OVERRIDE
2 parents 0df2e0d + 997ca9e commit 2470706

8 files changed

Lines changed: 153 additions & 51 deletions

File tree

yarn-project/end-to-end/src/fixtures/setup.ts

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
import type { Delayer } from '@aztec/ethereum/l1-tx-utils';
2525
import { EthCheatCodes, EthCheatCodesWithState, startAnvil, warmBlobKzg } from '@aztec/ethereum/test';
2626
import type { Anvil } from '@aztec/ethereum/test';
27+
import type { ExtendedViemWalletClient } from '@aztec/ethereum/types';
2728
import { BlockNumber, EpochNumber } from '@aztec/foundation/branded-types';
2829
import { SecretValue } from '@aztec/foundation/config';
2930
import { randomBytes } from '@aztec/foundation/crypto/random';
@@ -152,7 +153,7 @@ export async function setupPXEAndGetWallet(
152153
}
153154

154155
/** Options for the e2e tests setup */
155-
export type SetupOptions = {
156+
export type SetupOptions<TDeployExtraL1ContractsReturnType = unknown> = {
156157
/** State load */
157158
stateLoad?: string;
158159
/** Whether to enable metrics collection, if undefined, metrics collection is disabled */
@@ -190,6 +191,20 @@ export type SetupOptions = {
190191
* that assert on genesis-relative L1 timing need to opt out with `false`.
191192
*/
192193
automineL1Setup?: boolean;
194+
/**
195+
* Hook invoked after the Aztec L1 rollup contracts are deployed but BEFORE the node/sequencer
196+
* start, while anvil automine is still enabled (when `automineL1Setup` is true, the default).
197+
* Deploy extra L1 contracts a test needs here (e.g. a cross-chain token portal + ERC20) so they
198+
* mine instantly under automine instead of paying the L1 block interval once the node is running
199+
* (and racing the live sequencer/archiver). The resolved value is exposed on the returned context
200+
* as `extraL1DeployResult`. The hook receives setup's L1 deployer client (the same one used to
201+
* deploy Multicall3).
202+
*/
203+
deployExtraL1Contracts?: (deps: {
204+
l1Client: ExtendedViemWalletClient;
205+
deployL1ContractsValues: DeployAztecL1ContractsReturnType;
206+
logger: Logger;
207+
}) => Promise<TDeployExtraL1ContractsReturnType>;
193208
/** How many accounts to seed and unlock in anvil. */
194209
anvilAccounts?: number;
195210
/** Port to start anvil (defaults to 8545) */
@@ -220,7 +235,7 @@ export type SetupOptions = {
220235
} & Partial<AztecNodeConfig>;
221236

222237
/** Context for an end-to-end test as returned by the `setup` function */
223-
export type EndToEndContext = {
238+
export type EndToEndContext<TDeployExtraL1ContractsReturnType = unknown> = {
224239
/** The Anvil instance (only set if anvil was started locally). */
225240
anvil: Anvil | undefined;
226241
/** The Aztec Node service or client a connected to it. */
@@ -263,6 +278,8 @@ export type EndToEndContext = {
263278
proverDelayer: Delayer | undefined;
264279
/** Genesis data used for setting up nodes. */
265280
genesis: GenesisData | undefined;
281+
/** Resolved value of the `deployExtraL1Contracts` setup hook, if one was provided. */
282+
extraL1DeployResult: TDeployExtraL1ContractsReturnType;
266283
/** ACVM config (only set if running locally). */
267284
acvmConfig: Awaited<ReturnType<typeof getACVMConfig>>;
268285
/** BB config (only set if running locally). */
@@ -305,12 +322,12 @@ function assertContractArtifactsVersion() {
305322
* @param opts - Options to pass to the node initialization and to the setup script.
306323
* @param pxeOpts - Options to pass to the PXE initialization.
307324
*/
308-
export function setup(
325+
export function setup<TDeployExtraL1ContractsReturnType = unknown>(
309326
numberOfAccounts = 1,
310-
opts: SetupOptions = {},
327+
opts: SetupOptions<TDeployExtraL1ContractsReturnType> = {},
311328
pxeOpts: Partial<PXEConfig> = {},
312329
chain: Chain = foundry,
313-
): Promise<EndToEndContext> {
330+
): Promise<EndToEndContext<TDeployExtraL1ContractsReturnType>> {
314331
// Tag the top-level env spin-up with the prover mode (none → fake → real), the largest config-driven
315332
// swing in setup cost, so the three factories are comparable on the span leaderboard. The internals
316333
// (anvil / l1-deploy / sequencer-start / pxe / wallet:create) decompose this further.
@@ -325,12 +342,12 @@ export function setup(
325342
});
326343
}
327344

328-
async function setupInner(
345+
async function setupInner<TDeployExtraL1ContractsReturnType = unknown>(
329346
numberOfAccounts: number,
330-
opts: SetupOptions,
347+
opts: SetupOptions<TDeployExtraL1ContractsReturnType>,
331348
pxeOpts: Partial<PXEConfig>,
332349
chain: Chain,
333-
): Promise<EndToEndContext> {
350+
): Promise<EndToEndContext<TDeployExtraL1ContractsReturnType>> {
334351
assertContractArtifactsVersion();
335352
const logger = getLogger();
336353
let anvil: Anvil | undefined;
@@ -508,6 +525,21 @@ async function setupInner(
508525
}
509526
}
510527

528+
// Deploy any test-specific L1 contracts while automine is still on and before the node starts,
529+
// so they mine instantly rather than paying the L1 block interval once the sequencer is live.
530+
let extraL1DeployResult: TDeployExtraL1ContractsReturnType = undefined as TDeployExtraL1ContractsReturnType;
531+
if (opts.deployExtraL1Contracts) {
532+
logger.trace('Running deployExtraL1Contracts hook');
533+
extraL1DeployResult = await opts.deployExtraL1Contracts({
534+
l1Client,
535+
deployL1ContractsValues,
536+
logger,
537+
});
538+
// The hook reused `l1Client` to send deploy txs, so refresh viem's nonce cache to avoid a
539+
// stale cached nonce for later transactions on the publisher account.
540+
await l1Client.getTransactionCount({ address: l1Client.account.address });
541+
}
542+
511543
if (enableAutomine) {
512544
await ethCheatCodes.setAutomine(false);
513545
await ethCheatCodes.setIntervalMining(config.ethereumSlotDuration);
@@ -713,6 +745,7 @@ async function setupInner(
713745
logger,
714746
mockGossipSubNetwork,
715747
genesis,
748+
extraL1DeployResult,
716749
proverNode,
717750
sequencerDelayer,
718751
proverDelayer,

yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export async function deployAndInitializeTokenAndBridgeContracts(
4343
rollupRegistryAddress: EthAddress,
4444
owner: AztecAddress,
4545
underlyingERC20Address: EthAddress,
46+
predeployedTokenPortalAddress?: EthAddress,
4647
): Promise<{
4748
/**
4849
* The L2 token contract instance.
@@ -65,8 +66,9 @@ export async function deployAndInitializeTokenAndBridgeContracts(
6566
*/
6667
underlyingERC20: any;
6768
}> {
68-
// deploy the token portal
69-
const { address: tokenPortalAddress } = await deployL1Contract(l1Client, TokenPortalAbi, TokenPortalBytecode);
69+
// Deploy the token portal, unless it was already deployed before the node started (under automine).
70+
const tokenPortalAddress =
71+
predeployedTokenPortalAddress ?? (await deployL1Contract(l1Client, TokenPortalAbi, TokenPortalBytecode)).address;
7072
const tokenPortal = getContract({
7173
address: tokenPortalAddress.toString(),
7274
abi: TokenPortalAbi,
@@ -135,6 +137,7 @@ export class CrossChainTestHarness {
135137
ownerAddress: AztecAddress,
136138
logger: Logger,
137139
underlyingERC20Address: EthAddress,
140+
predeployedTokenPortalAddress?: EthAddress,
138141
): Promise<CrossChainTestHarness> {
139142
const ethAccount = EthAddress.fromString((await l1Client.getAddresses())[0]);
140143
const l1ContractAddresses = (await aztecNode.getNodeInfo()).l1ContractAddresses;
@@ -147,6 +150,7 @@ export class CrossChainTestHarness {
147150
l1ContractAddresses.registryAddress,
148151
ownerAddress,
149152
underlyingERC20Address,
153+
predeployedTokenPortalAddress,
150154
);
151155
logger.info('Deployed and initialized token, portal and its bridge.');
152156

yarn-project/end-to-end/src/single-node/cross-chain/cross_chain_messaging_test.ts

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,33 @@ import type {
1313
} from '@aztec/ethereum/deploy-aztec-l1-contracts';
1414
import { deployL1Contract } from '@aztec/ethereum/deploy-l1-contract';
1515
import { pickL1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
16+
import type { ExtendedViemWalletClient } from '@aztec/ethereum/types';
1617
import { EpochNumber } from '@aztec/foundation/branded-types';
1718
import { retryUntil } from '@aztec/foundation/retry';
1819
import { sleep } from '@aztec/foundation/sleep';
19-
import { TestERC20Abi, TestERC20Bytecode } from '@aztec/l1-artifacts';
20+
import { TestERC20Abi, TestERC20Bytecode, TokenPortalAbi, TokenPortalBytecode } from '@aztec/l1-artifacts';
2021
import { TokenContract } from '@aztec/noir-contracts.js/Token';
2122
import { TokenBridgeContract } from '@aztec/noir-contracts.js/TokenBridge';
2223
import type { PXEConfig } from '@aztec/pxe/server';
2324
import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
2425
import type { AztecNodeAdmin } from '@aztec/stdlib/interfaces/client';
2526

27+
import { mnemonicToAccount } from 'viem/accounts';
28+
2629
import { MNEMONIC } from '../../fixtures/fixtures.js';
2730
import { type SetupOptions, ensureAuthRegistryPublished, setup } from '../../fixtures/setup.js';
2831
import { CrossChainTestHarness } from '../../shared/cross_chain_test_harness.js';
2932
import type { TestWallet } from '../../test-wallet/test_wallet.js';
3033
import { SingleNodeTestContext, type SingleNodeTestOpts } from '../single_node_test_context.js';
3134

35+
/** Optional configuration for {@link CrossChainMessagingTest}. */
36+
export type CrossChainMessagingTestOpts = {
37+
/** Mnemonic account index for the harness L1 client (token portal, inbox, minting). Defaults to the first account. */
38+
l1HarnessAccountIndex?: number;
39+
/** Whether to deploy the L1/L2 token bridge. Set to false for suites that only pass arbitrary messages. Defaults to true. */
40+
deployTokenBridge?: boolean;
41+
};
42+
3243
/**
3344
* The cross-chain-messaging harness over the single-node topology: extends {@link SingleNodeTestContext}
3445
* so it reuses the base node tracking / chain monitor / teardown machinery, but builds its environment
@@ -42,6 +53,9 @@ export class CrossChainMessagingTest extends SingleNodeTestContext {
4253
private deployL1ContractsArgs: Partial<DeployAztecL1ContractsArgs>;
4354
private pxeOpts: Partial<PXEConfig>;
4455
private l1HarnessAccountIndex?: number;
56+
private deployTokenBridge: boolean;
57+
/** L1 token portal + ERC20 deployed before the node started (under automine) by the setup hook. */
58+
private preDeployedCrossChainL1?: { underlyingERC20Address: EthAddress; tokenPortalAddress: EthAddress };
4559
private testName: string;
4660
aztecNode!: AztecNode;
4761
aztecNodeConfig!: AztecNodeConfig;
@@ -53,6 +67,8 @@ export class CrossChainMessagingTest extends SingleNodeTestContext {
5367
user2Address!: AztecAddress;
5468
crossChainTestHarness!: CrossChainTestHarness;
5569
ethAccount!: EthAddress;
70+
/** L1 client used for cross-chain L1 interactions (token portal, inbox), tied to `l1HarnessAccountIndex`. */
71+
harnessL1Client!: ExtendedViemWalletClient;
5672
l2Token!: TokenContract;
5773
l2Bridge!: TokenBridgeContract;
5874

@@ -77,7 +93,7 @@ export class CrossChainMessagingTest extends SingleNodeTestContext {
7793
opts: SetupOptions = {},
7894
deployL1ContractsArgs: Partial<DeployAztecL1ContractsArgs> = {},
7995
pxeOpts: Partial<PXEConfig> = {},
80-
l1HarnessAccountIndex?: number,
96+
crossChainOpts: CrossChainMessagingTestOpts = {},
8197
) {
8298
super();
8399
this.testName = testName;
@@ -88,7 +104,8 @@ export class CrossChainMessagingTest extends SingleNodeTestContext {
88104
...deployL1ContractsArgs,
89105
};
90106
this.pxeOpts = pxeOpts;
91-
this.l1HarnessAccountIndex = l1HarnessAccountIndex;
107+
this.l1HarnessAccountIndex = crossChainOpts.l1HarnessAccountIndex;
108+
this.deployTokenBridge = crossChainOpts.deployTokenBridge ?? true;
92109
this.requireEpochProven = opts.startProverNode ?? false;
93110
}
94111

@@ -112,6 +129,35 @@ export class CrossChainMessagingTest extends SingleNodeTestContext {
112129
...opts.proverNodeConfig,
113130
txGatheringTimeoutMs: opts.proverNodeConfig?.txGatheringTimeoutMs ?? 10 * 60 * 1000,
114131
},
132+
// Deploy the cross-chain token portal + ERC20 before the node starts, while anvil is still
133+
// automining, instead of paying the L1 block interval for them once the sequencer is live.
134+
deployExtraL1Contracts: this.deployTokenBridge
135+
? async ({ l1Client, logger }) => {
136+
// The harness mints the underlying ERC20 from `l1HarnessAccountIndex` and `TestERC20.mint`
137+
// is onlyMinter, so its owner/initial-minter must be that account even though setup's
138+
// publisher client sends the deploy tx.
139+
const harnessAddress = mnemonicToAccount(MNEMONIC, {
140+
addressIndex: this.l1HarnessAccountIndex ?? 0,
141+
}).address;
142+
const { address: underlyingERC20Address } = await deployL1Contract(
143+
l1Client,
144+
TestERC20Abi,
145+
TestERC20Bytecode,
146+
['Underlying', 'UND', harnessAddress],
147+
);
148+
// The TokenPortal's initialize is permissionless, so it can be deployed by the publisher.
149+
const { address: tokenPortalAddress } = await deployL1Contract(
150+
l1Client,
151+
TokenPortalAbi,
152+
TokenPortalBytecode,
153+
);
154+
this.preDeployedCrossChainL1 = { underlyingERC20Address, tokenPortalAddress };
155+
logger.verbose(
156+
`Pre-deployed cross-chain L1 ERC20 ${underlyingERC20Address} and portal ${tokenPortalAddress}`,
157+
);
158+
return this.preDeployedCrossChainL1;
159+
}
160+
: undefined,
115161
},
116162
{ ...this.pxeOpts, ...pxeOpts },
117163
);
@@ -193,12 +239,29 @@ export class CrossChainMessagingTest extends SingleNodeTestContext {
193239
undefined,
194240
this.l1HarnessAccountIndex,
195241
);
242+
this.harnessL1Client = harnessL1Client;
243+
this.ethAccount = EthAddress.fromString((await harnessL1Client.getAddresses())[0]);
244+
245+
// L1 contract handles every cross-chain test needs, independent of the token bridge.
246+
const l1Client = createExtendedL1Client(this.aztecNodeConfig.l1RpcUrls, MNEMONIC);
247+
this.l1Client = l1Client;
248+
const l1Contracts = pickL1ContractAddresses(this.aztecNodeConfig);
249+
this.rollup = new RollupContract(l1Client, l1Contracts.rollupAddress.toString());
250+
this.inbox = new InboxContract(l1Client, l1Contracts.inboxAddress.toString());
251+
this.outbox = new OutboxContract(l1Client, l1Contracts.outboxAddress.toString());
196252

197-
const underlyingERC20Address = await deployL1Contract(harnessL1Client, TestERC20Abi, TestERC20Bytecode, [
198-
'Underlying',
199-
'UND',
200-
harnessL1Client.account.address,
201-
]).then(({ address }) => address);
253+
// Tests that only pass arbitrary L1<->L2 messages (not token bridging) never touch the L2 token,
254+
// bridge, or portal. Skip the token+portal+bridge deploy (an L1 ERC20, an L1 portal, and two L2
255+
// contract deploys plus their init txs) entirely for them.
256+
if (!this.deployTokenBridge) {
257+
this.logger.info('Skipping token bridge deploy; test only needs L1 handles and ethAccount');
258+
return;
259+
}
260+
261+
// The ERC20 and token portal were deployed before the node started (under automine) by the
262+
// `deployExtraL1Contracts` setup hook above; the harness reuses them and only deploys the L2 token,
263+
// L2 bridge, and the portal init that need the running node.
264+
const { underlyingERC20Address, tokenPortalAddress: predeployedTokenPortalAddress } = this.preDeployedCrossChainL1!;
202265

203266
this.logger.verbose(`Setting up cross chain harness...`);
204267
this.crossChainTestHarness = await CrossChainTestHarness.new(
@@ -208,6 +271,7 @@ export class CrossChainMessagingTest extends SingleNodeTestContext {
208271
this.ownerAddress,
209272
this.logger,
210273
underlyingERC20Address,
274+
predeployedTokenPortalAddress,
211275
);
212276

213277
this.logger.verbose(`L2 token deployed to: ${this.crossChainTestHarness.l2Token.address}`);
@@ -221,14 +285,6 @@ export class CrossChainMessagingTest extends SingleNodeTestContext {
221285
this.ethAccount = EthAddress.fromString(crossChainContext.ethAccount.toString());
222286
const tokenPortalAddress = EthAddress.fromString(crossChainContext.tokenPortal.toString());
223287

224-
const l1Client = createExtendedL1Client(this.aztecNodeConfig.l1RpcUrls, MNEMONIC);
225-
this.l1Client = l1Client;
226-
227-
const l1Contracts = pickL1ContractAddresses(this.aztecNodeConfig);
228-
this.rollup = new RollupContract(l1Client, l1Contracts.rollupAddress.toString());
229-
this.inbox = new InboxContract(l1Client, l1Contracts.inboxAddress.toString());
230-
this.outbox = new OutboxContract(l1Client, l1Contracts.outboxAddress.toString());
231-
232288
this.crossChainTestHarness = new CrossChainTestHarness(
233289
this.aztecNode,
234290
this.logger,

0 commit comments

Comments
 (0)