@aztec/end-to-end 0.78.1 → 0.80.0

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 (60) hide show
  1. package/dest/bench/utils.d.ts +1 -13
  2. package/dest/bench/utils.d.ts.map +1 -1
  3. package/dest/bench/utils.js +6 -6
  4. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts +2 -1
  5. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts.map +1 -1
  6. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.js +2 -1
  7. package/dest/e2e_epochs/epochs_test.d.ts +1 -1
  8. package/dest/e2e_epochs/epochs_test.d.ts.map +1 -1
  9. package/dest/e2e_epochs/epochs_test.js +1 -1
  10. package/dest/e2e_fees/fees_test.d.ts +3 -1
  11. package/dest/e2e_fees/fees_test.d.ts.map +1 -1
  12. package/dest/e2e_fees/fees_test.js +3 -1
  13. package/dest/e2e_p2p/p2p_network.d.ts +1 -1
  14. package/dest/e2e_p2p/p2p_network.d.ts.map +1 -1
  15. package/dest/e2e_p2p/p2p_network.js +1 -2
  16. package/dest/e2e_prover/e2e_prover_test.d.ts +3 -1
  17. package/dest/e2e_prover/e2e_prover_test.d.ts.map +1 -1
  18. package/dest/e2e_prover/e2e_prover_test.js +2 -1
  19. package/dest/fixtures/setup_l1_contracts.d.ts +3 -3
  20. package/dest/fixtures/setup_l1_contracts.d.ts.map +1 -1
  21. package/dest/fixtures/setup_l1_contracts.js +1 -1
  22. package/dest/fixtures/snapshot_manager.d.ts +3 -2
  23. package/dest/fixtures/snapshot_manager.d.ts.map +1 -1
  24. package/dest/fixtures/snapshot_manager.js +27 -23
  25. package/dest/fixtures/utils.d.ts +3 -2
  26. package/dest/fixtures/utils.d.ts.map +1 -1
  27. package/dest/fixtures/utils.js +16 -14
  28. package/dest/sample-dapp/deploy.js +1 -0
  29. package/dest/shared/cross_chain_test_harness.d.ts +1 -1
  30. package/dest/shared/cross_chain_test_harness.d.ts.map +1 -1
  31. package/dest/shared/cross_chain_test_harness.js +2 -1
  32. package/dest/shared/uniswap_l1_l2.d.ts +2 -1
  33. package/dest/shared/uniswap_l1_l2.d.ts.map +1 -1
  34. package/dest/simulators/lending_simulator.d.ts +2 -1
  35. package/dest/simulators/lending_simulator.d.ts.map +1 -1
  36. package/dest/simulators/token_simulator.d.ts.map +1 -1
  37. package/dest/simulators/token_simulator.js +3 -8
  38. package/dest/spartan/setup_test_wallets.d.ts +1 -1
  39. package/dest/spartan/setup_test_wallets.d.ts.map +1 -1
  40. package/dest/spartan/setup_test_wallets.js +4 -6
  41. package/dest/spartan/utils.d.ts +15 -1
  42. package/dest/spartan/utils.d.ts.map +1 -1
  43. package/dest/spartan/utils.js +75 -12
  44. package/package.json +32 -32
  45. package/src/bench/utils.ts +6 -8
  46. package/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts +1 -1
  47. package/src/e2e_epochs/epochs_test.ts +1 -1
  48. package/src/e2e_fees/fees_test.ts +2 -2
  49. package/src/e2e_p2p/p2p_network.ts +1 -2
  50. package/src/e2e_prover/e2e_prover_test.ts +2 -3
  51. package/src/fixtures/setup_l1_contracts.ts +2 -2
  52. package/src/fixtures/snapshot_manager.ts +42 -30
  53. package/src/fixtures/utils.ts +21 -16
  54. package/src/sample-dapp/deploy.mjs +2 -0
  55. package/src/shared/cross_chain_test_harness.ts +6 -2
  56. package/src/shared/uniswap_l1_l2.ts +1 -1
  57. package/src/simulators/lending_simulator.ts +2 -1
  58. package/src/simulators/token_simulator.ts +5 -8
  59. package/src/spartan/setup_test_wallets.ts +4 -4
  60. package/src/spartan/utils.ts +60 -2
@@ -2,23 +2,25 @@ import { SchnorrAccountContractArtifact } from '@aztec/accounts/schnorr';
2
2
  import { type InitialAccountData, deployFundedSchnorrAccounts, generateSchnorrAccounts } from '@aztec/accounts/testing';
3
3
  import { type AztecNodeConfig, AztecNodeService, getConfigEnvVars } from '@aztec/aztec-node';
4
4
  import {
5
- AnvilTestWatcher,
6
5
  type AztecAddress,
7
6
  BatchCall,
8
- type Capsule,
9
- CheatCodes,
10
7
  type CompleteAddress,
11
8
  type ContractFunctionInteraction,
12
- type DeployL1ContractsReturnType,
13
- type FunctionCall,
14
9
  type Logger,
15
10
  type PXE,
16
11
  type Wallet,
17
12
  getContractClassFromArtifact,
18
13
  } from '@aztec/aztec.js';
19
14
  import { deployInstance, registerContractClass } from '@aztec/aztec.js/deployment';
15
+ import { AnvilTestWatcher, CheatCodes } from '@aztec/aztec.js/testing';
20
16
  import { type BlobSinkServer, createBlobSinkServer } from '@aztec/blob-sink/server';
21
- import { type DeployL1ContractsArgs, createL1Clients, getL1ContractsConfigEnvVars, l1Artifacts } from '@aztec/ethereum';
17
+ import {
18
+ type DeployL1ContractsArgs,
19
+ type DeployL1ContractsReturnType,
20
+ createL1Clients,
21
+ getL1ContractsConfigEnvVars,
22
+ l1Artifacts,
23
+ } from '@aztec/ethereum';
22
24
  import { EthCheatCodesWithState, startAnvil } from '@aztec/ethereum/test';
23
25
  import { asyncMap } from '@aztec/foundation/async-map';
24
26
  import { randomBytes } from '@aztec/foundation/crypto';
@@ -306,16 +308,6 @@ async function setupFromFresh(
306
308
  }
307
309
  aztecNodeConfig.blobSinkUrl = `http://localhost:${blobSinkPort}`;
308
310
 
309
- // Setup blob sink service
310
- const blobSink = await createBlobSinkServer({
311
- port: blobSinkPort,
312
- dataStoreConfig: {
313
- dataDirectory: aztecNodeConfig.dataDirectory,
314
- dataStoreMapSizeKB: aztecNodeConfig.dataStoreMapSizeKB,
315
- },
316
- });
317
- await blobSink.start();
318
-
319
311
  // Start anvil. We go via a wrapper script to ensure if the parent dies, anvil dies.
320
312
  logger.verbose('Starting anvil...');
321
313
  const res = await startAnvil({ l1BlockTime: opts.ethereumSlotDuration });
@@ -401,6 +393,22 @@ async function setupFromFresh(
401
393
 
402
394
  const telemetry = getEndToEndTestTelemetryClient(opts.metricsPort);
403
395
 
396
+ // Setup blob sink service
397
+ const blobSink = await createBlobSinkServer(
398
+ {
399
+ l1ChainId: aztecNodeConfig.l1ChainId,
400
+ l1RpcUrls: aztecNodeConfig.l1RpcUrls,
401
+ rollupAddress: aztecNodeConfig.l1Contracts.rollupAddress,
402
+ port: blobSinkPort,
403
+ dataStoreConfig: {
404
+ dataDirectory: aztecNodeConfig.dataDirectory,
405
+ dataStoreMapSizeKB: aztecNodeConfig.dataStoreMapSizeKB,
406
+ },
407
+ },
408
+ telemetry,
409
+ );
410
+ await blobSink.start();
411
+
404
412
  logger.verbose('Creating and synching an aztec node...');
405
413
  const dateProvider = new TestDateProvider();
406
414
  const aztecNode = await AztecNodeService.createAndSync(
@@ -475,15 +483,6 @@ async function setupFromState(statePath: string, logger: Logger): Promise<Subsys
475
483
  JSON.parse(readFileSync(`${statePath}/accounts.json`, 'utf-8'), reviver) || [];
476
484
  const { prefilledPublicData } = await getGenesisValues(initialFundedAccounts.map(a => a.address));
477
485
 
478
- const blobSink = await createBlobSinkServer({
479
- port: blobSinkPort,
480
- dataStoreConfig: {
481
- dataDirectory: statePath,
482
- dataStoreMapSizeKB: aztecNodeConfig.dataStoreMapSizeKB,
483
- },
484
- });
485
- await blobSink.start();
486
-
487
486
  // Start anvil. We go via a wrapper script to ensure if the parent dies, anvil dies.
488
487
  const { anvil, rpcUrl } = await startAnvil();
489
488
  aztecNodeConfig.l1RpcUrls = [rpcUrl];
@@ -515,9 +514,24 @@ async function setupFromState(statePath: string, logger: Logger): Promise<Subsys
515
514
  );
516
515
  await watcher.start();
517
516
 
518
- logger.verbose('Creating aztec node...');
519
517
  const telemetry = initTelemetryClient(getTelemetryConfig());
520
518
  const dateProvider = new TestDateProvider();
519
+ const blobSink = await createBlobSinkServer(
520
+ {
521
+ l1ChainId: aztecNodeConfig.l1ChainId,
522
+ l1RpcUrls: aztecNodeConfig.l1RpcUrls,
523
+ rollupAddress: aztecNodeConfig.l1Contracts.rollupAddress,
524
+ port: blobSinkPort,
525
+ dataStoreConfig: {
526
+ dataDirectory: statePath,
527
+ dataStoreMapSizeKB: aztecNodeConfig.dataStoreMapSizeKB,
528
+ },
529
+ },
530
+ telemetry,
531
+ );
532
+ await blobSink.start();
533
+
534
+ logger.verbose('Creating aztec node...');
521
535
  const aztecNode = await AztecNodeService.createAndSync(
522
536
  aztecNodeConfig,
523
537
  { telemetry, dateProvider },
@@ -604,14 +618,12 @@ export async function publicDeployAccounts(
604
618
  const contractClass = await getContractClassFromArtifact(SchnorrAccountContractArtifact);
605
619
  const alreadyRegistered = (await sender.getContractClassMetadata(contractClass.id)).isContractClassPubliclyRegistered;
606
620
 
607
- const fns: ContractFunctionInteraction[] = await Promise.all([
621
+ const calls: ContractFunctionInteraction[] = await Promise.all([
608
622
  ...(!alreadyRegistered ? [registerContractClass(sender, SchnorrAccountContractArtifact)] : []),
609
623
  ...instances.map(instance => deployInstance(sender, instance!)),
610
624
  ]);
611
- const calls: FunctionCall[] = await Promise.all(fns.map(fn => fn.request()));
612
- const capsules: Capsule[] = fns.map(fn => fn.getCapsules()).flat();
613
625
 
614
626
  const batch = new BatchCall(sender, calls);
615
- batch.addCapsules(capsules);
627
+
616
628
  await batch.send().wait({ proven: waitUntilProven });
617
629
  }
@@ -10,13 +10,10 @@ import { type Archiver, createArchiver } from '@aztec/archiver';
10
10
  import { type AztecNodeConfig, AztecNodeService, getConfigEnvVars } from '@aztec/aztec-node';
11
11
  import {
12
12
  type AccountWalletWithSecretKey,
13
- AnvilTestWatcher,
14
13
  type AztecAddress,
15
14
  type AztecNode,
16
15
  BatchCall,
17
- CheatCodes,
18
16
  type ContractMethod,
19
- type DeployL1ContractsReturnType,
20
17
  FeeJuicePaymentMethod,
21
18
  type Logger,
22
19
  type PXE,
@@ -25,20 +22,22 @@ import {
25
22
  createAztecNodeClient,
26
23
  createLogger,
27
24
  createPXEClient,
28
- deployL1Contracts,
29
25
  makeFetch,
30
26
  waitForPXE,
31
27
  } from '@aztec/aztec.js';
32
28
  import { deployInstance, registerContractClass } from '@aztec/aztec.js/deployment';
29
+ import { AnvilTestWatcher, CheatCodes } from '@aztec/aztec.js/testing';
33
30
  import type { BBNativePrivateKernelProver } from '@aztec/bb-prover';
34
31
  import { createBlobSinkClient } from '@aztec/blob-sink/client';
35
32
  import { type BlobSinkServer, createBlobSinkServer } from '@aztec/blob-sink/server';
36
33
  import { FEE_JUICE_INITIAL_MINT, GENESIS_ARCHIVE_ROOT, GENESIS_BLOCK_HASH } from '@aztec/constants';
37
34
  import {
38
35
  type DeployL1ContractsArgs,
36
+ type DeployL1ContractsReturnType,
39
37
  ForwarderContract,
40
38
  NULL_KEY,
41
39
  createL1Clients,
40
+ deployL1Contracts,
42
41
  getL1ContractsConfigEnvVars,
43
42
  isAnvilTestChain,
44
43
  l1Artifacts,
@@ -408,12 +407,6 @@ export async function setup(
408
407
  return await setupWithRemoteEnvironment(publisherHdAccount!, config, logger, numberOfAccounts);
409
408
  }
410
409
 
411
- // Blob sink service - blobs get posted here and served from here
412
- const blobSinkPort = await getPort();
413
- const blobSink = await createBlobSinkServer({ port: blobSinkPort });
414
- await blobSink.start();
415
- config.blobSinkUrl = `http://localhost:${blobSinkPort}`;
416
-
417
410
  const initialFundedAccounts =
418
411
  opts.initialFundedAccounts ??
419
412
  (await generateSchnorrAccounts(opts.numberOfInitialFundedAccounts ?? numberOfAccounts));
@@ -475,6 +468,22 @@ export async function setup(
475
468
 
476
469
  await watcher.start();
477
470
 
471
+ const telemetry = getTelemetryClient(opts.telemetryConfig);
472
+
473
+ // Blob sink service - blobs get posted here and served from here
474
+ const blobSinkPort = await getPort();
475
+ const blobSink = await createBlobSinkServer(
476
+ {
477
+ l1ChainId: config.l1ChainId,
478
+ l1RpcUrls: config.l1RpcUrls,
479
+ rollupAddress: config.l1Contracts.rollupAddress,
480
+ port: blobSinkPort,
481
+ },
482
+ telemetry,
483
+ );
484
+ await blobSink.start();
485
+ config.blobSinkUrl = `http://localhost:${blobSinkPort}`;
486
+
478
487
  logger.verbose('Creating and synching an aztec node...');
479
488
 
480
489
  const acvmConfig = await getACVMConfig(logger);
@@ -490,8 +499,6 @@ export async function setup(
490
499
  }
491
500
  config.l1PublishRetryIntervalMS = 100;
492
501
 
493
- const telemetry = getTelemetryClient(opts.telemetryConfig);
494
-
495
502
  const blobSinkClient = createBlobSinkClient(config);
496
503
  const aztecNode = await AztecNodeService.createAndSync(
497
504
  config,
@@ -628,10 +635,8 @@ export async function ensureAccountsPubliclyDeployed(sender: Wallet, accountsToD
628
635
  if (!(await sender.getContractClassMetadata(contractClass.id, true)).isContractClassPubliclyRegistered) {
629
636
  await (await registerContractClass(sender, SchnorrAccountContractArtifact)).send().wait();
630
637
  }
631
- const requests = await Promise.all(
632
- instances.map(async instance => (await deployInstance(sender, instance!)).request()),
633
- );
634
- const batch = new BatchCall(sender, [...requests]);
638
+ const requests = await Promise.all(instances.map(async instance => await deployInstance(sender, instance!)));
639
+ const batch = new BatchCall(sender, requests);
635
640
  await batch.send().wait();
636
641
  }
637
642
  // docs:end:public_deploy_accounts
@@ -2,7 +2,9 @@
2
2
  import { getInitialTestAccountsWallets } from '@aztec/accounts/testing';
3
3
  import { Contract, createPXEClient, loadContractArtifact, waitForPXE } from '@aztec/aztec.js';
4
4
  // docs:end:deploy-imports
5
+ // docs:start:import_artifact
5
6
  import { TokenContractArtifact } from '@aztec/noir-contracts.js/Token';
7
+ // docs:end:import_artifact
6
8
  import { TokenContract } from '@aztec/noir-contracts.js/Token';
7
9
 
8
10
  import { writeFileSync } from 'fs';
@@ -15,10 +15,14 @@ import {
15
15
  type SiblingPath,
16
16
  type TxReceipt,
17
17
  type Wallet,
18
- deployL1Contract,
19
18
  retryUntil,
20
19
  } from '@aztec/aztec.js';
21
- import type { L1ContractAddresses, ViemPublicClient, ViemWalletClient } from '@aztec/ethereum';
20
+ import {
21
+ type L1ContractAddresses,
22
+ type ViemPublicClient,
23
+ type ViemWalletClient,
24
+ deployL1Contract,
25
+ } from '@aztec/ethereum';
22
26
  import { TestERC20Abi, TokenPortalAbi, TokenPortalBytecode } from '@aztec/l1-artifacts';
23
27
  import { TokenContract } from '@aztec/noir-contracts.js/Token';
24
28
  import { TokenBridgeContract } from '@aztec/noir-contracts.js/TokenBridge';
@@ -2,7 +2,6 @@ import {
2
2
  type AccountWallet,
3
3
  AztecAddress,
4
4
  type AztecNode,
5
- type CheatCodes,
6
5
  EthAddress,
7
6
  Fr,
8
7
  type Logger,
@@ -10,6 +9,7 @@ import {
10
9
  computeAuthWitMessageHash,
11
10
  generateClaimSecret,
12
11
  } from '@aztec/aztec.js';
12
+ import { CheatCodes } from '@aztec/aztec.js/testing';
13
13
  import {
14
14
  type DeployL1ContractsReturnType,
15
15
  type ViemPublicClient,
@@ -1,5 +1,6 @@
1
1
  // Convenience struct to hold an account's address and secret that can easily be passed around.
2
- import { AztecAddress, type CheatCodes, Fr } from '@aztec/aztec.js';
2
+ import { AztecAddress, Fr } from '@aztec/aztec.js';
3
+ import { CheatCodes } from '@aztec/aztec.js/testing';
3
4
  import { pedersenHash } from '@aztec/foundation/crypto';
4
5
  import type { TestDateProvider } from '@aztec/foundation/timer';
5
6
  import type { RollupAbi } from '@aztec/l1-artifacts';
@@ -97,10 +97,10 @@ export class TokenSimulator {
97
97
 
98
98
  async checkPublic() {
99
99
  // public calls
100
- const calls = [await this.token.methods.total_supply().request()];
101
- for (const address of this.accounts) {
102
- calls.push(await this.token.methods.balance_of_public(address).request());
103
- }
100
+ const calls = [
101
+ this.token.methods.total_supply(),
102
+ ...this.accounts.map(address => this.token.methods.balance_of_public(address)),
103
+ ];
104
104
 
105
105
  const results = (
106
106
  await Promise.all(chunk(calls, 4).map(batch => new BatchCall(this.defaultWallet, batch).simulate()))
@@ -126,10 +126,7 @@ export class TokenSimulator {
126
126
  }
127
127
  }
128
128
 
129
- const defaultCalls = [];
130
- for (const address of defaultLookups) {
131
- defaultCalls.push(await this.token.methods.balance_of_private(address).request());
132
- }
129
+ const defaultCalls = defaultLookups.map(address => this.token.methods.balance_of_private(address));
133
130
  const results = (
134
131
  await Promise.all(chunk(defaultCalls, 4).map(batch => new BatchCall(this.defaultWallet, batch).simulate()))
135
132
  ).flat();
@@ -50,7 +50,7 @@ export async function setupTestWalletsWithTokens(
50
50
  export async function deployTestWalletWithTokens(
51
51
  pxeUrl: string,
52
52
  nodeUrl: string,
53
- l1RpcUrl: string,
53
+ l1RpcUrls: string[],
54
54
  mnemonicOrPrivateKey: string,
55
55
  mintAmount: bigint,
56
56
  logger: Logger,
@@ -71,7 +71,7 @@ export async function deployTestWalletWithTokens(
71
71
 
72
72
  const claims = await Promise.all(
73
73
  fundedAccounts.map(a =>
74
- bridgeL1FeeJuice(l1RpcUrl, mnemonicOrPrivateKey, pxe, a.getAddress(), initialFeeJuice, logger),
74
+ bridgeL1FeeJuice(l1RpcUrls, mnemonicOrPrivateKey, pxe, a.getAddress(), initialFeeJuice, logger),
75
75
  ),
76
76
  );
77
77
 
@@ -97,7 +97,7 @@ export async function deployTestWalletWithTokens(
97
97
  }
98
98
 
99
99
  async function bridgeL1FeeJuice(
100
- l1RpcUrl: string,
100
+ l1RpcUrls: string[],
101
101
  mnemonicOrPrivateKey: string,
102
102
  pxe: PXE,
103
103
  recipient: AztecAddress,
@@ -105,7 +105,7 @@ async function bridgeL1FeeJuice(
105
105
  log: Logger,
106
106
  ) {
107
107
  const { l1ChainId } = await pxe.getNodeInfo();
108
- const chain = createEthereumChain([l1RpcUrl], l1ChainId);
108
+ const chain = createEthereumChain(l1RpcUrls, l1ChainId);
109
109
  const { publicClient, walletClient } = createL1Clients(chain.rpcUrls, mnemonicOrPrivateKey, chain.chainInfo);
110
110
 
111
111
  const portal = await L1FeeJuicePortalManager.new(pxe, publicClient, walletClient, log);
@@ -1,5 +1,5 @@
1
1
  import { createAztecNodeClient, createLogger, sleep } from '@aztec/aztec.js';
2
- import type { RollupCheatCodes } from '@aztec/aztec.js/ethereum';
2
+ import type { RollupCheatCodes } from '@aztec/aztec.js/testing';
3
3
  import type { Logger } from '@aztec/foundation/log';
4
4
  import type { SequencerConfig } from '@aztec/sequencer-client';
5
5
 
@@ -86,6 +86,49 @@ export function setupEnvironment(env: unknown): EnvConfig {
86
86
  return config;
87
87
  }
88
88
 
89
+ /**
90
+ * @param path - The path to the script, relative to the project root
91
+ * @param args - The arguments to pass to the script
92
+ * @param logger - The logger to use
93
+ * @returns The exit code of the script
94
+ */
95
+ function runScript(path: string, args: string[], logger: Logger, env?: Record<string, string>) {
96
+ const childProcess = spawn(path, args, {
97
+ stdio: ['ignore', 'pipe', 'pipe'],
98
+ env: env ? { ...process.env, ...env } : process.env,
99
+ });
100
+ return new Promise<number>((resolve, reject) => {
101
+ childProcess.on('close', (code: number | null) => resolve(code ?? 0));
102
+ childProcess.on('error', reject);
103
+ childProcess.stdout?.on('data', (data: Buffer) => {
104
+ logger.info(data.toString());
105
+ });
106
+ childProcess.stderr?.on('data', (data: Buffer) => {
107
+ logger.error(data.toString());
108
+ });
109
+ });
110
+ }
111
+
112
+ export function getAztecBin() {
113
+ return path.join(getGitProjectRoot(), 'yarn-project/aztec/dest/bin/index.js');
114
+ }
115
+
116
+ /**
117
+ * Runs the Aztec binary
118
+ * @param args - The arguments to pass to the Aztec binary
119
+ * @param logger - The logger to use
120
+ * @param env - Optional environment variables to set for the process
121
+ * @returns The exit code of the Aztec binary
122
+ */
123
+ export function runAztecBin(args: string[], logger: Logger, env?: Record<string, string>) {
124
+ return runScript('node', [getAztecBin(), ...args], logger, env);
125
+ }
126
+
127
+ export function runProjectScript(script: string, args: string[], logger: Logger, env?: Record<string, string>) {
128
+ const scriptPath = script.startsWith('/') ? script : path.join(getGitProjectRoot(), script);
129
+ return runScript(scriptPath, args, logger, env);
130
+ }
131
+
89
132
  export async function startPortForward({
90
133
  resource,
91
134
  namespace,
@@ -128,7 +171,6 @@ export async function startPortForward({
128
171
  }
129
172
  const portNumber = parseInt(str.slice(port + 1));
130
173
  logger.info(`Port forward connected: ${portNumber}`);
131
- logger.info(`Port forward connected: ${portNumber}`);
132
174
  resolve(portNumber);
133
175
  } else {
134
176
  logger.silent(str);
@@ -580,3 +622,19 @@ export async function rollAztecPods(namespace: string) {
580
622
  await waitForResourceByLabel({ resource: 'pods', namespace: namespace, label: 'app=validator' });
581
623
  await waitForResourceByLabel({ resource: 'pods', namespace: namespace, label: 'app=pxe' });
582
624
  }
625
+
626
+ /**
627
+ * Returns the absolute path to the git repository root
628
+ */
629
+ export function getGitProjectRoot(): string {
630
+ try {
631
+ const rootDir = execSync('git rev-parse --show-toplevel', {
632
+ encoding: 'utf-8',
633
+ stdio: ['ignore', 'pipe', 'ignore'],
634
+ }).trim();
635
+
636
+ return rootDir;
637
+ } catch (error) {
638
+ throw new Error(`Failed to determine git project root: ${error}`);
639
+ }
640
+ }