Skip to content

Commit 6a53d40

Browse files
authored
test(txe): struct support in oracle serialization roundtrip tests (#24588)
1 parent a1ec88f commit 6a53d40

9 files changed

Lines changed: 582 additions & 234 deletions

File tree

noir-projects/aztec-nr/aztec/src/context/utility_context.nr

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::oracle::{execution::get_utility_context, storage::storage_read};
1+
use crate::oracle::{execution::{get_utility_context, UtilityContextData}, storage::storage_read};
22
use crate::protocol::{
33
abis::block_header::BlockHeader, address::AztecAddress, constants::NULL_MSG_SENDER_CONTRACT_ADDRESS,
44
traits::Packable,
@@ -11,6 +11,12 @@ pub struct UtilityContext {
1111
msg_sender: AztecAddress,
1212
}
1313

14+
impl From<UtilityContextData> for UtilityContext {
15+
fn from(data: UtilityContextData) -> Self {
16+
Self { block_header: data.block_header, contract_address: data.contract_address, msg_sender: data.msg_sender }
17+
}
18+
}
19+
1420
impl UtilityContext {
1521
pub unconstrained fn new() -> Self {
1622
get_utility_context()

noir-projects/aztec-nr/aztec/src/macros/oracle_testing.nr

Lines changed: 373 additions & 191 deletions
Large diffs are not rendered by default.
Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
use crate::context::UtilityContext;
2+
use crate::protocol::{abis::block_header::BlockHeader, address::AztecAddress};
3+
4+
/// Wire shape of [`get_utility_context_oracle`]'s response. [`UtilityContext`] is built from it rather than returned
5+
/// directly so its fields stay private to its module.
6+
#[derive(Eq)]
7+
pub(crate) struct UtilityContextData {
8+
pub(crate) block_header: BlockHeader,
9+
pub(crate) contract_address: AztecAddress,
10+
pub(crate) msg_sender: AztecAddress,
11+
}
212

313
#[oracle(aztec_utl_getUtilityContext)]
4-
unconstrained fn get_utility_context_oracle() -> UtilityContext {}
14+
unconstrained fn get_utility_context_oracle() -> UtilityContextData {}
515

616
/// Returns a utility context built from the global variables of anchor block and the contract address of the function
717
/// being executed.
818
pub unconstrained fn get_utility_context() -> UtilityContext {
9-
get_utility_context_oracle()
19+
UtilityContext::from(get_utility_context_oracle())
1020
}

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

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,25 @@ use crate::macros::oracle_testing::{generate_oracle_tests, generate_oracle_tests
1616
quote { calldata_copy_opcode },
1717
quote { return_opcode },
1818
quote { revert_opcode },
19-
// TODO: implement once we support more complex types
19+
// TODO: implement once we support slices
2020
quote { emit_public_log_opcode },
2121
quote { returndata_copy_opcode },
2222
],
2323
)]
2424
pub mod avm;
25+
#[generate_oracle_tests_excluding(
26+
@[
27+
// TODO: cover once the iv/sym_key BUFFER mapping is expressible as a plain fixed array of bytes, so the
28+
// fixture synthesizer does not need to special-case it.
29+
quote { aes128_decrypt_oracle },
30+
],
31+
)]
2532
pub mod aes128_decrypt;
2633
#[generate_oracle_tests]
2734
pub mod auth_witness;
35+
#[generate_oracle_tests]
2836
pub mod block_header;
37+
#[generate_oracle_tests]
2938
pub mod call_private_function;
3039
#[generate_oracle_tests]
3140
pub mod call_utility_function;
@@ -39,45 +48,71 @@ pub(crate) mod transient_oracles;
3948
pub mod contract_sync;
4049
#[generate_oracle_tests_excluding(
4150
@[
42-
quote { record_fact_oracle }, // TODO: implement once we support more complex types
43-
quote { get_fact_collection_oracle }, // TODO: implement once we support more complex types
44-
quote { get_fact_collections_by_type_oracle }, // TODO: implement once we support more complex types
51+
// TODO: implement once the test resolver can serve EphemeralArray contents.
52+
quote { record_fact_oracle },
53+
quote { get_fact_collection_oracle },
54+
quote { get_fact_collections_by_type_oracle },
4555
],
4656
)]
4757
pub mod fact_store;
4858
#[generate_oracle_tests]
4959
pub mod public_call;
60+
// TODO: cover with oracle tests once the test resolver can serve EphemeralArray contents.
5061
pub mod tx_resolution;
5162
#[generate_oracle_tests]
5263
pub mod resolve_custom_request;
5364
#[generate_oracle_tests_excluding(
5465
@[
55-
quote { resolve_tagging_strategy_oracle }, // TODO: implement once we support more complex types
66+
// TODO: needs a hand-written multi-scenario synthesizer entry (enum-like type with private fields,
67+
// one named scenario per constructor) on both sides.
68+
quote { resolve_tagging_strategy_oracle },
5669
],
5770
)]
5871
pub mod resolve_tagging_strategy;
5972
#[generate_oracle_tests]
6073
pub mod tx_phase;
74+
#[generate_oracle_tests]
6175
pub mod execution;
6276
#[generate_oracle_tests]
6377
pub mod execution_cache;
78+
#[generate_oracle_tests]
79+
pub mod get_contract_instance;
6480
#[generate_oracle_tests_excluding(
6581
@[
66-
quote { get_contract_instance_oracle }, // TODO: implement once we support more complex types
82+
// TODO: cover once the sibling-path mapping is expressible as a plain fixed array
83+
quote { get_l1_to_l2_membership_witness_oracle },
6784
],
6885
)]
69-
pub mod get_contract_instance;
7086
pub mod get_l1_to_l2_membership_witness;
87+
#[generate_oracle_tests_excluding(
88+
@[
89+
// TODO: cover once the sibling-path mapping is expressible as a plain fixed array
90+
quote { get_low_nullifier_membership_witness_oracle },
91+
quote { get_nullifier_membership_witness_oracle },
92+
],
93+
)]
7194
pub mod get_nullifier_membership_witness;
95+
#[generate_oracle_tests_excluding(
96+
@[
97+
// TODO: cover once the sibling-path mapping is expressible as a plain fixed array
98+
quote { get_public_data_witness_oracle },
99+
],
100+
)]
72101
pub mod get_public_data_witness;
102+
#[generate_oracle_tests]
73103
pub mod get_membership_witness;
104+
#[generate_oracle_tests]
74105
pub mod keys;
106+
#[generate_oracle_tests]
75107
pub mod key_validation_request;
108+
// TODO: cover with oracle tests once the two sides agree on the param grouping (TS wraps them in a single struct).
76109
pub mod logs;
110+
// TODO: cover with oracle tests once the test resolver can serve EphemeralArray contents.
77111
pub mod message_processing;
78112
#[generate_oracle_tests_excluding(
79113
@[
80-
quote { get_notes_oracle }, // TODO: implement once we support more complex types
114+
// TODO: implement once the test resolver can serve EphemeralArray contents.
115+
quote { get_notes_oracle },
81116
],
82117
)]
83118
pub mod notes;
@@ -89,6 +124,7 @@ pub mod offchain_effect;
89124
pub mod version;
90125
#[generate_oracle_tests]
91126
pub mod random;
127+
// TODO: cover with oracle tests once the test resolver can serve EphemeralArray contents.
92128
pub mod shared_secret;
93129
#[generate_oracle_tests]
94130
pub mod storage;

yarn-project/pxe/src/contract_function_simulator/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,16 @@ export {
3232
PENDING_TAGGED_LOG,
3333
POINT,
3434
PROVIDED_SECRET,
35+
SLOT_NUMBER,
3536
STR,
3637
STRUCT,
3738
U32,
39+
tryFieldWidth,
3840
type InputSlot,
3941
type MaybePromise,
4042
type OutputSlot,
4143
type SlotShape,
44+
type StructField,
4245
type TypeMapping,
4346
} from './oracle/oracle_type_mappings.js';
4447
export { ExecutionNoteCache } from './execution_note_cache.js';

yarn-project/pxe/src/contract_function_simulator/oracle/oracle_type_mappings.ts

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ export const ETH_ADDRESS: TypeMapping<EthAddress> = {
292292
shape: ['scalar'],
293293
};
294294

295-
const SLOT_NUMBER: TypeMapping<SlotNumber> = {
295+
export const SLOT_NUMBER: TypeMapping<SlotNumber> = {
296296
serialization: { fn: v => [new Fr(v)] },
297297
shape: ['scalar'],
298298
};
@@ -619,20 +619,26 @@ export function ARRAY<T>(inner: TypeMapping<T>): TypeMapping<T[]> & { kind: 'arr
619619
}
620620

621621
/**
622-
* Noir's fixed-length array `[T; maxLength]` in one slot: serializes `values` (each flattened via `element`)
623-
* zero-padded to exactly `maxLength * elementWidth` fields, and deserializes all `maxLength` elements back. An absent
622+
* Noir's fixed-length array `[T; length]` in one slot: serializes `values` (each flattened via `element`)
623+
* zero-padded to exactly `length * elementWidth` fields, and deserializes all `length` elements back. An absent
624624
* element is the zero encoding, so the padding is derived from the shape.
625625
*/
626-
export function FIXED_ARRAY<T>(element: TypeMapping<T>, maxLength: number): TypeMapping<T[]> {
626+
export function FIXED_ARRAY<T>(
627+
element: TypeMapping<T>,
628+
length: number,
629+
): TypeMapping<T[]> & { kind: 'fixed-array'; inner: TypeMapping<T>; length: number } {
627630
const elementWidth = fieldWidth(element.shape);
628631
return {
632+
kind: 'fixed-array',
633+
inner: element,
634+
length,
629635
serialization: element.serialization
630-
? { fn: values => [padArrayEnd(packElements(element, values), Fr.ZERO, maxLength * elementWidth)] }
636+
? { fn: values => [padArrayEnd(packElements(element, values), Fr.ZERO, length * elementWidth)] }
631637
: undefined,
632638
deserialization: element.deserialization
633-
? { fn: ([reader]) => unpackElements(element, reader, maxLength) }
639+
? { fn: ([reader]) => unpackElements(element, reader, length) }
634640
: undefined,
635-
shape: [{ len: maxLength * elementWidth }],
641+
shape: [{ len: length * elementWidth }],
636642
};
637643
}
638644

@@ -692,9 +698,15 @@ export function BOUNDED_VEC<T>(
692698
* (zero-padded) followed by the actual length, with no length prefix, so the width is statically known. Serialize-only.
693699
* Throws if the input exceeds `maxLength`.
694700
*/
695-
export function FIXED_BOUNDED_VEC<T>(element: TypeMapping<T>, maxLength: number): TypeMapping<T[]> {
701+
export function FIXED_BOUNDED_VEC<T>(
702+
element: TypeMapping<T>,
703+
maxLength: number,
704+
): TypeMapping<T[]> & { kind: 'fixed-bounded-vec'; inner: TypeMapping<T>; maxLength: number } {
696705
const width = fieldWidth(element.shape);
697706
return {
707+
kind: 'fixed-bounded-vec',
708+
inner: element,
709+
maxLength,
698710
serialization: element.serialization
699711
? {
700712
fn: values => {
@@ -805,15 +817,19 @@ export function EPHEMERAL_ARRAY<T>(element: TypeMapping<T>): TypeMapping<Ephemer
805817
}
806818

807819
/** A named field within a {@link STRUCT}: it owns however many wire slots its {@link TypeMapping} declares. */
808-
type StructField<TName extends string = string, T = any> = { name: TName; type: TypeMapping<T> };
820+
export type StructField<TName extends string = string, T = any> = { name: TName; type: TypeMapping<T> };
809821

810822
/**
811823
* A Noir struct: its `shape` and (de)serialization are the concatenation of its fields', so callers never hand-write a
812824
* `shape`. `T` is the struct's TS value type and must match the field layout — serialization reads each field by name
813825
* off the value, deserialization returns the decoded bag as `T`; convert in the handler, not here, when `T` differs.
814826
*/
815-
export function STRUCT<T>(fields: readonly StructField[]): TypeMapping<T> {
827+
export function STRUCT<T>(
828+
fields: readonly StructField[],
829+
): TypeMapping<T> & { kind: 'struct'; fields: readonly StructField[] } {
816830
return {
831+
kind: 'struct',
832+
fields,
817833
serialization: fields.every(f => f.type.serialization)
818834
? {
819835
fn: (value: T) => {
@@ -846,7 +862,7 @@ export function slotsOf(mapping: TypeMapping): number {
846862
}
847863

848864
/** Number of fields a fully-static shape occupies, or `undefined` if any slot is variable-width. */
849-
function tryFieldWidth(shape: SlotShape[]): number | undefined {
865+
export function tryFieldWidth(shape: SlotShape[]): number | undefined {
850866
let total = 0;
851867
for (const slot of shape) {
852868
if (slot === 'scalar') {

0 commit comments

Comments
 (0)