@aztec-foundation/constants-codegen 0.0.1-commit.b66364b
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 +49 -0
- package/dest/cli.d.ts +2 -0
- package/dest/cli.js +60 -0
- package/dest/generator.d.ts +131 -0
- package/dest/generator.js +291 -0
- package/dest/selection.d.ts +7 -0
- package/dest/selection.js +68 -0
- package/inputs/constants.nr +1447 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
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 Noir source file and writes one of the supported outputs.
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
constants-codegen \
|
|
11
|
+
[--input <constants.nr>] \
|
|
12
|
+
[--selection <selection.json>] \
|
|
13
|
+
(--typescript <output.ts> | --cpp <output.hpp> | --pil <output.pil> | --solidity <output.sol> | --rust <output.rs>)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- `--input` defaults to `inputs/constants.nr` under the package root, the file `scripts/embed-inputs.sh` copies in
|
|
17
|
+
as part of the package build, so both the published npm package and the built in-repo package work without
|
|
18
|
+
`--input`. In-repo callers invoke `scripts/generate.sh`, which refreshes the embedded copy from the monorepo
|
|
19
|
+
sources before running the CLI.
|
|
20
|
+
- Exactly one output option is required. Run the command once per desired output.
|
|
21
|
+
- `--selection` filters the output to the selected symbols. Without it, the output contains every supported symbol
|
|
22
|
+
from the input.
|
|
23
|
+
- Relative paths given as arguments are resolved from the caller's working directory.
|
|
24
|
+
- Invalid arguments, an unreadable input, an unsupported expression, or an output failure produce a diagnostic on
|
|
25
|
+
stderr and a nonzero exit status.
|
|
26
|
+
|
|
27
|
+
A selection file is a JSON array naming Noir source symbols, including the `DOM_SEP__` prefix for domain
|
|
28
|
+
separators:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
["ARCHIVE_HEIGHT", "MAX_.*_PER_TX", "DOM_SEP__MERKLE_HASH"]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
An entry that is not a valid symbol name is treated as a regular expression selecting every symbol whose whole
|
|
35
|
+
name matches it. Duplicate entries, invalid patterns, unknown symbols, and patterns that match no symbol are rejected.
|
|
36
|
+
|
|
37
|
+
Rust emits all parsed constants and domain separators: values that fit `u128` become `pub const NAME: u128` items,
|
|
38
|
+
and larger field-sized values become `pub const NAME: &str` hex-string items.
|
|
39
|
+
|
|
40
|
+
## Compatibility target
|
|
41
|
+
|
|
42
|
+
The implementation must preserve the symbols and values currently checked in at:
|
|
43
|
+
|
|
44
|
+
- `yarn-project/constants/src/constants.gen.ts`
|
|
45
|
+
- `barretenberg/cpp/src/barretenberg/aztec/aztec_constants.hpp`
|
|
46
|
+
- `barretenberg/cpp/pil/vm2/constants_gen.pil`
|
|
47
|
+
- `l1-contracts/src/core/libraries/ConstantsGen.sol`
|
|
48
|
+
|
|
49
|
+
Generator instructions and formatter-only whitespace may change intentionally.
|
package/dest/cli.d.ts
ADDED
package/dest/cli.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { parseArgs } from 'node:util';
|
|
6
|
+
import { evaluateExpressions, generateCppConstants, generatePilConstants, generateRustConstants, generateSolidityConstants, generateTypescriptConstants, parseNoirFile, } from "./generator.js";
|
|
7
|
+
import { readSymbolSelection, selectSymbols } from "./selection.js";
|
|
8
|
+
function run(args) {
|
|
9
|
+
const { values } = parseArgs({
|
|
10
|
+
args,
|
|
11
|
+
allowPositionals: false,
|
|
12
|
+
options: {
|
|
13
|
+
input: { type: 'string' },
|
|
14
|
+
selection: { type: 'string' },
|
|
15
|
+
typescript: { type: 'string' },
|
|
16
|
+
cpp: { type: 'string' },
|
|
17
|
+
pil: { type: 'string' },
|
|
18
|
+
solidity: { type: 'string' },
|
|
19
|
+
rust: { type: 'string' },
|
|
20
|
+
},
|
|
21
|
+
strict: true,
|
|
22
|
+
});
|
|
23
|
+
// The default input is embedded by scripts/embed-inputs.sh: prepack ships it in the published
|
|
24
|
+
// tarball, while in-repo callers pass --input (via scripts/generate.sh).
|
|
25
|
+
const defaultInput = fileURLToPath(new URL('../inputs/constants.nr', import.meta.url));
|
|
26
|
+
const input = values.input ?? (existsSync(defaultInput) ? defaultInput : undefined);
|
|
27
|
+
if (!input) {
|
|
28
|
+
throw new Error('--input is required when the package has no embedded inputs');
|
|
29
|
+
}
|
|
30
|
+
const generators = [
|
|
31
|
+
[values.typescript, generateTypescriptConstants],
|
|
32
|
+
[values.cpp, generateCppConstants],
|
|
33
|
+
[values.pil, generatePilConstants],
|
|
34
|
+
[values.solidity, generateSolidityConstants],
|
|
35
|
+
[values.rust, generateRustConstants],
|
|
36
|
+
];
|
|
37
|
+
const outputs = generators.filter(([path]) => path !== undefined);
|
|
38
|
+
if (outputs.length !== 1) {
|
|
39
|
+
throw new Error('exactly one output option is required');
|
|
40
|
+
}
|
|
41
|
+
const [outputPath, generate] = outputs[0];
|
|
42
|
+
const { constantsExpressions, domainSeparatorEnum } = parseNoirFile(readFileSync(input, 'utf8'));
|
|
43
|
+
const parsedContent = {
|
|
44
|
+
constants: evaluateExpressions(constantsExpressions),
|
|
45
|
+
domainSeparatorEnum,
|
|
46
|
+
};
|
|
47
|
+
const outputContent = values.selection
|
|
48
|
+
? selectSymbols(parsedContent, readSymbolSelection(values.selection))
|
|
49
|
+
: parsedContent;
|
|
50
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
51
|
+
generate(outputContent, outputPath);
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
run(process.argv.slice(2));
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
58
|
+
console.error(`constants-codegen: ${message}`);
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parsed content.
|
|
3
|
+
*/
|
|
4
|
+
export interface ParsedContent {
|
|
5
|
+
/**
|
|
6
|
+
* Constants of the form "CONSTANT_NAME: number_as_string".
|
|
7
|
+
*/
|
|
8
|
+
constants: {
|
|
9
|
+
[key: string]: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* DomainSeparatorEnum.
|
|
13
|
+
*/
|
|
14
|
+
domainSeparatorEnum: {
|
|
15
|
+
[key: string]: number;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Raw expressions parsed from a Noir file, prior to evaluation. Keeping expressions unevaluated lets callers merge
|
|
20
|
+
* constants from multiple files and resolve cross-file references in a single evaluation pass.
|
|
21
|
+
*/
|
|
22
|
+
export interface ParsedExpressions {
|
|
23
|
+
/** Ordered list of constant name and expression pairs. */
|
|
24
|
+
constantsExpressions: [string, string][];
|
|
25
|
+
/** DomainSeparatorEnum members. */
|
|
26
|
+
domainSeparatorEnum: {
|
|
27
|
+
[key: string]: number;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Processes a collection of constants and generates code to export them as TypeScript constants.
|
|
32
|
+
*
|
|
33
|
+
* @param constants - An object containing key-value pairs representing constants.
|
|
34
|
+
* @returns A string containing code that exports the constants as TypeScript constants.
|
|
35
|
+
*/
|
|
36
|
+
export declare function processConstantsTS(constants: {
|
|
37
|
+
[key: string]: string;
|
|
38
|
+
}): string;
|
|
39
|
+
/**
|
|
40
|
+
* Processes a collection of constants and generates code to export them as cpp constants.
|
|
41
|
+
* Required to ensure consistency between the constants used in pil and used in the vm witness generator.
|
|
42
|
+
*
|
|
43
|
+
* @param constants - An object containing key-value pairs representing constants.
|
|
44
|
+
* @returns A string containing code that exports the constants as cpp constants.
|
|
45
|
+
*/
|
|
46
|
+
export declare function processConstantsCpp(constants: {
|
|
47
|
+
[key: string]: string;
|
|
48
|
+
}, generatorIndices: {
|
|
49
|
+
[key: string]: number;
|
|
50
|
+
}): string;
|
|
51
|
+
/**
|
|
52
|
+
* Processes a collection of constants and generates code to export them as PIL constants.
|
|
53
|
+
* Required to ensure consistency between the constants used in pil and used in the vm witness generator.
|
|
54
|
+
*
|
|
55
|
+
* @param constants - An object containing key-value pairs representing constants.
|
|
56
|
+
* @returns A string containing code that exports the constants as cpp constants.
|
|
57
|
+
*/
|
|
58
|
+
export declare function processConstantsPil(constants: {
|
|
59
|
+
[key: string]: string;
|
|
60
|
+
}, generatorIndices: {
|
|
61
|
+
[key: string]: number;
|
|
62
|
+
}): string;
|
|
63
|
+
/**
|
|
64
|
+
* Processes an enum and generates code to export it as a TypeScript enum.
|
|
65
|
+
*
|
|
66
|
+
* @param enumName - The name of the enum.
|
|
67
|
+
* @param enumValues - An object containing key-value pairs representing enum values.
|
|
68
|
+
* @returns A string containing code that exports the enum as a TypeScript enum.
|
|
69
|
+
*/
|
|
70
|
+
export declare function processEnumTS(enumName: string, enumValues: {
|
|
71
|
+
[key: string]: number;
|
|
72
|
+
}): string;
|
|
73
|
+
/**
|
|
74
|
+
* Processes a collection of constants and generates code to export them as Solidity constants.
|
|
75
|
+
*
|
|
76
|
+
* @param constants - An object containing key-value pairs representing constants.
|
|
77
|
+
* @param generatorIndices - An object containing key-value pairs representing domain separator indices.
|
|
78
|
+
* @param prefix - A prefix to add to the constant names.
|
|
79
|
+
* @returns A string containing code that exports the constants as Noir constants.
|
|
80
|
+
*/
|
|
81
|
+
export declare function processConstantsSolidity(constants: {
|
|
82
|
+
[key: string]: string;
|
|
83
|
+
}, generatorIndices: {
|
|
84
|
+
[key: string]: number;
|
|
85
|
+
}, prefix?: string): string;
|
|
86
|
+
/**
|
|
87
|
+
* Processes a collection of constants and generates code to export them as Rust constants.
|
|
88
|
+
*
|
|
89
|
+
* @param constants - An object containing key-value pairs representing constants.
|
|
90
|
+
* @param generatorIndices - An object containing key-value pairs representing domain separator indices.
|
|
91
|
+
* @returns A string containing code that exports the constants as Rust constants.
|
|
92
|
+
*/
|
|
93
|
+
export declare function processConstantsRust(constants: {
|
|
94
|
+
[key: string]: string;
|
|
95
|
+
}, generatorIndices: {
|
|
96
|
+
[key: string]: number;
|
|
97
|
+
}): string;
|
|
98
|
+
/**
|
|
99
|
+
* Generate the constants file in Typescript.
|
|
100
|
+
*/
|
|
101
|
+
export declare function generateTypescriptConstants({ constants, domainSeparatorEnum }: ParsedContent, targetPath: string): void;
|
|
102
|
+
/**
|
|
103
|
+
* Generate the constants file in C++.
|
|
104
|
+
*/
|
|
105
|
+
export declare function generateCppConstants({ constants, domainSeparatorEnum }: ParsedContent, targetPath: string): void;
|
|
106
|
+
/**
|
|
107
|
+
* Generate the constants file in PIL.
|
|
108
|
+
*/
|
|
109
|
+
export declare function generatePilConstants({ constants, domainSeparatorEnum }: ParsedContent, targetPath: string): void;
|
|
110
|
+
/**
|
|
111
|
+
* Generate the constants file in Solidity.
|
|
112
|
+
*/
|
|
113
|
+
export declare function generateSolidityConstants({ constants, domainSeparatorEnum }: ParsedContent, targetPath: string): void;
|
|
114
|
+
/**
|
|
115
|
+
* Generate the constants file in Rust.
|
|
116
|
+
*/
|
|
117
|
+
export declare function generateRustConstants({ constants, domainSeparatorEnum }: ParsedContent, targetPath: string): void;
|
|
118
|
+
/**
|
|
119
|
+
* Parse the content of the constants file in Noir.
|
|
120
|
+
*/
|
|
121
|
+
export declare function parseNoirFile(fileContent: string): ParsedExpressions;
|
|
122
|
+
/**
|
|
123
|
+
* Converts constants defined as expressions to constants with actual values.
|
|
124
|
+
* @param expressions Ordered list of expressions of the type: "CONSTANT_NAME: expression".
|
|
125
|
+
* where the expression is a string that can be evaluated to a number.
|
|
126
|
+
* For example: "CONSTANT_NAME: 2 + 2" or "CONSTANT_NAME: CONSTANT_A * CONSTANT_B".
|
|
127
|
+
* @returns Parsed expressions of the form: "CONSTANT_NAME: number_as_string".
|
|
128
|
+
*/
|
|
129
|
+
export declare function evaluateExpressions(expressions: [string, string][]): {
|
|
130
|
+
[key: string]: string;
|
|
131
|
+
};
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
/**
|
|
3
|
+
* Processes a collection of constants and generates code to export them as TypeScript constants.
|
|
4
|
+
*
|
|
5
|
+
* @param constants - An object containing key-value pairs representing constants.
|
|
6
|
+
* @returns A string containing code that exports the constants as TypeScript constants.
|
|
7
|
+
*/
|
|
8
|
+
export function processConstantsTS(constants) {
|
|
9
|
+
const code = [];
|
|
10
|
+
Object.entries(constants).forEach(([key, value]) => {
|
|
11
|
+
code.push(`export const ${key} = ${+value > Number.MAX_SAFE_INTEGER ? value + 'n' : value};`);
|
|
12
|
+
});
|
|
13
|
+
return code.join('\n');
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Processes a collection of constants and generates code to export them as cpp constants.
|
|
17
|
+
* Required to ensure consistency between the constants used in pil and used in the vm witness generator.
|
|
18
|
+
*
|
|
19
|
+
* @param constants - An object containing key-value pairs representing constants.
|
|
20
|
+
* @returns A string containing code that exports the constants as cpp constants.
|
|
21
|
+
*/
|
|
22
|
+
export function processConstantsCpp(constants, generatorIndices) {
|
|
23
|
+
const code = [];
|
|
24
|
+
Object.entries(constants).forEach(([key, value]) => {
|
|
25
|
+
if (BigInt(value) <= 2n ** 31n - 1n) {
|
|
26
|
+
code.push(`#define ${key} ${value}`);
|
|
27
|
+
}
|
|
28
|
+
else if (BigInt(value) <= 2n ** 64n - 1n) {
|
|
29
|
+
code.push(`#define ${key} 0x${BigInt(value).toString(16)}`); // hex literals
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
code.push(`#define ${key} "0x${BigInt(value).toString(16).padStart(64, '0')}"`); // stringify large numbers
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
Object.entries(generatorIndices).forEach(([key, value]) => {
|
|
36
|
+
code.push(`#define DOM_SEP__${key} ${value}UL`);
|
|
37
|
+
});
|
|
38
|
+
return code.join('\n');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Processes a collection of constants and generates code to export them as PIL constants.
|
|
42
|
+
* Required to ensure consistency between the constants used in pil and used in the vm witness generator.
|
|
43
|
+
*
|
|
44
|
+
* @param constants - An object containing key-value pairs representing constants.
|
|
45
|
+
* @returns A string containing code that exports the constants as cpp constants.
|
|
46
|
+
*/
|
|
47
|
+
export function processConstantsPil(constants, generatorIndices) {
|
|
48
|
+
const code = [];
|
|
49
|
+
Object.entries(constants).forEach(([key, value]) => {
|
|
50
|
+
code.push(` pol ${key} = ${value};`);
|
|
51
|
+
});
|
|
52
|
+
Object.entries(generatorIndices).forEach(([key, value]) => {
|
|
53
|
+
code.push(` pol DOM_SEP__${key} = ${value};`);
|
|
54
|
+
});
|
|
55
|
+
return code.join('\n');
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Processes an enum and generates code to export it as a TypeScript enum.
|
|
59
|
+
*
|
|
60
|
+
* @param enumName - The name of the enum.
|
|
61
|
+
* @param enumValues - An object containing key-value pairs representing enum values.
|
|
62
|
+
* @returns A string containing code that exports the enum as a TypeScript enum.
|
|
63
|
+
*/
|
|
64
|
+
export function processEnumTS(enumName, enumValues) {
|
|
65
|
+
const code = [];
|
|
66
|
+
code.push(`export enum ${enumName} {`);
|
|
67
|
+
Object.entries(enumValues).forEach(([key, value]) => {
|
|
68
|
+
code.push(` ${key} = ${value},`);
|
|
69
|
+
});
|
|
70
|
+
code.push('}');
|
|
71
|
+
return code.join('\n');
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Processes a collection of constants and generates code to export them as Solidity constants.
|
|
75
|
+
*
|
|
76
|
+
* @param constants - An object containing key-value pairs representing constants.
|
|
77
|
+
* @param generatorIndices - An object containing key-value pairs representing domain separator indices.
|
|
78
|
+
* @param prefix - A prefix to add to the constant names.
|
|
79
|
+
* @returns A string containing code that exports the constants as Noir constants.
|
|
80
|
+
*/
|
|
81
|
+
export function processConstantsSolidity(constants, generatorIndices, prefix = '') {
|
|
82
|
+
const code = [];
|
|
83
|
+
Object.entries(constants).forEach(([key, value]) => {
|
|
84
|
+
code.push(` uint256 internal constant ${prefix}${key} = ${value};`);
|
|
85
|
+
});
|
|
86
|
+
Object.entries(generatorIndices).forEach(([key, value]) => {
|
|
87
|
+
code.push(` uint256 internal constant ${prefix}DOM_SEP__${key} = ${value};`);
|
|
88
|
+
});
|
|
89
|
+
return code.join('\n');
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Processes a collection of constants and generates code to export them as Rust constants.
|
|
93
|
+
*
|
|
94
|
+
* @param constants - An object containing key-value pairs representing constants.
|
|
95
|
+
* @param generatorIndices - An object containing key-value pairs representing domain separator indices.
|
|
96
|
+
* @returns A string containing code that exports the constants as Rust constants.
|
|
97
|
+
*/
|
|
98
|
+
export function processConstantsRust(constants, generatorIndices) {
|
|
99
|
+
const code = [];
|
|
100
|
+
Object.entries(constants).forEach(([key, value]) => {
|
|
101
|
+
if (BigInt(value) <= 2n ** 128n - 1n) {
|
|
102
|
+
code.push(`pub const ${key}: u128 = ${value};`);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// Field-sized values exceed u128, so they are emitted as hex strings.
|
|
106
|
+
code.push(`pub const ${key}: &str = "0x${BigInt(value).toString(16).padStart(64, '0')}";`);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
Object.entries(generatorIndices).forEach(([key, value]) => {
|
|
110
|
+
code.push(`pub const DOM_SEP__${key}: u128 = ${value};`);
|
|
111
|
+
});
|
|
112
|
+
return code.join('\n');
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Generate the constants file in Typescript.
|
|
116
|
+
*/
|
|
117
|
+
export function generateTypescriptConstants({ constants, domainSeparatorEnum }, targetPath) {
|
|
118
|
+
const result = [
|
|
119
|
+
'// GENERATED FILE - DO NOT EDIT',
|
|
120
|
+
processConstantsTS(constants),
|
|
121
|
+
processEnumTS('DomainSeparator', domainSeparatorEnum),
|
|
122
|
+
].join('\n');
|
|
123
|
+
fs.writeFileSync(targetPath, result);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Generate the constants file in C++.
|
|
127
|
+
*/
|
|
128
|
+
export function generateCppConstants({ constants, domainSeparatorEnum }, targetPath) {
|
|
129
|
+
const resultCpp = `// GENERATED FILE - DO NOT EDIT
|
|
130
|
+
#pragma once
|
|
131
|
+
|
|
132
|
+
${processConstantsCpp(constants, domainSeparatorEnum)}
|
|
133
|
+
`;
|
|
134
|
+
fs.writeFileSync(targetPath, resultCpp);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Generate the constants file in PIL.
|
|
138
|
+
*/
|
|
139
|
+
export function generatePilConstants({ constants, domainSeparatorEnum }, targetPath) {
|
|
140
|
+
const resultPil = `// GENERATED FILE - DO NOT EDIT
|
|
141
|
+
namespace constants;
|
|
142
|
+
${processConstantsPil(constants, domainSeparatorEnum)}
|
|
143
|
+
\n`;
|
|
144
|
+
fs.writeFileSync(targetPath, resultPil);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Generate the constants file in Solidity.
|
|
148
|
+
*/
|
|
149
|
+
export function generateSolidityConstants({ constants, domainSeparatorEnum }, targetPath) {
|
|
150
|
+
const resultSolidity = `// GENERATED FILE - DO NOT EDIT
|
|
151
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
152
|
+
// Copyright 2023 Aztec Labs.
|
|
153
|
+
pragma solidity >=0.8.27;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @title Constants Library
|
|
157
|
+
* @author Aztec Labs
|
|
158
|
+
* @notice Library that contains constants used throughout the Aztec protocol
|
|
159
|
+
*/
|
|
160
|
+
library Constants {
|
|
161
|
+
// Prime field modulus
|
|
162
|
+
uint256 internal constant P =
|
|
163
|
+
21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
|
164
|
+
|
|
165
|
+
${processConstantsSolidity(constants, domainSeparatorEnum)}
|
|
166
|
+
}\n`;
|
|
167
|
+
fs.writeFileSync(targetPath, resultSolidity);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Generate the constants file in Rust.
|
|
171
|
+
*/
|
|
172
|
+
export function generateRustConstants({ constants, domainSeparatorEnum }, targetPath) {
|
|
173
|
+
const resultRust = `// GENERATED FILE - DO NOT EDIT
|
|
174
|
+
${processConstantsRust(constants, domainSeparatorEnum)}
|
|
175
|
+
`;
|
|
176
|
+
fs.writeFileSync(targetPath, resultRust);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Parse the content of the constants file in Noir.
|
|
180
|
+
*/
|
|
181
|
+
export function parseNoirFile(fileContent) {
|
|
182
|
+
const constantsExpressions = [];
|
|
183
|
+
const domainSeparatorEnum = {};
|
|
184
|
+
const emptyExpression = () => ({ name: '', content: [] });
|
|
185
|
+
let expression = emptyExpression();
|
|
186
|
+
fileContent.split('\n').forEach(l => {
|
|
187
|
+
// Line comments are stripped so they never leak into expressions, where they would swallow
|
|
188
|
+
// the rest of the expression when it is later evaluated as JavaScript.
|
|
189
|
+
const line = l.replace(/\/\/.*$/, '').trim();
|
|
190
|
+
if (!line) {
|
|
191
|
+
// Empty line.
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (line.match(/^\/\/|^\s*\/?\*/)) {
|
|
195
|
+
// Comment.
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
{
|
|
199
|
+
const [, name, _type, value, end] = line.match(/global\s+(\w+)(\s*:\s*\w+)?\s*=\s*([^;]*)(;)?/) || [];
|
|
200
|
+
if (name && value) {
|
|
201
|
+
const [, indexName] = name.match(/DOM_SEP__(\w+)/) || [];
|
|
202
|
+
if (indexName) {
|
|
203
|
+
// Generator index.
|
|
204
|
+
domainSeparatorEnum[indexName] = +value;
|
|
205
|
+
}
|
|
206
|
+
else if (end) {
|
|
207
|
+
// A single line of expression.
|
|
208
|
+
constantsExpressions.push([name, value]);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
// The first line of an expression.
|
|
212
|
+
expression = { name, content: [value] };
|
|
213
|
+
}
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
else if (name) {
|
|
217
|
+
// This case happens if we have only a name, with the value being on the next line
|
|
218
|
+
expression = { name, content: [] };
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (expression.name) {
|
|
223
|
+
// The expression continues...
|
|
224
|
+
const [, content, end] = line.match(/\s*([^;]+)(;)?/) || [];
|
|
225
|
+
expression.content.push(content);
|
|
226
|
+
if (end) {
|
|
227
|
+
// The last line of an expression.
|
|
228
|
+
constantsExpressions.push([expression.name, expression.content.join('')]);
|
|
229
|
+
expression = emptyExpression();
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (!line.includes('use crate')) {
|
|
234
|
+
// eslint-disable-next-line no-console
|
|
235
|
+
console.warn(`Unknown content: ${line}`);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
return { constantsExpressions, domainSeparatorEnum };
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Converts constants defined as expressions to constants with actual values.
|
|
242
|
+
* @param expressions Ordered list of expressions of the type: "CONSTANT_NAME: expression".
|
|
243
|
+
* where the expression is a string that can be evaluated to a number.
|
|
244
|
+
* For example: "CONSTANT_NAME: 2 + 2" or "CONSTANT_NAME: CONSTANT_A * CONSTANT_B".
|
|
245
|
+
* @returns Parsed expressions of the form: "CONSTANT_NAME: number_as_string".
|
|
246
|
+
*/
|
|
247
|
+
export function evaluateExpressions(expressions) {
|
|
248
|
+
const constants = {};
|
|
249
|
+
const knownBigInts = ['AZTEC_EPOCH_DURATION', 'FEE_RECIPIENT_LENGTH'];
|
|
250
|
+
// Create JS expressions. It is not as easy as just evaluating the expression!
|
|
251
|
+
// We basically need to convert everything to BigInts, otherwise things don't fit.
|
|
252
|
+
// However, (1) the bigints need to be initialized from strings; (2) everything needs to
|
|
253
|
+
// be a bigint, even the actual constant values!
|
|
254
|
+
const prelude = expressions
|
|
255
|
+
.map(([name, rhs]) => {
|
|
256
|
+
const guardedRhs = rhs
|
|
257
|
+
// Remove 'as u8', 'as u32' and 'as u64' castings
|
|
258
|
+
.replaceAll(' as u8', '')
|
|
259
|
+
.replaceAll(' as u32', '')
|
|
260
|
+
.replaceAll(' as u64', '')
|
|
261
|
+
// Remove the 'AztecAddress::from_field(...)' pattern.
|
|
262
|
+
// Also copes with the noir formatter re-formatting over multiple lines.
|
|
263
|
+
.replace(/AztecAddress::from_field\(\s*(0x[a-fA-F0-9]+|\d+)\s*,?\s*\)/gs, '$1')
|
|
264
|
+
// We make some space around the parentheses, so that constant numbers are still split.
|
|
265
|
+
.replace(/\(/g, '( ')
|
|
266
|
+
.replace(/\)/g, ' )')
|
|
267
|
+
// We also make some space around common operators
|
|
268
|
+
.replace(/\+/g, ' + ')
|
|
269
|
+
.replace(/(?<!\/)\*(?!\/)/, ' * ')
|
|
270
|
+
// We split the expression into terms...
|
|
271
|
+
.split(/\s+/)
|
|
272
|
+
// ...and then we convert each term to a BigInt if it is a number.
|
|
273
|
+
.map(term => {
|
|
274
|
+
// Remove underscores from numeric literals (e.g., 6_000_000 -> 6000000)
|
|
275
|
+
const termWithoutUnderscores = term.replace(/_/g, '');
|
|
276
|
+
return isNaN(+termWithoutUnderscores) ? term : `BigInt('${termWithoutUnderscores}')`;
|
|
277
|
+
})
|
|
278
|
+
// .. also, we convert the known bigints to BigInts.
|
|
279
|
+
.map(term => (knownBigInts.includes(term) ? `BigInt(${term})` : term))
|
|
280
|
+
// We join the terms back together.
|
|
281
|
+
.join(' ');
|
|
282
|
+
return `var ${name} = ${guardedRhs};`;
|
|
283
|
+
})
|
|
284
|
+
.join('\n');
|
|
285
|
+
// Extract each value from the expressions. Observe that this will still be a string,
|
|
286
|
+
// so that we can then choose to express it as BigInt or Number depending on the size.
|
|
287
|
+
for (const [name, _] of expressions) {
|
|
288
|
+
constants[name] = eval(prelude + `; BigInt(${name}).toString()`);
|
|
289
|
+
}
|
|
290
|
+
return constants;
|
|
291
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ParsedContent } from './generator.ts';
|
|
2
|
+
/** Source symbols to include in one generated output. */
|
|
3
|
+
export type SymbolSelection = string[];
|
|
4
|
+
/** Reads and validates a symbol selection JSON file. */
|
|
5
|
+
export declare function readSymbolSelection(path: string): SymbolSelection;
|
|
6
|
+
/** Filters parsed Noir content to the symbols requested for one output. */
|
|
7
|
+
export declare function selectSymbols(content: ParsedContent, selection: SymbolSelection): ParsedContent;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
/** Conventional prefix for domain separators. */
|
|
3
|
+
const DOMAIN_SEPARATOR_PREFIX = 'DOM_SEP__';
|
|
4
|
+
// Symbol names contain no regex metacharacters, so any entry that is not a
|
|
5
|
+
// valid name can only be intended as a pattern.
|
|
6
|
+
function isExactName(entry) {
|
|
7
|
+
return /^[A-Za-z_]\w*$/.test(entry);
|
|
8
|
+
}
|
|
9
|
+
function compilePattern(entry) {
|
|
10
|
+
return new RegExp(`^(?:${entry})$`);
|
|
11
|
+
}
|
|
12
|
+
/** Reads and validates a symbol selection JSON file. */
|
|
13
|
+
export function readSymbolSelection(path) {
|
|
14
|
+
let value;
|
|
15
|
+
try {
|
|
16
|
+
value = JSON.parse(readFileSync(path, 'utf8'));
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
throw new Error(`could not read selection ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
20
|
+
}
|
|
21
|
+
if (!Array.isArray(value) || !value.every(entry => typeof entry === 'string')) {
|
|
22
|
+
throw new Error(`selection ${path} must be a JSON array of strings`);
|
|
23
|
+
}
|
|
24
|
+
for (const entry of value) {
|
|
25
|
+
if (!isExactName(entry)) {
|
|
26
|
+
try {
|
|
27
|
+
compilePattern(entry);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
throw new Error(`invalid pattern '${entry}' in ${path}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const duplicate = value.find((entry, index) => value.indexOf(entry) !== index);
|
|
35
|
+
if (duplicate) {
|
|
36
|
+
throw new Error(`duplicate entry '${duplicate}' in ${path}`);
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
/** Filters parsed Noir content to the symbols requested for one output. */
|
|
41
|
+
export function selectSymbols(content, selection) {
|
|
42
|
+
const sourceNames = [
|
|
43
|
+
...Object.keys(content.constants),
|
|
44
|
+
...Object.keys(content.domainSeparatorEnum).map(name => DOMAIN_SEPARATOR_PREFIX + name),
|
|
45
|
+
];
|
|
46
|
+
const sourceNameSet = new Set(sourceNames);
|
|
47
|
+
const selected = new Set();
|
|
48
|
+
for (const entry of selection) {
|
|
49
|
+
if (isExactName(entry)) {
|
|
50
|
+
if (!sourceNameSet.has(entry)) {
|
|
51
|
+
throw new Error(`unknown symbol '${entry}' in selection`);
|
|
52
|
+
}
|
|
53
|
+
selected.add(entry);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const pattern = compilePattern(entry);
|
|
57
|
+
const matches = sourceNames.filter(name => pattern.test(name));
|
|
58
|
+
if (matches.length === 0) {
|
|
59
|
+
throw new Error(`pattern '${entry}' in selection matched no symbols`);
|
|
60
|
+
}
|
|
61
|
+
matches.forEach(name => selected.add(name));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
constants: Object.fromEntries(Object.entries(content.constants).filter(([name]) => selected.has(name))),
|
|
66
|
+
domainSeparatorEnum: Object.fromEntries(Object.entries(content.domainSeparatorEnum).filter(([name]) => selected.has(DOMAIN_SEPARATOR_PREFIX + name))),
|
|
67
|
+
};
|
|
68
|
+
}
|