diff --git a/bootstrap.sh b/bootstrap.sh index 42e87fc8eb47..ba62a9aa5166 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -579,6 +579,7 @@ function release { noir l1-contracts noir-projects/aztec-nr + protocol/constants-codegen yarn-project boxes aztec-up @@ -603,10 +604,11 @@ function release_dryrun { function private_release { # Release flow for the private repo, run on a (nightly) ci-private-release PR. We publish only to our # internal GCP Artifact Registry: the docker image (release-image -> INTERNAL_DOCKER_REGISTRY that - # GKE/staging pulls from) and the npm packages (barretenberg/ts, noir, wsdb, yarn-project -> the - # INTERNAL_NPM_REGISTRY npm repo). We run the release step for real on exactly those components and do - # not invoke the others — the remaining release sources publish public artifacts (github releases, - # crates.io, the aztec-up/playground S3 installers) and are not interrelated with these. + # GKE/staging pulls from) and the npm packages (barretenberg/ts, noir, ipc-runtime, wsdb, + # protocol/constants-codegen, yarn-project -> the INTERNAL_NPM_REGISTRY npm repo). We run the release + # step for real on exactly those components and do not invoke the others — the remaining release + # sources publish public artifacts (github releases, crates.io, the aztec-up/playground S3 installers) + # and are not interrelated with these. echo_header "private release" # Default to the private staging Artifact Registry; override via the INTERNAL_*_REGISTRY env vars. @@ -671,7 +673,7 @@ function private_release { # them. @aztec/world-state has a runtime dependency on @aztec/wsdb, and the ipc-codegen-generated # @aztec/wsdb in turn has a runtime dependency on @aztec/ipc-runtime, so ipc-runtime must precede wsdb. # npm packages are platform-independent, so only the docker image is published on arm64. - local publish=(barretenberg/ts noir ipc-runtime wsdb yarn-project release-image) + local publish=(barretenberg/ts noir ipc-runtime wsdb protocol/constants-codegen yarn-project release-image) if [ $(arch) == arm64 ]; then publish=(release-image) fi diff --git a/protocol/constants-codegen/README.md b/protocol/constants-codegen/README.md index 382a70f43568..e1d911abd0f8 100644 --- a/protocol/constants-codegen/README.md +++ b/protocol/constants-codegen/README.md @@ -5,7 +5,7 @@ This directory will contain the standalone cross-language generator for Aztec pr ## Version 1 interface The command reads a primary Noir source file, optionally adds named constants from other Noir files, and writes any -requested combination of the four outputs produced by the existing generator. +requested combination of the supported outputs. ```text constants-codegen \ @@ -14,7 +14,8 @@ constants-codegen \ [--typescript ] \ [--cpp ] \ [--pil ] \ - [--solidity ] + [--solidity ] \ + [--rust ] ``` - `--input` is required. @@ -29,6 +30,9 @@ Version 1 preserves the existing renderer behavior, including each language's cu TypeScript emits all parsed constants and domain separators; C++, PIL, and Solidity retain their current selected subsets and formatting. +Rust emits all parsed constants and domain separators: values that fit `u128` become `pub const NAME: u128` items, +and larger field-sized values become `pub const NAME: &str` hex-string items. + ## Compatibility target The implementation must preserve the symbols and values currently checked in at: diff --git a/protocol/constants-codegen/bootstrap.sh b/protocol/constants-codegen/bootstrap.sh index b43e60f6b33e..cc7fe43c409d 100755 --- a/protocol/constants-codegen/bootstrap.sh +++ b/protocol/constants-codegen/bootstrap.sh @@ -15,6 +15,7 @@ function build { function test_cmds { echo "$hash cd protocol/constants-codegen && node --test src/*.test.ts" + echo "$hash cd protocol/constants-codegen && ./scripts/test-package.sh" } function test { @@ -22,6 +23,11 @@ function test { test_cmds | filter_test_cmds | parallelize } +function release { + npm_install_deps + retry "deploy_npm ${REF_NAME#v}" +} + case "$cmd" in "") build diff --git a/protocol/constants-codegen/package.json b/protocol/constants-codegen/package.json index ae3b761286e3..2d201f76bce7 100644 --- a/protocol/constants-codegen/package.json +++ b/protocol/constants-codegen/package.json @@ -1,5 +1,5 @@ { - "name": "@aztec-foundation/constants-codegen", + "name": "@aztec/constants-codegen", "version": "0.0.0", "description": "Generate Aztec protocol constants from Noir definitions", "license": "Apache-2.0", @@ -15,7 +15,7 @@ "build": "tsc -p tsconfig.json", "clean": "rm -rf dest", "prepack": "yarn build", - "test": "node --test src/*.test.ts" + "test": "node --test src/*.test.ts && yarn build && ./scripts/test-package.sh" }, "devDependencies": { "@types/node": "^22", diff --git a/protocol/constants-codegen/scripts/test-package.sh b/protocol/constants-codegen/scripts/test-package.sh new file mode 100755 index 000000000000..3bdbf78d7f69 --- /dev/null +++ b/protocol/constants-codegen/scripts/test-package.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +set -euo pipefail + +package_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +work_dir=$(mktemp -d) +trap 'rm -rf "$work_dir"' EXIT + +(cd "$package_dir" && npm pack --ignore-scripts --pack-destination "$work_dir" --quiet >/dev/null) + +shopt -s nullglob +tarballs=("$work_dir"/*.tgz) +if [ "${#tarballs[@]}" -ne 1 ]; then + echo "expected npm pack to produce one tarball, found ${#tarballs[@]}" >&2 + exit 1 +fi + +input="$work_dir/constants.nr" +cat > "$input" <<'EOF' +pub global MAX_FIELD_VALUE: Field = + 21888242871839275222246405745257275088548364400416034343698204186575808495616; +pub global MAX_ETH_ADDRESS_VALUE: Field = 0xffffffffffffffffffffffffffffffffffffffff; +pub global ARCHIVE_HEIGHT: u32 = 30; +EOF + +mkdir "$work_dir/consumer" +( + cd "$work_dir/consumer" + npm init --yes >/dev/null + npm install --ignore-scripts "${tarballs[0]}" >/dev/null + ./node_modules/.bin/constants-codegen \ + --input "$input" \ + --typescript "$work_dir/constants.ts" \ + --cpp "$work_dir/constants.hpp" \ + --pil "$work_dir/constants.pil" \ + --solidity "$work_dir/Constants.sol" \ + --rust "$work_dir/constants.rs" +) + +function check_output { + if ! grep -Fq "$2" "$work_dir/$1"; then + echo "installed constants-codegen produced unexpected $1:" >&2 + cat "$work_dir/$1" >&2 + exit 1 + fi +} + +# Each language receives a different allowlisted subset of the input constants, +# so each check uses a constant known to be in that language's subset. +check_output constants.ts 'export const ARCHIVE_HEIGHT = 30;' +check_output constants.hpp '#define ARCHIVE_HEIGHT 30' +check_output constants.pil 'pol MAX_ETH_ADDRESS_VALUE = 1461501637330902918203684832716283019655932542975;' +check_output Constants.sol 'uint256 internal constant MAX_FIELD_VALUE = 21888242871839275222246405745257275088548364400416034343698204186575808495616;' +check_output constants.rs 'pub const ARCHIVE_HEIGHT: u128 = 30;' diff --git a/protocol/constants-codegen/src/cli.ts b/protocol/constants-codegen/src/cli.ts index 42838e64c626..8bf4ca063481 100644 --- a/protocol/constants-codegen/src/cli.ts +++ b/protocol/constants-codegen/src/cli.ts @@ -8,6 +8,7 @@ import { evaluateExpressions, generateCppConstants, generatePilConstants, + generateRustConstants, generateSolidityConstants, generateTypescriptConstants, parseNoirFile, @@ -41,6 +42,7 @@ function run(args: string[]): void { cpp: { type: 'string' }, pil: { type: 'string' }, solidity: { type: 'string' }, + rust: { type: 'string' }, }, strict: true, }); @@ -54,6 +56,7 @@ function run(args: string[]): void { values.cpp ? { path: values.cpp, generate: generateCppConstants } : undefined, values.pil ? { path: values.pil, generate: generatePilConstants } : undefined, values.solidity ? { path: values.solidity, generate: generateSolidityConstants } : undefined, + values.rust ? { path: values.rust, generate: generateRustConstants } : undefined, ].filter((output): output is RequestedOutput => output !== undefined); if (outputs.length === 0) { diff --git a/protocol/constants-codegen/src/generator.test.ts b/protocol/constants-codegen/src/generator.test.ts index 5449afc10a9b..ddb6e62bab22 100644 --- a/protocol/constants-codegen/src/generator.test.ts +++ b/protocol/constants-codegen/src/generator.test.ts @@ -11,6 +11,7 @@ import { evaluateExpressions, generateCppConstants, generatePilConstants, + generateRustConstants, generateSolidityConstants, generateTypescriptConstants, parseNoirFile, @@ -66,6 +67,21 @@ test('generates the existing PIL subset', () => { assert.doesNotMatch(output, /ARCHIVE_HEIGHT/); }); +test('generates Rust constants', () => { + const output = generateToString(generateRustConstants); + + assert.match(output, /pub const ARCHIVE_HEIGHT: u128 = 30;/); + assert.match( + output, + /pub const MAX_ETH_ADDRESS_VALUE: &str = "0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff";/, + ); + assert.match( + output, + /pub const MAX_FIELD_VALUE: &str = "0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000";/, + ); + assert.match(output, /pub const DOM_SEP__MERKLE_HASH: u128 = 2982624097;/); +}); + test('generates the existing Solidity subset', () => { const output = generateToString(generateSolidityConstants); diff --git a/protocol/constants-codegen/src/generator.ts b/protocol/constants-codegen/src/generator.ts index daad3e49504d..e69e69307be8 100644 --- a/protocol/constants-codegen/src/generator.ts +++ b/protocol/constants-codegen/src/generator.ts @@ -474,6 +474,32 @@ export function processConstantsSolidity(constants: { [key: string]: string }, p return code.join('\n'); } +/** + * Processes a collection of constants and generates code to export them as Rust constants. + * + * @param constants - An object containing key-value pairs representing constants. + * @param generatorIndices - An object containing key-value pairs representing domain separator indices. + * @returns A string containing code that exports the constants as Rust constants. + */ +export function processConstantsRust( + constants: { [key: string]: string }, + generatorIndices: { [key: string]: number }, +): string { + const code: string[] = []; + Object.entries(constants).forEach(([key, value]) => { + if (BigInt(value) <= 2n ** 128n - 1n) { + code.push(`pub const ${key}: u128 = ${value};`); + } else { + // Field-sized values exceed u128, so they are emitted as hex strings. + code.push(`pub const ${key}: &str = "0x${BigInt(value).toString(16).padStart(64, '0')}";`); + } + }); + Object.entries(generatorIndices).forEach(([key, value]) => { + code.push(`pub const DOM_SEP__${key}: u128 = ${value};`); + }); + return code.join('\n'); +} + /** * Generate the constants file in Typescript. */ @@ -537,6 +563,17 @@ ${processConstantsSolidity(constants)} fs.writeFileSync(targetPath, resultSolidity); } +/** + * Generate the constants file in Rust. + */ +export function generateRustConstants({ constants, domainSeparatorEnum }: ParsedContent, targetPath: string) { + const resultRust: string = `// GENERATED FILE - DO NOT EDIT, RUN yarn remake-constants +${processConstantsRust(constants, domainSeparatorEnum)} +`; + + fs.writeFileSync(targetPath, resultRust); +} + /** * Parse the content of the constants file in Noir. */ diff --git a/protocol/constants-codegen/yarn.lock b/protocol/constants-codegen/yarn.lock index dc1100870b8f..aa3fbe3a92fc 100644 --- a/protocol/constants-codegen/yarn.lock +++ b/protocol/constants-codegen/yarn.lock @@ -5,9 +5,9 @@ __metadata: version: 8 cacheKey: 10c0 -"@aztec-foundation/constants-codegen@workspace:.": +"@aztec/constants-codegen@workspace:.": version: 0.0.0-use.local - resolution: "@aztec-foundation/constants-codegen@workspace:." + resolution: "@aztec/constants-codegen@workspace:." dependencies: "@types/node": "npm:^22" typescript: "npm:^5.7.0"