@nite-framework/nite-zk-profiler 0.2.0 → 0.2.2

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.
package/README.md CHANGED
@@ -38,13 +38,14 @@ It reports the cost class of every circuit in your contract.
38
38
  ```text
39
39
  $ nite-zk profile Sample.compact
40
40
 
41
- circuit rows k capacity cost
42
- bump 24 5 32 1x
43
- balanceOf 305 9 512 16x
44
- register 368 9 512 16x
45
- insert32 2299 13 8192 256x
46
-
47
- 4 circuits, toolchain 0.31.1, zkir 2.1.0, 0.4s
41
+ circuit rows k capacity cost est. prove
42
+ bump 24 5 32 1x ~10ms
43
+ balanceOf 305 9 512 16x ~165ms
44
+ register 368 9 512 16x ~165ms
45
+ insert32 2299 13 8192 256x ~2.6s
46
+
47
+ 4 circuits, toolchain 0.31.1, midnight-zkir 2.1.0, 0.9s
48
+ est. prove is modelled as 2^k on an uncalibrated default. Run `nite-zk calibrate` to anchor it.
48
49
  ```
49
50
 
50
51
  `k` is the number that matters. `cost` is `2^k` expressed relative to the cheapest circuit in the contract, so you can see at a glance which circuits dominate your proving budget.
@@ -370,20 +371,21 @@ Anything outside this range is rejected with a clear error naming the version fo
370
371
  ## Command line surface
371
372
 
372
373
  ```text
373
- nite-zk profile <source...> Report rows, k and relative cost per circuit
374
+ nite-zk profile <source...> Report rows, k, cost and estimated proving time
374
375
  nite-zk save <source...> Write zk-budget.json from current measurements
375
376
  nite-zk check [<source...>] Compare against zk-budget.json
376
377
  nite-zk diff <ref> [<source>] Compare a contract against a git ref
377
378
  nite-zk calibrate --observed <ms> --at-k <k>
378
379
  Anchor proving estimates to a real proof
379
380
 
380
- --estimate Show modelled proving time per circuit
381
+ --no-estimate Hide the modelled proving time column
381
382
  --deep Also generate real proving keys, and report
382
383
  measured setup time and prover key size
383
384
  --json Machine readable output
384
385
  --out <dir> Compile into a specific directory
385
386
  --budget <file> Use a different baseline path
386
387
  --strict check: fail on circuits missing from the budget
388
+ --replace save: overwrite the budget instead of merging
387
389
  --no-color Plain output (NO_COLOR is honoured too)
388
390
  --no-cache Ignore cached measurements for this run
389
391
  +VERSION Pin the Compact toolchain, e.g. +0.31.1
@@ -401,6 +403,23 @@ Wrote zk-budget.json: 2 contracts, 16 circuits
401
403
  `check` then reads every contract back out of the budget, so CI stays one step
402
404
  whether the repository holds one contract or ten.
403
405
 
406
+ Contracts can also be added one at a time. `save` merges into an existing
407
+ budget rather than replacing it, and says what it did:
408
+
409
+ ```text
410
+ $ nite-zk save packages/mint/src/mint.compact
411
+ Wrote /repo/zk-budget.json
412
+ 1 contract, 3 circuits
413
+ added: packages/mint/src/mint.compact
414
+ kept: packages/pool/src/lending.compact
415
+ ```
416
+
417
+ `--replace` writes a fresh file when you do want the old entries gone.
418
+
419
+ The budget is written relative to the working directory, and `save` prints the
420
+ full path it wrote, so running it from the wrong directory is visible
421
+ immediately rather than leaving a file somewhere unexpected.
422
+
404
423
  ### Comparing against a branch
405
424
 
406
425
  ```text
@@ -420,8 +439,9 @@ check without committing a budget file first.
420
439
 
421
440
  ### Estimated proving time
422
441
 
423
- `--estimate` models proving time as `time = rate * 2^k`, since a Halo2 proof is
424
- dominated by work over the full `2^k` domain.
442
+ `profile` shows an estimated proving time by default, modelled as
443
+ `time = rate * 2^k`, since a Halo2 proof is dominated by work over the full
444
+ `2^k` domain. `--no-estimate` hides the column.
425
445
 
426
446
  The rate is machine specific, so the shipped default is only an order of
427
447
  magnitude. Time one real proof and record it, and every estimate becomes
package/dist/budget.d.ts CHANGED
@@ -44,6 +44,22 @@ export interface ContractCosts {
44
44
  }
45
45
  /** Build a budget granting every circuit exactly what it currently costs. */
46
46
  export declare function budgetFrom(contracts: ContractCosts[], toolchainLine: string): Budget;
47
+ export interface MergeSummary {
48
+ added: string[];
49
+ updated: string[];
50
+ kept: string[];
51
+ }
52
+ /**
53
+ * Fold a new save into an existing budget.
54
+ *
55
+ * Saving one contract must not silently discard the others. A monorepo is
56
+ * saved a contract at a time, and without merging each save wipes the previous
57
+ * one, which looks like it worked and quietly loses the ceilings you committed.
58
+ */
59
+ export declare function mergeBudgets(existing: Budget, incoming: Budget): {
60
+ budget: Budget;
61
+ summary: MergeSummary;
62
+ };
47
63
  export declare function writeBudget(path: string, budget: Budget): void;
48
64
  export declare function readBudget(path: string): Budget;
49
65
  /** Every contract the budget describes. */
package/dist/budget.js CHANGED
@@ -24,6 +24,33 @@ export function budgetFrom(contracts, toolchainLine) {
24
24
  contracts: out,
25
25
  };
26
26
  }
27
+ /**
28
+ * Fold a new save into an existing budget.
29
+ *
30
+ * Saving one contract must not silently discard the others. A monorepo is
31
+ * saved a contract at a time, and without merging each save wipes the previous
32
+ * one, which looks like it worked and quietly loses the ceilings you committed.
33
+ */
34
+ export function mergeBudgets(existing, incoming) {
35
+ const contracts = { ...existing.contracts };
36
+ const summary = { added: [], updated: [], kept: [] };
37
+ for (const [source, contract] of Object.entries(incoming.contracts)) {
38
+ (source in contracts ? summary.updated : summary.added).push(source);
39
+ contracts[source] = contract;
40
+ }
41
+ for (const source of Object.keys(existing.contracts)) {
42
+ if (!(source in incoming.contracts) && source.length > 0)
43
+ summary.kept.push(source);
44
+ }
45
+ return {
46
+ budget: { ...incoming, contracts },
47
+ summary: {
48
+ added: summary.added.sort(),
49
+ updated: summary.updated.sort(),
50
+ kept: summary.kept.sort(),
51
+ },
52
+ };
53
+ }
27
54
  export function writeBudget(path, budget) {
28
55
  writeFileSync(path, `${JSON.stringify(budget, null, 2)}\n`, "utf8");
29
56
  }
package/dist/cli.d.ts CHANGED
@@ -5,6 +5,7 @@ interface Options {
5
5
  ref?: string;
6
6
  json: boolean;
7
7
  strict: boolean;
8
+ replace: boolean;
8
9
  deep: boolean;
9
10
  estimate: boolean;
10
11
  noColor: boolean;
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { dirname, isAbsolute, relative, resolve } from "node:path";
3
3
  import { analyze } from "./analyze.js";
4
- import { DEFAULT_BUDGET_PATH, budgetFrom, budgetSources, check, readBudget, writeBudget, } from "./budget.js";
4
+ import { DEFAULT_BUDGET_PATH, budgetFrom, budgetSources, check, mergeBudgets, readBudget, writeBudget, } from "./budget.js";
5
5
  import { cacheKey, readCache, writeCache } from "./cache.js";
6
6
  import { setColorEnabled } from "./colors.js";
7
7
  import { compileSkipZk } from "./compile.js";
@@ -17,7 +17,7 @@ import { toolVersion } from "./version.js";
17
17
  const USAGE = `nite-zk - see what a Compact circuit costs to prove
18
18
 
19
19
  Usage:
20
- nite-zk profile <source...> Report rows, k and relative cost per circuit
20
+ nite-zk profile <source...> Report rows, k, cost and estimated proving time
21
21
  nite-zk save <source...> Write zk-budget.json from current measurements
22
22
  nite-zk check [<source...>] Compare against zk-budget.json
23
23
  Sources are optional once saved, since the
@@ -27,13 +27,14 @@ Usage:
27
27
  Anchor proving estimates to a real proof
28
28
 
29
29
  Options:
30
- --estimate Show modelled proving time per circuit
30
+ --no-estimate Hide the modelled proving time column
31
31
  --deep Also generate real proving keys and report
32
32
  measured setup time and prover key size
33
33
  --json Machine readable output
34
34
  --out <dir> Compile into a specific directory (kept)
35
35
  --budget <file> Budget path (default: ${DEFAULT_BUDGET_PATH})
36
36
  --strict check: fail on circuits missing from the budget
37
+ --replace save: overwrite the budget instead of merging
37
38
  --no-color Plain output (also honours NO_COLOR)
38
39
  --no-cache Ignore cached measurements for this run
39
40
  +VERSION Pin the Compact toolchain, e.g. +0.31.1
@@ -47,8 +48,9 @@ export function parseArgs(argv) {
47
48
  sources: [],
48
49
  json: false,
49
50
  strict: false,
51
+ replace: false,
50
52
  deep: false,
51
- estimate: false,
53
+ estimate: true,
52
54
  noColor: false,
53
55
  noCache: false,
54
56
  budget: DEFAULT_BUDGET_PATH,
@@ -61,10 +63,14 @@ export function parseArgs(argv) {
61
63
  opts.json = true;
62
64
  else if (arg === "--strict")
63
65
  opts.strict = true;
66
+ else if (arg === "--replace")
67
+ opts.replace = true;
64
68
  else if (arg === "--deep")
65
69
  opts.deep = true;
66
70
  else if (arg === "--estimate")
67
71
  opts.estimate = true;
72
+ else if (arg === "--no-estimate")
73
+ opts.estimate = false;
68
74
  else if (arg === "--no-color")
69
75
  opts.noColor = true;
70
76
  else if (arg === "--no-cache")
@@ -260,12 +266,36 @@ export async function run(argv) {
260
266
  // stored relative to the budget file so the pair stays portable.
261
267
  const base = dirname(resolve(opts.budget));
262
268
  const line = `${run_.toolchain.version.split(".").slice(0, 2).join(".")}.x`;
263
- const budget = budgetFrom(run_.contracts.map((c) => ({ source: relative(base, resolve(c.source)), costs: c.costs })), line);
264
- writeBudget(opts.budget, budget);
269
+ const fresh = budgetFrom(run_.contracts.map((c) => ({ source: relative(base, resolve(c.source)), costs: c.costs })), line);
270
+ // Merge rather than overwrite. Saving one contract at a time is the normal
271
+ // way to set up a monorepo, and replacing the file each time would discard
272
+ // ceilings that are already committed, silently and while reporting success.
273
+ let existing;
274
+ if (!opts.replace) {
275
+ try {
276
+ existing = readBudget(opts.budget);
277
+ }
278
+ catch {
279
+ existing = undefined;
280
+ }
281
+ }
282
+ const merged = existing ? mergeBudgets(existing, fresh) : { budget: fresh, summary: undefined };
283
+ writeBudget(opts.budget, merged.budget);
265
284
  const circuits = run_.contracts.reduce((n, c) => n + c.costs.length, 0);
266
- process.stdout.write(`Wrote ${opts.budget}: ${run_.contracts.length} contract${run_.contracts.length === 1 ? "" : "s"}, ` +
267
- `${circuits} circuit${circuits === 1 ? "" : "s"}\n` +
268
- "Check it in, then run `nite-zk check` in CI.\n");
285
+ const written = resolve(opts.budget);
286
+ process.stdout.write(`Wrote ${written}\n` +
287
+ ` ${run_.contracts.length} contract${run_.contracts.length === 1 ? "" : "s"}, ` +
288
+ `${circuits} circuit${circuits === 1 ? "" : "s"}\n`);
289
+ const s_ = merged.summary;
290
+ if (s_) {
291
+ if (s_.added.length)
292
+ process.stdout.write(` added: ${s_.added.join(", ")}\n`);
293
+ if (s_.updated.length)
294
+ process.stdout.write(` updated: ${s_.updated.join(", ")}\n`);
295
+ if (s_.kept.length)
296
+ process.stdout.write(` kept: ${s_.kept.join(", ")}\n`);
297
+ }
298
+ process.stdout.write("Check it in, then run `nite-zk check` in CI.\n");
269
299
  return 0;
270
300
  }
271
301
  export async function main(argv) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nite-framework/nite-zk-profiler",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "See what a Compact circuit costs to prove, without generating proving keys",
5
5
  "type": "module",
6
6
  "bin": {