@aztec/aztec 0.0.1-commit.cf93bcc56 → 0.0.1-commit.d117d021b

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 (102) hide show
  1. package/dest/bin/index.js +7 -3
  2. package/dest/cli/admin_api_key_store.d.ts +3 -3
  3. package/dest/cli/admin_api_key_store.d.ts.map +1 -1
  4. package/dest/cli/admin_api_key_store.js +3 -3
  5. package/dest/cli/aztec_start_action.d.ts +1 -1
  6. package/dest/cli/aztec_start_action.d.ts.map +1 -1
  7. package/dest/cli/aztec_start_action.js +9 -7
  8. package/dest/cli/aztec_start_options.js +6 -6
  9. package/dest/cli/cli.d.ts +1 -1
  10. package/dest/cli/cli.d.ts.map +1 -1
  11. package/dest/cli/cli.js +3 -4
  12. package/dest/cli/cmds/compile.d.ts +4 -0
  13. package/dest/cli/cmds/compile.d.ts.map +1 -0
  14. package/dest/cli/cmds/compile.js +165 -0
  15. package/dest/cli/cmds/profile.d.ts +4 -0
  16. package/dest/cli/cmds/profile.d.ts.map +1 -0
  17. package/dest/cli/cmds/profile.js +8 -0
  18. package/dest/cli/cmds/profile_flamegraph.d.ts +4 -0
  19. package/dest/cli/cmds/profile_flamegraph.d.ts.map +1 -0
  20. package/dest/cli/cmds/profile_flamegraph.js +51 -0
  21. package/dest/cli/cmds/profile_gates.d.ts +4 -0
  22. package/dest/cli/cmds/profile_gates.d.ts.map +1 -0
  23. package/dest/cli/cmds/profile_gates.js +57 -0
  24. package/dest/cli/cmds/profile_utils.d.ts +18 -0
  25. package/dest/cli/cmds/profile_utils.d.ts.map +1 -0
  26. package/dest/cli/cmds/profile_utils.js +50 -0
  27. package/dest/cli/cmds/standby.d.ts +27 -0
  28. package/dest/cli/cmds/standby.d.ts.map +1 -0
  29. package/dest/cli/cmds/standby.js +78 -0
  30. package/dest/cli/cmds/start_archiver.d.ts +2 -2
  31. package/dest/cli/cmds/start_archiver.d.ts.map +1 -1
  32. package/dest/cli/cmds/start_archiver.js +1 -1
  33. package/dest/cli/cmds/start_node.d.ts +3 -2
  34. package/dest/cli/cmds/start_node.d.ts.map +1 -1
  35. package/dest/cli/cmds/start_node.js +15 -17
  36. package/dest/cli/cmds/start_prover_broker.d.ts +1 -1
  37. package/dest/cli/cmds/start_prover_broker.d.ts.map +1 -1
  38. package/dest/cli/cmds/start_prover_broker.js +6 -6
  39. package/dest/cli/cmds/utils/artifacts.d.ts +21 -0
  40. package/dest/cli/cmds/utils/artifacts.d.ts.map +1 -0
  41. package/dest/cli/cmds/utils/artifacts.js +24 -0
  42. package/dest/cli/cmds/utils/needs_recompile.d.ts +10 -0
  43. package/dest/cli/cmds/utils/needs_recompile.d.ts.map +1 -0
  44. package/dest/cli/cmds/utils/needs_recompile.js +124 -0
  45. package/dest/cli/cmds/utils/spawn.d.ts +3 -0
  46. package/dest/cli/cmds/utils/spawn.d.ts.map +1 -0
  47. package/dest/cli/cmds/utils/spawn.js +16 -0
  48. package/dest/cli/util.d.ts +3 -5
  49. package/dest/cli/util.d.ts.map +1 -1
  50. package/dest/cli/util.js +40 -81
  51. package/dest/examples/token.js +4 -4
  52. package/dest/local-network/banana_fpc.js +1 -1
  53. package/dest/local-network/local-network.d.ts +1 -1
  54. package/dest/local-network/local-network.d.ts.map +1 -1
  55. package/dest/local-network/local-network.js +35 -6
  56. package/dest/testing/anvil_test_watcher.d.ts +9 -1
  57. package/dest/testing/anvil_test_watcher.d.ts.map +1 -1
  58. package/dest/testing/anvil_test_watcher.js +52 -15
  59. package/dest/testing/index.d.ts +2 -1
  60. package/dest/testing/index.d.ts.map +1 -1
  61. package/dest/testing/index.js +1 -0
  62. package/dest/testing/token_allowed_setup.d.ts +7 -0
  63. package/dest/testing/token_allowed_setup.d.ts.map +1 -0
  64. package/dest/testing/token_allowed_setup.js +20 -0
  65. package/package.json +35 -34
  66. package/scripts/add_crate.sh +102 -0
  67. package/scripts/aztec.sh +11 -5
  68. package/scripts/init.sh +23 -19
  69. package/scripts/new.sh +48 -24
  70. package/scripts/setup_workspace.sh +68 -0
  71. package/src/bin/index.ts +7 -3
  72. package/src/cli/admin_api_key_store.ts +4 -4
  73. package/src/cli/aztec_start_action.ts +9 -7
  74. package/src/cli/aztec_start_options.ts +6 -6
  75. package/src/cli/cli.ts +3 -4
  76. package/src/cli/cmds/compile.ts +190 -0
  77. package/src/cli/cmds/profile.ts +25 -0
  78. package/src/cli/cmds/profile_flamegraph.ts +63 -0
  79. package/src/cli/cmds/profile_gates.ts +67 -0
  80. package/src/cli/cmds/profile_utils.ts +58 -0
  81. package/src/cli/cmds/standby.ts +111 -0
  82. package/src/cli/cmds/start_archiver.ts +1 -1
  83. package/src/cli/cmds/start_node.ts +25 -24
  84. package/src/cli/cmds/start_prover_broker.ts +7 -14
  85. package/src/cli/cmds/utils/artifacts.ts +44 -0
  86. package/src/cli/cmds/utils/needs_recompile.ts +139 -0
  87. package/src/cli/cmds/utils/spawn.ts +16 -0
  88. package/src/cli/util.ts +43 -76
  89. package/src/examples/token.ts +6 -4
  90. package/src/local-network/banana_fpc.ts +1 -1
  91. package/src/local-network/local-network.ts +33 -4
  92. package/src/testing/anvil_test_watcher.ts +59 -15
  93. package/src/testing/index.ts +1 -0
  94. package/src/testing/token_allowed_setup.ts +19 -0
  95. package/dest/cli/release_version.d.ts +0 -2
  96. package/dest/cli/release_version.d.ts.map +0 -1
  97. package/dest/cli/release_version.js +0 -14
  98. package/scripts/compile.sh +0 -44
  99. package/scripts/extract_function.js +0 -47
  100. package/scripts/flamegraph.sh +0 -59
  101. package/scripts/setup_project.sh +0 -31
  102. package/src/cli/release_version.ts +0 -21
@@ -0,0 +1,44 @@
1
+ import { readFile, readdir } from 'fs/promises';
2
+ import { join } from 'path';
3
+
4
+ export interface CompiledArtifact {
5
+ noir_version: string;
6
+ file_map: unknown;
7
+ functions: ContractFunction[];
8
+ bytecode?: string;
9
+ }
10
+
11
+ export interface ContractFunction {
12
+ name: string;
13
+ abi: unknown;
14
+ bytecode: string;
15
+ debug_symbols: unknown;
16
+ is_unconstrained?: boolean;
17
+ }
18
+
19
+ export interface ArtifactFile {
20
+ name: string;
21
+ filePath: string;
22
+ content: CompiledArtifact;
23
+ }
24
+
25
+ /** Reads all JSON artifact files from a target directory and returns their parsed contents. */
26
+ export async function readArtifactFiles(targetDir: string): Promise<ArtifactFile[]> {
27
+ let entries: string[];
28
+ try {
29
+ entries = (await readdir(targetDir)).filter(f => f.endsWith('.json'));
30
+ } catch (err: any) {
31
+ if (err?.code === 'ENOENT') {
32
+ throw new Error(`Target directory '${targetDir}' does not exist. Compile first with 'aztec compile'.`);
33
+ }
34
+ throw err;
35
+ }
36
+
37
+ const artifacts: ArtifactFile[] = [];
38
+ for (const file of entries) {
39
+ const filePath = join(targetDir, file);
40
+ const content = JSON.parse(await readFile(filePath, 'utf-8')) as CompiledArtifact;
41
+ artifacts.push({ name: file.replace('.json', ''), filePath, content });
42
+ }
43
+ return artifacts;
44
+ }
@@ -0,0 +1,139 @@
1
+ import TOML from '@iarna/toml';
2
+ import { readFile, readdir, stat } from 'fs/promises';
3
+ import { join, resolve } from 'path';
4
+
5
+ /**
6
+ * Returns true if recompilation is needed: either no artifacts exist in target/ or any .nr or Nargo.toml source file
7
+ * (including path-based dependencies) is newer than the oldest artifact. We compare against the oldest artifact so
8
+ * that a source change between the oldest and newest compilation (e.g. in a multi-contract workspace) still triggers
9
+ * a recompile.
10
+ *
11
+ * Note: The above implies that if there is a random json file in the target dir we would be always recompiling.
12
+ */
13
+ export async function needsRecompile(): Promise<boolean> {
14
+ const oldestArtifactMs = await getOldestArtifactModificationTime('target');
15
+ if (oldestArtifactMs === undefined) {
16
+ return true;
17
+ }
18
+
19
+ const crateDirs = await collectCrateDirs('.');
20
+ return hasNewerSourceFile(crateDirs, oldestArtifactMs);
21
+ }
22
+
23
+ /**
24
+ * Returns the last modification time (timestamp in ms) of the oldest .json artifact in targetDir, or undefined if
25
+ * none exist.
26
+ */
27
+ async function getOldestArtifactModificationTime(targetDir: string): Promise<number | undefined> {
28
+ let entries: string[];
29
+ try {
30
+ entries = (await readdir(targetDir)).filter(f => f.endsWith('.json'));
31
+ } catch (err: any) {
32
+ if (err?.code === 'ENOENT') {
33
+ return undefined;
34
+ }
35
+ throw err;
36
+ }
37
+
38
+ if (entries.length === 0) {
39
+ return undefined;
40
+ }
41
+
42
+ let oldest = Infinity;
43
+ for (const entry of entries) {
44
+ const s = await stat(join(targetDir, entry));
45
+ if (s.mtimeMs < oldest) {
46
+ oldest = s.mtimeMs;
47
+ }
48
+ }
49
+ return oldest;
50
+ }
51
+
52
+ /**
53
+ * Recursively collects crate directories starting from startCrateDir by following path-based dependencies declared in
54
+ * Nargo.toml files. Git-based deps are ignored (they only change when Nargo.toml itself is modified since the deps are
55
+ * tagged).
56
+ */
57
+ async function collectCrateDirs(startCrateDir: string): Promise<string[]> {
58
+ // We have a set of visited dirs we check against when entering a new dir because we could stumble upon a directory
59
+ // multiple times in case multiple deps shared a dep (e.g. dep A and dep B both sharing dep C).
60
+ const visited = new Set<string>();
61
+
62
+ async function visit(crateDir: string): Promise<void> {
63
+ const absDir = resolve(crateDir);
64
+ if (visited.has(absDir)) {
65
+ return;
66
+ }
67
+ visited.add(absDir);
68
+
69
+ // Every dep is its own crate and every crate needs to have Nargo.toml defined in the root so we try to load it and
70
+ // error out if it's not the case.
71
+ const tomlPath = join(absDir, 'Nargo.toml');
72
+ const content = await readFile(tomlPath, 'utf-8').catch(() => {
73
+ throw new Error(`Incorrectly defined dependency. Nargo.toml not found in ${absDir}`);
74
+ });
75
+
76
+ // We parse and iterate over the dependencies
77
+ const parsed = TOML.parse(content) as Record<string, any>;
78
+ const deps = (parsed.dependencies as Record<string, any>) ?? {};
79
+ for (const dep of Object.values(deps)) {
80
+ if (dep && typeof dep === 'object' && typeof dep.path === 'string') {
81
+ const depPath = resolve(absDir, dep.path);
82
+ const s = await stat(depPath);
83
+ if (!s.isDirectory()) {
84
+ throw new Error(
85
+ `Dependency path "${dep.path}" in ${tomlPath} resolves to ${depPath} which is not a directory`,
86
+ );
87
+ }
88
+ await visit(depPath);
89
+ }
90
+ }
91
+ }
92
+
93
+ await visit(startCrateDir);
94
+ return [...visited];
95
+ }
96
+
97
+ /**
98
+ * Walks crate dirs looking for .nr and Nargo.toml files newer than thresholdMs. Short-circuits on the first match.
99
+ */
100
+ async function hasNewerSourceFile(crateDirs: string[], thresholdMs: number): Promise<boolean> {
101
+ // Returns true if it find a new file than thresholdMs, false otherwise
102
+ async function walkForNewer(dir: string): Promise<boolean> {
103
+ let entries;
104
+ try {
105
+ entries = await readdir(dir, { withFileTypes: true });
106
+ } catch {
107
+ return false;
108
+ }
109
+
110
+ // We iterate over the entries in the dir
111
+ for (const entry of entries) {
112
+ const fullPath = join(dir, entry.name);
113
+ if (entry.isDirectory()) {
114
+ // If the entry is a dir and it's not called `target` we recursively enter it
115
+ if (entry.name === 'target') {
116
+ continue;
117
+ }
118
+ if (await walkForNewer(fullPath)) {
119
+ return true;
120
+ }
121
+ } else if (entry.name === 'Nargo.toml' || entry.name.endsWith('.nr')) {
122
+ // The entry is a Nargo.toml file or *.nr file so we check the timestamp
123
+ const s = await stat(fullPath);
124
+ if (s.mtimeMs > thresholdMs) {
125
+ return true;
126
+ }
127
+ }
128
+ }
129
+ return false;
130
+ }
131
+
132
+ // We search through the crate dirs
133
+ for (const dir of crateDirs) {
134
+ if (await walkForNewer(dir)) {
135
+ return true;
136
+ }
137
+ }
138
+ return false;
139
+ }
@@ -0,0 +1,16 @@
1
+ import { spawn } from 'child_process';
2
+
3
+ /** Spawns a command with inherited stdio and rejects on non-zero exit. */
4
+ export function run(cmd: string, args: string[]): Promise<void> {
5
+ return new Promise((resolve, reject) => {
6
+ const child = spawn(cmd, args, { stdio: 'inherit' });
7
+ child.on('error', reject);
8
+ child.on('close', code => {
9
+ if (code !== 0) {
10
+ reject(new Error(`${cmd} exited with code ${code}`));
11
+ } else {
12
+ resolve();
13
+ }
14
+ });
15
+ });
16
+ }
package/src/cli/util.ts CHANGED
@@ -1,17 +1,18 @@
1
1
  import type { AztecNodeConfig } from '@aztec/aztec-node';
2
2
  import type { AccountManager } from '@aztec/aztec.js/wallet';
3
+ import { getNetworkConfig } from '@aztec/cli/config';
4
+ import { RegistryContract } from '@aztec/ethereum/contracts';
3
5
  import type { ViemClient } from '@aztec/ethereum/types';
4
- import type { ConfigMappingsType } from '@aztec/foundation/config';
5
- import { EthAddress } from '@aztec/foundation/eth-address';
6
+ import type { ConfigMappingsType, NetworkNames } from '@aztec/foundation/config';
6
7
  import { jsonStringify } from '@aztec/foundation/json-rpc';
7
8
  import { type LogFn, createLogger } from '@aztec/foundation/log';
8
- import type { SharedNodeConfig } from '@aztec/node-lib/config';
9
9
  import type { ProverConfig } from '@aztec/stdlib/interfaces/server';
10
- import { getTelemetryClient } from '@aztec/telemetry-client/start';
10
+ import { type VersionCheck, getPackageVersion } from '@aztec/stdlib/update-checker';
11
11
  import type { EmbeddedWallet } from '@aztec/wallets/embedded';
12
12
 
13
13
  import chalk from 'chalk';
14
14
  import type { Command } from 'commander';
15
+ import type { Hex } from 'viem';
15
16
 
16
17
  import { type AztecStartOption, aztecStartOptions } from './aztec_start_options.js';
17
18
 
@@ -271,7 +272,7 @@ export async function preloadCrsDataForVerifying(
271
272
  ): Promise<void> {
272
273
  if (realProofs) {
273
274
  const { Crs, GrumpkinCrs } = await import('@aztec/bb.js');
274
- await Promise.all([Crs.new(2 ** 1, undefined, log), GrumpkinCrs.new(2 ** 16 + 1, undefined, log)]);
275
+ await Promise.all([Crs.new(2 ** 1, undefined, log), GrumpkinCrs.new(2 ** 16, undefined, log)]);
275
276
  }
276
277
  }
277
278
 
@@ -286,96 +287,62 @@ export async function preloadCrsDataForServerSideProving(
286
287
  ): Promise<void> {
287
288
  if (realProofs) {
288
289
  const { Crs, GrumpkinCrs } = await import('@aztec/bb.js');
289
- await Promise.all([Crs.new(2 ** 25 + 1, undefined, log), GrumpkinCrs.new(2 ** 18 + 1, undefined, log)]);
290
+ await Promise.all([Crs.new(2 ** 25, undefined, log), GrumpkinCrs.new(2 ** 18, undefined, log)]);
290
291
  }
291
292
  }
292
293
 
293
- export async function setupUpdateMonitor(
294
- autoUpdateMode: SharedNodeConfig['autoUpdate'],
295
- updatesLocation: URL,
294
+ export async function setupVersionChecker(
295
+ network: NetworkNames,
296
296
  followsCanonicalRollup: boolean,
297
297
  publicClient: ViemClient,
298
- registryContractAddress: EthAddress,
299
298
  signalHandlers: Array<() => Promise<void>>,
300
- updateNodeConfig?: (config: object) => Promise<void>,
301
- ) {
302
- const logger = createLogger('update-check');
303
- const { UpdateChecker } = await import('@aztec/stdlib/update-checker');
304
- const checker = await UpdateChecker.new({
305
- baseURL: updatesLocation,
306
- publicClient,
307
- registryContractAddress,
308
- });
299
+ cacheDir?: string,
300
+ ): Promise<void> {
301
+ const networkConfig = await getNetworkConfig(network, cacheDir);
302
+ if (!networkConfig) {
303
+ return;
304
+ }
309
305
 
310
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
311
- checker.on('newRollupVersion', async ({ latestVersion, currentVersion }) => {
312
- if (isShuttingDown()) {
313
- return;
314
- }
306
+ const { VersionChecker } = await import('@aztec/stdlib/update-checker');
315
307
 
316
- // if node follows canonical rollup then this is equivalent to a config update
317
- if (!followsCanonicalRollup) {
318
- return;
319
- }
308
+ const logger = createLogger('version_check');
309
+ const registry = new RegistryContract(publicClient, networkConfig.registryAddress as Hex);
320
310
 
321
- if (autoUpdateMode === 'config' || autoUpdateMode === 'config-and-version') {
322
- logger.info(`New rollup version detected. Please restart the node`, { latestVersion, currentVersion });
323
- await shutdown(logger.info, ExitCode.ROLLUP_UPGRADE, signalHandlers);
324
- } else if (autoUpdateMode === 'notify') {
325
- logger.warn(`New rollup detected. Please restart the node`, { latestVersion, currentVersion });
326
- }
311
+ const checks: Array<VersionCheck> = [];
312
+ checks.push({
313
+ name: 'node',
314
+ currentVersion: getPackageVersion() ?? 'unknown',
315
+ getLatestVersion: async () => {
316
+ const cfg = await getNetworkConfig(network, cacheDir);
317
+ return cfg?.nodeVersion;
318
+ },
327
319
  });
328
320
 
329
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
330
- checker.on('newNodeVersion', async ({ latestVersion, currentVersion }) => {
331
- if (isShuttingDown()) {
332
- return;
333
- }
334
- if (autoUpdateMode === 'config-and-version') {
335
- logger.info(`New node version detected. Please update and restart the node`, { latestVersion, currentVersion });
336
- await shutdown(logger.info, ExitCode.VERSION_UPGRADE, signalHandlers);
337
- } else if (autoUpdateMode === 'notify') {
338
- logger.info(`New node version detected. Please update and restart the node`, { latestVersion, currentVersion });
321
+ if (followsCanonicalRollup) {
322
+ const getLatestVersion = async () => {
323
+ const version = (await registry.getRollupVersions()).at(-1);
324
+ return version !== undefined ? String(version) : undefined;
325
+ };
326
+ const currentVersion = await getLatestVersion();
327
+ if (currentVersion !== undefined) {
328
+ checks.push({
329
+ name: 'rollup',
330
+ currentVersion,
331
+ getLatestVersion,
332
+ });
339
333
  }
340
- });
334
+ }
341
335
 
342
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
343
- checker.on('updateNodeConfig', async config => {
336
+ const checker = new VersionChecker(checks, 600_000, logger);
337
+ checker.on('newVersion', ({ name, latestVersion, currentVersion }) => {
344
338
  if (isShuttingDown()) {
345
339
  return;
346
340
  }
347
341
 
348
- if ((autoUpdateMode === 'config' || autoUpdateMode === 'config-and-version') && updateNodeConfig) {
349
- logger.warn(`Config change detected. Updating node`, config);
350
- try {
351
- await updateNodeConfig(config);
352
- } catch (err) {
353
- logger.warn('Failed to update config', { err });
354
- }
355
- }
356
- // don't notify on these config changes
357
- });
358
-
359
- checker.on('updatePublicTelemetryConfig', config => {
360
- if (autoUpdateMode === 'config' || autoUpdateMode === 'config-and-version') {
361
- logger.warn(`Public telemetry config change detected. Updating telemetry client`, config);
362
- try {
363
- const publicIncludeMetrics: unknown = (config as any).publicIncludeMetrics;
364
- if (Array.isArray(publicIncludeMetrics) && publicIncludeMetrics.every(m => typeof m === 'string')) {
365
- getTelemetryClient().setExportedPublicTelemetry(publicIncludeMetrics);
366
- }
367
- const publicMetricsCollectFrom: unknown = (config as any).publicMetricsCollectFrom;
368
- if (Array.isArray(publicMetricsCollectFrom) && publicMetricsCollectFrom.every(m => typeof m === 'string')) {
369
- getTelemetryClient().setPublicTelemetryCollectFrom(publicMetricsCollectFrom);
370
- }
371
- } catch (err) {
372
- logger.warn('Failed to update config', { err });
373
- }
374
- }
375
- // don't notify on these config changes
342
+ logger.warn(`New ${name} version available`, { latestVersion, currentVersion });
376
343
  });
377
-
378
344
  checker.start();
345
+ signalHandlers.push(() => checker.stop());
379
346
  }
380
347
 
381
348
  export function stringifyConfig(config: object): string {
@@ -32,7 +32,9 @@ async function main() {
32
32
  logger.info(`Fetched Alice and Bob accounts: ${alice.toString()}, ${bob.toString()}`);
33
33
 
34
34
  logger.info('Deploying Token...');
35
- const token = await TokenContract.deploy(wallet, alice, 'TokenName', 'TokenSymbol', 18).send({ from: alice });
35
+ const { contract: token } = await TokenContract.deploy(wallet, alice, 'TokenName', 'TokenSymbol', 18).send({
36
+ from: alice,
37
+ });
36
38
  logger.info('Token deployed');
37
39
 
38
40
  // Mint tokens to Alice
@@ -41,7 +43,7 @@ async function main() {
41
43
 
42
44
  logger.info(`${ALICE_MINT_BALANCE} tokens were successfully minted by Alice and transferred to private`);
43
45
 
44
- const balanceAfterMint = await token.methods.balance_of_private(alice).simulate({ from: alice });
46
+ const { result: balanceAfterMint } = await token.methods.balance_of_private(alice).simulate({ from: alice });
45
47
  logger.info(`Tokens successfully minted. New Alice's balance: ${balanceAfterMint}`);
46
48
 
47
49
  // We will now transfer tokens from Alice to Bob
@@ -49,10 +51,10 @@ async function main() {
49
51
  await token.methods.transfer(bob, TRANSFER_AMOUNT).send({ from: alice });
50
52
 
51
53
  // Check the new balances
52
- const aliceBalance = await token.methods.balance_of_private(alice).simulate({ from: alice });
54
+ const { result: aliceBalance } = await token.methods.balance_of_private(alice).simulate({ from: alice });
53
55
  logger.info(`Alice's balance ${aliceBalance}`);
54
56
 
55
- const bobBalance = await token.methods.balance_of_private(bob).simulate({ from: bob });
57
+ const { result: bobBalance } = await token.methods.balance_of_private(bob).simulate({ from: bob });
56
58
  logger.info(`Bob's balance ${bobBalance}`);
57
59
  }
58
60
 
@@ -48,7 +48,7 @@ export async function getBananaFPCAddress(initialAccounts: InitialAccountData[])
48
48
  export async function setupBananaFPC(initialAccounts: InitialAccountData[], wallet: Wallet, log: LogFn) {
49
49
  const bananaCoinAddress = await getBananaCoinAddress(initialAccounts);
50
50
  const admin = getBananaAdmin(initialAccounts);
51
- const [bananaCoin, fpc] = await Promise.all([
51
+ const [{ contract: bananaCoin }, { contract: fpc }] = await Promise.all([
52
52
  TokenContract.deploy(wallet, admin, bananaCoinArgs.name, bananaCoinArgs.symbol, bananaCoinArgs.decimal).send({
53
53
  from: admin,
54
54
  contractAddressSalt: BANANA_COIN_SALT,
@@ -18,6 +18,8 @@ import type { LogFn } from '@aztec/foundation/log';
18
18
  import { DateProvider, TestDateProvider } from '@aztec/foundation/timer';
19
19
  import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree';
20
20
  import { protocolContractsHash } from '@aztec/protocol-contracts';
21
+ import { SequencerState } from '@aztec/sequencer-client';
22
+ import { AztecAddress } from '@aztec/stdlib/aztec-address';
21
23
  import type { ProvingJobBroker } from '@aztec/stdlib/interfaces/server';
22
24
  import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees';
23
25
  import {
@@ -37,6 +39,7 @@ import { createAccountLogs } from '../cli/util.js';
37
39
  import { DefaultMnemonic } from '../mnemonic.js';
38
40
  import { AnvilTestWatcher } from '../testing/anvil_test_watcher.js';
39
41
  import { EpochTestSettler } from '../testing/epoch_test_settler.js';
42
+ import { getTokenAllowedSetupFunctions } from '../testing/token_allowed_setup.js';
40
43
  import { getBananaFPCAddress, setupBananaFPC } from './banana_fpc.js';
41
44
  import { getSponsoredFPCAddress } from './sponsored_fpc.js';
42
45
 
@@ -100,9 +103,14 @@ export async function createLocalNetwork(config: Partial<LocalNetworkConfig> = {
100
103
  logger.warn(`Multiple L1 RPC URLs provided. Local networks will only use the first one: ${l1RpcUrl}`);
101
104
  }
102
105
 
106
+ // The local network deploys a banana FPC with Token contracts, so include Token entries
107
+ // in the setup allowlist so FPC-based fee payments work out of the box.
108
+ const tokenAllowList = await getTokenAllowedSetupFunctions();
109
+
103
110
  const aztecNodeConfig: AztecNodeConfig = {
104
111
  ...getConfigEnvVars(),
105
112
  ...config,
113
+ txPublicSetupAllowListExtend: [...tokenAllowList, ...(config.txPublicSetupAllowListExtend ?? [])],
106
114
  };
107
115
  const hdAccount = mnemonicToAccount(config.l1Mnemonic || DefaultMnemonic);
108
116
  if (
@@ -137,9 +145,12 @@ export async function createLocalNetwork(config: Partial<LocalNetworkConfig> = {
137
145
 
138
146
  const bananaFPC = await getBananaFPCAddress(initialAccounts);
139
147
  const sponsoredFPC = await getSponsoredFPCAddress();
140
- const fundedAddresses = initialAccounts.length
141
- ? [...initialAccounts.map(a => a.address), bananaFPC, sponsoredFPC]
142
- : [];
148
+ const prefundAddresses = (aztecNodeConfig.prefundAddresses ?? []).map(a => AztecAddress.fromString(a));
149
+ const fundedAddresses = [
150
+ ...initialAccounts.map(a => a.address),
151
+ ...(initialAccounts.length ? [bananaFPC, sponsoredFPC] : []),
152
+ ...prefundAddresses,
153
+ ];
143
154
  const { genesisArchiveRoot, prefilledPublicData, fundingNeeded } = await getGenesisValues(fundedAddresses);
144
155
 
145
156
  const dateProvider = new TestDateProvider();
@@ -181,6 +192,21 @@ export async function createLocalNetwork(config: Partial<LocalNetworkConfig> = {
181
192
  const blobClient = createBlobClient();
182
193
  const node = await createAztecNode(aztecNodeConfig, { telemetry, blobClient, dateProvider }, { prefilledPublicData });
183
194
 
195
+ // Now that the node is up, let the watcher check for pending txs so it can skip unfilled slots faster when
196
+ // transactions are waiting in the mempool. Also let it check if the sequencer is actively building, to avoid
197
+ // warping time out from under an in-progress block.
198
+ watcher?.setGetPendingTxCount(() => node.getPendingTxCount());
199
+ const sequencer = node.getSequencer()?.getSequencer();
200
+ if (sequencer) {
201
+ const idleStates: Set<string> = new Set([
202
+ SequencerState.STOPPED,
203
+ SequencerState.STOPPING,
204
+ SequencerState.IDLE,
205
+ SequencerState.SYNCHRONIZING,
206
+ ]);
207
+ watcher?.setIsSequencerBuilding(() => !idleStates.has(sequencer.getState()));
208
+ }
209
+
184
210
  let epochTestSettler: EpochTestSettler | undefined;
185
211
  if (!aztecNodeConfig.p2pEnabled) {
186
212
  epochTestSettler = new EpochTestSettler(
@@ -194,7 +220,10 @@ export async function createLocalNetwork(config: Partial<LocalNetworkConfig> = {
194
220
  }
195
221
 
196
222
  if (initialAccounts.length) {
197
- const wallet = await EmbeddedWallet.create(node, { pxeConfig: { proverEnabled: aztecNodeConfig.realProofs } });
223
+ const wallet = await EmbeddedWallet.create(node, {
224
+ pxeConfig: { proverEnabled: aztecNodeConfig.realProofs },
225
+ ephemeral: true,
226
+ });
198
227
 
199
228
  userLog('Setting up funded test accounts...');
200
229
  const accountManagers = await deployFundedSchnorrAccounts(wallet, initialAccounts);
@@ -31,6 +31,15 @@ export class AnvilTestWatcher {
31
31
 
32
32
  private isMarkingAsProven = true;
33
33
 
34
+ // Optional callback to check if there are pending txs in the mempool.
35
+ private getPendingTxCount?: () => Promise<number>;
36
+
37
+ // Optional callback to check if the sequencer is actively building a block.
38
+ private isSequencerBuilding?: () => boolean;
39
+
40
+ // Tracks when we first observed the current unfilled slot with pending txs (real wall time).
41
+ private unfilledSlotFirstSeen?: { slot: number; realTime: number };
42
+
34
43
  constructor(
35
44
  private cheatcodes: EthCheatCodes,
36
45
  rollupAddress: EthAddress,
@@ -59,6 +68,16 @@ export class AnvilTestWatcher {
59
68
  this.isLocalNetwork = isLocalNetwork;
60
69
  }
61
70
 
71
+ /** Sets a callback to check for pending txs, used to skip unfilled slots faster when txs are waiting. */
72
+ setGetPendingTxCount(fn: () => Promise<number>) {
73
+ this.getPendingTxCount = fn;
74
+ }
75
+
76
+ /** Sets a callback to check if the sequencer is actively building, to avoid warping while it works. */
77
+ setIsSequencerBuilding(fn: () => boolean) {
78
+ this.isSequencerBuilding = fn;
79
+ }
80
+
62
81
  async start() {
63
82
  if (this.filledRunningPromise) {
64
83
  throw new Error('Watcher already watching for filled slot');
@@ -131,15 +150,8 @@ export class AnvilTestWatcher {
131
150
  const nextSlotTimestamp = Number(await this.rollup.read.getTimestampForSlot([BigInt(nextSlot)]));
132
151
 
133
152
  if (BigInt(currentSlot) === checkpointLog.slotNumber) {
134
- // We should jump to the next slot
135
- try {
136
- await this.cheatcodes.warp(nextSlotTimestamp, {
137
- resetBlockInterval: true,
138
- });
139
- } catch (e) {
140
- this.logger.error(`Failed to warp to timestamp ${nextSlotTimestamp}: ${e}`);
141
- }
142
-
153
+ // The current slot has been filled, we should jump to the next slot.
154
+ await this.warpToTimestamp(nextSlotTimestamp);
143
155
  this.logger.info(`Slot ${currentSlot} was filled, jumped to next slot`);
144
156
  return;
145
157
  }
@@ -149,18 +161,50 @@ export class AnvilTestWatcher {
149
161
  return;
150
162
  }
151
163
 
152
- const currentTimestamp = this.dateProvider?.now() ?? Date.now();
153
- if (currentTimestamp > nextSlotTimestamp * 1000) {
154
- try {
155
- await this.cheatcodes.warp(nextSlotTimestamp, { resetBlockInterval: true });
156
- } catch (e) {
157
- this.logger.error(`Failed to warp to timestamp ${nextSlotTimestamp}: ${e}`);
164
+ // If there are pending txs and the sequencer missed them, warp quickly (after a 2s real-time debounce) so the
165
+ // sequencer can retry in the next slot. Without this, we'd have to wait a full real-time slot duration (~36s) for
166
+ // the dateProvider to catch up to the next slot timestamp. We skip the warp if the sequencer is actively building
167
+ // to avoid invalidating its in-progress work.
168
+ if (this.getPendingTxCount) {
169
+ const pendingTxs = await this.getPendingTxCount();
170
+ if (pendingTxs > 0) {
171
+ if (this.isSequencerBuilding?.()) {
172
+ this.unfilledSlotFirstSeen = undefined;
173
+ return;
174
+ }
175
+
176
+ const realNow = Date.now();
177
+ if (!this.unfilledSlotFirstSeen || this.unfilledSlotFirstSeen.slot !== currentSlot) {
178
+ this.unfilledSlotFirstSeen = { slot: currentSlot, realTime: realNow };
179
+ return;
180
+ }
181
+
182
+ if (realNow - this.unfilledSlotFirstSeen.realTime > 2000) {
183
+ await this.warpToTimestamp(nextSlotTimestamp);
184
+ this.unfilledSlotFirstSeen = undefined;
185
+ this.logger.info(`Slot ${currentSlot} was missed with pending txs, jumped to next slot`);
186
+ }
187
+
188
+ return;
158
189
  }
190
+ }
159
191
 
192
+ // Fallback: warp when the dateProvider time has passed the next slot timestamp.
193
+ const currentTimestamp = this.dateProvider?.now() ?? Date.now();
194
+ if (currentTimestamp > nextSlotTimestamp * 1000) {
195
+ await this.warpToTimestamp(nextSlotTimestamp);
160
196
  this.logger.info(`Slot ${currentSlot} was missed, jumped to next slot`);
161
197
  }
162
198
  } catch {
163
199
  this.logger.error('mineIfSlotFilled failed');
164
200
  }
165
201
  }
202
+
203
+ private async warpToTimestamp(timestamp: number) {
204
+ try {
205
+ await this.cheatcodes.warp(timestamp, { resetBlockInterval: true });
206
+ } catch (e) {
207
+ this.logger.error(`Failed to warp to timestamp ${timestamp}: ${e}`);
208
+ }
209
+ }
166
210
  }
@@ -2,3 +2,4 @@ export { AnvilTestWatcher } from './anvil_test_watcher.js';
2
2
  export { EthCheatCodes, RollupCheatCodes } from '@aztec/ethereum/test';
3
3
  export { CheatCodes } from './cheat_codes.js';
4
4
  export { EpochTestSettler } from './epoch_test_settler.js';
5
+ export { getTokenAllowedSetupFunctions } from './token_allowed_setup.js';
@@ -0,0 +1,19 @@
1
+ import { TokenContractArtifact } from '@aztec/noir-contracts.js/Token';
2
+ import { buildAllowedElement } from '@aztec/p2p/msg_validators';
3
+ import { getContractClassFromArtifact } from '@aztec/stdlib/contract';
4
+ import type { AllowedElement } from '@aztec/stdlib/interfaces/server';
5
+
6
+ /**
7
+ * Returns Token-specific allowlist entries needed for FPC-based fee payments.
8
+ * These are test-only: FPC-based fee payment with custom tokens won't work on mainnet alpha.
9
+ */
10
+ export async function getTokenAllowedSetupFunctions(): Promise<AllowedElement[]> {
11
+ const tokenClassId = (await getContractClassFromArtifact(TokenContractArtifact)).id;
12
+ const target = { classId: tokenClassId };
13
+ return Promise.all([
14
+ // Token: needed for private transfers via FPC (transfer_to_public enqueues this)
15
+ buildAllowedElement(TokenContractArtifact, target, '_increase_public_balance', { onlySelf: true }),
16
+ // Token: needed for public transfers via FPC (fee_entrypoint_public enqueues this)
17
+ buildAllowedElement(TokenContractArtifact, target, 'transfer_in_public'),
18
+ ]);
19
+ }
@@ -1,2 +0,0 @@
1
- export declare const getCliVersion: () => any;
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVsZWFzZV92ZXJzaW9uLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY2xpL3JlbGVhc2VfdmVyc2lvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFLQSxlQUFPLE1BQU0sYUFBYSxXQWV6QixDQUFDIn0=
@@ -1 +0,0 @@
1
- {"version":3,"file":"release_version.d.ts","sourceRoot":"","sources":["../../src/cli/release_version.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,aAAa,WAezB,CAAC"}
@@ -1,14 +0,0 @@
1
- import { fileURLToPath } from '@aztec/foundation/url';
2
- import { readFileSync } from 'fs';
3
- import { dirname, resolve } from 'path';
4
- export const getCliVersion = ()=>{
5
- const packageJsonPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../package.json');
6
- const cliVersion = JSON.parse(readFileSync(packageJsonPath).toString()).version;
7
- // If the version is 0.1.0, this is a placeholder version and we are in a docker container; query release please for the latest version
8
- if (cliVersion === '0.1.0') {
9
- const releasePleasePath = resolve(dirname(fileURLToPath(import.meta.url)), '../../../../.release-please-manifest.json');
10
- const releaseVersion = JSON.parse(readFileSync(releasePleasePath).toString())['.'];
11
- return releaseVersion;
12
- }
13
- return cliVersion;
14
- };