Skip to content

Commit d10c85a

Browse files
committed
feat(protocol-contracts): autogen Noir interface stubs from compiled artifacts
1 parent 763b311 commit d10c85a

10 files changed

Lines changed: 553 additions & 74 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// GENERATED FILE - DO NOT EDIT
2+
//
3+
// Written by `yarn-project/protocol-contracts/src/scripts/generate_interfaces.ts` from the compiled
4+
// `AuthRegistry` artifact. Regenerate with
5+
// `yarn workspace @aztec/protocol-contracts run regen:canonical-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+
13+
use crate::context::calls::{PublicCall, PublicStaticCall};
14+
use crate::protocol::{abis::function_selector::FunctionSelector, address::AztecAddress, traits::ToField};
15+
16+
pub struct AuthRegistryInterface {
17+
pub target_contract: AztecAddress,
18+
}
19+
20+
impl AuthRegistryInterface {
21+
pub fn at(target_contract: AztecAddress) -> Self {
22+
Self { target_contract }
23+
}
24+
25+
pub fn _set_authorized(
26+
self,
27+
approver: AztecAddress,
28+
message_hash: Field,
29+
authorize: bool,
30+
) -> PublicCall<15, 3, ()> {
31+
let selector = comptime { FunctionSelector::from_signature("_set_authorized((Field),Field,bool)") };
32+
PublicCall::new(
33+
self.target_contract,
34+
selector,
35+
"_set_authorized",
36+
[approver.to_field(), message_hash, authorize as Field],
37+
)
38+
}
39+
40+
pub fn consume(self, on_behalf_of: AztecAddress, inner_hash: Field) -> PublicCall<7, 2, Field> {
41+
let selector = comptime { FunctionSelector::from_signature("consume((Field),Field)") };
42+
PublicCall::new(
43+
self.target_contract,
44+
selector,
45+
"consume",
46+
[on_behalf_of.to_field(), inner_hash],
47+
)
48+
}
49+
50+
pub fn is_consumable(self, on_behalf_of: AztecAddress, message_hash: Field) -> PublicStaticCall<13, 2, bool> {
51+
let selector = comptime { FunctionSelector::from_signature("is_consumable((Field),Field)") };
52+
PublicStaticCall::new(
53+
self.target_contract,
54+
selector,
55+
"is_consumable",
56+
[on_behalf_of.to_field(), message_hash],
57+
)
58+
}
59+
60+
pub fn is_reject_all(self, on_behalf_of: AztecAddress) -> PublicStaticCall<13, 1, bool> {
61+
let selector = comptime { FunctionSelector::from_signature("is_reject_all((Field))") };
62+
PublicStaticCall::new(
63+
self.target_contract,
64+
selector,
65+
"is_reject_all",
66+
[on_behalf_of.to_field()],
67+
)
68+
}
69+
70+
pub fn set_authorized(self, message_hash: Field, authorize: bool) -> PublicCall<14, 2, ()> {
71+
let selector = comptime { FunctionSelector::from_signature("set_authorized(Field,bool)") };
72+
PublicCall::new(
73+
self.target_contract,
74+
selector,
75+
"set_authorized",
76+
[message_hash, authorize as Field],
77+
)
78+
}
79+
80+
pub fn set_reject_all(self, reject: bool) -> PublicCall<14, 1, ()> {
81+
let selector = comptime { FunctionSelector::from_signature("set_reject_all(bool)") };
82+
PublicCall::new(
83+
self.target_contract,
84+
selector,
85+
"set_reject_all",
86+
[reject as Field],
87+
)
88+
}
89+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Authorization.
22

33
pub mod account;
4+
pub mod auth_registry_interface;
45
pub mod authorization_interface;
56
mod authorization_selector;
67
pub use authorization_selector::AuthorizationSelector;

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

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
use crate::{authwit::utils::{compute_inner_authwit_hash, IS_VALID_SELECTOR}, context::{gas::GasOpts, PublicContext}};
2-
use crate::protocol::{abis::function_selector::FunctionSelector, address::AztecAddress, traits::ToField};
3-
use crate::protocol::constants::CANONICAL_AUTH_REGISTRY_ADDRESS as AUTH_REGISTRY_ADDRESS;
1+
use crate::authwit::auth_registry_interface::AuthRegistryInterface;
2+
use crate::authwit::utils::{compute_inner_authwit_hash, IS_VALID_SELECTOR};
3+
use crate::context::PublicContext;
4+
use crate::protocol::{
5+
address::AztecAddress, constants::CANONICAL_AUTH_REGISTRY_ADDRESS as AUTH_REGISTRY_ADDRESS, traits::ToField,
6+
};
47

58
/// Public-flow authwit helpers.
69
///
@@ -86,14 +89,8 @@ pub unconstrained fn assert_inner_hash_valid_authwit_public(
8689
on_behalf_of: AztecAddress,
8790
inner_hash: Field,
8891
) {
89-
let results: [Field] = context.call_public_function(
90-
AUTH_REGISTRY_ADDRESS,
91-
comptime { FunctionSelector::from_signature("consume((Field),Field)") },
92-
[on_behalf_of.to_field(), inner_hash],
93-
GasOpts::default(),
94-
);
95-
assert(results.len() == 1, "Invalid response from registry");
96-
assert(results[0] == IS_VALID_SELECTOR, "Message not authorized by account");
92+
let result = AuthRegistryInterface::at(AUTH_REGISTRY_ADDRESS).consume(on_behalf_of, inner_hash).call(context);
93+
assert(result == IS_VALID_SELECTOR, "Message not authorized by account");
9794
}
9895

9996
/// Helper function to set the authorization status of a message hash
@@ -103,13 +100,7 @@ pub unconstrained fn assert_inner_hash_valid_authwit_public(
103100
/// @param message_hash The hash of the message to authorize @param authorize True if the message should be authorized,
104101
/// false if it should be revoked
105102
pub unconstrained fn set_authorized(context: PublicContext, message_hash: Field, authorize: bool) {
106-
let res = context.call_public_function(
107-
AUTH_REGISTRY_ADDRESS,
108-
comptime { FunctionSelector::from_signature("set_authorized(Field,bool)") },
109-
[message_hash, authorize as Field],
110-
GasOpts::default(),
111-
);
112-
assert(res.len() == 0);
103+
AuthRegistryInterface::at(AUTH_REGISTRY_ADDRESS).set_authorized(message_hash, authorize).call(context);
113104
}
114105

115106
/// Helper function to reject all authwits
@@ -118,11 +109,5 @@ pub unconstrained fn set_authorized(context: PublicContext, message_hash: Field,
118109
///
119110
/// @param reject True if all authwits should be rejected, false otherwise
120111
pub unconstrained fn set_reject_all(context: PublicContext, reject: bool) {
121-
let res = context.call_public_function(
122-
AUTH_REGISTRY_ADDRESS,
123-
comptime { FunctionSelector::from_signature("set_reject_all(bool)") },
124-
[reject as Field],
125-
GasOpts::default(),
126-
);
127-
assert(res.len() == 0);
112+
AuthRegistryInterface::at(AUTH_REGISTRY_ADDRESS).set_reject_all(reject).call(context);
128113
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ pub(crate) mod logging;
4747
pub mod utils;
4848
pub mod authwit;
4949
pub mod public_checks;
50+
pub mod public_checks_interface;
5051
pub mod macros;
5152

5253
pub mod test;

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

Lines changed: 3 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,6 @@
1-
use crate::{
2-
context::{calls::PublicStaticCall, PrivateContext},
3-
protocol::{abis::function_selector::FunctionSelector, address::AztecAddress, constants::PUBLIC_CHECKS_ADDRESS},
4-
};
5-
6-
/// Hand-written interface stub for the `PublicChecks` canonical contract.
7-
///
8-
/// The `PublicChecks` contract exposes two view functions that can be enqueued
9-
/// from private context via `enqueue_view_incognito` to assert timestamp or
10-
/// block-number constraints without revealing the calling contract address.
11-
///
12-
/// This stub exists so that the `public_checks_contract` crate itself does not
13-
/// need to depend on `canonical_addresses` (which would create a structural
14-
/// cycle). Consumer contracts call the helpers below directly via
15-
/// `use aztec::public_checks::*` without taking a dep on the contract crate.
16-
///
17-
/// The selectors are derived with `comptime { FunctionSelector::from_signature(...) }`,
18-
/// which matches exactly what the `#[aztec]` macro generates for the real contract.
19-
struct PublicChecksInterface {
20-
target_contract: AztecAddress,
21-
}
22-
23-
impl PublicChecksInterface {
24-
pub fn at(target_contract: AztecAddress) -> Self {
25-
Self { target_contract }
26-
}
27-
28-
/// Returns a `PublicStaticCall` that asserts `timestamp <op> value`.
29-
pub fn check_timestamp(self, operation: u8, value: u64) -> PublicStaticCall<15, 2, ()> {
30-
let selector = comptime { FunctionSelector::from_signature("check_timestamp(u8,u64)") };
31-
PublicStaticCall::new(
32-
self.target_contract,
33-
selector,
34-
"check_timestamp",
35-
[operation as Field, value as Field],
36-
)
37-
}
38-
39-
/// Returns a `PublicStaticCall` that asserts `block_number <op> value`.
40-
pub fn check_block_number(self, operation: u8, value: u32) -> PublicStaticCall<18, 2, ()> {
41-
let selector = comptime { FunctionSelector::from_signature("check_block_number(u8,u32)") };
42-
PublicStaticCall::new(
43-
self.target_contract,
44-
selector,
45-
"check_block_number",
46-
[operation as Field, value as Field],
47-
)
48-
}
49-
}
1+
use crate::context::PrivateContext;
2+
use crate::protocol::constants::PUBLIC_CHECKS_ADDRESS;
3+
use crate::public_checks_interface::PublicChecksInterface;
504

515
// docs:start:helper_public_checks_functions
526
/// Asserts that the current timestamp in the enqueued public call enqueued by `check_timestamp` satisfies
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// GENERATED FILE - DO NOT EDIT
2+
//
3+
// Written by `yarn-project/protocol-contracts/src/scripts/generate_interfaces.ts` from the compiled
4+
// `PublicChecks` artifact. Regenerate with
5+
// `yarn workspace @aztec/protocol-contracts run regen:canonical-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+
13+
use crate::context::calls::PublicStaticCall;
14+
use crate::protocol::{abis::function_selector::FunctionSelector, address::AztecAddress};
15+
16+
pub struct PublicChecksInterface {
17+
pub target_contract: AztecAddress,
18+
}
19+
20+
impl PublicChecksInterface {
21+
pub fn at(target_contract: AztecAddress) -> Self {
22+
Self { target_contract }
23+
}
24+
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)") };
27+
PublicStaticCall::new(
28+
self.target_contract,
29+
selector,
30+
"check_block_number",
31+
[operation as Field, value as Field],
32+
)
33+
}
34+
35+
pub fn check_timestamp(self, operation: u8, value: u64) -> PublicStaticCall<15, 2, ()> {
36+
let selector = comptime { FunctionSelector::from_signature("check_timestamp(u8,u64)") };
37+
PublicStaticCall::new(
38+
self.target_contract,
39+
selector,
40+
"check_timestamp",
41+
[operation as Field, value as Field],
42+
)
43+
}
44+
}

yarn-project/protocol-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:canonical-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: 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+
'canonical interface stubs are stale; run `yarn workspace @aztec/protocol-contracts run regen:canonical-interfaces` and commit the result.';
9+
10+
describe('canonical 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)