diff --git a/docs/docs/migration_notes.md b/docs/docs/migration_notes.md index 816bd834e43..6b12658a854 100644 --- a/docs/docs/migration_notes.md +++ b/docs/docs/migration_notes.md @@ -19,6 +19,42 @@ The name of the canonical Gas contract has changed to Fee Juice. Update noir cod Additionally, `NativePaymentMethod` and `NativePaymentMethodWithClaim` have been renamed to `FeeJuicePaymentMethod` and `FeeJuicePaymentMethodWithClaim`. +### PrivateSet::pop_notes(...) + +The most common flow when working with notes is obtaining them from a `PrivateSet` via `get_notes(...)` and then removing them via `PrivateSet::remove(...)`. +This is cumbersome and it results in unnecessary constraints due to a redundant note read request checks in the remove function. + +For this reason we've implemented `pop_notes(...)` which gets the notes, removes them from the set and returns them. +This tight coupling of getting notes and removing them allowed us to safely remove the redundant read request check. + +Token contract diff: + +```diff +-let options = NoteGetterOptions::with_filter(filter_notes_min_sum, target_amount).set_limit(max_notes); +-let notes = self.map.at(owner).get_notes(options); +-let mut subtracted = U128::from_integer(0); +-for i in 0..options.limit { +- if i < notes.len() { +- let note = notes.get_unchecked(i); +- self.map.at(owner).remove(note); +- subtracted = subtracted + note.get_amount(); +- } +-} +-assert(minuend >= subtrahend, "Balance too low"); ++let options = NoteGetterOptions::with_filter(filter_notes_min_sum, target_amount).set_limit(max_notes); ++let notes = self.map.at(owner).pop_notes(options); ++let mut subtracted = U128::from_integer(0); ++for i in 0..options.limit { ++ if i < notes.len() { ++ let note = notes.get_unchecked(i); ++ subtracted = subtracted + note.get_amount(); ++ } ++} ++assert(minuend >= subtrahend, "Balance too low"); +``` + +Note that `pop_notes` may not have obtained and removed any notes! The caller must place checks on the returned notes, e.g. in the example above by checking a sum of balances, or by checking the number of returned notes (`assert_eq(notes.len(), expected_num_notes)`). + ## 0.47.0 # [Aztec sandbox] TXE deployment changes diff --git a/docs/docs/reference/developer_references/smart_contract_reference/storage/private_state.md b/docs/docs/reference/developer_references/smart_contract_reference/storage/private_state.md index c8aa400f0ad..66e9ccf9821 100644 --- a/docs/docs/reference/developer_references/smart_contract_reference/storage/private_state.md +++ b/docs/docs/reference/developer_references/smart_contract_reference/storage/private_state.md @@ -212,27 +212,28 @@ The usage is similar to using the `insert` method with the difference that this #include_code insert_from_public /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust -### `remove` +### `pop_notes` -Will remove a note from the `PrivateSet` if it previously has been read from storage, e.g. you have fetched it through a `get_notes` call. This is useful when you want to remove a note that you have previously read from storage and do not have to read it again. +This function pops (gets, removes and returns) the notes the account has access to based on the provided filter. + +The kernel circuits are constrained to a maximum number of notes this function can return at a time. Check [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr) and look for `MAX_NOTE_HASH_READ_REQUESTS_PER_CALL` for the up-to-date number. -Nullifiers are emitted when reading values to make sure that they are up to date. +Because of this limit, we should always consider using the second argument `NoteGetterOptions` to limit the number of notes we need to read and constrain in our programs. This is quite important as every extra call increases the time used to prove the program and we don't want to spend more time than necessary. -An example of how to use this operation is visible in the `easy_private_state`: +An example of such options is using the [filter_notes_min_sum (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/value-note/src/filter.nr) to get "enough" notes to cover a given value. Essentially, this function will return just enough notes to cover the amount specified such that we don't need to read all our notes. For users with a lot of notes, this becomes increasingly important. -#include_code remove /noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr rust +#include_code pop_notes /noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr rust ### `get_notes` -This function returns the notes the account has access to. +This function has the same behavior as `pop_notes` above but it does not delete the notes. -The kernel circuits are constrained to a maximum number of notes this function can return at a time. Check [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr) and look for `MAX_NOTE_HASH_READ_REQUESTS_PER_CALL` for the up-to-date number. -Because of this limit, we should always consider using the second argument `NoteGetterOptions` to limit the number of notes we need to read and constrain in our programs. This is quite important as every extra call increases the time used to prove the program and we don't want to spend more time than necessary. +### `remove` -An example of such options is using the [filter_notes_min_sum (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/value-note/src/filter.nr) to get "enough" notes to cover a given value. Essentially, this function will return just enough notes to cover the amount specified such that we don't need to read all our notes. For users with a lot of notes, this becomes increasingly important. +Will remove a note from the `PrivateSet` if it previously has been read from storage, e.g. you have fetched it through a `get_notes` call. This is useful when you want to remove a note that you have previously read from storage and do not have to read it again. -#include_code get_notes /noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr rust +Note that if you obtained the note you are about to remove via `get_notes` it's much better to use `pop_notes` as `pop_notes` results in significantly fewer constraints since it doesn't need to check that the note has been previously read, as it reads and deletes at once. ### `view_notes` 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 7902602ca09..4765e94368f 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 @@ -41,7 +41,28 @@ impl PrivateSet where N } // docs:end:insert - // docs:start:remove + pub fn pop_notes( + self, + options: NoteGetterOptions + ) -> BoundedVec { + let notes = get_notes(self.context, self.storage_slot, options); + // We iterate in a range 0..options.limit instead of 0..notes.len() because options.limit is known at compile + // time and hence will result in less constraints when set to a lower value than + // MAX_NOTE_HASH_READ_REQUESTS_PER_CALL. + for i in 0..options.limit { + if i < notes.len() { + let note = notes.get_unchecked(i); + // We immediately destroy the note without doing any of the read request checks `remove` typically + // performs because we know that the `get_notes` call has already placed those constraints. + destroy_note(self.context, note); + } + } + + notes + } + + /// Note that if you obtained the note via `get_notes` it's much better to use `pop_notes` as `pop_notes` results + /// in significantly less constrains due to avoiding an extra hash and read request check. pub fn remove(self, note: Note) { let note_hash = compute_note_hash_for_read_request(note); let has_been_read = self.context.note_hash_read_requests.any(|r: ReadRequest| r.value == note_hash); @@ -49,16 +70,15 @@ impl PrivateSet where N destroy_note(self.context, note); } - // docs:end:remove - // docs:start:get_notes + /// Note that if you later on remove the note it's much better to use `pop_notes` as `pop_notes` results + /// in significantly less constrains due to avoiding 1 read request check. pub fn get_notes( self, options: NoteGetterOptions ) -> BoundedVec { get_notes(self.context, self.storage_slot, options) } - // docs:end:get_notes } impl PrivateSet where Note: NoteInterface { diff --git a/noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr b/noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr index 0d93ae6fc21..799bd038f9c 100644 --- a/noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr +++ b/noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr @@ -39,21 +39,16 @@ impl EasyPrivateUint<&mut PrivateContext> { let header = self.context.get_header(); let owner_npk_m_hash = header.get_npk_m_hash(self.context, owner); - // docs:start:get_notes + // docs:start:pop_notes let options = NoteGetterOptions::with_filter(filter_notes_min_sum, subtrahend as Field); - let notes = self.set.get_notes(options); - // docs:end:get_notes + let notes = self.set.pop_notes(options); + // docs:end:pop_notes let mut minuend: u64 = 0; for i in 0..options.limit { if i < notes.len() { let note = notes.get_unchecked(i); - // Removes the note from the owner's set of notes. - // docs:start:remove - self.set.remove(note); - // docs:end:remove - minuend += note.value as u64; } } diff --git a/noir-projects/aztec-nr/value-note/src/utils.nr b/noir-projects/aztec-nr/value-note/src/utils.nr index a66a951b00d..107562ef2b6 100644 --- a/noir-projects/aztec-nr/value-note/src/utils.nr +++ b/noir-projects/aztec-nr/value-note/src/utils.nr @@ -55,14 +55,13 @@ pub fn decrement_by_at_most( outgoing_viewer: AztecAddress ) -> Field { let options = create_note_getter_options_for_decreasing_balance(max_amount); - let notes = balance.get_notes(options); + let notes = balance.pop_notes(options); let mut decremented = 0; for i in 0..options.limit { if i < notes.len() { let note = notes.get_unchecked(i); - - decremented += destroy_note(balance, note); + decremented += note.value; } } @@ -76,11 +75,3 @@ pub fn decrement_by_at_most( decremented } - -// Removes the note from the owner's set of notes. -// Returns the value of the destroyed note. -pub fn destroy_note(balance: PrivateSet, note: ValueNote) -> Field { - balance.remove(note); - - note.value -} diff --git a/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr b/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr index 8285e40177c..5f7690615da 100644 --- a/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr @@ -31,9 +31,8 @@ contract Benchmarking { fn recreate_note(owner: AztecAddress, outgoing_viewer: AztecAddress, index: u32) { let owner_notes = storage.notes.at(owner); let mut getter_options = NoteGetterOptions::new(); - let notes = owner_notes.get_notes(getter_options.set_limit(1).set_offset(index)); + let notes = owner_notes.pop_notes(getter_options.set_limit(1).set_offset(index)); let note = notes.get(0); - owner_notes.remove(note); increment(owner_notes, note.value, owner, outgoing_viewer); } diff --git a/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr b/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr index 0d315f4e314..871332ae601 100644 --- a/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr +++ b/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr @@ -120,9 +120,9 @@ impl Deck<&mut PrivateContext> { inserted_cards } - pub fn get_cards(&mut self, cards: [Card; N]) -> [CardNote; N] { + pub fn remove_cards(&mut self, cards: [Card; N]) { let options = NoteGetterOptions::with_filter(filter_cards, cards); - let notes = self.set.get_notes(options); + let notes = self.set.pop_notes(options); // This array will hold the notes that correspond to each of the requested cards. It begins empty (with all the // options being none) and we gradually fill it up as we find the matching notes. @@ -144,18 +144,8 @@ impl Deck<&mut PrivateContext> { } // And then we assert that we did indeed find all cards, since found_cards and cards have the same length. - found_cards.map( - |card_note: Option| { - assert(card_note.is_some(), "Card not found"); - card_note.unwrap_unchecked() - } - ) - } - - pub fn remove_cards(&mut self, cards: [Card; N]) { - let card_notes = self.get_cards(cards); - for card_note in card_notes { - self.set.remove(card_note.note); + for i in 0..found_cards.len() { + assert(found_cards[i].is_some(), "Card not found"); } } } diff --git a/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr index 2c9e092e2da..d91d5455807 100644 --- a/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr @@ -149,10 +149,8 @@ contract InclusionProofs { let private_values = storage.private_values.at(owner); let mut options = NoteGetterOptions::new(); options = options.set_limit(1); - let notes = private_values.get_notes(options); - let note = notes.get(0); - - private_values.remove(note); + let notes = private_values.pop_notes(options); + assert(notes.len() == 1, "note not popped"); } // docs:end:nullify_note diff --git a/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr b/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr index 5cd3a8e7cac..371cc6afbef 100644 --- a/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr @@ -39,14 +39,12 @@ contract PendingNoteHashes { let options = NoteGetterOptions::with_filter(filter_notes_min_sum, amount); // get note inserted above - let notes = owner_balance.get_notes(options); + let notes = owner_balance.pop_notes(options); let note0 = notes.get(0); assert(note.value == note0.value); assert(notes.len() == 1); - owner_balance.remove(note0); - note0.value } @@ -137,12 +135,9 @@ contract PendingNoteHashes { let mut options = NoteGetterOptions::new(); options = options.set_limit(1); - let note = owner_balance.get_notes(options).get(0); + let note = owner_balance.pop_notes(options).get(0); assert(expected_value == note.value); - - owner_balance.remove(note); - expected_value } @@ -378,12 +373,9 @@ contract PendingNoteHashes { #[contract_library_method] fn destroy_max_notes(owner: AztecAddress, storage: Storage<&mut PrivateContext>) { let owner_balance = storage.balances.at(owner); - let notes = owner_balance.get_notes(NoteGetterOptions::new()); - - for i in 0..max_notes_per_call() { - let note = notes.get(i); - owner_balance.remove(note); - } + // Note that we're relying on PXE actually returning the notes, we're not constraining that any specific + // numer of notes are deleted. + let _ = owner_balance.pop_notes(NoteGetterOptions::new()); } #[contract_library_method] diff --git a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr index 0a19e7583da..27a2f6cd6b8 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr @@ -474,9 +474,8 @@ contract Test { let secret_hash = compute_secret_hash(secret); let mut options = NoteGetterOptions::new(); options = options.select(TestNote::properties().value, secret_hash, Option::none()).set_limit(1); - let notes = notes_set.get_notes(options); - let note = notes.get(0); - notes_set.remove(note); + let notes = notes_set.pop_notes(options); + assert(notes.len() == 1, "note not popped"); } unconstrained fn get_constant() -> pub Field { diff --git a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr index 73685be5bb9..c445e179405 100644 --- a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr @@ -162,20 +162,19 @@ contract TokenBlacklist { let to_roles = storage.roles.at(to).get_current_value_in_private(); assert(!to_roles.is_blacklisted, "Blacklisted: Recipient"); - let pending_shields = storage.pending_shields; let secret_hash = compute_secret_hash(secret); - // Get 1 note (set_limit(1)) which has amount stored in field with index 0 (select(0, amount)) and secret_hash - // stored in field with index 1 (select(1, secret_hash)). + + // Pop 1 note (set_limit(1)) which has an amount stored in a field with index 0 (select(0, amount)) and + // a secret_hash stored in a field with index 1 (select(1, secret_hash)). let mut options = NoteGetterOptions::new(); options = options.select(TransparentNote::properties().amount, amount, Option::none()).select( TransparentNote::properties().secret_hash, secret_hash, Option::none() ).set_limit(1); - let notes = pending_shields.get_notes(options); - let note = notes.get(0); - // Remove the note from the pending shields set - pending_shields.remove(note); + + let notes = storage.pending_shields.pop_notes(options); + assert(notes.len() == 1, "note not popped"); // Add the token note to user's balances set let caller = context.msg_sender(); 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 c2a43a8b0f6..1ce88b39889 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 @@ -81,24 +81,13 @@ impl BalancesMap { owner: AztecAddress, subtrahend: U128 ) -> OuterNoteEmission where T: NoteInterface + OwnedNote + Eq { - // docs:start:get_notes let options = NoteGetterOptions::with_filter(filter_notes_min_sum, subtrahend); - let notes = self.map.at(owner).get_notes(options); - // docs:end:get_notes + let notes = self.map.at(owner).pop_notes(options); let mut minuend: U128 = U128::from_integer(0); for i in 0..options.limit { if i < notes.len() { - let note = notes.get_unchecked(i); - - // Removes the note from the owner's set of notes. - // This will call the `compute_nullifer` function of the `token_note` - // which require knowledge of the secret key (currently the users encryption key). - // The contract logic must ensure that the spending key is used as well. - // docs:start:remove - self.map.at(owner).remove(note); - // docs:end:remove - + let note: T = notes.get_unchecked(i); minuend = minuend + note.get_amount(); } } diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index 8b455041f7d..489b9fce80e 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -293,20 +293,19 @@ contract Token { // docs:start:redeem_shield #[aztec(private)] fn redeem_shield(to: AztecAddress, amount: Field, secret: Field) { - let pending_shields = storage.pending_shields; let secret_hash = compute_secret_hash(secret); - // Get 1 note (set_limit(1)) which has amount stored in field with index 0 (select(0, amount)) and secret_hash - // stored in field with index 1 (select(1, secret_hash)). + + // Pop 1 note (set_limit(1)) which has an amount stored in a field with index 0 (select(0, amount)) and + // a secret_hash stored in a field with index 1 (select(1, secret_hash)). let mut options = NoteGetterOptions::new(); options = options.select(TransparentNote::properties().amount, amount, Option::none()).select( TransparentNote::properties().secret_hash, secret_hash, Option::none() ).set_limit(1); - let notes = pending_shields.get_notes(options); - let note = notes.get(0); - // Remove the note from the pending shields set - pending_shields.remove(note); + + let notes = storage.pending_shields.pop_notes(options); + assert(notes.len() == 1, "note not popped"); // Add the token note to user's balances set // Note: Using context.msg_sender() as a sender below makes this incompatible with escrows because we send diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr index 45a60898e62..159c4daf0fc 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr @@ -88,7 +88,7 @@ unconstrained fn mint_private_success() { utils::check_private_balance(token_contract_address, owner, mint_amount); } -#[test(should_fail_with="Attempted to read past end of BoundedVec")] +#[test(should_fail_with="note not popped")] unconstrained fn mint_private_failure_double_spend() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/types/balances_map.nr b/noir-projects/noir-contracts/contracts/token_contract/src/types/balances_map.nr index 2035976606f..6a582c259f7 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/types/balances_map.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/types/balances_map.nr @@ -107,24 +107,13 @@ impl BalancesMap { target_amount: U128, max_notes: u32 ) -> U128 where T: NoteInterface + OwnedNote + Eq { - // docs:start:get_notes let options = NoteGetterOptions::with_filter(filter_notes_min_sum, target_amount).set_limit(max_notes); - let notes = self.map.at(owner).get_notes(options); - // docs:end:get_notes + let notes = self.map.at(owner).pop_notes(options); let mut subtracted = U128::from_integer(0); for i in 0..options.limit { if i < notes.len() { let note = notes.get_unchecked(i); - - // Removes the note from the owner's set of notes. - // This will call the `compute_nullifer` function of the `token_note` - // which require knowledge of the secret key (currently the users encryption key). - // The contract logic must ensure that the spending key is used as well. - // docs:start:remove - self.map.at(owner).remove(note); - // docs:end:remove - subtracted = subtracted + note.get_amount(); } } diff --git a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr index a7bdec92902..f60a2cffea7 100644 --- a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr @@ -280,20 +280,19 @@ contract TokenWithRefunds { // docs:start:redeem_shield #[aztec(private)] fn redeem_shield(to: AztecAddress, amount: Field, secret: Field) { - let pending_shields = storage.pending_shields; let secret_hash = compute_secret_hash(secret); - // Get 1 note (set_limit(1)) which has amount stored in field with index 0 (select(0, amount)) and secret_hash - // stored in field with index 1 (select(1, secret_hash)). + + // Pop 1 note (set_limit(1)) which has an amount stored in a field with index 0 (select(0, amount)) and + // a secret_hash stored in a field with index 1 (select(1, secret_hash)). let mut options = NoteGetterOptions::new(); options = options.select(TransparentNote::properties().amount, amount, Option::none()).select( TransparentNote::properties().secret_hash, secret_hash, Option::none() ).set_limit(1); - let notes = pending_shields.get_notes(options); - let note = notes.get_unchecked(0); - // Remove the note from the pending shields set - pending_shields.remove(note); + + let notes = storage.pending_shields.pop_notes(options); + assert(notes.len() == 1, "note not popped"); // Add the token note to user's balances set // Note: Using context.msg_sender() as a sender below makes this incompatible with escrows because we send diff --git a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/balances_map.nr b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/balances_map.nr index 1e16e80f616..dc31fc842ce 100644 --- a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/balances_map.nr +++ b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/balances_map.nr @@ -84,24 +84,13 @@ impl BalancesMap { owner: AztecAddress, subtrahend: U128 ) -> OuterNoteEmission where T: NoteInterface + OwnedNote + Eq { - // docs:start:get_notes let options = NoteGetterOptions::with_filter(filter_notes_min_sum, subtrahend); - let notes = self.map.at(owner).get_notes(options); - // docs:end:get_notes + let notes = self.map.at(owner).pop_notes(options); let mut minuend: U128 = U128::from_integer(0); for i in 0..options.limit { if i < notes.len() { let note = notes.get_unchecked(i); - - // Removes the note from the owner's set of notes. - // This will call the `compute_nullifer` function of the `token_note` - // which require knowledge of the secret key (currently the users encryption key). - // The contract logic must ensure that the spending key is used as well. - // docs:start:remove - self.map.at(owner).remove(note); - // docs:end:remove - minuend = minuend + note.get_amount(); } } diff --git a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/minting.test.ts b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/minting.test.ts index 37016cdbd20..19dd05a9aad 100644 --- a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/minting.test.ts +++ b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/minting.test.ts @@ -111,7 +111,7 @@ describe('e2e_blacklist_token_contract mint', () => { 'The note has been destroyed.', ); await expect(asset.methods.redeem_shield(wallets[0].getAddress(), amount, secret).prove()).rejects.toThrow( - `Assertion failed: Attempted to read past end of BoundedVec`, + `Assertion failed: note not popped 'notes.len() == 1'`, ); }); diff --git a/yarn-project/end-to-end/src/e2e_token_contract/minting.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/minting.test.ts index 83d3ea02ce6..208cfe2902c 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/minting.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/minting.test.ts @@ -101,7 +101,7 @@ describe('e2e_token_contract minting', () => { 'The note has been destroyed.', ); await expect(asset.methods.redeem_shield(accounts[0].address, amount, secret).simulate()).rejects.toThrow( - `Assertion failed: Attempted to read past end of BoundedVec`, + `Assertion failed: note not popped 'notes.len() == 1'`, ); });