Skip to content

Commit 2b9aad6

Browse files
committed
feat(canonical-contracts): autogen Noir interface stubs from compiled artifacts
1 parent 19b1501 commit 2b9aad6

6 files changed

Lines changed: 428 additions & 25 deletions

File tree

noir-projects/aztec-nr/aztec/src/authwit/auth_registry_interface.nr

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
// GENERATED FILE - DO NOT EDIT
2+
//
3+
// Written by `yarn-project/standard-contracts/src/scripts/generate_interfaces.ts` from the compiled
4+
// `AuthRegistry` artifact. Regenerate with
5+
// `yarn workspace @aztec/standard-contracts run regen:standard-interfaces`.
6+
//
7+
// The selectors below are derived via `comptime { FunctionSelector::from_signature(...) }` at
8+
// Noir compile time, with the signature string emitted from the artifact's parameter list. This
9+
// keeps the wrapper in lockstep with whatever the `#[aztec]` macro generates for the real
10+
// contract; any drift between the contract's external signatures and this file fails the
11+
// `generate_interfaces.test.ts` freshness gate.
12+
113
use crate::context::calls::{PublicCall, PublicStaticCall};
214
use crate::protocol::{abis::function_selector::FunctionSelector, address::AztecAddress, traits::ToField};
315

4-
/// Hand-written interface stub for the `AuthRegistry` canonical contract.
5-
///
6-
/// The `AuthRegistry` contract records public authentication witnesses
7-
/// (`set_authorized`, `set_reject_all`) and atomically consumes them at
8-
/// the consumer (`consume`). Consumer code in aztec-nr's `authwit::public`
9-
/// module calls into the registry via this interface.
10-
///
11-
/// The selectors are derived with `comptime { FunctionSelector::from_signature(...) }`,
12-
/// which matches exactly what the `#[aztec]` macro generates for the real contract.
1316
pub struct AuthRegistryInterface {
1417
pub target_contract: AztecAddress,
1518
}

noir-projects/aztec-nr/aztec/src/public_checks_interface.nr

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
// GENERATED FILE - DO NOT EDIT
2+
//
3+
// Written by `yarn-project/standard-contracts/src/scripts/generate_interfaces.ts` from the compiled
4+
// `PublicChecks` artifact. Regenerate with
5+
// `yarn workspace @aztec/standard-contracts run regen:standard-interfaces`.
6+
//
7+
// The selectors below are derived via `comptime { FunctionSelector::from_signature(...) }` at
8+
// Noir compile time, with the signature string emitted from the artifact's parameter list. This
9+
// keeps the wrapper in lockstep with whatever the `#[aztec]` macro generates for the real
10+
// contract; any drift between the contract's external signatures and this file fails the
11+
// `generate_interfaces.test.ts` freshness gate.
12+
113
use crate::context::calls::PublicStaticCall;
2-
use crate::protocol::abis::function_selector::FunctionSelector;
3-
use crate::protocol::address::AztecAddress;
14+
use crate::protocol::{abis::function_selector::FunctionSelector, address::AztecAddress};
415

5-
/// Hand-written interface stub for the `PublicChecks` standard contract.
6-
///
7-
/// The `PublicChecks` contract exposes two view functions that can be enqueued
8-
/// from private context via `enqueue_view_incognito` to assert timestamp or
9-
/// block-number constraints without revealing the calling contract address.
10-
///
11-
/// The selectors are derived with `comptime { FunctionSelector::from_signature(...) }`,
12-
/// which matches exactly what the `#[aztec]` macro generates for the real contract.
1316
pub struct PublicChecksInterface {
1417
pub target_contract: AztecAddress,
1518
}
@@ -19,22 +22,22 @@ impl PublicChecksInterface {
1922
Self { target_contract }
2023
}
2124

22-
pub fn check_timestamp(self, operation: u8, value: u64) -> PublicStaticCall<15, 2, ()> {
23-
let selector = comptime { FunctionSelector::from_signature("check_timestamp(u8,u64)") };
25+
pub fn check_block_number(self, operation: u8, value: u32) -> PublicStaticCall<18, 2, ()> {
26+
let selector = comptime { FunctionSelector::from_signature("check_block_number(u8,u32)") };
2427
PublicStaticCall::new(
2528
self.target_contract,
2629
selector,
27-
"check_timestamp",
30+
"check_block_number",
2831
[operation as Field, value as Field],
2932
)
3033
}
3134

32-
pub fn check_block_number(self, operation: u8, value: u32) -> PublicStaticCall<18, 2, ()> {
33-
let selector = comptime { FunctionSelector::from_signature("check_block_number(u8,u32)") };
35+
pub fn check_timestamp(self, operation: u8, value: u64) -> PublicStaticCall<15, 2, ()> {
36+
let selector = comptime { FunctionSelector::from_signature("check_timestamp(u8,u64)") };
3437
PublicStaticCall::new(
3538
self.target_contract,
3639
selector,
37-
"check_block_number",
40+
"check_timestamp",
3841
[operation as Field, value as Field],
3942
)
4043
}

yarn-project/standard-contracts/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"generate": "yarn generate:data",
2727
"generate:cleanup-artifacts": "node --no-warnings --loader @swc-node/register/esm src/scripts/cleanup_artifacts.ts",
2828
"generate:data": "node --no-warnings --loader @swc-node/register/esm src/scripts/generate_data.ts",
29+
"regen:standard-interfaces": "node --no-warnings --loader @swc-node/register/esm src/scripts/generate_interfaces.ts",
2930
"build:dev": "../scripts/tsc.sh --watch",
3031
"build:ts": "../scripts/tsc.sh",
3132
"clean": "rm -rf ./dest .tsbuildinfo ./artifacts",
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// CLI entry for regenerating the autogenerated Noir interface stubs that wrap the standard
2+
// `AuthRegistry` and `PublicChecks` contracts' external functions.
3+
//
4+
// Reads the compiled artifacts (produced by phase 1 of
5+
// `noir-projects/noir-contracts/bootstrap.sh`), renders each `.gen.nr` deterministically from
6+
// the artifact's parameter list, and writes the result back. The freshness test in
7+
// `../standard-interfaces/generate_interfaces.test.ts` re-runs this renderer and asserts
8+
// byte-equality against what is committed.
9+
import type { NoirCompiledContract } from '@aztec/stdlib/noir';
10+
11+
import { promises as fs } from 'node:fs';
12+
import path from 'node:path';
13+
14+
import {
15+
ALL_INTERFACES,
16+
type InterfaceSpec,
17+
renderInterfaceFile,
18+
} from '../standard-interfaces/generate_interfaces.js';
19+
20+
async function writeIfChanged(filePath: string, content: string): Promise<void> {
21+
try {
22+
const existing = await fs.readFile(filePath, 'utf8');
23+
if (existing === content) {
24+
return;
25+
}
26+
} catch (err: any) {
27+
if (err.code !== 'ENOENT') {
28+
throw err;
29+
}
30+
}
31+
await fs.mkdir(path.dirname(filePath), { recursive: true });
32+
await fs.writeFile(filePath, content);
33+
}
34+
35+
async function regenerateInterface(spec: InterfaceSpec): Promise<void> {
36+
const artifactRaw = await fs.readFile(spec.artifactPath, 'utf8');
37+
const artifact = JSON.parse(artifactRaw) as NoirCompiledContract;
38+
const content = renderInterfaceFile(spec, artifact);
39+
await writeIfChanged(spec.outputPath, content);
40+
process.stdout.write(`regenerated ${spec.interfaceName} -> ${spec.outputPath}\n`);
41+
}
42+
43+
async function main() {
44+
for (const spec of ALL_INTERFACES) {
45+
await regenerateInterface(spec);
46+
}
47+
}
48+
49+
if (import.meta.url === `file://${process.argv[1]}`) {
50+
await main();
51+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { NoirCompiledContract } from '@aztec/stdlib/noir';
2+
3+
import { promises as fs } from 'node:fs';
4+
5+
import { ALL_INTERFACES, renderInterfaceFile } from './generate_interfaces.js';
6+
7+
const REGEN_HINT =
8+
'standard interface stubs are stale; run `yarn workspace @aztec/standard-contracts run regen:standard-interfaces` and commit the result.';
9+
10+
describe('standard interface stub freshness', () => {
11+
for (const spec of ALL_INTERFACES) {
12+
describe(spec.interfaceName, () => {
13+
let artifactExists = false;
14+
beforeAll(async () => {
15+
artifactExists = await fs
16+
.access(spec.artifactPath)
17+
.then(() => true)
18+
.catch(() => false);
19+
});
20+
21+
it('on-disk .gen.nr matches the freshly-rendered output', async () => {
22+
if (!artifactExists) {
23+
// Artifact is produced by `./bootstrap.sh build` (or `nargo compile` +
24+
// `bb aztec_process` for the noir-contracts package). Skip with a clear message
25+
// rather than fail when the artifact has not been built yet — the dedicated CI
26+
// job that runs this test ensures the artifact is on disk before invoking jest.
27+
console.warn(`Skipping ${spec.interfaceName}: ${spec.artifactPath} not found.`);
28+
return;
29+
}
30+
const artifact = JSON.parse(await fs.readFile(spec.artifactPath, 'utf8')) as NoirCompiledContract;
31+
const expected = renderInterfaceFile(spec, artifact);
32+
const actual = await fs.readFile(spec.outputPath, 'utf8');
33+
34+
if (actual !== expected) {
35+
throw new Error(REGEN_HINT);
36+
}
37+
});
38+
39+
it('render is deterministic for the same artifact', async () => {
40+
if (!artifactExists) {
41+
return;
42+
}
43+
const artifact = JSON.parse(await fs.readFile(spec.artifactPath, 'utf8')) as NoirCompiledContract;
44+
expect(renderInterfaceFile(spec, artifact)).toEqual(renderInterfaceFile(spec, artifact));
45+
});
46+
});
47+
}
48+
});

0 commit comments

Comments
 (0)