@aztec/end-to-end 0.0.1-commit.b2a5d0dd1 → 0.0.1-commit.b3d3157a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/dest/bench/client_flows/benchmark.d.ts +15 -1
  2. package/dest/bench/client_flows/benchmark.d.ts.map +1 -1
  3. package/dest/bench/client_flows/benchmark.js +17 -0
  4. package/dest/bench/client_flows/client_flows_benchmark.d.ts +2 -2
  5. package/dest/bench/client_flows/client_flows_benchmark.d.ts.map +1 -1
  6. package/dest/bench/client_flows/client_flows_benchmark.js +13 -9
  7. package/dest/bench/utils.d.ts +1 -1
  8. package/dest/bench/utils.d.ts.map +1 -1
  9. package/dest/bench/utils.js +2 -0
  10. package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts +3 -3
  11. package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts.map +1 -1
  12. package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.js +13 -4
  13. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts +15 -4
  14. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts.map +1 -1
  15. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.js +39 -7
  16. package/dest/e2e_deploy_contract/deploy_test.d.ts +3 -3
  17. package/dest/e2e_deploy_contract/deploy_test.d.ts.map +1 -1
  18. package/dest/e2e_deploy_contract/deploy_test.js +2 -1
  19. package/dest/e2e_epochs/epochs_test.d.ts +8 -2
  20. package/dest/e2e_epochs/epochs_test.d.ts.map +1 -1
  21. package/dest/e2e_epochs/epochs_test.js +30 -7
  22. package/dest/e2e_fees/fees_test.d.ts +5 -2
  23. package/dest/e2e_fees/fees_test.d.ts.map +1 -1
  24. package/dest/e2e_fees/fees_test.js +29 -3
  25. package/dest/e2e_nested_contract/nested_contract_test.d.ts +3 -3
  26. package/dest/e2e_nested_contract/nested_contract_test.d.ts.map +1 -1
  27. package/dest/e2e_nested_contract/nested_contract_test.js +2 -1
  28. package/dest/e2e_p2p/inactivity_slash_test.d.ts +1 -1
  29. package/dest/e2e_p2p/inactivity_slash_test.d.ts.map +1 -1
  30. package/dest/e2e_p2p/inactivity_slash_test.js +1 -0
  31. package/dest/e2e_p2p/reqresp/utils.d.ts +1 -1
  32. package/dest/e2e_p2p/reqresp/utils.d.ts.map +1 -1
  33. package/dest/e2e_p2p/reqresp/utils.js +28 -12
  34. package/dest/e2e_p2p/shared.d.ts +19 -8
  35. package/dest/e2e_p2p/shared.d.ts.map +1 -1
  36. package/dest/e2e_p2p/shared.js +33 -13
  37. package/dest/e2e_token_contract/token_contract_test.d.ts +3 -3
  38. package/dest/e2e_token_contract/token_contract_test.d.ts.map +1 -1
  39. package/dest/e2e_token_contract/token_contract_test.js +4 -2
  40. package/dest/fixtures/e2e_prover_test.d.ts +3 -3
  41. package/dest/fixtures/e2e_prover_test.d.ts.map +1 -1
  42. package/dest/fixtures/e2e_prover_test.js +2 -1
  43. package/dest/fixtures/fixtures.d.ts +59 -1
  44. package/dest/fixtures/fixtures.d.ts.map +1 -1
  45. package/dest/fixtures/fixtures.js +55 -0
  46. package/dest/fixtures/schnorr_hardcoded_account_contract.js +2 -4
  47. package/dest/fixtures/setup.d.ts +30 -4
  48. package/dest/fixtures/setup.d.ts.map +1 -1
  49. package/dest/fixtures/setup.js +117 -7
  50. package/dest/fixtures/setup_p2p_test.d.ts +5 -1
  51. package/dest/fixtures/setup_p2p_test.d.ts.map +1 -1
  52. package/dest/fixtures/utils.d.ts +2 -2
  53. package/dest/fixtures/utils.d.ts.map +1 -1
  54. package/dest/fixtures/utils.js +1 -1
  55. package/dest/install_legacy_contracts.d.cts +10 -0
  56. package/dest/install_legacy_contracts.d.cts.map +1 -0
  57. package/dest/legacy-jest-resolver.d.cts +1 -1
  58. package/dest/legacy-jest-resolver.d.cts.map +1 -1
  59. package/dest/shared/cross_chain_test_harness.d.ts +4 -2
  60. package/dest/shared/cross_chain_test_harness.d.ts.map +1 -1
  61. package/dest/shared/cross_chain_test_harness.js +9 -5
  62. package/dest/shared/gas_portal_test_harness.d.ts +8 -5
  63. package/dest/shared/gas_portal_test_harness.d.ts.map +1 -1
  64. package/dest/shared/gas_portal_test_harness.js +16 -7
  65. package/dest/shared/uniswap_l1_l2.d.ts +1 -1
  66. package/dest/shared/uniswap_l1_l2.d.ts.map +1 -1
  67. package/dest/shared/uniswap_l1_l2.js +50 -28
  68. package/dest/shared/wait_for_l1_to_l2_message.d.ts +13 -0
  69. package/dest/shared/wait_for_l1_to_l2_message.d.ts.map +1 -0
  70. package/dest/shared/wait_for_l1_to_l2_message.js +10 -0
  71. package/dest/simulators/lending_simulator.d.ts +10 -3
  72. package/dest/simulators/lending_simulator.d.ts.map +1 -1
  73. package/dest/simulators/lending_simulator.js +23 -11
  74. package/dest/spartan/setup_test_wallets.d.ts +1 -1
  75. package/dest/spartan/setup_test_wallets.d.ts.map +1 -1
  76. package/dest/spartan/setup_test_wallets.js +9 -3
  77. package/dest/spartan/tx_metrics.d.ts +18 -4
  78. package/dest/spartan/tx_metrics.d.ts.map +1 -1
  79. package/dest/spartan/tx_metrics.js +73 -20
  80. package/dest/spartan/utils/config.d.ts +9 -35
  81. package/dest/spartan/utils/config.d.ts.map +1 -1
  82. package/dest/spartan/utils/nodes.js +2 -2
  83. package/dest/test-wallet/test_wallet.d.ts +3 -2
  84. package/dest/test-wallet/test_wallet.d.ts.map +1 -1
  85. package/dest/test-wallet/test_wallet.js +49 -27
  86. package/dest/test-wallet/utils.d.ts +3 -3
  87. package/dest/test-wallet/utils.d.ts.map +1 -1
  88. package/dest/test-wallet/utils.js +2 -7
  89. package/dest/test-wallet/wallet_worker_script.js +7 -2
  90. package/dest/test-wallet/worker_wallet.d.ts +2 -1
  91. package/dest/test-wallet/worker_wallet.d.ts.map +1 -1
  92. package/dest/test-wallet/worker_wallet.js +6 -2
  93. package/dest/test-wallet/worker_wallet_schema.d.ts +110 -229
  94. package/dest/test-wallet/worker_wallet_schema.d.ts.map +1 -1
  95. package/dest/test-wallet/worker_wallet_schema.js +14 -2
  96. package/package.json +45 -41
  97. package/src/bench/client_flows/benchmark.ts +19 -0
  98. package/src/bench/client_flows/client_flows_benchmark.ts +32 -30
  99. package/src/bench/utils.ts +2 -1
  100. package/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts +21 -4
  101. package/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts +49 -11
  102. package/src/e2e_deploy_contract/deploy_test.ts +3 -2
  103. package/src/e2e_epochs/epochs_test.ts +32 -5
  104. package/src/e2e_fees/fees_test.ts +33 -2
  105. package/src/e2e_nested_contract/nested_contract_test.ts +3 -1
  106. package/src/e2e_p2p/inactivity_slash_test.ts +1 -0
  107. package/src/e2e_p2p/reqresp/utils.ts +29 -11
  108. package/src/e2e_p2p/shared.ts +31 -12
  109. package/src/e2e_token_contract/token_contract_test.ts +12 -2
  110. package/src/fixtures/e2e_prover_test.ts +3 -1
  111. package/src/fixtures/fixtures.ts +61 -0
  112. package/src/fixtures/schnorr_hardcoded_account_contract.ts +2 -2
  113. package/src/fixtures/setup.ts +134 -10
  114. package/src/fixtures/setup_p2p_test.ts +4 -0
  115. package/src/fixtures/utils.ts +2 -0
  116. package/src/install_legacy_contracts.cjs +75 -0
  117. package/src/legacy-jest-resolver.cjs +25 -48
  118. package/src/shared/cross_chain_test_harness.ts +14 -4
  119. package/src/shared/gas_portal_test_harness.ts +20 -9
  120. package/src/shared/uniswap_l1_l2.ts +84 -36
  121. package/src/shared/wait_for_l1_to_l2_message.ts +23 -0
  122. package/src/simulators/lending_simulator.ts +27 -13
  123. package/src/spartan/setup_test_wallets.ts +6 -4
  124. package/src/spartan/tx_metrics.ts +81 -23
  125. package/src/spartan/utils/nodes.ts +2 -2
  126. package/src/test-wallet/test_wallet.ts +46 -23
  127. package/src/test-wallet/utils.ts +3 -7
  128. package/src/test-wallet/wallet_worker_script.ts +5 -2
  129. package/src/test-wallet/worker_wallet.ts +7 -3
  130. package/src/test-wallet/worker_wallet_schema.ts +2 -2
@@ -8,63 +8,36 @@
8
8
  // would couple this test to a moving aztec.js surface and break at import time on unrelated breaking changes; we want
9
9
  // to fail only on actual artifact-compat regressions.
10
10
  //
11
- // The cache is populated on demand by running `npm install` into .legacy-contracts/<version>/.
11
+ // Cache population lives in install_legacy_contracts.cjs invoked lazily here for local dev, and eagerly
12
+ // by bootstrap.sh ci-compat-e2e before hermetic test containers (which run with --net=none) launch.
13
+ //
14
+ // Missing artifacts: legacy version directories are immutable, so an artifact missing from the cache means the
15
+ // contract was added after the pinned release — there's nothing to compat-test. Rather than failing or silently
16
+ // falling back to the workspace artifact (which would turn the compat run into a regular e2e run that always
17
+ // passes), we log the miss and exit the process cleanly with code 0. The test never runs, but the per-test CI
18
+ // log captures the explanatory line so the reason is auditable. This keeps the change scoped to this resolver,
19
+ // avoiding a new exit-code contract in the shared ci3 test runner.
12
20
  //
13
21
  // Activated by env var; passthrough otherwise.
14
22
  /* eslint-disable @typescript-eslint/no-require-imports */
15
23
 
16
24
  const path = require('path');
17
25
  const fs = require('fs');
18
- const { execSync } = require('child_process');
26
+ const { installLegacyContracts, REDIRECTED, cacheRoot } = require('./install_legacy_contracts.cjs');
19
27
 
20
28
  const version = process.env.CONTRACT_ARTIFACTS_VERSION;
21
- const REDIRECTED = ['@aztec/noir-contracts.js', '@aztec/noir-test-contracts.js', '@aztec/accounts'];
22
-
23
- // Jest sets rootDir to <e2e>/src; this file lives there too.
24
- const e2eRoot = path.resolve(__dirname, '..');
25
- const cacheRoot = version ? path.join(e2eRoot, '.legacy-contracts', version) : null;
29
+ const cacheDir = version ? cacheRoot(version) : null;
26
30
 
27
31
  function pkgJsonPath(name) {
28
- return path.join(cacheRoot, 'node_modules', name, 'package.json');
29
- }
30
-
31
- function ensureCache() {
32
- const missing = REDIRECTED.some(p => !fs.existsSync(pkgJsonPath(p)));
33
- if (!missing) {
34
- return;
35
- }
36
- fs.mkdirSync(cacheRoot, { recursive: true });
37
- // Seed a standalone package.json so `npm install --prefix` treats cacheRoot as its own project. Without this, npm
38
- // walks up and finds the yarn-project workspace root, which breaks on `workspace:` protocol deps and risks
39
- // clobbering the monorepo's node_modules.
40
- const seed = path.join(cacheRoot, 'package.json');
41
- if (!fs.existsSync(seed)) {
42
- fs.writeFileSync(seed, JSON.stringify({ name: 'legacy-contracts-cache', private: true }));
43
- }
44
-
45
- const specs = REDIRECTED.map(p => `${p}@${version}`).join(' ');
46
- process.stderr.write(`[legacy-contracts] installing ${specs} into ${cacheRoot}\n`);
47
- // --prefix: install into cacheRoot instead of cwd, so the cache is isolated from the monorepo.
48
- // --no-save: don't write the installed packages back to the seeded package.json.
49
- // --ignore-scripts: skip lifecycle scripts (preinstall/postinstall) of the legacy packages and their transitive
50
- // deps; we only want the files on disk, not to run any build steps.
51
- // --legacy-peer-deps: tolerate peer-dependency mismatches between the pinned legacy @aztec/* graph and whatever
52
- // current versions npm would otherwise try to reconcile.
53
- execSync(`npm install --prefix "${cacheRoot}" --no-save --ignore-scripts --legacy-peer-deps ${specs}`, {
54
- stdio: 'inherit',
55
- });
56
-
57
- // Verify versions on disk match the requested version.
58
- for (const p of REDIRECTED) {
59
- const onDisk = JSON.parse(fs.readFileSync(pkgJsonPath(p), 'utf8')).version;
60
- if (onDisk !== version) {
61
- throw new Error(`[legacy-contracts] ${p} on disk is ${onDisk}, expected ${version}`);
62
- }
63
- }
32
+ return path.join(cacheDir, 'node_modules', name, 'package.json');
64
33
  }
65
34
 
35
+ // Kept in a separate module (not inlined) because bootstrap.sh ci-compat-e2e also calls it directly
36
+ // via `node .../install_legacy_contracts.cjs <version>` to pre-populate the cache on the host before
37
+ // hermetic --net=none test containers launch. Inlining here would force us to duplicate the logic
38
+ // in bash or re-run jest just to trigger the install.
66
39
  if (version) {
67
- ensureCache();
40
+ installLegacyContracts(version);
68
41
  }
69
42
 
70
43
  let bannerPrinted = false;
@@ -102,7 +75,7 @@ function legacyArtifactPath(resolved) {
102
75
  continue;
103
76
  }
104
77
  const basename = resolved.slice(idx + marker.length);
105
- return path.join(cacheRoot, 'node_modules', pkg, 'artifacts', basename);
78
+ return path.join(cacheDir, 'node_modules', pkg, 'artifacts', basename);
106
79
  }
107
80
  return null;
108
81
  }
@@ -122,10 +95,14 @@ module.exports = function legacyResolver(request, options) {
122
95
  return resolved;
123
96
  }
124
97
  if (!fs.existsSync(legacy)) {
125
- throw new Error(
126
- `[legacy-contracts] artifact ${path.basename(legacy)} not present in legacy cache @${version}; ` +
127
- `the contract may have been added after that release. Pin a newer CONTRACT_ARTIFACTS_VERSION or skip this test.`,
98
+ // Contract was added after this historical release, there is nothing to compat-test for it. Exit the process
99
+ // cleanly with code 0 so the test runner reports the run as passed.
100
+ fs.writeSync(
101
+ 2,
102
+ `[legacy-contracts][jest] artifact ${path.basename(legacy)} not in legacy cache @${version}; ` +
103
+ `assumed added after this release. No compat coverage applicable for this version, treating as passed.\n`,
128
104
  );
105
+ process.exit(0);
129
106
  }
130
107
  if (!seen.has(resolved)) {
131
108
  seen.add(resolved);
@@ -13,11 +13,11 @@ import type { AztecNode } from '@aztec/aztec.js/node';
13
13
  import type { SiblingPath } from '@aztec/aztec.js/trees';
14
14
  import type { TxReceipt } from '@aztec/aztec.js/tx';
15
15
  import type { Wallet } from '@aztec/aztec.js/wallet';
16
+ import { OutboxContract } from '@aztec/ethereum/contracts';
16
17
  import { deployL1Contract } from '@aztec/ethereum/deploy-l1-contract';
17
18
  import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
18
19
  import type { ExtendedViemWalletClient } from '@aztec/ethereum/types';
19
20
  import { EpochNumber } from '@aztec/foundation/branded-types';
20
- import { retryUntil } from '@aztec/foundation/retry';
21
21
  import { TestERC20Abi, TokenPortalAbi, TokenPortalBytecode } from '@aztec/l1-artifacts';
22
22
  import { TokenContract } from '@aztec/noir-contracts.js/Token';
23
23
  import { TokenBridgeContract } from '@aztec/noir-contracts.js/TokenBridge';
@@ -25,6 +25,7 @@ import { TokenBridgeContract } from '@aztec/noir-contracts.js/TokenBridge';
25
25
  import { type Hex, getContract } from 'viem';
26
26
 
27
27
  import { mintTokensToPrivate } from '../fixtures/token_utils.js';
28
+ import { waitForL1ToL2MessageSeen } from './wait_for_l1_to_l2_message.js';
28
29
 
29
30
  /**
30
31
  * Deploy L1 token and portal, initialize portal, deploy a non native l2 token contract, its L2 bridge contract and attach is to the portal.
@@ -166,6 +167,7 @@ export class CrossChainTestHarness {
166
167
 
167
168
  private readonly l1TokenManager: L1TokenManager;
168
169
  private readonly l1TokenPortalManager: L1TokenPortalManager;
170
+ public readonly outboxContract: OutboxContract;
169
171
 
170
172
  constructor(
171
173
  /** Aztec node instance. */
@@ -206,6 +208,7 @@ export class CrossChainTestHarness {
206
208
  this.logger,
207
209
  );
208
210
  this.l1TokenManager = this.l1TokenPortalManager.getTokenManager();
211
+ this.outboxContract = new OutboxContract(this.l1Client, this.l1ContractAddresses.outboxAddress);
209
212
  }
210
213
 
211
214
  async mintTokensOnL1(amount: bigint) {
@@ -319,10 +322,18 @@ export class CrossChainTestHarness {
319
322
  withdrawFundsFromBridgeOnL1(
320
323
  amount: bigint,
321
324
  epochNumber: EpochNumber,
325
+ numCheckpointsInEpoch: number,
322
326
  messageIndex: bigint,
323
327
  siblingPath: SiblingPath<number>,
324
328
  ) {
325
- return this.l1TokenPortalManager.withdrawFunds(amount, this.ethAccount, epochNumber, messageIndex, siblingPath);
329
+ return this.l1TokenPortalManager.withdrawFunds(
330
+ amount,
331
+ this.ethAccount,
332
+ epochNumber,
333
+ numCheckpointsInEpoch,
334
+ messageIndex,
335
+ siblingPath,
336
+ );
326
337
  }
327
338
 
328
339
  async transferToPrivateOnL2(shieldAmount: bigint) {
@@ -346,8 +357,7 @@ export class CrossChainTestHarness {
346
357
  */
347
358
  async makeMessageConsumable(msgHash: Fr | Hex) {
348
359
  const frMsgHash = typeof msgHash === 'string' ? Fr.fromHexString(msgHash) : msgHash;
349
- // We poll isL1ToL2MessageSynced endpoint until the message is available
350
- await retryUntil(async () => await this.aztecNode.isL1ToL2MessageSynced(frMsgHash), 'message sync', 10);
360
+ await waitForL1ToL2MessageSeen(this.aztecNode, frMsgHash, { timeoutSeconds: 10 });
351
361
 
352
362
  await this.mintTokensPublicOnL2(0n);
353
363
  await this.mintTokensPublicOnL2(0n);
@@ -8,7 +8,12 @@ import type { ExtendedViemWalletClient } from '@aztec/ethereum/types';
8
8
  import { retryUntil } from '@aztec/foundation/retry';
9
9
  import { FeeJuiceContract } from '@aztec/noir-contracts.js/FeeJuice';
10
10
  import { ProtocolContractAddress } from '@aztec/protocol-contracts';
11
- import type { AztecNodeAdmin } from '@aztec/stdlib/interfaces/client';
11
+ import type { AztecNodeAdmin, AztecNodeDebug } from '@aztec/stdlib/interfaces/client';
12
+
13
+ import { waitForL1ToL2MessageSeen } from './wait_for_l1_to_l2_message.js';
14
+
15
+ /** Aztec node that may expose the debug mining API in local e2e setups. */
16
+ type MiningAztecNode = AztecNode & Partial<AztecNodeDebug>;
12
17
 
13
18
  export interface IGasBridgingTestHarness {
14
19
  getL1FeeJuiceBalance(address: EthAddress): Promise<bigint>;
@@ -19,7 +24,7 @@ export interface IGasBridgingTestHarness {
19
24
  }
20
25
 
21
26
  export interface FeeJuicePortalTestingHarnessFactoryConfig {
22
- aztecNode: AztecNode;
27
+ aztecNode: MiningAztecNode;
23
28
  aztecNodeAdmin?: AztecNodeAdmin;
24
29
  l1Client: ExtendedViemWalletClient;
25
30
  wallet: Wallet;
@@ -75,7 +80,7 @@ export class GasBridgingTestHarness implements IGasBridgingTestHarness {
75
80
 
76
81
  constructor(
77
82
  /** Aztec node */
78
- public aztecNode: AztecNode,
83
+ public aztecNode: MiningAztecNode,
79
84
  /** Aztec node admin interface */
80
85
  public aztecNodeAdmin: AztecNodeAdmin | undefined,
81
86
  /** Wallet. */
@@ -144,8 +149,7 @@ export class GasBridgingTestHarness implements IGasBridgingTestHarness {
144
149
  await this.mintTokensOnL1();
145
150
  const claim = await this.sendTokensToPortalPublic(bridgeAmount, owner);
146
151
 
147
- const isSynced = async () => await this.aztecNode.isL1ToL2MessageSynced(Fr.fromHexString(claim.messageHash));
148
- await retryUntil(isSynced, `message ${claim.messageHash} sync`, 24, 1);
152
+ await waitForL1ToL2MessageSeen(this.aztecNode, Fr.fromHexString(claim.messageHash), { timeoutSeconds: 24 });
149
153
 
150
154
  // Progress by 2 L2 blocks so that the l1ToL2Message added above will be available to use on L2.
151
155
  await this.advanceL2Block();
@@ -163,6 +167,11 @@ export class GasBridgingTestHarness implements IGasBridgingTestHarness {
163
167
  }
164
168
 
165
169
  private async advanceL2Block() {
170
+ if (this.aztecNode.mineBlock) {
171
+ await this.aztecNode.mineBlock();
172
+ return;
173
+ }
174
+
166
175
  const initialBlockNumber = await this.aztecNode.getBlockNumber();
167
176
 
168
177
  let minTxsPerBlock = undefined;
@@ -171,10 +180,12 @@ export class GasBridgingTestHarness implements IGasBridgingTestHarness {
171
180
  await this.aztecNodeAdmin.setConfig({ minTxsPerBlock: 0 }); // Set to 0 to ensure we can advance the block
172
181
  }
173
182
 
174
- await retryUntil(async () => (await this.aztecNode.getBlockNumber()) >= initialBlockNumber + 1);
175
-
176
- if (this.aztecNodeAdmin && minTxsPerBlock !== undefined) {
177
- await this.aztecNodeAdmin.setConfig({ minTxsPerBlock });
183
+ try {
184
+ await retryUntil(async () => (await this.aztecNode.getBlockNumber()) >= initialBlockNumber + 1);
185
+ } finally {
186
+ if (this.aztecNodeAdmin && minTxsPerBlock !== undefined) {
187
+ await this.aztecNodeAdmin.setConfig({ minTxsPerBlock });
188
+ }
178
189
  }
179
190
  }
180
191
  }
@@ -13,15 +13,19 @@ import type { ExtendedViemWalletClient } from '@aztec/ethereum/types';
13
13
  import { extractEvent } from '@aztec/ethereum/utils';
14
14
  import { EpochNumber } from '@aztec/foundation/branded-types';
15
15
  import { sha256ToField } from '@aztec/foundation/crypto/sha256';
16
+ import { retryUntil } from '@aztec/foundation/retry';
16
17
  import { InboxAbi, UniswapPortalAbi, UniswapPortalBytecode } from '@aztec/l1-artifacts';
17
18
  import { UniswapContract } from '@aztec/noir-contracts.js/Uniswap';
18
19
  import { computeL2ToL1MessageHash } from '@aztec/stdlib/hash';
19
- import { computeL2ToL1MembershipWitness } from '@aztec/stdlib/messaging';
20
20
 
21
21
  import { jest } from '@jest/globals';
22
22
  import { type GetContractReturnType, getContract, parseEther, toFunctionSelector } from 'viem';
23
23
 
24
- import { type EndToEndContext, ensureAccountContractsPublished } from '../fixtures/utils.js';
24
+ import {
25
+ type EndToEndContext,
26
+ ensureAccountContractsPublished,
27
+ ensureAuthRegistryPublished,
28
+ } from '../fixtures/utils.js';
25
29
  import type { TestWallet } from '../test-wallet/test_wallet.js';
26
30
  import { CrossChainTestHarness } from './cross_chain_test_harness.js';
27
31
 
@@ -32,7 +36,7 @@ import { CrossChainTestHarness } from './cross_chain_test_harness.js';
32
36
  // anvil --fork-url https://mainnet.infura.io/v3/9928b52099854248b3a096be07a6b23c --fork-block-number 17514288 --chain-id 31337
33
37
  // For CI, this is configured in `run_tests.sh` and `docker-compose-images.yml`
34
38
 
35
- const TIMEOUT = 360_000;
39
+ const TIMEOUT = 15 * 60 * 1000;
36
40
 
37
41
  export const uniswapL1L2TestSuite = (
38
42
  setup: () => Promise<EndToEndContext>,
@@ -98,6 +102,7 @@ export const uniswapL1L2TestSuite = (
98
102
  ownerEthAddress = EthAddress.fromString((await l1Client.getAddresses())[0]);
99
103
 
100
104
  await ensureAccountContractsPublished(wallet, [ownerAddress, sponsorAddress]);
105
+ await ensureAuthRegistryPublished(wallet, ownerAddress);
101
106
 
102
107
  logger.info('Deploying DAI Portal, initializing and deploying l2 contract...');
103
108
  daiCrossChainHarness = await CrossChainTestHarness.new(
@@ -250,14 +255,13 @@ export const uniswapL1L2TestSuite = (
250
255
  // ensure that uniswap contract didn't eat the funds.
251
256
  await wethCrossChainHarness.expectPublicBalanceOnL2(uniswapL2Contract.address, 0n);
252
257
 
253
- // Since the outbox is only consumable when the epoch is proven, we need to advance to the next epoch.
254
- const swapResult = (await computeL2ToL1MembershipWitness(
255
- aztecNode,
256
- swapPrivateLeaf,
257
- l2UniswapInteractionReceipt.txHash,
258
- ))!;
259
- const { epochNumber: epoch } = swapResult;
260
- await cheatCodes.rollup.advanceToEpoch(EpochNumber(epoch + 1));
258
+ // Since the outbox is only consumable when the epoch is proven, we need to advance to the next epoch
259
+ // before we can ask the witness helper for a covering root.
260
+ const minedReceipt = await aztecNode.getTxReceipt(l2UniswapInteractionReceipt.txHash);
261
+ if (minedReceipt.epochNumber === undefined) {
262
+ throw new Error('L2 Uniswap interaction tx is not yet in an epoch');
263
+ }
264
+ await cheatCodes.rollup.advanceToEpoch(EpochNumber(minedReceipt.epochNumber + 1));
261
265
  await waitForProven(aztecNode, l2UniswapInteractionReceipt, { provenTimeout: 300 });
262
266
 
263
267
  // 5. Consume L2 to L1 message by calling uniswapPortal.swap_private()
@@ -265,11 +269,19 @@ export const uniswapL1L2TestSuite = (
265
269
  const daiL1BalanceOfPortalBeforeSwap = await daiCrossChainHarness.getL1BalanceOf(
266
270
  daiCrossChainHarness.tokenPortalAddress,
267
271
  );
268
- const withdrawResult = (await computeL2ToL1MembershipWitness(
269
- aztecNode,
270
- withdrawLeaf,
271
- l2UniswapInteractionReceipt.txHash,
272
- ))!;
272
+ const swapResult = await retryUntil(
273
+ () => aztecNode.getL2ToL1MembershipWitness(minedReceipt.txHash, swapPrivateLeaf),
274
+ 'swap private l2 to l1 witness',
275
+ 60,
276
+ 1,
277
+ );
278
+ const { epochNumber: epoch, numCheckpointsInEpoch } = swapResult;
279
+ const withdrawResult = await retryUntil(
280
+ () => aztecNode.getL2ToL1MembershipWitness(minedReceipt.txHash, withdrawLeaf),
281
+ 'withdraw l2 to l1 witness',
282
+ 60,
283
+ 1,
284
+ );
273
285
 
274
286
  const swapPrivateL2MessageIndex = swapResult.leafIndex;
275
287
  const swapPrivateSiblingPath = swapResult.siblingPath;
@@ -279,6 +291,7 @@ export const uniswapL1L2TestSuite = (
279
291
 
280
292
  const withdrawMessageMetadata = {
281
293
  _epoch: BigInt(epoch),
294
+ _numCheckpointsInEpoch: BigInt(withdrawResult.numCheckpointsInEpoch),
282
295
  _leafIndex: BigInt(withdrawL2MessageIndex),
283
296
  _path: withdrawSiblingPath
284
297
  .toBufferArray()
@@ -287,6 +300,7 @@ export const uniswapL1L2TestSuite = (
287
300
 
288
301
  const swapPrivateMessageMetadata = {
289
302
  _epoch: BigInt(epoch),
303
+ _numCheckpointsInEpoch: BigInt(numCheckpointsInEpoch),
290
304
  _leafIndex: BigInt(swapPrivateL2MessageIndex),
291
305
  _path: swapPrivateSiblingPath
292
306
  .toBufferArray()
@@ -843,9 +857,31 @@ export const uniswapL1L2TestSuite = (
843
857
  chainId: new Fr(l1Client.chain.id),
844
858
  });
845
859
 
846
- const swapResult = (await computeL2ToL1MembershipWitness(aztecNode, swapPrivateLeaf, withdrawReceipt.txHash))!;
847
- const { epochNumber: epoch } = swapResult;
848
- const withdrawResult = (await computeL2ToL1MembershipWitness(aztecNode, withdrawLeaf, withdrawReceipt.txHash))!;
860
+ // ensure that user's funds were burnt
861
+ await wethCrossChainHarness.expectPrivateBalanceOnL2(ownerAddress, wethL2BalanceBeforeSwap - wethAmountToBridge);
862
+
863
+ // Since the outbox is only consumable when the epoch is proven, we need to advance to the next epoch
864
+ // before we can ask the witness helper for a covering root.
865
+ const minedReceipt = await aztecNode.getTxReceipt(withdrawReceipt.txHash);
866
+ if (minedReceipt.epochNumber === undefined) {
867
+ throw new Error('Withdraw tx is not yet in an epoch');
868
+ }
869
+ await cheatCodes.rollup.advanceToEpoch(EpochNumber(minedReceipt.epochNumber + 1));
870
+ await waitForProven(aztecNode, withdrawReceipt, { provenTimeout: 300 });
871
+
872
+ const swapResult = await retryUntil(
873
+ () => aztecNode.getL2ToL1MembershipWitness(minedReceipt.txHash, swapPrivateLeaf),
874
+ 'swap private l2 to l1 witness',
875
+ 60,
876
+ 1,
877
+ );
878
+ const { epochNumber: epoch, numCheckpointsInEpoch } = swapResult;
879
+ const withdrawResult = await retryUntil(
880
+ () => aztecNode.getL2ToL1MembershipWitness(minedReceipt.txHash, withdrawLeaf),
881
+ 'withdraw l2 to l1 witness',
882
+ 60,
883
+ 1,
884
+ );
849
885
 
850
886
  const swapPrivateL2MessageIndex = swapResult.leafIndex;
851
887
  const swapPrivateSiblingPath = swapResult.siblingPath;
@@ -855,6 +891,7 @@ export const uniswapL1L2TestSuite = (
855
891
 
856
892
  const withdrawMessageMetadata = {
857
893
  _epoch: BigInt(epoch),
894
+ _numCheckpointsInEpoch: BigInt(withdrawResult.numCheckpointsInEpoch),
858
895
  _leafIndex: BigInt(withdrawL2MessageIndex),
859
896
  _path: withdrawSiblingPath
860
897
  .toBufferArray()
@@ -863,19 +900,13 @@ export const uniswapL1L2TestSuite = (
863
900
 
864
901
  const swapPrivateMessageMetadata = {
865
902
  _epoch: BigInt(epoch),
903
+ _numCheckpointsInEpoch: BigInt(numCheckpointsInEpoch),
866
904
  _leafIndex: BigInt(swapPrivateL2MessageIndex),
867
905
  _path: swapPrivateSiblingPath
868
906
  .toBufferArray()
869
907
  .map((buf: Buffer) => `0x${buf.toString('hex')}`) as readonly `0x${string}`[],
870
908
  };
871
909
 
872
- // ensure that user's funds were burnt
873
- await wethCrossChainHarness.expectPrivateBalanceOnL2(ownerAddress, wethL2BalanceBeforeSwap - wethAmountToBridge);
874
-
875
- // Since the outbox is only consumable when the epoch is proven, we need to advance to the next epoch.
876
- await cheatCodes.rollup.advanceToEpoch(EpochNumber(epoch + 1));
877
- await waitForProven(aztecNode, withdrawReceipt, { provenTimeout: 300 });
878
-
879
910
  // On L1 call swap_public!
880
911
  logger.info('call swap_public on L1');
881
912
  const swapArgs = [
@@ -975,9 +1006,31 @@ export const uniswapL1L2TestSuite = (
975
1006
  chainId: new Fr(l1Client.chain.id),
976
1007
  });
977
1008
 
978
- const swapResult = (await computeL2ToL1MembershipWitness(aztecNode, swapPublicLeaf, withdrawReceipt.txHash))!;
979
- const { epochNumber: epoch } = swapResult;
980
- const withdrawResult = (await computeL2ToL1MembershipWitness(aztecNode, withdrawLeaf, withdrawReceipt.txHash))!;
1009
+ // check weth balance of owner on L2 (we first bridged `wethAmountToBridge` into L2 and now withdrew it!)
1010
+ await wethCrossChainHarness.expectPublicBalanceOnL2(ownerAddress, 0n);
1011
+
1012
+ // Since the outbox is only consumable when the epoch is proven, we need to advance to the next epoch
1013
+ // before we can ask the witness helper for a covering root.
1014
+ const minedReceipt = await aztecNode.getTxReceipt(withdrawReceipt.txHash);
1015
+ if (minedReceipt.epochNumber === undefined) {
1016
+ throw new Error('Withdraw tx is not yet in an epoch');
1017
+ }
1018
+ await cheatCodes.rollup.advanceToEpoch(EpochNumber(minedReceipt.epochNumber + 1));
1019
+ await waitForProven(aztecNode, withdrawReceipt, { provenTimeout: 300 });
1020
+
1021
+ const swapResult = await retryUntil(
1022
+ () => aztecNode.getL2ToL1MembershipWitness(minedReceipt.txHash, swapPublicLeaf),
1023
+ 'swap public l2 to l1 witness',
1024
+ 60,
1025
+ 1,
1026
+ );
1027
+ const { epochNumber: epoch, numCheckpointsInEpoch } = swapResult;
1028
+ const withdrawResult = await retryUntil(
1029
+ () => aztecNode.getL2ToL1MembershipWitness(minedReceipt.txHash, withdrawLeaf),
1030
+ 'withdraw l2 to l1 witness',
1031
+ 60,
1032
+ 1,
1033
+ );
981
1034
 
982
1035
  const swapPublicL2MessageIndex = swapResult.leafIndex;
983
1036
  const swapPublicSiblingPath = swapResult.siblingPath;
@@ -987,6 +1040,7 @@ export const uniswapL1L2TestSuite = (
987
1040
 
988
1041
  const withdrawMessageMetadata = {
989
1042
  _epoch: BigInt(epoch),
1043
+ _numCheckpointsInEpoch: BigInt(withdrawResult.numCheckpointsInEpoch),
990
1044
  _leafIndex: BigInt(withdrawL2MessageIndex),
991
1045
  _path: withdrawSiblingPath
992
1046
  .toBufferArray()
@@ -995,19 +1049,13 @@ export const uniswapL1L2TestSuite = (
995
1049
 
996
1050
  const swapPublicMessageMetadata = {
997
1051
  _epoch: BigInt(epoch),
1052
+ _numCheckpointsInEpoch: BigInt(numCheckpointsInEpoch),
998
1053
  _leafIndex: BigInt(swapPublicL2MessageIndex),
999
1054
  _path: swapPublicSiblingPath
1000
1055
  .toBufferArray()
1001
1056
  .map((buf: Buffer) => `0x${buf.toString('hex')}`) as readonly `0x${string}`[],
1002
1057
  };
1003
1058
 
1004
- // check weth balance of owner on L2 (we first bridged `wethAmountToBridge` into L2 and now withdrew it!)
1005
- await wethCrossChainHarness.expectPublicBalanceOnL2(ownerAddress, 0n);
1006
-
1007
- // Since the outbox is only consumable when the epoch is proven, we need to advance to the next epoch.
1008
- await cheatCodes.rollup.advanceToEpoch(EpochNumber(epoch + 1));
1009
- await waitForProven(aztecNode, withdrawReceipt, { provenTimeout: 300 });
1010
-
1011
1059
  // Call swap_private on L1
1012
1060
  logger.info('Execute withdraw and swap on the uniswapPortal!');
1013
1061
 
@@ -0,0 +1,23 @@
1
+ import type { Fr } from '@aztec/aztec.js/fields';
2
+ import type { AztecNode } from '@aztec/aztec.js/node';
3
+ import { retryUntil } from '@aztec/foundation/retry';
4
+
5
+ /**
6
+ * Waits until the archiver has seen the L1 to L2 message and indexed it. Unlike
7
+ * {@link waitForL1ToL2MessageReady} from `@aztec/aztec.js/messaging`, this does not
8
+ * require the L2 chain to have advanced to the message's checkpoint — it only confirms
9
+ * the message has been picked up from L1. Use this in tests that explicitly produce L2
10
+ * blocks afterwards to make the message consumable.
11
+ */
12
+ export function waitForL1ToL2MessageSeen(
13
+ node: Pick<AztecNode, 'getL1ToL2MessageCheckpoint'>,
14
+ l1ToL2MessageHash: Fr,
15
+ opts: { timeoutSeconds: number },
16
+ ) {
17
+ return retryUntil(
18
+ async () => (await node.getL1ToL2MessageCheckpoint(l1ToL2MessageHash)) !== undefined,
19
+ `L1 to L2 message ${l1ToL2MessageHash.toString()} seen`,
20
+ opts.timeoutSeconds,
21
+ 1,
22
+ );
23
+ }
@@ -7,6 +7,7 @@ import { SlotNumber } from '@aztec/foundation/branded-types';
7
7
  import { poseidon2Hash } from '@aztec/foundation/crypto/poseidon';
8
8
  import type { TestDateProvider } from '@aztec/foundation/timer';
9
9
  import type { LendingContract } from '@aztec/noir-contracts.js/Lending';
10
+ import type { AztecNode, AztecNodeDebug } from '@aztec/stdlib/interfaces/client';
10
11
 
11
12
  import type { TokenSimulator } from './token_simulator.js';
12
13
 
@@ -92,15 +93,25 @@ export class LendingSimulator {
92
93
  public stableCoin: TokenSimulator,
93
94
  ) {}
94
95
 
95
- async prepare() {
96
+ prepare() {
96
97
  this.accumulator = BASE;
97
- const slot = await this.rollup.getSlotAt(
98
- BigInt(await this.cc.eth.lastBlockTimestamp()) + BigInt(this.ethereumSlotDuration),
99
- );
100
- this.time = Number(await this.rollup.getTimestampForSlot(slot));
98
+ this.time = 0;
101
99
  }
102
100
 
103
- async progressSlots(diff: number, dateProvider?: TestDateProvider) {
101
+ /**
102
+ * Advances the simulator's accumulator and clock to match a block timestamp observed on chain.
103
+ * Call this BEFORE applying any accumulator-sensitive mutation (borrow/repay) so the mutation
104
+ * sees the same accumulator as the contract did during execution.
105
+ */
106
+ observeBlockTimestamp(ts: number) {
107
+ const diff = ts - this.time;
108
+ if (diff > 0) {
109
+ this.accumulator = muldivDown(this.accumulator, computeMultiplier(this.rate, BigInt(diff)), BASE);
110
+ }
111
+ this.time = ts;
112
+ }
113
+
114
+ async progressSlots(diff: number, _dateProvider?: TestDateProvider, node?: AztecNode & AztecNodeDebug) {
104
115
  if (diff <= 1) {
105
116
  return;
106
117
  }
@@ -108,16 +119,19 @@ export class LendingSimulator {
108
119
  const slot = await this.rollup.getSlotAt(BigInt(await this.cc.eth.lastBlockTimestamp()));
109
120
  const targetSlot = SlotNumber(slot + diff);
110
121
  const ts = Number(await this.rollup.getTimestampForSlot(targetSlot));
111
- const timeDiff = ts - this.time;
112
- this.time = ts;
113
122
 
114
- // Mine ethereum blocks such that the next block will be in a new slot
115
- await this.cc.eth.warp(this.time - this.ethereumSlotDuration);
116
- if (dateProvider) {
117
- dateProvider.setTime(this.time * 1000);
123
+ // Queue-aware warp under AutomineSequencer: atomic warp + mineBlock that advances L2 time to the
124
+ // target slot. The cheat code routes through the AutomineSequencer queue when one is installed,
125
+ // and otherwise falls back to a manual warp + mineBlock loop.
126
+ if (node) {
127
+ await this.cc.warpL2TimeAtLeastTo(node, ts);
128
+ } else {
129
+ await this.cc.eth.warp(ts - this.ethereumSlotDuration);
118
130
  }
131
+
132
+ // Mark the latest checkpoint as proven so the rollup does not reorg pending checkpoints when
133
+ // time jumps far enough forward to cross an unproven epoch boundary.
119
134
  await this.cc.rollup.markAsProven(await this.rollup.getCheckpointNumber());
120
- this.accumulator = muldivDown(this.accumulator, computeMultiplier(this.rate, BigInt(timeDiff)), BASE);
121
135
  }
122
136
 
123
137
  depositPrivate(from: AztecAddress, onBehalfOf: Fr, amount: bigint) {
@@ -6,6 +6,7 @@ import { L1FeeJuicePortalManager } from '@aztec/aztec.js/ethereum';
6
6
  import { FeeJuicePaymentMethodWithClaim } from '@aztec/aztec.js/fee';
7
7
  import { type FeePaymentMethod, SponsoredFeePaymentMethod } from '@aztec/aztec.js/fee';
8
8
  import { Fr } from '@aztec/aztec.js/fields';
9
+ import { waitForL1ToL2MessageReady } from '@aztec/aztec.js/messaging';
9
10
  import { type AztecNode, createAztecNodeClient, waitForTx } from '@aztec/aztec.js/node';
10
11
  import type { Wallet } from '@aztec/aztec.js/wallet';
11
12
  import { createEthereumChain } from '@aztec/ethereum/chain';
@@ -14,6 +15,7 @@ import type { Logger } from '@aztec/foundation/log';
14
15
  import { makeBackoff, retry, retryUntil } from '@aztec/foundation/retry';
15
16
  import { TokenContract } from '@aztec/noir-contracts.js/Token';
16
17
  import type { AztecNodeAdmin } from '@aztec/stdlib/interfaces/client';
18
+ import { TxStatus } from '@aztec/stdlib/tx';
17
19
  import { registerInitialLocalNetworkAccountsInWallet } from '@aztec/wallets/testing';
18
20
 
19
21
  import { getACVMConfig } from '../fixtures/get_acvm_config.js';
@@ -332,9 +334,7 @@ async function bridgeL1FeeJuice(
332
334
  const portal = await L1FeeJuicePortalManager.new(aztecNode, l1Client, log);
333
335
  const claim = await portal.bridgeTokensPublic(recipient, amount, true /* mint */);
334
336
 
335
- const isSynced = async () =>
336
- (await aztecNode.getL1ToL2MessageCheckpoint(Fr.fromHexString(claim.messageHash))) !== undefined;
337
- await retryUntil(isSynced, `message ${claim.messageHash} sync`, 24, 0.5);
337
+ await waitForL1ToL2MessageReady(aztecNode, Fr.fromHexString(claim.messageHash), { timeoutSeconds: 24 });
338
338
 
339
339
  log.info(`Created a claim for ${amount} L1 fee juice to ${recipient}.`, claim);
340
340
  return claim;
@@ -427,7 +427,9 @@ export async function performTransfers({
427
427
 
428
428
  const provenTxs = await Promise.all(txs);
429
429
 
430
- await Promise.all(provenTxs.map(t => t.send({ wait: { timeout: 600 } })));
430
+ // Wait only for the txs to be proposed, not checkpointed. This is enough to keep the chain
431
+ // loaded for the reorg scenario, and avoids each round blocking on the (slower) checkpoint lag.
432
+ await Promise.all(provenTxs.map(t => t.send({ wait: { timeout: 600, waitForStatus: TxStatus.PROPOSED } })));
431
433
 
432
434
  logger.info(`Completed round ${i + 1} / ${rounds}`);
433
435
  }