diff --git a/noir-projects/aztec-nr/aztec/src/note/note_getter.nr b/noir-projects/aztec-nr/aztec/src/note/note_getter.nr index 42c310e82199..aa6dab2374f2 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_getter.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_getter.nr @@ -8,7 +8,7 @@ use crate::note::{ utils::compute_note_hash_for_read_request, }; use crate::oracle; -use crate::utils::comparison::compare; +use crate::utils::{array, comparison::compare}; use dep::protocol_types::{ constants::{GET_NOTES_ORACLE_RETURN_LENGTH, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL}, traits::{Packable, ToField}, @@ -99,6 +99,14 @@ where (retrieved_note, note_hash_for_read_request) } +/// Returns a BoundedVec of notes that have been proven to have been created by this contract, either in the current or +/// past transactions (i.e. pending or settled notes). A second BoundedVec contains the note hashes used for the read +/// requests, which can save constraints when computing the note's nullifiers. +/// +/// WARNING: recall that notes are never destroyed! Note existence therefore does not imply that the note is _current_ +/// or _valid_ - this typically requires also emitting the note's nullifier to prove that it had not been emitted +/// before. Because of this, calling this function directly from end-user applications should be discouraged, and safe +/// abstractions such as aztec-nr's state variables should be used instead. pub fn get_notes( context: &mut PrivateContext, storage_slot: Field, @@ -140,7 +148,7 @@ where let filter_args = options.filter_args; let filtered_notes = filter_fn(opt_notes, filter_args); - let notes = crate::utils::array::collapse(filtered_notes); + let notes = array::collapse(filtered_notes); let mut note_hashes = BoundedVec::new(); // We have now collapsed the sparse array of Options into a BoundedVec. This is a more ergonomic type and also @@ -239,18 +247,22 @@ where apply_preprocessor(opt_notes, options.preprocessor, options.preprocessor_args) } +/// Unconstrained variant of `get_notes`, meant to be used in unconstrained execution contexts. Notably only the note +/// content is returned, and not any of the information used when proving its existence (e.g. nonce, note hash, etc.). pub unconstrained fn view_notes( storage_slot: Field, options: NoteViewerOptions, ) -> BoundedVec where - Note: NoteType + Packable, + Note: NoteType + Packable + Eq, { let (num_selects, select_by_indexes, select_by_offsets, select_by_lengths, select_values, select_comparators, sort_by_indexes, sort_by_offsets, sort_by_lengths, sort_order) = flatten_options(options.selects, options.sorts); let placeholder_fields = [0; VIEW_NOTE_ORACLE_RETURN_LENGTH]; - let notes_array: [_; MAX_NOTES_PER_PAGE] = oracle::notes::get_notes( + // We fetch the notes from the same oracle we use in the constrained case, except we don't bother inspecting the + // metadata in order to prove existence. + let opt_notes = oracle::notes::get_notes( storage_slot, num_selects, select_by_indexes, @@ -268,15 +280,12 @@ where placeholder_fields, ); - let mut notes = BoundedVec::new(); - for i in 0..notes_array.len() { - if notes_array[i].is_some() { - // We're not constraining note existence so we just drop the metadata - notes.push(notes_array[i].unwrap_unchecked().note); - } - } - - notes + // Even though we don't expect for the opt_notes array to be sparse, collapse is still useful in this case to + // convert it into a BoundedVec. + array::collapse(opt_notes).map( + // view_notes just returns the actual note, so we drop the metadata + |retrieved_note| retrieved_note.note, + ) } unconstrained fn flatten_options( diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr index da9da820e683..9365d530aa84 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr @@ -82,7 +82,10 @@ impl PrivateImmutable { // docs:end:get_note } -impl PrivateImmutable { +impl PrivateImmutable +where + Note: NoteType + NoteHash + Packable + Eq, +{ // docs:start:is_initialized pub unconstrained fn is_initialized(self) -> bool { let nullifier = self.compute_initialization_nullifier(); @@ -92,10 +95,7 @@ impl PrivateImmutable { // view_note does not actually use the context, but it calls oracles that are only available in private // docs:start:view_note - pub unconstrained fn view_note(self) -> Note - where - Note: NoteType + NoteHash + Packable, - { + pub unconstrained fn view_note(self) -> Note { let mut options = NoteViewerOptions::new(); view_notes(self.storage_slot, options.set_limit(1)).get(0) } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr index 01a2d40aea7b..e4cab365aa4c 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr @@ -127,7 +127,7 @@ where impl PrivateMutable where - Note: NoteType + NoteHash + Packable, + Note: NoteType + NoteHash + Packable + Eq, { pub unconstrained fn is_initialized(self) -> bool { let nullifier = self.compute_initialization_nullifier(); diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr index 7188f4a7b6fe..7492ca8e6859 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr @@ -97,7 +97,7 @@ where impl PrivateSet where - Note: NoteType + NoteHash + Packable, + Note: NoteType + NoteHash + Packable + Eq, { // docs:start:view_notes pub unconstrained fn view_notes( diff --git a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr index a180f621a52a..ea968f7c9233 100644 --- a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr +++ b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr @@ -3,6 +3,7 @@ use dep::aztec::{ }; #[note] +#[derive(Eq)] pub struct SubscriptionNote { owner: AztecAddress, expiry_block_number: Field, diff --git a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr index c954d3dcb601..86329f61d15b 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr @@ -3,6 +3,7 @@ use aztec::{macros::notes::note, protocol_types::address::AztecAddress}; // Stores a public key composed of two fields // TODO: Do we need to include a nonce, in case we want to read/nullify/recreate with the same pubkey value? #[note] +#[derive(Eq)] pub struct PublicKeyNote { x: Field, y: Field, diff --git a/noir-projects/noir-contracts/contracts/spam_contract/src/types/balance_set.nr b/noir-projects/noir-contracts/contracts/spam_contract/src/types/balance_set.nr index b21d48459c90..4a51babd40eb 100644 --- a/noir-projects/noir-contracts/contracts/spam_contract/src/types/balance_set.nr +++ b/noir-projects/noir-contracts/contracts/spam_contract/src/types/balance_set.nr @@ -23,18 +23,15 @@ impl BalanceSet { } } -impl BalanceSet { - pub unconstrained fn balance_of(self: Self) -> u128 - where - Note: NoteType + NoteHash + OwnedNote + Packable, - { +impl BalanceSet +where + Note: NoteType + NoteHash + OwnedNote + Packable + Eq, +{ + pub unconstrained fn balance_of(self: Self) -> u128 { self.balance_of_with_offset(0) } - pub unconstrained fn balance_of_with_offset(self: Self, offset: u32) -> u128 - where - Note: NoteType + NoteHash + OwnedNote + Packable, - { + pub unconstrained fn balance_of_with_offset(self: Self, offset: u32) -> u128 { let mut balance = 0 as u128; // docs:start:view_notes let mut options = NoteViewerOptions::new(); diff --git a/noir-projects/noir-contracts/contracts/test_contract/src/test_note.nr b/noir-projects/noir-contracts/contracts/test_contract/src/test_note.nr index b1a6e3bd6be0..8bc743c6bdfc 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/src/test_note.nr +++ b/noir-projects/noir-contracts/contracts/test_contract/src/test_note.nr @@ -10,6 +10,7 @@ use dep::aztec::{ /// A note used only for testing purposes. #[custom_note] +#[derive(Eq)] pub struct TestNote { value: Field, } diff --git a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/balances_map.nr b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/balances_map.nr index 0f3dd13738da..0d4c3e7db652 100644 --- a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/balances_map.nr +++ b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/balances_map.nr @@ -28,11 +28,11 @@ impl BalancesMap { } } -impl BalancesMap { - pub unconstrained fn balance_of(self: Self, owner: AztecAddress) -> u128 - where - Note: NoteType + NoteHash + OwnedNote + Packable, - { +impl BalancesMap +where + Note: NoteType + NoteHash + OwnedNote + Packable + Eq, +{ + pub unconstrained fn balance_of(self: Self, owner: AztecAddress) -> u128 { self.balance_of_with_offset(owner, 0) } @@ -40,10 +40,7 @@ impl BalancesMap { self: Self, owner: AztecAddress, offset: u32, - ) -> u128 - where - Note: NoteType + NoteHash + OwnedNote + Packable, - { + ) -> u128 { let mut balance = 0 as u128; // docs:start:view_notes let mut options = NoteViewerOptions::new();