@@ -164,13 +164,26 @@ where
164164 let filtered_notes = filter_fn (maybe_hinted_notes , filter_args );
165165
166166 let hinted_notes = array:: collapse (filtered_notes );
167- let mut confirmed_notes = BoundedVec ::new ();
168167
169168 // We have now collapsed the sparse array of Options into a BoundedVec. This is a more ergonomic type and also
170169 // results in reduced gate counts when setting a limit value, since we guarantee that the limit is an upper bound
171170 // for the runtime length, and can therefore have fewer loop iterations.
172171 assert (hinted_notes .len () <= options .limit , "Got more notes than limit." );
173172
173+ // What remains is to iterate over the hinted notes, assert their existence, and convert them into confirmed notes.
174+ // Naively, we would construct a `BoundedVec<ConfirmedNote, _>` and simply `push` into it as we process each hinted
175+ // note. We cannot use `BoundedVec::map` as the user specified the maximum number of notes in `options.limit`
176+ // instead of a numeric type parameter (which is more ergonomic), and `map` requires the latter.
177+ // Unfortunately, this results in terrible proving time performance. This is because the compiler is not smart
178+ // enough to understand the structure of looping over the `BoundedVec<HintedNote, _>`: it treats every `push` as a
179+ // conditional write to the confirmed array, resulting in runtime write indices (e.g. iteration 1 could write to
180+ // indices either 0 or 1, beucase iteration 0 might not push).
181+ // The loop does however have an interesting structure that we can reason about to achieve better performance:
182+ // because we're just going over a `BoundedVec`, the first `vec.len()` iterations will result in writes, and the
183+ // rest will not. Hence, we can just _unconditionally_ write to a raw storage array at the iteration index: we know
184+ // the resulting array will have no gaps. Because of this, we can then manually create a correct `BoundedVec`.
185+ let mut confirmed_notes_bvec_storage : [ConfirmedNote <_ >; _ ] = std::mem:: zeroed ();
186+
174187 let mut prev_packed_note = [0 ; M ];
175188 for i in 0 ..options .limit {
176189 if i < hinted_notes .len () {
@@ -204,11 +217,14 @@ where
204217
205218 let note_existence_request = compute_note_existence_request (hinted_note );
206219 context .assert_note_exists (note_existence_request );
207- confirmed_notes .push (ConfirmedNote ::new (hinted_note , note_existence_request .note_hash ()));
220+
221+ confirmed_notes_bvec_storage [i ] = ConfirmedNote ::new (hinted_note , note_existence_request .note_hash ());
208222 };
209223 }
210224
211- confirmed_notes
225+ // We can use `from_parts_unchecked` instead of `from_parts` because we know that `confirmed_notes_bvec_storage`
226+ // contains all zeroes past `hinted_notes.len()` due to how it was initialized.
227+ BoundedVec ::from_parts_unchecked (confirmed_notes_bvec_storage , hinted_notes .len ())
212228}
213229
214230pub unconstrained fn view_note <Note >(owner : Option <AztecAddress >, storage_slot : Field ) -> HintedNote <Note >
0 commit comments