Skip to content

Commit db2b240

Browse files
authored
test(txe): extend oracle roundtrip coverage to more scalar oracles (#24550)
1 parent c4af198 commit db2b240

10 files changed

Lines changed: 81 additions & 25 deletions

File tree

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

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
//! produces two tests: `some+some` and `none+none`. Each scenario becomes its own `#[test]`, suffixed by index
5656
//! (`__0`, `__1`, …).
5757

58+
use crate::messages::delivery::OnchainDeliveryMode;
59+
use crate::oracle::get_contract_instance::GetContractInstanceResult;
5860
use crate::protocol::{
5961
abis::function_selector::FunctionSelector,
6062
address::{AztecAddress, EthAddress},
@@ -143,6 +145,26 @@ impl OracleTestValue<1> for EthAddress {
143145
[Scenario::unnamed(EthAddress::from_field(seed as Field))]
144146
}
145147
}
148+
impl OracleTestValue<1> for OnchainDeliveryMode {
149+
fn oracle_test_value(seed: u32) -> [Scenario<Self>; 1] {
150+
// The seed alternates between the only two valid modes
151+
let mode = if (seed % 2) == 0 {
152+
OnchainDeliveryMode::onchain_unconstrained()
153+
} else {
154+
OnchainDeliveryMode::onchain_constrained()
155+
};
156+
[Scenario::unnamed(mode)]
157+
}
158+
}
159+
impl OracleTestValue<1> for GetContractInstanceResult {
160+
fn oracle_test_value(seed: u32) -> [Scenario<Self>; 1] {
161+
[
162+
Scenario::unnamed(
163+
GetContractInstanceResult { exists: (seed % 2) != 0, member: (seed + 1) as Field },
164+
),
165+
]
166+
}
167+
}
146168
impl<T> OracleTestValue<2> for Option<T>
147169
where
148170
T: OracleTestValue<1>,
@@ -390,8 +412,9 @@ pub(crate) unconstrained fn add_scenario_to_oracle_test(_name: BoundedVec<u8, MA
390412
global TEST_COLLECTION_LEN: u32 = 3;
391413
global TEST_BOUNDED_VEC_CAP: u32 = 5;
392414

393-
/// Rewrites `typ`, replacing every array length with [`TEST_COLLECTION_LEN`] and every bounded-vec capacity with
394-
/// [`TEST_BOUNDED_VEC_CAP`] (recursing through `Option` inners). Returns it as a `Quoted` type to splice into the test.
415+
/// Rewrites `typ`, replacing every generic array length with [`TEST_COLLECTION_LEN`] and every bounded-vec capacity
416+
/// with [`TEST_BOUNDED_VEC_CAP`] (recursing through `Option` inners). Returns it as a `Quoted` type to splice into the
417+
/// test.
395418
///
396419
/// This exists because the annotated oracles are *generic* over their lengths (e.g.
397420
/// `get_hash_preimage_oracle<let N: u32>(...) -> [Field; N]`), but the generated test is not generic, so it has no `N`
@@ -401,9 +424,14 @@ global TEST_BOUNDED_VEC_CAP: u32 = 5;
401424
comptime fn pinned_type(typ: Type) -> Quoted {
402425
let array = typ.as_array();
403426
if array.is_some() {
404-
let (element_type, _generic_length) = array.unwrap();
427+
let (element_type, length_type) = array.unwrap();
405428
let pinned_element = pinned_type(element_type);
406-
let len = f"{TEST_COLLECTION_LEN}".quoted_contents();
429+
// A concrete length (e.g. `[T; 1]`) is part of the oracle's signature and must be kept; only a generic
430+
// length has no value to instantiate and gets pinned.
431+
let len = length_type
432+
.as_constant()
433+
.map(|concrete_length| f"{concrete_length}".quoted_contents())
434+
.unwrap_or_else(|| f"{TEST_COLLECTION_LEN}".quoted_contents());
407435
quote { [$pinned_element; $len] }
408436
} else if is_data_type_named(typ, quote { BoundedVec }) {
409437
let element_type = typ.as_data_type().unwrap().1[0];

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ pub fn get_contract_instance(address: AztecAddress) -> ContractInstance {
2121
instance
2222
}
2323

24-
struct GetContractInstanceResult {
25-
exists: bool,
26-
member: Field,
24+
#[derive(Eq)]
25+
pub(crate) struct GetContractInstanceResult {
26+
pub(crate) exists: bool,
27+
pub(crate) member: Field,
2728
}
2829

2930
// These oracles each return a ContractInstance member plus a boolean indicating whether the instance was found.

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ pub(crate) mod ephemeral_oracles;
3737
pub(crate) mod transient_oracles;
3838
#[generate_oracle_tests]
3939
pub mod contract_sync;
40+
#[generate_oracle_tests_excluding(
41+
@[
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
45+
],
46+
)]
4047
pub mod fact_store;
4148
#[generate_oracle_tests]
4249
pub mod public_call;
@@ -54,6 +61,11 @@ pub mod tx_phase;
5461
pub mod execution;
5562
#[generate_oracle_tests]
5663
pub mod execution_cache;
64+
#[generate_oracle_tests_excluding(
65+
@[
66+
quote { get_contract_instance_oracle }, // TODO: implement once we support more complex types
67+
],
68+
)]
5769
pub mod get_contract_instance;
5870
pub mod get_l1_to_l2_membership_witness;
5971
pub mod get_nullifier_membership_witness;
@@ -66,7 +78,6 @@ pub mod message_processing;
6678
#[generate_oracle_tests_excluding(
6779
@[
6880
quote { get_notes_oracle }, // TODO: implement once we support more complex types
69-
quote { get_next_tagging_index_oracle }, // TODO: implement once we support more complex types
7081
],
7182
)]
7283
pub mod notes;

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export {
2121
ETH_ADDRESS,
2222
EVENT_VALIDATION_REQUEST,
2323
FIELD,
24+
FIXED_ARRAY,
2425
FUNCTION_SELECTOR,
2526
LOG_RETRIEVAL_REQUEST,
2627
LOG_RETRIEVAL_RESPONSE,
@@ -33,6 +34,7 @@ export {
3334
POINT,
3435
PROVIDED_SECRET,
3536
STR,
37+
STRUCT,
3638
U32,
3739
type InputSlot,
3840
type MaybePromise,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,7 @@ export function ARRAY<T>(inner: TypeMapping<T>): TypeMapping<T[]> & { kind: 'arr
631631
* zero-padded to exactly `maxLength * elementWidth` fields, and deserializes all `maxLength` elements back. An absent
632632
* element is the zero encoding, so the padding is derived from the shape.
633633
*/
634-
function FIXED_ARRAY<T>(element: TypeMapping<T>, maxLength: number): TypeMapping<T[]> {
634+
export function FIXED_ARRAY<T>(element: TypeMapping<T>, maxLength: number): TypeMapping<T[]> {
635635
const elementWidth = fieldWidth(element.shape);
636636
return {
637637
serialization: element.serialization

yarn-project/txe/src/oracle/interfaces.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ export interface IAvmExecutionOracle {
3939
nullifierExists(siloedNullifier: Fr): Promise<boolean>;
4040
storageWrite(slot: Fr, value: Fr): Promise<void>;
4141
storageRead(slot: Fr, contractAddress: AztecAddress): Promise<Fr>;
42-
getContractInstanceDeployer(address: AztecAddress): Promise<{ member: Fr; exists: boolean }>;
43-
getContractInstanceClassId(address: AztecAddress): Promise<{ member: Fr; exists: boolean }>;
44-
getContractInstanceInitializationHash(address: AztecAddress): Promise<{ member: Fr; exists: boolean }>;
45-
getContractInstanceImmutablesHash(address: AztecAddress): Promise<{ member: Fr; exists: boolean }>;
42+
getContractInstanceDeployer(address: AztecAddress): Promise<{ member: Fr; exists: boolean }[]>;
43+
getContractInstanceClassId(address: AztecAddress): Promise<{ member: Fr; exists: boolean }[]>;
44+
getContractInstanceInitializationHash(address: AztecAddress): Promise<{ member: Fr; exists: boolean }[]>;
45+
getContractInstanceImmutablesHash(address: AztecAddress): Promise<{ member: Fr; exists: boolean }[]>;
4646
returndataSize(): Promise<number>;
4747
returndataCopy(rdOffset: number, copySize: number): Promise<Fr[]>;
4848
call(l2Gas: number, daGas: number, address: AztecAddress, argsLength: number, args: Fr[]): Promise<void>;

yarn-project/txe/src/oracle/test-resolver/default_fixtures.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
BOOL,
1111
BYTE,
1212
BoundedVec,
13+
DELIVERY_MODE,
1314
ETH_ADDRESS,
1415
FIELD,
1516
FUNCTION_SELECTOR,
@@ -21,7 +22,9 @@ import {
2122
} from '@aztec/pxe/simulator';
2223
import { FunctionSelector, NoteSelector } from '@aztec/stdlib/abi';
2324
import { BlockHash } from '@aztec/stdlib/block';
25+
import { AppTaggingSecretKind } from '@aztec/stdlib/logs';
2426

27+
import { CONTRACT_INSTANCE_MEMBER } from '../txe_oracle_registry.js';
2528
import type { OracleTestScenario } from './resolver.js';
2629

2730
/**
@@ -60,6 +63,11 @@ const TEST_VALUE_IMPLS: TestValueImpl[] = [
6063
scalar(FUNCTION_SELECTOR, seed => FunctionSelector.fromField(new Fr(seed))),
6164
scalar(NOTE_SELECTOR, seed => NoteSelector.fromField(new Fr(seed))),
6265
scalar(BLOCK_HASH, seed => new BlockHash(new Fr(seed))),
66+
// Only two delivery modes are valid for tagging, so the seed alternates between them, matching the Noir impl.
67+
scalar(DELIVERY_MODE, seed =>
68+
seed % 2 === 0 ? AppTaggingSecretKind.UNCONSTRAINED : AppTaggingSecretKind.CONSTRAINED,
69+
),
70+
scalar(CONTRACT_INSTANCE_MEMBER, seed => [{ exists: seed % 2 !== 0, member: new Fr(seed + 1) }]),
6371
composite(isOption, (type, seed) => [
6472
named(Option.some(firstValue(type.inner, seed)), 'some'),
6573
named(Option.none(firstValue(type.inner, seed)), 'none'),

yarn-project/txe/src/oracle/txe_oracle_public_context.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -125,32 +125,33 @@ export class TXEOraclePublicContext implements IAvmExecutionOracle {
125125
return value;
126126
}
127127

128-
getContractInstanceDeployer(address: AztecAddress): Promise<{ member: Fr; exists: boolean }> {
128+
getContractInstanceDeployer(address: AztecAddress): Promise<{ member: Fr; exists: boolean }[]> {
129129
return this.getContractInstanceMember(address, i => i.deployer.toField());
130130
}
131131

132-
getContractInstanceClassId(address: AztecAddress): Promise<{ member: Fr; exists: boolean }> {
132+
getContractInstanceClassId(address: AztecAddress): Promise<{ member: Fr; exists: boolean }[]> {
133133
// TXE has no contract updates, so the current class always equals the original.
134134
return this.getContractInstanceMember(address, i => i.originalContractClassId);
135135
}
136136

137-
getContractInstanceInitializationHash(address: AztecAddress): Promise<{ member: Fr; exists: boolean }> {
137+
getContractInstanceInitializationHash(address: AztecAddress): Promise<{ member: Fr; exists: boolean }[]> {
138138
return this.getContractInstanceMember(address, i => i.initializationHash);
139139
}
140140

141-
getContractInstanceImmutablesHash(address: AztecAddress): Promise<{ member: Fr; exists: boolean }> {
141+
getContractInstanceImmutablesHash(address: AztecAddress): Promise<{ member: Fr; exists: boolean }[]> {
142142
return this.getContractInstanceMember(address, i => i.immutablesHash);
143143
}
144144

145+
// The one-element array mirrors the oracles' Noir return type, `[GetContractInstanceResult; 1]`.
145146
private async getContractInstanceMember(
146147
address: AztecAddress,
147148
accessor: (instance: ContractInstancePreimageWithAddress) => Fr,
148-
): Promise<{ member: Fr; exists: boolean }> {
149+
): Promise<{ member: Fr; exists: boolean }[]> {
149150
const instance = await this.contractStore.getContractInstance(address);
150151
if (!instance) {
151-
return { member: Fr.ZERO, exists: false };
152+
return [{ member: Fr.ZERO, exists: false }];
152153
}
153-
return { member: accessor(instance), exists: true };
154+
return [{ member: accessor(instance), exists: true }];
154155
}
155156

156157
returndataSize(): Promise<number> {

yarn-project/txe/src/oracle/txe_oracle_registry.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
BOOL,
2020
ETH_ADDRESS,
2121
FIELD,
22+
FIXED_ARRAY,
2223
FUNCTION_SELECTOR,
2324
type InputSlot,
2425
type MaybePromise,
@@ -28,6 +29,7 @@ import {
2829
type OutputSlot,
2930
type ParamTypes,
3031
STR,
32+
STRUCT,
3133
type SlotShape,
3234
type TypeMapping,
3335
U32,
@@ -193,10 +195,13 @@ const TXE_CALL_CONTEXT: TypeMapping<{ txHash: Fr; anchorBlockTimestamp: bigint }
193195
shape: ['scalar', 'scalar', 'scalar'], // discriminant, txHash, anchor block timestamp
194196
};
195197

196-
const CONTRACT_INSTANCE_MEMBER: TypeMapping<{ member: Fr; exists: boolean }> = {
197-
serialization: { fn: ({ member, exists }) => [member, new Fr(exists)] },
198-
shape: ['scalar', 'scalar'],
199-
};
198+
export const CONTRACT_INSTANCE_MEMBER: TypeMapping<{ exists: boolean; member: Fr }[]> = FIXED_ARRAY(
199+
STRUCT([
200+
{ name: 'exists', type: BOOL },
201+
{ name: 'member', type: FIELD },
202+
]),
203+
1,
204+
);
200205

201206
const EVENT_SELECTOR: TypeMapping<EventSelector> = {
202207
serialization: { fn: v => [v.toField()] },

yarn-project/txe/src/oracle/txe_oracle_version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ export const TXE_ORACLE_VERSION_MINOR = 2;
1414
* - TXE_ORACLE_VERSION_MAJOR (and reset MINOR to 0) for breaking changes, or
1515
* - TXE_ORACLE_VERSION_MINOR for additive changes (new oracle method added).
1616
*/
17-
export const TXE_ORACLE_INTERFACE_HASH = 'f6694961673ada551f57f5c09fa04cd566b175e63b7b965343bd4cf02b3cbdf6';
17+
export const TXE_ORACLE_INTERFACE_HASH = '4ed3618087fafb4aa63c6580996a69bf6bc257844035a2019692586b5b8daf34';

0 commit comments

Comments
 (0)