Skip to content

Commit 38c0eb2

Browse files
authored
chore: barebones constants-codegen project (#24687)
Introduces a new `constants-codegen` package at a new `protocol` tld. - Moves generator functions from yarn-projects/constants to this new package. - Removes hardcoded output file paths in favor of explicit CLI arguments - Preserves yarn-projects/constants `remake-constants` task to keep blast ratio low for now `yarn-projects/constants` will be further simplified in subsequent PRs Closes F-814, F-815, F-819
1 parent 4c2675c commit 38c0eb2

16 files changed

Lines changed: 509 additions & 100 deletions

File tree

Makefile

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ endef
5555

5656
# Fast bootstrap.
5757
fast: release-image barretenberg boxes playground docs aztec-up \
58-
bb-tests l1-contracts-tests yarn-project-tests boxes-tests playground-tests aztec-up-tests docs-tests noir-protocol-circuits-tests contract-snapshots-tests release-image-tests spartan claude-tests ipc-codegen-tests
58+
bb-tests l1-contracts-tests yarn-project-tests boxes-tests playground-tests aztec-up-tests docs-tests noir-protocol-circuits-tests contract-snapshots-tests release-image-tests spartan claude-tests ipc-codegen-tests constants-codegen-tests
5959

6060
# Full bootstrap.
6161
full: fast bb-full-tests bb-cpp-full yarn-project-benches
@@ -294,6 +294,17 @@ bb-tests: bb-cpp-native-tests bb-acir-tests bb-ts-tests bb-sol-tests bb-bbup-tes
294294

295295
bb-full-tests: bb-cpp-wasm-threads-tests bb-cpp-asan-tests bb-cpp-smt-tests
296296

297+
#==============================================================================
298+
# Protocol Constants Codegen
299+
#==============================================================================
300+
301+
.PHONY: constants-codegen constants-codegen-tests
302+
constants-codegen:
303+
$(call build,$@,protocol/constants-codegen)
304+
305+
constants-codegen-tests: constants-codegen
306+
$(call test,$@,protocol/constants-codegen)
307+
297308
#==============================================================================
298309
# IPC Codegen
299310
#==============================================================================
@@ -421,7 +432,7 @@ l1-contracts-tests: l1-contracts-verifier
421432
# Yarn Project - TypeScript monorepo with all TS packages
422433
#==============================================================================
423434

424-
yarn-project: bb-ts noir-projects l1-contracts wsdb bb-avm-sim
435+
yarn-project: bb-ts noir-projects l1-contracts wsdb bb-avm-sim constants-codegen
425436
$(call build,$@,yarn-project)
426437

427438
yarn-project-tests: yarn-project
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dest/
2+
node_modules/
3+
.yarn/
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"singleQuote": true,
3+
"trailingComma": "all",
4+
"printWidth": 120,
5+
"arrowParens": "avoid"
6+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nodeLinker: node-modules
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Constants codegen
2+
3+
This directory will contain the standalone cross-language generator for Aztec protocol constants.
4+
5+
## Version 1 interface
6+
7+
The command reads a primary Noir source file, optionally adds named constants from other Noir files, and writes any
8+
requested combination of the four outputs produced by the existing generator.
9+
10+
```text
11+
constants-codegen \
12+
--input <constants.nr> \
13+
[--include <file.nr>:<symbol>]... \
14+
[--typescript <output.ts>] \
15+
[--cpp <output.hpp>] \
16+
[--pil <output.pil>] \
17+
[--solidity <output.sol>]
18+
```
19+
20+
- `--input` is required.
21+
- `--include` adds one named constant from another Noir file before evaluating expressions. It may be repeated.
22+
- At least one output option is required, and any combination of output options may be used in one invocation.
23+
- Relative paths are resolved from the caller's working directory. The tool does not infer paths from the monorepo
24+
layout.
25+
- Invalid arguments, an unreadable input, an unsupported expression, or an output failure produce a diagnostic on
26+
stderr and a nonzero exit status.
27+
28+
Version 1 preserves the existing renderer behavior, including each language's current embedded symbol allowlist.
29+
TypeScript emits all parsed constants and domain separators; C++, PIL, and Solidity retain their current selected
30+
subsets and formatting.
31+
32+
## Compatibility target
33+
34+
The implementation must preserve the symbols and values currently checked in at:
35+
36+
- `yarn-project/constants/src/constants.gen.ts`
37+
- `barretenberg/cpp/src/barretenberg/aztec/aztec_constants.hpp`
38+
- `barretenberg/cpp/pil/vm2/constants_gen.pil`
39+
- `l1-contracts/src/core/libraries/ConstantsGen.sol`
40+
41+
Generator instructions and formatter-only whitespace may change intentionally.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/env bash
2+
3+
source $(git rev-parse --show-toplevel)/ci3/source_bootstrap
4+
5+
hash=$(cache_content_hash .)
6+
7+
# Tests and in-repo generation (remake-constants.sh) run src/*.ts directly via node's built-in type stripping.
8+
# tsc only exists to emit the published npm artifact: node refuses to strip types under node_modules, so the package
9+
# must ship compiled JS. The build step validates that publish path.
10+
function build {
11+
echo_header "constants-codegen build"
12+
npm_install_deps
13+
yarn build
14+
}
15+
16+
function test_cmds {
17+
echo "$hash cd protocol/constants-codegen && node --test src/*.test.ts"
18+
}
19+
20+
function test {
21+
echo_header "constants-codegen test"
22+
test_cmds | filter_test_cmds | parallelize
23+
}
24+
25+
case "$cmd" in
26+
"")
27+
build
28+
;;
29+
hash)
30+
echo "$hash"
31+
;;
32+
*)
33+
default_cmd_handler "$@"
34+
;;
35+
esac
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "@aztec-foundation/constants-codegen",
3+
"version": "0.0.0",
4+
"description": "Generate Aztec protocol constants from Noir definitions",
5+
"license": "Apache-2.0",
6+
"type": "module",
7+
"bin": "./dest/cli.js",
8+
"files": [
9+
"dest",
10+
"!dest/*.test.d.ts",
11+
"!dest/*.test.js",
12+
"README.md"
13+
],
14+
"scripts": {
15+
"build": "tsc -p tsconfig.json",
16+
"clean": "rm -rf dest",
17+
"prepack": "yarn build",
18+
"test": "node --test src/*.test.ts"
19+
},
20+
"devDependencies": {
21+
"@types/node": "^22",
22+
"typescript": "^5.7.0"
23+
},
24+
"engines": {
25+
"node": ">=20.10"
26+
},
27+
"packageManager": "yarn@4.13.0",
28+
"publishConfig": {
29+
"access": "public"
30+
}
31+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/bin/env node
2+
import { mkdirSync, readFileSync } from 'node:fs';
3+
import { dirname } from 'node:path';
4+
import { parseArgs } from 'node:util';
5+
6+
import {
7+
type ParsedContent,
8+
evaluateExpressions,
9+
generateCppConstants,
10+
generatePilConstants,
11+
generateSolidityConstants,
12+
generateTypescriptConstants,
13+
parseNoirFile,
14+
} from './generator.ts';
15+
16+
type GenerateOutput = (content: ParsedContent, targetPath: string) => void;
17+
18+
interface RequestedOutput {
19+
path: string;
20+
generate: GenerateOutput;
21+
}
22+
23+
function parseIncludedConstant(value: string): { path: string; symbol: string } {
24+
const separatorIndex = value.lastIndexOf(':');
25+
const path = value.slice(0, separatorIndex);
26+
const symbol = value.slice(separatorIndex + 1);
27+
if (separatorIndex <= 0 || !/^\w+$/.test(symbol)) {
28+
throw new Error(`invalid --include value '${value}', expected <file.nr>:<symbol>`);
29+
}
30+
return { path, symbol };
31+
}
32+
33+
function run(args: string[]): void {
34+
const { values } = parseArgs({
35+
args,
36+
allowPositionals: false,
37+
options: {
38+
input: { type: 'string' },
39+
include: { type: 'string', multiple: true },
40+
typescript: { type: 'string' },
41+
cpp: { type: 'string' },
42+
pil: { type: 'string' },
43+
solidity: { type: 'string' },
44+
},
45+
strict: true,
46+
});
47+
48+
if (!values.input) {
49+
throw new Error('--input is required');
50+
}
51+
52+
const outputs = [
53+
values.typescript ? { path: values.typescript, generate: generateTypescriptConstants } : undefined,
54+
values.cpp ? { path: values.cpp, generate: generateCppConstants } : undefined,
55+
values.pil ? { path: values.pil, generate: generatePilConstants } : undefined,
56+
values.solidity ? { path: values.solidity, generate: generateSolidityConstants } : undefined,
57+
].filter((output): output is RequestedOutput => output !== undefined);
58+
59+
if (outputs.length === 0) {
60+
throw new Error('at least one output option is required');
61+
}
62+
63+
const { constantsExpressions, domainSeparatorEnum } = parseNoirFile(readFileSync(values.input, 'utf8'));
64+
for (const value of values.include ?? []) {
65+
const { path, symbol } = parseIncludedConstant(value);
66+
const { constantsExpressions: includedExpressions } = parseNoirFile(readFileSync(path, 'utf8'), {
67+
stripLineComments: true,
68+
});
69+
const expression = includedExpressions.find(([name]) => name === symbol);
70+
if (!expression) {
71+
throw new Error(`constant '${symbol}' not found in ${path}`);
72+
}
73+
constantsExpressions.push(expression);
74+
}
75+
76+
const parsedContent: ParsedContent = {
77+
constants: evaluateExpressions(constantsExpressions),
78+
domainSeparatorEnum,
79+
};
80+
81+
for (const output of outputs) {
82+
mkdirSync(dirname(output.path), { recursive: true });
83+
output.generate(parsedContent, output.path);
84+
}
85+
}
86+
87+
try {
88+
run(process.argv.slice(2));
89+
} catch (error) {
90+
const message = error instanceof Error ? error.message : String(error);
91+
console.error(`constants-codegen: ${message}`);
92+
process.exitCode = 1;
93+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import assert from 'node:assert/strict';
2+
import { execFileSync } from 'node:child_process';
3+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
4+
import { tmpdir } from 'node:os';
5+
import { dirname, join } from 'node:path';
6+
import { test } from 'node:test';
7+
import { fileURLToPath } from 'node:url';
8+
9+
import {
10+
type ParsedContent,
11+
evaluateExpressions,
12+
generateCppConstants,
13+
generatePilConstants,
14+
generateSolidityConstants,
15+
generateTypescriptConstants,
16+
parseNoirFile,
17+
} from './generator.ts';
18+
19+
const noirFixture = `
20+
pub global MAX_FIELD_VALUE: Field =
21+
21888242871839275222246405745257275088548364400416034343698204186575808495616;
22+
pub global MAX_ETH_ADDRESS_VALUE: Field = 0xffffffffffffffffffffffffffffffffffffffff;
23+
pub global ARCHIVE_HEIGHT: u32 = 30;
24+
pub global DOM_SEP__MERKLE_HASH: u32 = 2982624097;
25+
`;
26+
27+
function generateToString(generate: (content: ParsedContent, targetPath: string) => void): string {
28+
const tempDir = mkdtempSync(join(tmpdir(), 'constants-codegen-'));
29+
const targetPath = join(tempDir, 'output');
30+
try {
31+
const { constantsExpressions, domainSeparatorEnum } = parseNoirFile(noirFixture);
32+
generate({ constants: evaluateExpressions(constantsExpressions), domainSeparatorEnum }, targetPath);
33+
return readFileSync(targetPath, 'utf8');
34+
} finally {
35+
rmSync(tempDir, { recursive: true, force: true });
36+
}
37+
}
38+
39+
test('generates TypeScript constants and domain separators', () => {
40+
assert.equal(
41+
generateToString(generateTypescriptConstants),
42+
`// GENERATED FILE - DO NOT EDIT, RUN yarn remake-constants
43+
export const MAX_FIELD_VALUE = 21888242871839275222246405745257275088548364400416034343698204186575808495616n;
44+
export const MAX_ETH_ADDRESS_VALUE = 1461501637330902918203684832716283019655932542975n;
45+
export const ARCHIVE_HEIGHT = 30;
46+
export enum DomainSeparator {
47+
MERKLE_HASH = 2982624097,
48+
}`,
49+
);
50+
});
51+
52+
test('generates the existing C++ subset', () => {
53+
const output = generateToString(generateCppConstants);
54+
55+
assert.match(output, /#define MAX_ETH_ADDRESS_VALUE "0x0{24}f{40}"/);
56+
assert.match(output, /#define ARCHIVE_HEIGHT 30/);
57+
assert.match(output, /#define DOM_SEP__MERKLE_HASH 2982624097UL/);
58+
assert.doesNotMatch(output, /MAX_FIELD_VALUE/);
59+
});
60+
61+
test('generates the existing PIL subset', () => {
62+
const output = generateToString(generatePilConstants);
63+
64+
assert.match(output, /pol MAX_ETH_ADDRESS_VALUE = 1461501637330902918203684832716283019655932542975;/);
65+
assert.match(output, /pol DOM_SEP__MERKLE_HASH = 2982624097;/);
66+
assert.doesNotMatch(output, /ARCHIVE_HEIGHT/);
67+
});
68+
69+
test('generates the existing Solidity subset', () => {
70+
const output = generateToString(generateSolidityConstants);
71+
72+
assert.match(
73+
output,
74+
/uint256 internal constant MAX_FIELD_VALUE = 21888242871839275222246405745257275088548364400416034343698204186575808495616;/,
75+
);
76+
assert.doesNotMatch(output, /ARCHIVE_HEIGHT/);
77+
});
78+
79+
test('the CLI generates multiple requested outputs', () => {
80+
const tempDir = mkdtempSync(join(tmpdir(), 'constants-codegen-cli-'));
81+
const inputPath = join(tempDir, 'constants.nr');
82+
const includedInputPath = join(tempDir, 'additional.nr');
83+
const typescriptPath = join(tempDir, 'typescript', 'constants.ts');
84+
const cppPath = join(tempDir, 'cpp', 'constants.hpp');
85+
const cliPath = join(dirname(fileURLToPath(import.meta.url)), 'cli.ts');
86+
87+
try {
88+
writeFileSync(inputPath, noirFixture);
89+
writeFileSync(
90+
includedInputPath,
91+
`pub global INCLUDED_CONSTANT: u32 = ARCHIVE_HEIGHT + 1; // selected for export
92+
pub global EXCLUDED_CONSTANT: u32 = 100;
93+
`,
94+
);
95+
execFileSync(
96+
process.execPath,
97+
[
98+
cliPath,
99+
'--input',
100+
inputPath,
101+
'--include',
102+
`${includedInputPath}:INCLUDED_CONSTANT`,
103+
'--typescript',
104+
typescriptPath,
105+
'--cpp',
106+
cppPath,
107+
],
108+
{ stdio: 'pipe' },
109+
);
110+
111+
assert.match(readFileSync(typescriptPath, 'utf8'), /export const ARCHIVE_HEIGHT = 30;/);
112+
assert.match(readFileSync(typescriptPath, 'utf8'), /export const INCLUDED_CONSTANT = 31;/);
113+
assert.doesNotMatch(readFileSync(typescriptPath, 'utf8'), /EXCLUDED_CONSTANT/);
114+
assert.match(readFileSync(cppPath, 'utf8'), /#define ARCHIVE_HEIGHT 30/);
115+
} finally {
116+
rmSync(tempDir, { recursive: true, force: true });
117+
}
118+
});

0 commit comments

Comments
 (0)