diff --git a/avm-transpiler/Cargo.lock b/avm-transpiler/Cargo.lock index 8cc69462e76..5dd69ef7f61 100644 --- a/avm-transpiler/Cargo.lock +++ b/avm-transpiler/Cargo.lock @@ -318,6 +318,7 @@ dependencies = [ "iter-extended", "noirc_errors", "noirc_frontend", + "regex", ] [[package]] diff --git a/docs/docs/developers/contracts/references/storage/private_state.md b/docs/docs/developers/contracts/references/storage/private_state.md index 3e181bba649..dd82a21a02c 100644 --- a/docs/docs/developers/contracts/references/storage/private_state.md +++ b/docs/docs/developers/contracts/references/storage/private_state.md @@ -254,11 +254,11 @@ You can view the implementation [here](https://github.com/AztecProtocol/aztec-pa ### `selects: BoundedVec, N>` -`selects` is a collection of filtering criteria, specified by `Select { field_index: u8, value: Field, comparator: u3 }` structs. It instructs the data oracle to find notes whose (`field_index`)th field matches the provided `value`, according to the `comparator`. +`selects` is a collection of filtering criteria, specified by `Select { property_selector: PropertySelector, value: Field, comparator: u3 }` structs. It instructs the data oracle to find notes whose serialized field (as specified by the PropertySelector) matches the provided `value`, according to the `comparator`. The PropertySelector is in turn specified as having an `index` (nth position of the selected field in the serialized note), an `offset` (byte offset inside the selected serialized field) and `length` (bytes to read of the field from the offset) ### `sorts: BoundedVec, N>` -`sorts` is a set of sorting instructions defined by `Sort { field_index: u8, order: u2 }` structs. This directs the data oracle to sort the matching notes based on the value of the specified field index and in the indicated order. The value of order is **1** for _DESCENDING_ and **2** for _ASCENDING_. +`sorts` is a set of sorting instructions defined by `Sort { property_selector: PropertySelector, order: u2 }` structs. This directs the data oracle to sort the matching notes based on the value of the specified PropertySelector and in the indicated order. The value of order is **1** for _DESCENDING_ and **2** for _ASCENDING_. ### `limit: u32` diff --git a/docs/docs/misc/migration_notes.md b/docs/docs/misc/migration_notes.md index ca9e0d18c16..a82148537c0 100644 --- a/docs/docs/misc/migration_notes.md +++ b/docs/docs/misc/migration_notes.md @@ -6,6 +6,159 @@ keywords: [sandbox, cli, aztec, notes, migration, updating, upgrading] Aztec is in full-speed development. Literally every version breaks compatibility with the previous ones. This page attempts to target errors and difficulties you might encounter when upgrading, and how to resolve them. +## 0.28.0 + +### Automatic NoteInterface implementation and selector changes + +Implementing a note required a fair amount of boilerplate code, which has been substituted by the `#[aztec(note)]` attribute. + +Before: + +```rust +struct AddressNote { + address: AztecAddress, + owner: AztecAddress, + randomness: Field, + header: NoteHeader +} + +impl NoteInterface for AddressNote { + fn serialize_content(self) -> [Field; ADDRESS_NOTE_LEN]{ + [self.address.to_field(), self.owner.to_field(), self.randomness] + } + + fn deserialize_content(serialized_note: [Field; ADDRESS_NOTE_LEN]) -> Self { + AddressNote { + address: AztecAddress::from_field(serialized_note[0]), + owner: AztecAddress::from_field(serialized_note[1]), + randomness: serialized_note[2], + header: NoteHeader::empty(), + } + } + + fn compute_note_content_hash(self) -> Field { + pedersen_hash(self.serialize_content(), 0) + } + + fn compute_nullifier(self, context: &mut PrivateContext) -> Field { + let note_hash_for_nullify = compute_note_hash_for_consumption(self); + let secret = context.request_nullifier_secret_key(self.owner); + pedersen_hash([ + note_hash_for_nullify, + secret.low, + secret.high, + ],0) + } + + fn compute_nullifier_without_context(self) -> Field { + let note_hash_for_nullify = compute_note_hash_for_consumption(self); + let secret = get_nullifier_secret_key(self.owner); + pedersen_hash([ + note_hash_for_nullify, + secret.low, + secret.high, + ],0) + } + + fn set_header(&mut self, header: NoteHeader) { + self.header = header; + } + + fn get_header(note: Self) -> NoteHeader { + note.header + } + + fn broadcast(self, context: &mut PrivateContext, slot: Field) { + let encryption_pub_key = get_public_key(self.owner); + emit_encrypted_log( + context, + (*context).this_address(), + slot, + Self::get_note_type_id(), + encryption_pub_key, + self.serialize_content(), + ); + } + + fn get_note_type_id() -> Field { + 6510010011410111511578111116101 + } +} + +``` + +After: + +```rust +#[aztec(note)] +struct AddressNote { + address: AztecAddress, + owner: AztecAddress, + randomness: Field, +} + +impl NoteInterface for AddressNote { + fn compute_nullifier(self, context: &mut PrivateContext) -> Field { + let note_hash_for_nullify = compute_note_hash_for_consumption(self); + let secret = context.request_nullifier_secret_key(self.owner); + // TODO(#1205) Should use a non-zero generator index. + pedersen_hash([ + note_hash_for_nullify, + secret.low, + secret.high, + ],0) + } + + fn compute_nullifier_without_context(self) -> Field { + let note_hash_for_nullify = compute_note_hash_for_consumption(self); + let secret = get_nullifier_secret_key(self.owner); + pedersen_hash([ + note_hash_for_nullify, + secret.low, + secret.high, + ],0) + } + + fn broadcast(self, context: &mut PrivateContext, slot: Field) { + let encryption_pub_key = get_public_key(self.owner); + emit_encrypted_log( + context, + (*context).this_address(), + slot, + Self::get_note_type_id(), + encryption_pub_key, + self.serialize_content(), + ); + } +} +``` + +Automatic note (de)serialization implementation also means it is now easier to filter notes using `NoteGetterOptions.select` via the `::properties()` helper: + +Before: + +```rust +let options = NoteGetterOptions::new().select(0, amount, Option::none()).select(1, owner.to_field(), Option::none()).set_limit(1); +``` + +After: + +```rust +let options = NoteGetterOptions::new().select(ValueNote::properties().value, amount, Option::none()).select(ValueNote::properties().owner, owner.to_field(), Option::none()).set_limit(1); +``` + +The helper returns a metadata struct that looks like this (if autogenerated) + +```rust +ValueNoteProperties { + value: PropertySelector { index: 0, offset: 0, length: 32 }, + owner: PropertySelector { index: 1, offset: 0, length: 32 }, + randomness: PropertySelector { index: 2, offset: 0, length: 32 }, +} +``` + +It can also be used for the `.sort` method. + ## 0.27.0 ### `initializer` macro replaces `constructor` diff --git a/noir-projects/aztec-nr/address-note/src/address_note.nr b/noir-projects/aztec-nr/address-note/src/address_note.nr index ebacf94a631..cd5ab4b8f92 100644 --- a/noir-projects/aztec-nr/address-note/src/address_note.nr +++ b/noir-projects/aztec-nr/address-note/src/address_note.nr @@ -12,31 +12,14 @@ global ADDRESS_NOTE_LEN: Field = 3; // docs:start:address_note_def // Stores an address +#[aztec(note)] struct AddressNote { address: AztecAddress, owner: AztecAddress, randomness: Field, - header: NoteHeader, } impl NoteInterface for AddressNote { - fn serialize_content(self) -> [Field; ADDRESS_NOTE_LEN]{ - [self.address.to_field(), self.owner.to_field(), self.randomness] - } - - fn deserialize_content(serialized_note: [Field; ADDRESS_NOTE_LEN]) -> Self { - AddressNote { - address: AztecAddress::from_field(serialized_note[0]), - owner: AztecAddress::from_field(serialized_note[1]), - randomness: serialized_note[2], - header: NoteHeader::empty(), - } - } - - fn compute_note_content_hash(self) -> Field { - // TODO(#1205) Should use a non-zero generator index. - pedersen_hash(self.serialize_content(), 0) - } fn compute_nullifier(self, context: &mut PrivateContext) -> Field { let note_hash_for_nullify = compute_note_hash_for_consumption(self); @@ -60,14 +43,6 @@ impl NoteInterface for AddressNote { ],0) } - fn set_header(&mut self, header: NoteHeader) { - self.header = header; - } - - fn get_header(note: Self) -> NoteHeader { - note.header - } - // Broadcasts the note as an encrypted log on L1. fn broadcast(self, context: &mut PrivateContext, slot: Field) { let encryption_pub_key = get_public_key(self.owner); @@ -82,12 +57,6 @@ impl NoteInterface for AddressNote { ); // docs:end:encrypted } - - fn get_note_type_id() -> Field { - // TODO(#4519): autogenerate - // python -c "print(int(''.join(str(ord(c)) for c in 'AddressNote')))" - 6510010011410111511578111116101 - } } impl AddressNote { 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 25398adad28..679f097ee65 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_getter.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_getter.nr @@ -6,12 +6,30 @@ use dep::protocol_types::{ }; use crate::context::PrivateContext; use crate::note::{ - note_getter_options::{NoteGetterOptions, Select, Sort, SortOrder, Comparator, NoteStatus}, + note_getter_options::{NoteGetterOptions, Select, Sort, SortOrder, Comparator, NoteStatus, PropertySelector}, note_interface::NoteInterface, note_viewer_options::NoteViewerOptions, utils::compute_note_hash_for_consumption }; use crate::oracle; +fn extract_property_value_from_selector(serialized_note: [Field; N], selector: PropertySelector) -> Field { + // Selectors use PropertySelectors in order to locate note properties inside the serialized note. + // This allows easier packing and custom (de)serialization schemas. A note property is located + // inside the serialized note using the index inside the array, a byte offset and a length. + let value = serialized_note[selector.index].to_be_bytes(32); + let offset = selector.offset; + let length = selector.length; + let mut value_field = 0 as Field; + let mut acc: Field = 1; + for i in 0..32 { + if i < length { + value_field += value[31 + offset - i] as Field * acc; + acc = acc * 256; + } + } + value_field +} + fn check_note_header(context: PrivateContext, storage_slot: Field, note: Note) where Note: NoteInterface { let header = note.get_header(); let contract_address = context.this_address(); @@ -19,13 +37,14 @@ fn check_note_header(context: PrivateContext, storage_slot: Field, note assert(header.storage_slot == storage_slot); } -fn check_note_fields(fields: [Field; N], selects: BoundedVec, N>) { +fn check_note_fields(serialized_note: [Field; N], selects: BoundedVec, N>) { for i in 0..selects.len { let select = selects.get_unchecked(i).unwrap_unchecked(); + let value_field = extract_property_value_from_selector(serialized_note, select.property_selector); // Values are computed ahead of time because circuits evaluate all branches - let isEqual = fields[select.field_index] == select.value; - let isLt = fields[select.field_index].lt(select.value); + let isEqual = value_field == select.value.to_field(); + let isLt = value_field.lt(select.value.to_field()); if (select.comparator == Comparator.EQ) { assert(isEqual, "Mismatch return note field."); @@ -50,8 +69,10 @@ fn check_notes_order( ) { for i in 0..sorts.len { let sort = sorts.get_unchecked(i).unwrap_unchecked(); - let eq = fields_0[sort.field_index] == fields_1[sort.field_index]; - let lt = fields_0[sort.field_index].lt(fields_1[sort.field_index]); + let field_0 = extract_property_value_from_selector(fields_0, sort.property_selector); + let field_1 = extract_property_value_from_selector(fields_1, sort.property_selector); + let eq = field_0 == field_1; + let lt = field_0.lt(field_1); if sort.order == SortOrder.ASC { assert(eq | lt, "Return notes not sorted in ascending order."); } else if !eq { @@ -120,6 +141,10 @@ unconstrained fn get_note_internal(storage_slot: Field) -> Note where N [], [], [], + [], + [], + [], + [], 1, // limit 0, // offset NoteStatus.ACTIVE, @@ -133,17 +158,21 @@ unconstrained fn get_notes_internal( storage_slot: Field, options: NoteGetterOptions ) -> [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] where Note: NoteInterface { - let (num_selects, select_by, select_values, select_comparators, sort_by, sort_order) = flatten_options(options.selects, options.sorts); + 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_opt_notes = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; let placeholder_fields = [0; GET_NOTES_ORACLE_RETURN_LENGTH]; let placeholder_note_length = [0; N]; let opt_notes = oracle::notes::get_notes( storage_slot, num_selects, - select_by, + select_by_indexes, + select_by_offsets, + select_by_lengths, select_values, select_comparators, - sort_by, + sort_by_indexes, + sort_by_offsets, + sort_by_lengths, sort_order, options.limit, options.offset, @@ -162,17 +191,21 @@ unconstrained pub fn view_notes( storage_slot: Field, options: NoteViewerOptions ) -> [Option; MAX_NOTES_PER_PAGE] where Note: NoteInterface { - let (num_selects, select_by, select_values, select_comparators, sort_by, sort_order) = flatten_options(options.selects, options.sorts); + 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_opt_notes = [Option::none(); MAX_NOTES_PER_PAGE]; let placeholder_fields = [0; VIEW_NOTE_ORACLE_RETURN_LENGTH]; let placeholder_note_length = [0; N]; oracle::notes::get_notes( storage_slot, num_selects, - select_by, + select_by_indexes, + select_by_offsets, + select_by_lengths, select_values, select_comparators, - sort_by, + sort_by_indexes, + sort_by_offsets, + sort_by_lengths, sort_order, options.limit, options.offset, @@ -186,31 +219,41 @@ unconstrained pub fn view_notes( unconstrained fn flatten_options( selects: BoundedVec, N>, sorts: BoundedVec, N> -) -> (u8, [u8; N], [Field; N], [u8; N], [u8; N], [u8; N]) { +) -> (u8, [u8; N], [u8; N], [u8; N], [Field; N], [u8; N], [u8; N], [u8; N], [u8; N], [u8; N]) { let mut num_selects = 0; - let mut select_by = [0; N]; + let mut select_by_indexes = [0; N]; + let mut select_by_offsets = [0; N]; + let mut select_by_lengths = [0; N]; let mut select_values = [0; N]; let mut select_comparators = [0; N]; for i in 0..selects.len { let select = selects.get(i); if select.is_some() { - select_by[num_selects] = select.unwrap_unchecked().field_index; + select_by_indexes[num_selects] = select.unwrap_unchecked().property_selector.index; + select_by_offsets[num_selects] = select.unwrap_unchecked().property_selector.offset; + select_by_lengths[num_selects] = select.unwrap_unchecked().property_selector.length; select_values[num_selects] = select.unwrap_unchecked().value; select_comparators[num_selects] = select.unwrap_unchecked().comparator; num_selects += 1; }; } - let mut sort_by = [0; N]; + let mut sort_by_indexes = [0; N]; + let mut sort_by_offsets = [0; N]; + let mut sort_by_lengths = [0; N]; let mut sort_order = [0; N]; for i in 0..sorts.len { let sort = sorts.get(i); if sort.is_some() { - sort_by[i] = sort.unwrap_unchecked().field_index; + sort_by_indexes[i] = sort.unwrap_unchecked().property_selector.index; + sort_by_offsets[i] = sort.unwrap_unchecked().property_selector.offset; + sort_by_lengths[i] = sort.unwrap_unchecked().property_selector.length; sort_order[i] = sort.unwrap_unchecked().order; }; } - (num_selects, select_by, select_values, select_comparators, sort_by, sort_order) + ( + 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 + ) } diff --git a/noir-projects/aztec-nr/aztec/src/note/note_getter_options.nr b/noir-projects/aztec-nr/aztec/src/note/note_getter_options.nr index b24104a2662..0389ee76a18 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_getter_options.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_getter_options.nr @@ -1,6 +1,13 @@ -use dep::protocol_types::{constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL}; +use dep::std::option::Option; +use dep::protocol_types::{constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, traits::ToField}; use crate::note::note_interface::NoteInterface; +struct PropertySelector { + index: u8, + offset: u8, + length: u8, +} + struct ComparatorEnum { EQ: u8, NEQ: u8, @@ -20,14 +27,14 @@ global Comparator = ComparatorEnum { }; struct Select { - field_index: u8, + property_selector: PropertySelector, value: Field, comparator: u8, } impl Select { - pub fn new(field_index: u8, value: Field, comparator: u8) -> Self { - Select { field_index, value, comparator } + pub fn new(property_selector: PropertySelector, value: Field, comparator: u8) -> Self { + Select { property_selector, value, comparator } } } @@ -42,13 +49,13 @@ global SortOrder = SortOrderEnum { }; struct Sort { - field_index: u8, + property_selector: PropertySelector, order: u8, } impl Sort { - pub fn new(field_index: u8, order: u8) -> Self { - Sort { field_index, order } + pub fn new(property_selector: PropertySelector, order: u8) -> Self { + Sort { property_selector, order } } } @@ -118,18 +125,31 @@ impl NoteGetterOptions { } // This method adds a `Select` criterion to the options. - // It takes a field_index indicating which field to select, + // It takes a property_selector indicating which field to select, // a value representing the specific value to match in that field, and // a comparator (For possible values of comparators, please see the Comparator enum above) - pub fn select(&mut self, field_index: u8, value: Field, comparator: Option) -> Self { - self.selects.push(Option::some(Select::new(field_index, value, comparator.unwrap_or(Comparator.EQ)))); + pub fn select( + &mut self, + property_selector: PropertySelector, + value: T, + comparator: Option + ) -> Self where T: ToField { + self.selects.push( + Option::some( + Select::new( + property_selector, + value.to_field(), + comparator.unwrap_or(Comparator.EQ) + ) + ) + ); *self } // This method adds a `Sort` criterion to the options. // It takes a field_index indicating which field to sort by and an order (SortOrder) to determine the sorting direction. - pub fn sort(&mut self, field_index: u8, order: u8) -> Self { - self.sorts.push(Option::some(Sort::new(field_index, order))); + pub fn sort(&mut self, property_selector: PropertySelector, order: u8) -> Self { + self.sorts.push(Option::some(Sort::new(property_selector, order))); *self } diff --git a/noir-projects/aztec-nr/aztec/src/note/note_interface.nr b/noir-projects/aztec-nr/aztec/src/note/note_interface.nr index a663051fe1e..3eaf10c0b77 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_interface.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_interface.nr @@ -3,22 +3,28 @@ use crate::note::note_header::NoteHeader; // docs:start:note_interface trait NoteInterface { + fn compute_nullifier(self, context: &mut PrivateContext) -> Field; + + fn compute_nullifier_without_context(self) -> Field; + + fn broadcast(self, context: &mut PrivateContext, slot: Field) -> (); + + // Autogenerated by the #[aztec(note)] macro unless it is overridden by a custom implementation fn serialize_content(self) -> [Field; N]; + // Autogenerated by the #[aztec(note)] macro unless it is overridden by a custom implementation fn deserialize_content(fields: [Field; N]) -> Self; + // Autogenerated by the #[aztec(note)] macro unless it is overridden by a custom implementation fn compute_note_content_hash(self) -> Field; + // Autogenerated by the #[aztec(note)] macro unless it is overridden by a custom implementation fn get_header(self) -> NoteHeader; + // Autogenerated by the #[aztec(note)] macro unless it is overridden by a custom implementation fn set_header(&mut self, header: NoteHeader) -> (); - fn compute_nullifier(self, context: &mut PrivateContext) -> Field; - - fn compute_nullifier_without_context(self) -> Field; - - fn broadcast(self, context: &mut PrivateContext, slot: Field) -> (); - + // Autogenerated by the #[aztec(note)] macro unless it is overridden by a custom implementation fn get_note_type_id() -> Field; } // docs:end:note_interface diff --git a/noir-projects/aztec-nr/aztec/src/note/note_viewer_options.nr b/noir-projects/aztec-nr/aztec/src/note/note_viewer_options.nr index 97496eb7c42..eeed1ddac84 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_viewer_options.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_viewer_options.nr @@ -1,5 +1,6 @@ -use crate::note::note_getter_options::{Select, Sort, Comparator, NoteStatus}; -use dep::protocol_types::{constants::MAX_NOTES_PER_PAGE}; +use dep::std::option::Option; +use crate::note::note_getter_options::{PropertySelector, Select, Sort, Comparator, NoteStatus}; +use dep::protocol_types::{constants::MAX_NOTES_PER_PAGE, traits::ToField}; use crate::note::note_interface::NoteInterface; // docs:start:NoteViewerOptions @@ -27,13 +28,26 @@ impl NoteViewerOptions { // It takes a field_index indicating which field to select, // a value representing the specific value to match in that field, and // a comparator (For possible values of comparators, please see the Comparator enum from note_getter_options) - pub fn select(&mut self, field_index: u8, value: Field, comparator: Option) -> Self { - self.selects.push(Option::some(Select::new(field_index, value, comparator.unwrap_or(Comparator.EQ)))); + pub fn select( + &mut self, + property_selector: PropertySelector, + value: T, + comparator: Option + ) -> Self where T: ToField { + self.selects.push( + Option::some( + Select::new( + property_selector, + value.to_field(), + comparator.unwrap_or(Comparator.EQ) + ) + ) + ); *self } - pub fn sort(&mut self, field_index: u8, order: u8) -> Self { - self.sorts.push(Option::some(Sort::new(field_index, order))); + pub fn sort(&mut self, property_selector: PropertySelector, order: u8) -> Self { + self.sorts.push(Option::some(Sort::new(property_selector, order))); *self } diff --git a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr index cfad47101d2..50ee2367f0a 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr @@ -30,10 +30,14 @@ unconstrained pub fn notify_nullified_note(nullifier: Field, inner_note_hash: fn get_notes_oracle( _storage_slot: Field, _num_selects: u8, - _select_by: [u8; N], + _select_by_indexes: [u8; N], + _select_by_offsets: [u8; N], + _select_by_lengths: [u8; N], _select_values: [Field; N], _select_comparators: [u8; N], - _sort_by: [u8; N], + _sort_by_indexes: [u8; N], + _sort_by_offsets: [u8; N], + _sort_by_lengths: [u8; N], _sort_order: [u8; N], _limit: u32, _offset: u32, @@ -45,10 +49,14 @@ fn get_notes_oracle( unconstrained fn get_notes_oracle_wrapper( storage_slot: Field, num_selects: u8, - select_by: [u8; N], + select_by_indexes: [u8; N], + select_by_offsets: [u8; N], + select_by_lengths: [u8; N], select_values: [Field; N], select_comparators: [u8; N], - sort_by: [u8; N], + sort_by_indexes: [u8; N], + sort_by_offsets: [u8; N], + sort_by_lengths: [u8; N], sort_order: [u8; N], limit: u32, offset: u32, @@ -59,10 +67,14 @@ unconstrained fn get_notes_oracle_wrapper( get_notes_oracle( storage_slot, num_selects, - select_by, + select_by_indexes, + select_by_offsets, + select_by_lengths, select_values, select_comparators, - sort_by, + sort_by_indexes, + sort_by_offsets, + sort_by_lengths, sort_order, limit, offset, @@ -75,10 +87,14 @@ unconstrained fn get_notes_oracle_wrapper( unconstrained pub fn get_notes( storage_slot: Field, num_selects: u8, - select_by: [u8; M], + select_by_indexes: [u8; M], + select_by_offsets: [u8; M], + select_by_lengths: [u8; M], select_values: [Field; M], select_comparators: [u8; M], - sort_by: [u8; M], + sort_by_indexes: [u8; M], + sort_by_offsets: [u8; M], + sort_by_lengths: [u8; M], sort_order: [u8; M], limit: u32, offset: u32, @@ -90,10 +106,14 @@ unconstrained pub fn get_notes( let fields = get_notes_oracle_wrapper( storage_slot, num_selects, - select_by, + select_by_indexes, + select_by_offsets, + select_by_lengths, select_values, select_comparators, - sort_by, + sort_by_indexes, + sort_by_offsets, + sort_by_lengths, sort_order, limit, offset, diff --git a/noir-projects/aztec-nr/field-note/src/field_note.nr b/noir-projects/aztec-nr/field-note/src/field_note.nr index 1f54659636a..f6350ddd613 100644 --- a/noir-projects/aztec-nr/field-note/src/field_note.nr +++ b/noir-projects/aztec-nr/field-note/src/field_note.nr @@ -8,27 +8,12 @@ global FIELD_NOTE_LEN: Field = 1; // A note which stores a field and is expected to be passed around using the `addNote` function. // WARNING: This Note is not private as it does not contain randomness and hence it can be easy to perform serialized_note // attack on it. +#[aztec(note)] struct FieldNote { value: Field, - header: NoteHeader, } impl NoteInterface for FieldNote { - fn serialize_content(self) -> [Field; FIELD_NOTE_LEN]{ - [self.value] - } - - fn deserialize_content(serialized_note: [Field; FIELD_NOTE_LEN]) -> Self { - FieldNote { - value: serialized_note[0], - header: NoteHeader::empty(), - } - } - - fn compute_note_content_hash(self) -> Field { - // TODO(#1205) Should use a non-zero generator index. - pedersen_hash(self.serialize_content(), 0) - } fn compute_nullifier(self, _context: &mut PrivateContext) -> Field { // This note is expected to be shared between users and for this reason can't be nullified using a secret. @@ -40,25 +25,11 @@ impl NoteInterface for FieldNote { 0 } - fn set_header(&mut self, header: NoteHeader) { - self.header = header; - } - - fn get_header(self) -> NoteHeader { - self.header - } - fn broadcast(self, context: &mut PrivateContext, slot: Field) { assert( false, "FieldNote does not support broadcast. Add it to PXE directly using the `.addNote` function." ); } - - fn get_note_type_id() -> Field { - // TODO(#4519): autogenerate - // python -c "print(int(''.join(str(ord(c)) for c in 'FieldNote')))" - 7010510110810078111116101 - } } impl FieldNote { diff --git a/noir-projects/aztec-nr/value-note/src/utils.nr b/noir-projects/aztec-nr/value-note/src/utils.nr index 446a00c232c..5cb4b75b6c7 100644 --- a/noir-projects/aztec-nr/value-note/src/utils.nr +++ b/noir-projects/aztec-nr/value-note/src/utils.nr @@ -6,7 +6,7 @@ use crate::{filter::filter_notes_min_sum, value_note::{ValueNote, VALUE_NOTE_LEN // Sort the note values (0th field) in descending order. // Pick the fewest notes whose sum is equal to or greater than `amount`. pub fn create_note_getter_options_for_decreasing_balance(amount: Field) -> NoteGetterOptions { - NoteGetterOptions::with_filter(filter_notes_min_sum, amount).sort(0, SortOrder.DESC) + NoteGetterOptions::with_filter(filter_notes_min_sum, amount).sort(ValueNote::properties().value, SortOrder.DESC) } // Creates a new note for the recipient. diff --git a/noir-projects/aztec-nr/value-note/src/value_note.nr b/noir-projects/aztec-nr/value-note/src/value_note.nr index f0d59d31e3e..eb03acd210a 100644 --- a/noir-projects/aztec-nr/value-note/src/value_note.nr +++ b/noir-projects/aztec-nr/value-note/src/value_note.nr @@ -8,33 +8,15 @@ use dep::aztec::{ global VALUE_NOTE_LEN: Field = 3; // 3 plus a header. // docs:start:value-note-def +#[aztec(note)] struct ValueNote { value: Field, owner: AztecAddress, randomness: Field, - header: NoteHeader, } // docs:end:value-note-def impl NoteInterface for ValueNote { - fn serialize_content(self) -> [Field; VALUE_NOTE_LEN] { - [self.value, self.owner.to_field(), self.randomness] - } - - fn deserialize_content(serialized_note: [Field; VALUE_NOTE_LEN]) -> Self { - ValueNote { - value: serialized_note[0], - owner: AztecAddress::from_field(serialized_note[1]), - randomness: serialized_note[2], - header: NoteHeader::empty(), - } - } - - fn compute_note_content_hash(self) -> Field { - // TODO(#1205) Should use a non-zero generator index. - pedersen_hash(self.serialize_content(),0) - } - // docs:start:nullifier fn compute_nullifier(self, context: &mut PrivateContext) -> Field { @@ -61,14 +43,6 @@ impl NoteInterface for ValueNote { ],0) } - fn set_header(&mut self, header: NoteHeader) { - self.header = header; - } - - fn get_header(self) -> NoteHeader { - self.header - } - // Broadcasts the note as an encrypted log on L1. fn broadcast(self, context: &mut PrivateContext, slot: Field) { let encryption_pub_key = get_public_key(self.owner); @@ -81,12 +55,6 @@ impl NoteInterface for ValueNote { self.serialize_content(), ); } - - fn get_note_type_id() -> Field { - // TODO(#4519): autogenerate - // python -c "print(int(''.join(str(ord(c)) for c in 'ValueNote')))" - 869710811710178111116101 - } } impl ValueNote { 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 3f454f7c469..c541c92ae39 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 @@ -8,27 +8,14 @@ global SUBSCRIPTION_NOTE_LEN: Field = 3; // 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? +#[aztec(note)] struct SubscriptionNote { owner: AztecAddress, expiry_block_number: Field, remaining_txs: Field, - header: NoteHeader, } impl NoteInterface for SubscriptionNote { - fn serialize_content(self) -> [Field; SUBSCRIPTION_NOTE_LEN] { - [self.owner.to_field(), self.expiry_block_number, self.remaining_txs] - } - - fn deserialize_content(serialized_note: [Field; SUBSCRIPTION_NOTE_LEN]) -> SubscriptionNote { - SubscriptionNote { - owner: AztecAddress::from_field(serialized_note[0]), - expiry_block_number: serialized_note[1], - remaining_txs: serialized_note[2], - header: NoteHeader::empty() - } - } - fn compute_nullifier(self, context: &mut PrivateContext) -> Field { let unique_siloed_note_hash = compute_note_hash_for_consumption(self); let secret = context.request_nullifier_secret_key(self.owner); @@ -51,19 +38,6 @@ impl NoteInterface for SubscriptionNote { ],0) } - fn compute_note_content_hash(note: SubscriptionNote) -> Field { - // TODO(#1205) Should use a non-zero generator index. - pedersen_hash(note.serialize_content(), 0) - } - - fn set_header(&mut self, header: NoteHeader) { - self.header = header; - } - - fn get_header(self) -> NoteHeader { - self.header - } - // Broadcasts the note as an encrypted log on L1. fn broadcast(self, context: &mut PrivateContext, slot: Field) { let encryption_pub_key = get_public_key(self.owner); @@ -76,10 +50,6 @@ impl NoteInterface for SubscriptionNote { self.serialize_content(), ); } - - fn get_note_type_id() -> Field { - 1 - } } impl SubscriptionNote { 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 adf70bd0678..87d943fef6a 100644 --- a/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr @@ -2,7 +2,6 @@ // We should try to change this contract as little as possible, since any modification // would alter the metrics we're capturing in the benchmarks, and we want to keep the // subject being tested as unmodified as possible so we can detect metric changes that -// arise from code changes. contract Benchmarking { use dep::aztec::prelude::{ 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 8df4508c0b2..106d06365f3 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 @@ -1,7 +1,10 @@ use dep::aztec::prelude::{AztecAddress, FunctionSelector, PrivateContext, NoteHeader, NoteGetterOptions, NoteViewerOptions}; use dep::aztec::{ - protocol_types::constants::{MAX_NOTES_PER_PAGE, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL}, + protocol_types::{ + traits::{ToField, Serialize, FromField}, + constants::{MAX_NOTES_PER_PAGE, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL} +}, context::{PublicContext, Context}, note::note_getter::view_notes, state_vars::PrivateSet }; use dep::std; @@ -14,19 +17,23 @@ struct Card { points: u32, } -impl Card { - pub fn from_field(field: Field) -> Card { +impl FromField for Card { + fn from_field(field: Field) -> Card { let value_bytes = field.to_le_bytes(32); let strength = (value_bytes[0] as u32) + (value_bytes[1] as u32) * 256; let points = (value_bytes[2] as u32) + (value_bytes[3] as u32) * 256; Card { strength, points } } +} - pub fn to_field(self) -> Field { - self.strength as Field + (self.points as Field) * 65536 +impl ToField for Card { + fn to_field(self) -> Field { + self.strength as Field + (self.points as Field)*65536 } +} - pub fn serialize(self) -> [Field; 2] { +impl Serialize<2> for Card { + fn serialize(self) -> [Field; 2] { [self.strength as Field, self.points as Field] } } diff --git a/noir-projects/noir-contracts/contracts/child_contract/src/main.nr b/noir-projects/noir-contracts/contracts/child_contract/src/main.nr index 52b4c845362..101a96a066a 100644 --- a/noir-projects/noir-contracts/contracts/child_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/child_contract/src/main.nr @@ -6,7 +6,7 @@ contract Child { context::{PublicContext, Context}, protocol_types::{abis::{call_context::CallContext}}, note::{note_getter_options::NoteGetterOptions, note_header::NoteHeader} }; - use dep::value_note::value_note::{ValueNote, VALUE_NOTE_LEN}; + use dep::value_note::value_note::ValueNote; struct Storage { current_value: PublicMutable, @@ -56,7 +56,11 @@ contract Child { #[aztec(private)] fn privateGetValue(amount: Field, owner: AztecAddress) -> Field { - let options = NoteGetterOptions::new().select(0, amount, Option::none()).select(1, owner.to_field(), Option::none()).set_limit(1); + let options = NoteGetterOptions::new().select(ValueNote::properties().value, amount, Option::none()).select( + ValueNote::properties().owner, + owner.to_field(), + Option::none() + ).set_limit(1); let notes = storage.a_private_value.get_notes(options); notes[0].unwrap_unchecked().value } diff --git a/noir-projects/noir-contracts/contracts/delegated_on_contract/src/main.nr b/noir-projects/noir-contracts/contracts/delegated_on_contract/src/main.nr index 53460138436..ea3f6dea736 100644 --- a/noir-projects/noir-contracts/contracts/delegated_on_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/delegated_on_contract/src/main.nr @@ -4,9 +4,7 @@ contract DelegatedOn { AztecAddress, FunctionSelector, NoteHeader, NoteGetterOptions, NoteViewerOptions, emit_unencrypted_log, PublicMutable, PrivateSet, PrivateContext }; - - use dep::aztec::{context::{PublicContext, Context}, protocol_types::abis::call_context::CallContext}; - use dep::value_note::value_note::{ValueNote, VALUE_NOTE_LEN}; + use dep::value_note::value_note::ValueNote; struct Storage { current_value: PublicMutable, @@ -27,7 +25,11 @@ contract DelegatedOn { } unconstrained fn view_private_value(amount: Field, owner: AztecAddress) -> pub Field { - let options = NoteViewerOptions::new().select(0, amount, Option::none()).select(1, owner.to_field(), Option::none()).set_limit(1); + let options = NoteViewerOptions::new().select(ValueNote::properties().value, amount, Option::none()).select( + ValueNote::properties().owner, + owner.to_field(), + Option::none() + ).set_limit(1); let notes = storage.a_private_value.view_notes(options); notes[0].unwrap_unchecked().value } @@ -36,3 +38,4 @@ contract DelegatedOn { storage.current_value.read() } } + diff --git a/noir-projects/noir-contracts/contracts/delegator_contract/src/main.nr b/noir-projects/noir-contracts/contracts/delegator_contract/src/main.nr index 7d21c033785..14751ae8102 100644 --- a/noir-projects/noir-contracts/contracts/delegator_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/delegator_contract/src/main.nr @@ -4,8 +4,7 @@ contract Delegator { AztecAddress, FunctionSelector, NoteHeader, NoteViewerOptions, emit_unencrypted_log, PublicMutable, PrivateSet }; - - use dep::value_note::value_note::{ValueNote, VALUE_NOTE_LEN}; + use dep::value_note::value_note::ValueNote; struct Storage { current_value: PublicMutable, @@ -43,7 +42,11 @@ contract Delegator { } unconstrained fn view_private_value(amount: Field, owner: AztecAddress) -> pub Field { - let options = NoteViewerOptions::new().select(0, amount, Option::none()).select(1, owner.to_field(), Option::none()).set_limit(1); + let options = NoteViewerOptions::new().select(ValueNote::properties().value, amount, Option::none()).select( + ValueNote::properties().owner, + owner.to_field(), + Option::none() + ).set_limit(1); let notes = storage.a_private_value.view_notes(options); notes[0].unwrap_unchecked().value } diff --git a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr index 9df766144de..a9ffdbf0483 100644 --- a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr @@ -158,7 +158,11 @@ contract DocsExample { // docs:start:state_vars-NoteGetterOptionsComparatorExampleNoir unconstrained fn read_note(amount: Field, comparator: u8) -> pub [Option; 10] { - let options = NoteViewerOptions::new().select(0, amount, Option::some(comparator)); + let options = NoteViewerOptions::new().select( + CardNote::properties().points, + amount, + Option::some(comparator) + ); let notes = storage.set.view_notes(options); notes diff --git a/noir-projects/noir-contracts/contracts/docs_example_contract/src/options.nr b/noir-projects/noir-contracts/contracts/docs_example_contract/src/options.nr index 7083ff65f75..cf9aae1224d 100644 --- a/noir-projects/noir-contracts/contracts/docs_example_contract/src/options.nr +++ b/noir-projects/noir-contracts/contracts/docs_example_contract/src/options.nr @@ -11,7 +11,11 @@ pub fn create_account_card_getter_options( account: AztecAddress, offset: u32 ) -> NoteGetterOptions { - NoteGetterOptions::new().select(2, account.to_field(), Option::none()).sort(0, SortOrder.DESC).set_offset(offset) + NoteGetterOptions::new().select( + CardNote::properties().owner, + account.to_field(), + Option::none() + ).sort(CardNote::properties().points, SortOrder.DESC).set_offset(offset) } // docs:end:state_vars-NoteGetterOptionsSelectSortOffset @@ -21,7 +25,11 @@ pub fn create_exact_card_getter_options( secret: Field, account: AztecAddress ) -> NoteGetterOptions { - NoteGetterOptions::new().select(0, points as Field, Option::none()).select(1, secret, Option::none()).select(2, account.to_field(), Option::none()) + NoteGetterOptions::new().select(CardNote::properties().points, points as Field, Option::none()).select(CardNote::properties().randomness, secret, Option::none()).select( + CardNote::properties().owner, + account.to_field(), + Option::none() + ) } // docs:end:state_vars-NoteGetterOptionsMultiSelects @@ -44,12 +52,20 @@ pub fn filter_min_points( // docs:start:state_vars-NoteGetterOptionsFilter pub fn create_account_cards_with_min_points_getter_options(account: AztecAddress, min_points: u8) -> NoteGetterOptions { - NoteGetterOptions::with_filter(filter_min_points, min_points).select(2, account.to_field(), Option::none()).sort(0, SortOrder.ASC) + NoteGetterOptions::with_filter(filter_min_points, min_points).select( + CardNote::properties().owner, + account.to_field(), + Option::none() + ).sort(CardNote::properties().points, SortOrder.ASC) } // docs:end:state_vars-NoteGetterOptionsFilter // docs:start:state_vars-NoteGetterOptionsPickOne pub fn create_largest_account_card_getter_options(account: AztecAddress) -> NoteGetterOptions { - NoteGetterOptions::new().select(2, account.to_field(), Option::none()).sort(0, SortOrder.DESC).set_limit(1) + NoteGetterOptions::new().select( + CardNote::properties().owner, + account.to_field(), + Option::none() + ).sort(CardNote::properties().points, SortOrder.DESC).set_limit(1) } // docs:end:state_vars-NoteGetterOptionsPickOne diff --git a/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr b/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr index 6d9add2ee86..f66b855485a 100644 --- a/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr +++ b/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr @@ -10,11 +10,11 @@ use dep::aztec::{ global CARD_NOTE_LEN: Field = 3; // docs:start:state_vars-CardNote +#[aztec(note)] struct CardNote { points: u8, randomness: Field, owner: AztecAddress, - header: NoteHeader, } // docs:end:state_vars-CardNote @@ -25,23 +25,6 @@ impl CardNote { } impl NoteInterface for CardNote { - fn serialize_content(self) -> [Field; CARD_NOTE_LEN] { - [self.points as Field, self.randomness, self.owner.to_field()] - } - - fn deserialize_content(serialized_note: [Field; CARD_NOTE_LEN]) -> Self { - CardNote { - points: serialized_note[0] as u8, - randomness: serialized_note[1], - owner: AztecAddress::from_field(serialized_note[2]), - header: NoteHeader::empty(), - } - } - - fn compute_note_content_hash(self) -> Field { - pedersen_hash(self.serialize_content(), 0) - } - fn compute_nullifier(self, context: &mut PrivateContext) -> Field { let note_hash_for_nullify = compute_note_hash_for_consumption(self); let secret = context.request_nullifier_secret_key(self.owner); @@ -62,14 +45,6 @@ impl NoteInterface for CardNote { ],0) } - fn set_header(&mut self, header: NoteHeader) { - self.header = header; - } - - fn get_header(note: CardNote) -> NoteHeader { - note.header - } - // Broadcasts the note as an encrypted log on L1. fn broadcast(self, context: &mut PrivateContext, slot: Field) { let encryption_pub_key = get_public_key(self.owner); @@ -82,10 +57,4 @@ impl NoteInterface for CardNote { self.serialize_content(), ); } - - fn get_note_type_id() -> Field { - // TODO(#4519): autogenerate - // python -c "print(int(''.join(str(ord(c)) for c in 'CardNote')))" - 679711410078111116101 - } } diff --git a/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/ecdsa_public_key_note.nr b/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/ecdsa_public_key_note.nr index 992dd56b5a6..cd25fa907da 100644 --- a/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/ecdsa_public_key_note.nr +++ b/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/ecdsa_public_key_note.nr @@ -13,15 +13,15 @@ global ECDSA_PUBLIC_KEY_NOTE_LEN: Field = 5; // Stores an ECDSA public key composed of two 32-byte elements // TODO: Do we need to include a nonce, in case we want to read/nullify/recreate with the same pubkey value? +#[aztec(note)] struct EcdsaPublicKeyNote { x: [u8; 32], y: [u8; 32], owner: AztecAddress, // We store the owner address only to get the secret key to compute the nullifier - header: NoteHeader, } impl NoteInterface for EcdsaPublicKeyNote { - // serialize the note as 5 fields where: + // Cannot use the automatic serialization since x and y don't fit. Serialize the note as 5 fields where: // [0] = x[0..31] (upper bound excluded) // [1] = x[31] // [2] = y[0..31] @@ -46,6 +46,7 @@ impl NoteInterface for EcdsaPublicKeyNote { [x, last_x, y, last_y, self.owner.to_field()] } + // Cannot use the automatic deserialization for the aforementioned reasons fn deserialize_content(serialized_note: [Field; ECDSA_PUBLIC_KEY_NOTE_LEN]) -> EcdsaPublicKeyNote { let mut x: [u8; 32] = [0; 32]; let mut y: [u8; 32] = [0; 32]; @@ -65,11 +66,6 @@ impl NoteInterface for EcdsaPublicKeyNote { EcdsaPublicKeyNote { x, y, owner: AztecAddress::from_field(serialized_note[4]), header: NoteHeader::empty() } } - fn compute_note_content_hash(note: EcdsaPublicKeyNote) -> Field { - // TODO(#1205) Should use a non-zero generator index. - pedersen_hash(note.serialize_content(), 0) - } - fn compute_nullifier(self, context: &mut PrivateContext) -> Field { let unique_siloed_note_hash = compute_note_hash_for_consumption(self); let secret = context.request_nullifier_secret_key(self.owner); @@ -92,14 +88,6 @@ impl NoteInterface for EcdsaPublicKeyNote { ],0) } - fn set_header(&mut self, header: NoteHeader) { - self.header = header; - } - - fn get_header(self) -> NoteHeader { - self.header - } - // Broadcasts the note as an encrypted log on L1. fn broadcast(self, context: &mut PrivateContext, slot: Field) { let encryption_pub_key = get_public_key(self.owner); @@ -112,12 +100,6 @@ impl NoteInterface for EcdsaPublicKeyNote { self.serialize_content(), ); } - - fn get_note_type_id() -> Field { - // TODO(#4519): autogenerate - // python -c "print(int(''.join(str(ord(c)) for c in 'EcdsaPublicKeyNote')))" - 6999100115978011798108105997510112178111116101 - } } impl EcdsaPublicKeyNote { diff --git a/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr b/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr index 373d66ea76e..f02f1c8b679 100644 --- a/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr @@ -4,7 +4,7 @@ contract Escrow { use dep::aztec::{context::{PublicContext, Context}, oracle::get_public_key::get_public_key}; - use dep::address_note::address_note::{AddressNote, ADDRESS_NOTE_LEN}; + use dep::address_note::address_note::AddressNote; struct Storage { owner: PrivateImmutable, 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 4d57d958875..9fc57911d51 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 @@ -58,7 +58,11 @@ contract InclusionProofs { // docs:start:get_note_from_pxe // 1) Get the note from PXE. let private_values = storage.private_values.at(owner); - let mut options = NoteGetterOptions::new().select(1, owner.to_field(), Option::none()).set_limit(1); + let mut options = NoteGetterOptions::new().select( + ValueNote::properties().owner, + owner.to_field(), + Option::none() + ).set_limit(1); if (nullified) { options = options.set_status(NoteStatus.ACTIVE_OR_NULLIFIED); } @@ -104,7 +108,11 @@ contract InclusionProofs { ) { // 2) Get the note from PXE let private_values = storage.private_values.at(owner); - let mut options = NoteGetterOptions::new().select(1, owner.to_field(), Option::none()).set_limit(1); + let mut options = NoteGetterOptions::new().select( + ValueNote::properties().owner, + owner.to_field(), + Option::none() + ).set_limit(1); if (fail_case) { options = options.set_status(NoteStatus.ACTIVE_OR_NULLIFIED); } @@ -130,7 +138,11 @@ contract InclusionProofs { ) { // 1) Get the note from PXE. let private_values = storage.private_values.at(owner); - let mut options = NoteGetterOptions::new().select(1, owner.to_field(), Option::none()).set_limit(1); + let mut options = NoteGetterOptions::new().select( + ValueNote::properties().owner, + owner.to_field(), + Option::none() + ).set_limit(1); if (nullified) { options = options.set_status(NoteStatus.ACTIVE_OR_NULLIFIED); } @@ -151,7 +163,11 @@ contract InclusionProofs { #[aztec(private)] fn nullify_note(owner: AztecAddress) { let private_values = storage.private_values.at(owner); - let options = NoteGetterOptions::new().select(1, owner.to_field(), Option::none()).set_limit(1); + let options = NoteGetterOptions::new().select( + ValueNote::properties().owner, + owner.to_field(), + Option::none() + ).set_limit(1); let notes = private_values.get_notes(options); let note = notes[0].unwrap(); 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 7b55db4476c..f78ad778189 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 @@ -8,27 +8,14 @@ global PUBLIC_KEY_NOTE_LEN: Field = 3; // 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? +#[aztec(note)] struct PublicKeyNote { x: Field, y: Field, owner: AztecAddress, // We store the owner address only to get the secret key to compute the nullifier and to broadcast - header: NoteHeader, } impl NoteInterface for PublicKeyNote { - fn serialize_content(self) -> [Field; PUBLIC_KEY_NOTE_LEN] { - [self.x, self.y, self.owner.to_field()] - } - - fn deserialize_content(serialized_note: [Field; PUBLIC_KEY_NOTE_LEN]) -> PublicKeyNote { - PublicKeyNote { - x: serialized_note[0], - y: serialized_note[1], - owner: AztecAddress::from_field(serialized_note[2]), - header: NoteHeader::empty() - } - } - fn compute_nullifier(self, context: &mut PrivateContext) -> Field { let unique_siloed_note_hash = compute_note_hash_for_consumption(self); let secret = context.request_nullifier_secret_key(self.owner); @@ -51,19 +38,6 @@ impl NoteInterface for PublicKeyNote { ],0) } - fn compute_note_content_hash(note: PublicKeyNote) -> Field { - // TODO(#1205) Should use a non-zero generator index. - pedersen_hash(note.serialize_content(), 0) - } - - fn set_header(&mut self, header: NoteHeader) { - self.header = header; - } - - fn get_header(self) -> NoteHeader { - self.header - } - // Broadcasts the note as an encrypted log on L1. fn broadcast(self, context: &mut PrivateContext, slot: Field) { let encryption_pub_key = get_public_key(self.owner); @@ -76,12 +50,6 @@ impl NoteInterface for PublicKeyNote { self.serialize_content(), ); } - - fn get_note_type_id() -> Field { - // TODO(#4519): autogenerate - // python -c "print(int(''.join(str(ord(c)) for c in 'PublicKeyNote')))" - 8011798108105997510112178111116101 - } } impl PublicKeyNote { 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 22e3162d2e3..f5139cf61dc 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr @@ -20,7 +20,7 @@ contract Test { lifecycle::{create_note, destroy_note}, note_getter::{get_notes, view_notes}, note_getter_options::NoteStatus }, - deploy::{deploy_contract as aztec_deploy_contract}, + deploy::deploy_contract as aztec_deploy_contract, oracle::{get_public_key::get_public_key as get_public_key_oracle, context::get_portal_address, rand::rand}, log::emit_unencrypted_log_from_private }; @@ -257,11 +257,7 @@ contract Test { } #[aztec(public)] - fn consume_mint_public_message( - to: AztecAddress, - amount: Field, - secret: Field - ) { + fn consume_mint_public_message(to: AztecAddress, amount: Field, secret: Field) { let content_hash = get_mint_public_content_hash(to, amount); // Consume message and emit nullifier context.consume_l1_to_l2_message(content_hash, secret, context.this_portal_address()); @@ -351,7 +347,7 @@ contract Test { fn consume_note_from_secret(secret: Field) { let notes_set = storage.example_set; let secret_hash = compute_secret_hash(secret); - let options = NoteGetterOptions::new().select(0, secret_hash, Option::none()).set_limit(1); + let options = NoteGetterOptions::new().select(FieldNote::properties().value, secret_hash, Option::none()).set_limit(1); let notes = notes_set.get_notes(options); let note = notes[0].unwrap_unchecked(); notes_set.remove(note); 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 72879e4694c..cd046614445 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 @@ -18,14 +18,14 @@ contract TokenBlacklist { use dep::aztec::protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress}; use dep::aztec::{ note::{note_getter_options::NoteGetterOptions, note_header::NoteHeader}, - hash::{compute_secret_hash}, state_vars::{Map, PublicMutable, PrivateSet, SharedImmutable} + hash::compute_secret_hash, state_vars::{Map, PublicMutable, PrivateSet, SharedImmutable} }; use dep::field_note::field_note::FieldNote; use dep::authwit::{auth::{assert_current_call_valid_authwit, assert_current_call_valid_authwit_public}}; - use crate::types::{transparent_note::TransparentNote, token_note::TokenNote, balances_map::{BalancesMap}, roles::UserFlags}; + use crate::types::{transparent_note::TransparentNote, token_note::TokenNote, balances_map::BalancesMap, roles::UserFlags}; // docs:start:interface use crate::interfaces::SlowMap; // docs:end:interface @@ -200,7 +200,11 @@ contract TokenBlacklist { 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)). - let options = NoteGetterOptions::new().select(0, amount, Option::none()).select(1, secret_hash, Option::none()).set_limit(1); + let options = NoteGetterOptions::new().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[0].unwrap_unchecked(); // Remove the note from the pending shields set diff --git a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr index ee921232453..afb1e0b28a5 100644 --- a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr @@ -10,6 +10,7 @@ trait OwnedNote { global TOKEN_NOTE_LEN: Field = 3; // 3 plus a header. +#[aztec(note)] struct TokenNote { // the amount of tokens in the note amount: U128, @@ -19,31 +20,9 @@ struct TokenNote { owner: AztecAddress, // randomness of the note to hide contents. randomness: Field, - // the note header (contract_address, nonce, storage_slot) - // included in the note such that it becomes part of encrypted logs for later use. - header: NoteHeader, } impl NoteInterface for TokenNote { - fn serialize_content(self) -> [Field; TOKEN_NOTE_LEN] { - [self.amount.to_field(), self.owner.to_field(), self.randomness] - } - - fn deserialize_content(serialized_note: [Field; TOKEN_NOTE_LEN]) -> Self { - Self { - // TODO: check this type cast is right - amount: U128::from_integer(serialized_note[0]), - owner: AztecAddress::from_field(serialized_note[1]), - randomness: serialized_note[2], - header: NoteHeader::empty(), - } - } - - fn compute_note_content_hash(self) -> Field { - // TODO(#1205) Should use a non-zero generator index. - pedersen_hash(self.serialize_content(), 0) - } - // docs:start:nullifier fn compute_nullifier(self, context: &mut PrivateContext) -> Field { let note_hash_for_nullify = compute_note_hash_for_consumption(self); @@ -68,14 +47,6 @@ impl NoteInterface for TokenNote { ],0) } - fn set_header(&mut self, header: NoteHeader) { - self.header = header; - } - - fn get_header(self) -> NoteHeader { - self.header - } - // Broadcasts the note as an encrypted log on L1. fn broadcast(self, context: &mut PrivateContext, slot: Field) { // We only bother inserting the note if non-empty to save funds on gas. @@ -91,12 +62,6 @@ impl NoteInterface for TokenNote { ); } } - - fn get_note_type_id() -> Field { - // TODO(#4519): autogenerate - // python -c "print(int(''.join(str(ord(c)) for c in 'TokenNote')))" - 8411110710111078111116101 - } } impl OwnedNote for TokenNote { diff --git a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/transparent_note.nr b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/transparent_note.nr index 1f7136d8004..80748a32375 100644 --- a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/transparent_note.nr +++ b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/transparent_note.nr @@ -1,25 +1,35 @@ // docs:start:token_types_all use dep::aztec::prelude::{NoteHeader, NoteInterface, PrivateContext}; -use dep::aztec::{note::utils::compute_note_hash_for_consumption, hash::{compute_secret_hash, pedersen_hash}}; +use dep::aztec::{ + note::{note_getter_options::PropertySelector, utils::compute_note_hash_for_consumption}, + hash::{compute_secret_hash, pedersen_hash} +}; global TRANSPARENT_NOTE_LEN: Field = 2; // Transparent note represents a note that is created in the clear (public execution), // but can only be spent by those that know the preimage of the "secret_hash" +#[aztec(note)] struct TransparentNote { amount: Field, secret_hash: Field, // the secret is just here for ease of use and won't be (de)serialized secret: Field, - // header is just here to satisfy the NoteInterface - header: NoteHeader, +} + +struct TransparentNoteProperties { + amount: PropertySelector, + secret_hash: PropertySelector, } impl NoteInterface for TransparentNote { + + // Custom serialization to avoid disclosing the secret field fn serialize_content(self) -> [Field; TRANSPARENT_NOTE_LEN] { [self.amount, self.secret_hash] } + // Custom deserialization since we don't have access to the secret plaintext fn deserialize_content(serialized_note: [Field; TRANSPARENT_NOTE_LEN]) -> Self { TransparentNote { amount: serialized_note[0], @@ -29,11 +39,6 @@ impl NoteInterface for TransparentNote { } } - fn compute_note_content_hash(self) -> Field { - // TODO(#1205) Should use a non-zero generator index. - pedersen_hash(self.serialize_content(), 0) - } - fn compute_nullifier(self, _context: &mut PrivateContext) -> Field { self.compute_nullifier_without_context() } @@ -44,23 +49,9 @@ impl NoteInterface for TransparentNote { pedersen_hash([self.secret, siloed_note_hash],0) } - fn set_header(&mut self, header: NoteHeader) { - self.header = header; - } - - fn get_header(self) -> NoteHeader { - self.header - } - fn broadcast(self, context: &mut PrivateContext, slot: Field) { assert(false, "TransparentNote does not support broadcast"); } - - fn get_note_type_id() -> Field { - // TODO(#4519): autogenerate - // python -c "print(int(''.join(str(ord(c)) for c in 'TransparentNote')))" - 84114971101151129711410111011678111116101 - } } impl TransparentNote { @@ -83,5 +74,13 @@ impl TransparentNote { let hash = compute_secret_hash(secret); assert(self.secret_hash == hash); } + + // Custom serialization forces us to manually create the metadata struct and its getter + pub fn properties() -> TransparentNoteProperties { + TransparentNoteProperties { + amount: PropertySelector { index: 0, offset: 0, length: 32 }, + secret_hash: PropertySelector { index: 1, offset: 0, length: 32 } + } + } } // docs:end:token_types_all 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 5b8ed008687..27a0b12ff80 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -249,7 +249,11 @@ contract Token { 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)). - let options = NoteGetterOptions::new().select(0, amount, Option::none()).select(1, secret_hash, Option::none()).set_limit(1); + let options = NoteGetterOptions::new().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[0].unwrap_unchecked(); // Remove the note from the pending shields set diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr index 37179b2f326..f577cd5e37f 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr @@ -13,6 +13,7 @@ trait OwnedNote { global TOKEN_NOTE_LEN: Field = 3; // 3 plus a header. +#[aztec(note)] struct TokenNote { // the amount of tokens in the note amount: U128, @@ -22,31 +23,9 @@ struct TokenNote { owner: AztecAddress, // randomness of the note to hide contents. randomness: Field, - // the note header (contract_address, nonce, storage_slot) - // included in the note such that it becomes part of encrypted logs for later use. - header: NoteHeader, } impl NoteInterface for TokenNote { - fn serialize_content(self) -> [Field; TOKEN_NOTE_LEN] { - [self.amount.to_field(), self.owner.to_field(), self.randomness] - } - - fn deserialize_content(serialized_note: [Field; TOKEN_NOTE_LEN]) -> Self { - Self { - // TODO: check this type cast is right - amount: U128::from_integer(serialized_note[0]), - owner: AztecAddress::from_field(serialized_note[1]), - randomness: serialized_note[2], - header: NoteHeader::empty(), - } - } - - fn compute_note_content_hash(self) -> Field { - // TODO(#1205) Should use a non-zero generator index. - pedersen_hash(self.serialize_content(), 0) - } - // docs:start:nullifier fn compute_nullifier(self, context: &mut PrivateContext) -> Field { let note_hash_for_nullify = compute_note_hash_for_consumption(self); @@ -71,14 +50,6 @@ impl NoteInterface for TokenNote { ],0) } - fn set_header(&mut self, header: NoteHeader) { - self.header = header; - } - - fn get_header(self) -> NoteHeader { - self.header - } - // Broadcasts the note as an encrypted log on L1. fn broadcast(self, context: &mut PrivateContext, slot: Field) { // We only bother inserting the note if non-empty to save funds on gas. @@ -94,13 +65,7 @@ impl NoteInterface for TokenNote { ); } } - - fn get_note_type_id() -> Field { - // TODO(#4519): autogenerate - // python -c "print(int(''.join(str(ord(c)) for c in 'TokenNote')))" - 8411110710111078111116101 - } -} + } impl OwnedNote for TokenNote { fn new(amount: U128, owner: AztecAddress) -> Self { @@ -119,5 +84,5 @@ impl OwnedNote for TokenNote { fn get_owner(self) -> AztecAddress { self.owner } - + } diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/types/transparent_note.nr b/noir-projects/noir-contracts/contracts/token_contract/src/types/transparent_note.nr index 79f059b20c2..dfc106d2c13 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/types/transparent_note.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/types/transparent_note.nr @@ -1,25 +1,35 @@ // docs:start:token_types_all use dep::aztec::prelude::{NoteHeader, NoteInterface, PrivateContext}; -use dep::aztec::{note::{utils::compute_note_hash_for_consumption}, hash::{compute_secret_hash, pedersen_hash}}; +use dep::aztec::{ + note::{note_getter_options::PropertySelector, utils::compute_note_hash_for_consumption}, + hash::{compute_secret_hash, pedersen_hash} +}; global TRANSPARENT_NOTE_LEN: Field = 2; // Transparent note represents a note that is created in the clear (public execution), // but can only be spent by those that know the preimage of the "secret_hash" +#[aztec(note)] struct TransparentNote { amount: Field, secret_hash: Field, // the secret is just here for ease of use and won't be (de)serialized - secret: Field, - // header is just here to satisfy the NoteInterface - header: NoteHeader, + secret: Field +} + +struct TransparentNoteProperties { + amount: PropertySelector, + secret_hash: PropertySelector, } impl NoteInterface for TransparentNote { + + // Custom serialization to avoid disclosing the secret field fn serialize_content(self) -> [Field; TRANSPARENT_NOTE_LEN] { [self.amount, self.secret_hash] } + // Custom deserialization since we don't have access to the secret plaintext fn deserialize_content(serialized_note: [Field; TRANSPARENT_NOTE_LEN]) -> Self { TransparentNote { amount: serialized_note[0], @@ -29,11 +39,6 @@ impl NoteInterface for TransparentNote { } } - fn compute_note_content_hash(self) -> Field { - // TODO(#1205) Should use a non-zero generator index. - pedersen_hash(self.serialize_content(), 0) - } - fn compute_nullifier(self, _context: &mut PrivateContext) -> Field { self.compute_nullifier_without_context() } @@ -44,23 +49,9 @@ impl NoteInterface for TransparentNote { pedersen_hash([self.secret, siloed_note_hash],0) } - fn set_header(&mut self, header: NoteHeader) { - self.header = header; - } - - fn get_header(self) -> NoteHeader { - self.header - } - fn broadcast(self, context: &mut PrivateContext, slot: Field) { assert(false, "TransparentNote does not support broadcast"); } - - fn get_note_type_id() -> Field { - // TODO(#4519): autogenerate - // python -c "print(int(''.join(str(ord(c)) for c in 'TransparentNote')))" - 84114971101151129711410111011678111116101 - } } impl TransparentNote { @@ -83,5 +74,13 @@ impl TransparentNote { let hash = compute_secret_hash(secret); assert(self.secret_hash == hash); } + + // Custom serialization forces us to manually create the metadata struct and its getter + pub fn properties() -> TransparentNoteProperties { + TransparentNoteProperties { + amount: PropertySelector { index: 0, offset: 0, length: 32 }, + secret_hash: PropertySelector { index: 1, offset: 0, length: 32 } + } + } } // docs:end:token_types_all diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/function_selector.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/function_selector.nr index 82ab480da19..32dbce09dfa 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/function_selector.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/function_selector.nr @@ -1,6 +1,6 @@ use crate::utils::field::field_from_bytes; use dep::std::cmp::Eq; -use crate::traits::{Serialize, Deserialize}; +use crate::traits::{Serialize, Deserialize, FromField, ToField}; global SELECTOR_SIZE = 4; @@ -29,19 +29,23 @@ impl Deserialize<1> for FunctionSelector { } } -impl FunctionSelector { +impl FromField for FunctionSelector { + fn from_field(field: Field) -> Self { + Self { inner: field as u32 } + } +} + +impl ToField for FunctionSelector { fn to_field(self) -> Field { self.inner as Field } +} +impl FunctionSelector { pub fn from_u32(value: u32) -> Self { Self { inner: value } } - pub fn from_field(value: Field) -> Self { - Self { inner: value as u32 } - } - pub fn from_signature(signature: str) -> Self { let bytes = signature.as_bytes(); let hash = dep::std::hash::keccak256(bytes, bytes.len() as u32); diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/contract_class_id.nr b/noir-projects/noir-protocol-circuits/crates/types/src/contract_class_id.nr index 2b6c09ce802..1b0b4936b8d 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/contract_class_id.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/contract_class_id.nr @@ -1,4 +1,5 @@ use crate::constants::{GENERATOR_INDEX__CONTRACT_LEAF}; +use crate::traits::{ToField, FromField, Hash, Serialize, Deserialize}; struct ContractClassId { inner: Field @@ -10,6 +11,30 @@ impl Eq for ContractClassId { } } +impl ToField for ContractClassId { + fn to_field(self) -> Field { + self.inner + } +} + +impl FromField for ContractClassId { + fn from_field(value: Field) -> Self { + Self { inner: value } + } +} + +impl Serialize<1> for ContractClassId { + fn serialize(self: Self) -> [Field; 1] { + [self.inner] + } +} + +impl Deserialize<1> for ContractClassId { + fn deserialize(fields: [Field; 1]) -> Self { + Self { inner: fields[0] } + } +} + impl ContractClassId { pub fn compute( artifact_hash: Field, @@ -28,22 +53,6 @@ impl ContractClassId { ContractClassId::from_field(hash) } - fn to_field(self) -> Field { - self.inner as Field - } - - pub fn from_field(value: Field) -> Self { - Self { inner: value } - } - - pub fn serialize(self: Self) -> [Field; 1] { - [self.inner] - } - - pub fn deserialize(fields: [Field; 1]) -> Self { - Self { inner: fields[0] } - } - pub fn assert_is_zero(self) { assert(self.to_field() == 0); } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr b/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr index ffb4ceecadb..70870f64d84 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr @@ -45,12 +45,38 @@ impl ToField for Field { } } +impl ToField for bool { fn to_field(self) -> Field { self as Field } } +impl ToField for u1 { fn to_field(self) -> Field { self as Field } } +impl ToField for u8 { fn to_field(self) -> Field { self as Field } } +impl ToField for u32 { fn to_field(self) -> Field { self as Field } } +impl ToField for u64 { fn to_field(self) -> Field { self as Field } } impl ToField for U128 { fn to_field(self) -> Field { self.to_integer() } } +trait FromField { + fn from_field(value: Field) -> Self; +} + +impl FromField for Field { + fn from_field(value: Field) -> Self { + value + } +} + +impl FromField for bool { fn from_field(value: Field) -> Self { value as bool } } +impl FromField for u1 { fn from_field(value: Field) -> Self { value as u1 } } +impl FromField for u8 { fn from_field(value: Field) -> Self { value as u8 } } +impl FromField for u32 { fn from_field(value: Field) -> Self { value as u32 } } +impl FromField for u64 { fn from_field(value: Field) -> Self { value as u64 } } +impl FromField for U128 { + fn from_field(value: Field) -> Self { + U128::from_integer(value) + } +} + // docs:start:serialize trait Serialize { fn serialize(self) -> [Field; N]; diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index 3b7c1b6e56e..593347089ee 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -439,6 +439,7 @@ dependencies = [ "iter-extended", "noirc_errors", "noirc_frontend", + "regex", ] [[package]] @@ -2655,9 +2656,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memmap2" @@ -3822,14 +3823,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.1" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.3", - "regex-syntax 0.7.4", + "regex-automata 0.4.5", + "regex-syntax 0.8.2", ] [[package]] @@ -3846,10 +3847,16 @@ name = "regex-automata" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.4", + "regex-syntax 0.8.2", ] [[package]] @@ -3864,6 +3871,12 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + [[package]] name = "region" version = "3.0.0" diff --git a/noir/noir-repo/aztec_macros/Cargo.toml b/noir/noir-repo/aztec_macros/Cargo.toml index ed9821fabcf..355036d28a7 100644 --- a/noir/noir-repo/aztec_macros/Cargo.toml +++ b/noir/noir-repo/aztec_macros/Cargo.toml @@ -14,3 +14,5 @@ noirc_frontend.workspace = true noirc_errors.workspace = true iter-extended.workspace = true convert_case = "0.6.0" +regex = "1.10" + diff --git a/noir/noir-repo/aztec_macros/src/lib.rs b/noir/noir-repo/aztec_macros/src/lib.rs index e0100977eee..0fe450e6cb1 100644 --- a/noir/noir-repo/aztec_macros/src/lib.rs +++ b/noir/noir-repo/aztec_macros/src/lib.rs @@ -5,22 +5,27 @@ use transforms::{ compute_note_hash_and_nullifier::inject_compute_note_hash_and_nullifier, events::{generate_selector_impl, transform_events}, functions::{transform_function, transform_unconstrained, transform_vm_function}, + note_interface::generate_note_interface_impl, storage::{ assign_storage_slots, check_for_storage_definition, check_for_storage_implementation, generate_storage_implementation, }, }; -use noirc_frontend::hir::def_collector::dc_crate::{UnresolvedFunctions, UnresolvedTraitImpl}; - -use noirc_frontend::macros_api::SortedModule; -use noirc_frontend::macros_api::{CrateId, MacroError}; -use noirc_frontend::macros_api::{FileId, MacroProcessor}; -use noirc_frontend::macros_api::{HirContext, SecondaryAttribute, Span}; +use noirc_frontend::{ + hir::def_collector::dc_crate::{UnresolvedFunctions, UnresolvedTraitImpl}, + macros_api::{ + CrateId, FileId, HirContext, MacroError, MacroProcessor, SecondaryAttribute, SortedModule, + Span, + }, +}; -use utils::ast_utils::is_custom_attribute; -use utils::checks::{check_for_aztec_dependency, has_aztec_dependency}; -use utils::{constants::MAX_CONTRACT_PRIVATE_FUNCTIONS, errors::AztecMacroError}; +use utils::{ + ast_utils::is_custom_attribute, + checks::{check_for_aztec_dependency, has_aztec_dependency}, + constants::MAX_CONTRACT_PRIVATE_FUNCTIONS, + errors::AztecMacroError, +}; pub struct AztecMacro; impl MacroProcessor for AztecMacro { @@ -28,9 +33,10 @@ impl MacroProcessor for AztecMacro { &self, ast: SortedModule, crate_id: &CrateId, + file_id: FileId, context: &HirContext, ) -> Result { - transform(ast, crate_id, context) + transform(ast, crate_id, file_id, context) } fn process_collected_defs( @@ -61,38 +67,34 @@ impl MacroProcessor for AztecMacro { fn transform( mut ast: SortedModule, crate_id: &CrateId, + file_id: FileId, context: &HirContext, ) -> Result { // Usage -> mut ast -> aztec_library::transform(&mut ast) // Covers all functions in the ast for submodule in ast.submodules.iter_mut().filter(|submodule| submodule.is_contract) { - if transform_module(&mut submodule.contents, crate_id, context) - .map_err(|(err, file_id)| (err.into(), file_id))? - { + if transform_module(&mut submodule.contents).map_err(|err| (err.into(), file_id))? { check_for_aztec_dependency(crate_id, context)?; } } + + generate_note_interface_impl(&mut ast).map_err(|err| (err.into(), file_id))?; + Ok(ast) } /// Determines if ast nodes are annotated with aztec attributes. /// For annotated functions it calls the `transform` function which will perform the required transformations. /// Returns true if an annotated node is found, false otherwise -fn transform_module( - module: &mut SortedModule, - crate_id: &CrateId, - context: &HirContext, -) -> Result { +fn transform_module(module: &mut SortedModule) -> Result { let mut has_transformed_module = false; // Check for a user defined storage struct let storage_defined = check_for_storage_definition(module); let storage_implemented = check_for_storage_implementation(module); - let crate_graph = &context.crate_graph[crate_id]; - if storage_defined && !storage_implemented { - generate_storage_implementation(module).map_err(|err| (err, crate_graph.root_file_id))?; + generate_storage_implementation(module)?; } for structure in module.types.iter() { @@ -144,12 +146,10 @@ fn transform_module( is_initializer, insert_init_check, is_internal, - ) - .map_err(|err| (err, crate_graph.root_file_id))?; + )?; has_transformed_module = true; } else if is_public_vm { - transform_vm_function(func, storage_defined) - .map_err(|err| (err, crate_graph.root_file_id))?; + transform_vm_function(func, storage_defined)?; has_transformed_module = true; } else if storage_defined && func.def.is_unconstrained { transform_unconstrained(func); @@ -173,11 +173,9 @@ fn transform_module( .count(); if private_functions_count > MAX_CONTRACT_PRIVATE_FUNCTIONS { - let crate_graph = &context.crate_graph[crate_id]; - return Err(( - AztecMacroError::ContractHasTooManyPrivateFunctions { span: Span::default() }, - crate_graph.root_file_id, - )); + return Err(AztecMacroError::ContractHasTooManyPrivateFunctions { + span: Span::default(), + }); } } diff --git a/noir/noir-repo/aztec_macros/src/transforms/mod.rs b/noir/noir-repo/aztec_macros/src/transforms/mod.rs index 144ffc3efc3..5a454c75148 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/mod.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/mod.rs @@ -1,4 +1,5 @@ pub mod compute_note_hash_and_nullifier; pub mod events; pub mod functions; +pub mod note_interface; pub mod storage; diff --git a/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs b/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs new file mode 100644 index 00000000000..01d0272088b --- /dev/null +++ b/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs @@ -0,0 +1,583 @@ +use noirc_errors::Span; +use noirc_frontend::{ + parse_program, parser::SortedModule, ItemVisibility, NoirFunction, NoirStruct, PathKind, + TraitImplItem, TypeImpl, UnresolvedTypeData, UnresolvedTypeExpression, +}; +use regex::Regex; + +use crate::{ + chained_dep, + utils::{ + ast_utils::{ + check_trait_method_implemented, ident, ident_path, is_custom_attribute, make_type, + }, + errors::AztecMacroError, + }, +}; + +// Automatic implementation of most of the methods in the NoteInterface trait, guiding the user with meaningful error messages in case some +// methods must be implemented manually. +pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), AztecMacroError> { + // Find structs annotated with #[aztec(note)] + let annotated_note_structs = module + .types + .iter_mut() + .filter(|typ| typ.attributes.iter().any(|attr| is_custom_attribute(attr, "aztec(note)"))); + + let mut note_properties_structs = vec![]; + + for note_struct in annotated_note_structs { + // Look for the NoteInterface trait implementation for the note + let trait_impl = module + .trait_impls + .iter_mut() + .find(|trait_impl| { + if let UnresolvedTypeData::Named(struct_path, _, _) = &trait_impl.object_type.typ { + struct_path.last_segment() == note_struct.name + && trait_impl.trait_name.last_segment().0.contents == "NoteInterface" + } else { + false + } + }) + .ok_or(AztecMacroError::CouldNotImplementNoteInterface { + span: Some(note_struct.name.span()), + secondary_message: Some(format!( + "Could not find NoteInterface trait implementation for note: {}", + note_struct.name.0.contents + )), + })?; + let note_interface_impl_span: Option = trait_impl.object_type.span; + // Look for the note struct implementation, generate a default one if it doesn't exist (in order to append methods to it) + let existing_impl = module.impls.iter_mut().find(|r#impl| match &r#impl.object_type.typ { + UnresolvedTypeData::Named(path, _, _) => path.last_segment().eq(¬e_struct.name), + _ => false, + }); + let note_impl = if let Some(note_impl) = existing_impl { + note_impl + } else { + let default_impl = TypeImpl { + object_type: trait_impl.object_type.clone(), + type_span: note_struct.name.span(), + generics: vec![], + methods: vec![], + }; + module.impls.push(default_impl.clone()); + module.impls.last_mut().unwrap() + }; + // Identify the note type (struct name), its fields and its serialized length (generic param of NoteInterface trait impl) + let note_type = note_struct.name.0.contents.to_string(); + let mut note_fields = vec![]; + let note_serialized_len = match &trait_impl.trait_generics[0].typ { + UnresolvedTypeData::Named(path, _, _) => Ok(path.last_segment().0.contents.to_string()), + UnresolvedTypeData::Expression(UnresolvedTypeExpression::Constant(val, _)) => { + Ok(val.to_string()) + } + _ => Err(AztecMacroError::CouldNotImplementNoteInterface { + span: trait_impl.object_type.span, + secondary_message: Some(format!( + "Cannot find note serialization length for: {}", + note_type + )), + }), + }?; + + // Automatically inject the header field if it's not present + let (header_field_name, _) = if let Some(existing_header) = + note_struct.fields.iter().find(|(_, field_type)| match &field_type.typ { + UnresolvedTypeData::Named(path, _, _) => { + path.last_segment().0.contents == "NoteHeader" + } + _ => false, + }) { + existing_header.clone() + } else { + let generated_header = ( + ident("header"), + make_type(UnresolvedTypeData::Named( + chained_dep!("aztec", "note", "note_header", "NoteHeader"), + vec![], + false, + )), + ); + note_struct.fields.push(generated_header.clone()); + generated_header + }; + + for (field_ident, field_type) in note_struct.fields.iter() { + note_fields.push(( + field_ident.0.contents.to_string(), + field_type.typ.to_string().replace("plain::", ""), + )); + } + + if !check_trait_method_implemented(trait_impl, "serialize_content") + && !check_trait_method_implemented(trait_impl, "deserialize_content") + && !note_impl.methods.iter().any(|(func, _)| func.def.name.0.contents == "properties") + { + let note_serialize_content_fn = generate_note_serialize_content( + ¬e_type, + ¬e_fields, + ¬e_serialized_len, + &header_field_name.0.contents, + note_interface_impl_span, + )?; + trait_impl.items.push(TraitImplItem::Function(note_serialize_content_fn)); + + let note_deserialize_content_fn = generate_note_deserialize_content( + ¬e_type, + ¬e_fields, + ¬e_serialized_len, + &header_field_name.0.contents, + note_interface_impl_span, + )?; + trait_impl.items.push(TraitImplItem::Function(note_deserialize_content_fn)); + + let note_properties_struct = generate_note_properties_struct( + ¬e_type, + ¬e_fields, + &header_field_name.0.contents, + note_interface_impl_span, + )?; + note_properties_structs.push(note_properties_struct); + let note_properties_fn = generate_note_properties_fn( + ¬e_type, + ¬e_fields, + &header_field_name.0.contents, + note_interface_impl_span, + )?; + note_impl.methods.push((note_properties_fn, note_impl.type_span)); + } + + if !check_trait_method_implemented(trait_impl, "get_header") { + let get_header_fn = generate_note_get_header( + ¬e_type, + &header_field_name.0.contents, + note_interface_impl_span, + )?; + trait_impl.items.push(TraitImplItem::Function(get_header_fn)); + } + if !check_trait_method_implemented(trait_impl, "set_header") { + let set_header_fn = generate_note_set_header( + ¬e_type, + &header_field_name.0.contents, + note_interface_impl_span, + )?; + trait_impl.items.push(TraitImplItem::Function(set_header_fn)); + } + + if !check_trait_method_implemented(trait_impl, "get_note_type_id") { + let get_note_type_id_fn = + generate_note_get_type_id(¬e_type, note_interface_impl_span)?; + trait_impl.items.push(TraitImplItem::Function(get_note_type_id_fn)); + } + + if !check_trait_method_implemented(trait_impl, "compute_note_content_hash") { + let get_header_fn = + generate_compute_note_content_hash(¬e_type, note_interface_impl_span)?; + trait_impl.items.push(TraitImplItem::Function(get_header_fn)); + } + } + + module.types.extend(note_properties_structs); + Ok(()) +} + +fn generate_note_get_header( + note_type: &String, + note_header_field_name: &String, + impl_span: Option, +) -> Result { + let function_source = format!( + " + fn get_header(note: {}) -> dep::aztec::note::note_header::NoteHeader {{ + note.{} + }} + ", + note_type, note_header_field_name + ) + .to_string(); + + let (function_ast, errors) = parse_program(&function_source); + if !errors.is_empty() { + return Err(AztecMacroError::CouldNotImplementNoteInterface { + secondary_message: Some("Failed to parse Noir macro code (fn get_header). This is either a bug in the compiler or the Noir macro code".to_string()), + span: impl_span + }); + } + + let mut function_ast = function_ast.into_sorted(); + let mut noir_fn = function_ast.functions.remove(0); + noir_fn.def.span = impl_span.unwrap(); + noir_fn.def.visibility = ItemVisibility::Public; + Ok(noir_fn) +} + +fn generate_note_set_header( + note_type: &String, + note_header_field_name: &String, + impl_span: Option, +) -> Result { + let function_source = format!( + " + fn set_header(self: &mut {}, header: dep::aztec::note::note_header::NoteHeader) {{ + self.{} = header; + }} + ", + note_type, note_header_field_name + ); + + let (function_ast, errors) = parse_program(&function_source); + if !errors.is_empty() { + dbg!(errors); + return Err(AztecMacroError::CouldNotImplementNoteInterface { + secondary_message: Some("Failed to parse Noir macro code (fn set_header). This is either a bug in the compiler or the Noir macro code".to_string()), + span: impl_span + }); + } + + let mut function_ast = function_ast.into_sorted(); + let mut noir_fn = function_ast.functions.remove(0); + noir_fn.def.span = impl_span.unwrap(); + noir_fn.def.visibility = ItemVisibility::Public; + Ok(noir_fn) +} + +// Automatically generate the note type id getter method. The id itself its calculated as the concatenation +// of the conversion of the characters in the note's struct name to unsigned integers. +fn generate_note_get_type_id( + note_type: &str, + impl_span: Option, +) -> Result { + // TODO(#4519) Improve automatic note id generation and assignment + let note_id = + note_type.chars().map(|c| (c as u32).to_string()).collect::>().join(""); + let function_source = format!( + " + fn get_note_type_id() -> Field {{ + {} + }} + ", + note_id + ) + .to_string(); + + let (function_ast, errors) = parse_program(&function_source); + if !errors.is_empty() { + dbg!(errors); + return Err(AztecMacroError::CouldNotImplementNoteInterface { + secondary_message: Some("Failed to parse Noir macro code (fn get_note_type_id). This is either a bug in the compiler or the Noir macro code".to_string()), + span: impl_span + }); + } + + let mut function_ast = function_ast.into_sorted(); + let mut noir_fn = function_ast.functions.remove(0); + noir_fn.def.span = impl_span.unwrap(); + noir_fn.def.visibility = ItemVisibility::Public; + Ok(noir_fn) +} + +// Automatically generate a struct that represents the note's serialization metadata, as +// +// NoteTypeFields { +// field1: PropertySelector { index: 0, offset: 0, length: 32 }, +// field2: PropertySelector { index: 1, offset: 0, length: 32 }, +// ... +// } +// +// It assumes each field occupies an entire field and its serialized in definition order +fn generate_note_properties_struct( + note_type: &str, + note_fields: &[(String, String)], + note_header_field_name: &String, + impl_span: Option, +) -> Result { + let struct_source = + generate_note_properties_struct_source(note_type, note_fields, note_header_field_name); + + let (struct_ast, errors) = parse_program(&struct_source); + if !errors.is_empty() { + dbg!(errors); + return Err(AztecMacroError::CouldNotImplementNoteInterface { + secondary_message: Some(format!("Failed to parse Noir macro code (struct {}Properties). This is either a bug in the compiler or the Noir macro code", note_type)), + span: impl_span + }); + } + + let mut struct_ast = struct_ast.into_sorted(); + Ok(struct_ast.types.remove(0)) +} + +// Generate the deserialize_content method as +// +// fn deserialize_content(serialized_note: [Field; NOTE_SERILIZED_LEN]) -> Self { +// NoteType { +// note_field1: serialized_note[0] as Field, +// note_field2: NoteFieldType2::from_field(serialized_note[1])... +// } +// } +// It assumes every note field is stored in an individual serialized field, +// and can be converted to the original type via the from_field() trait (structs) or cast as Field (integers) +fn generate_note_deserialize_content( + note_type: &str, + note_fields: &[(String, String)], + note_serialize_len: &String, + note_header_field_name: &String, + impl_span: Option, +) -> Result { + let function_source = generate_note_deserialize_content_source( + note_type, + note_fields, + note_serialize_len, + note_header_field_name, + ); + + let (function_ast, errors) = parse_program(&function_source); + if !errors.is_empty() { + dbg!(errors); + return Err(AztecMacroError::CouldNotImplementNoteInterface { + secondary_message: Some("Failed to parse Noir macro code (fn deserialize_content). This is either a bug in the compiler or the Noir macro code".to_string()), + span: impl_span + }); + } + + let mut function_ast = function_ast.into_sorted(); + let mut noir_fn = function_ast.functions.remove(0); + noir_fn.def.span = impl_span.unwrap(); + noir_fn.def.visibility = ItemVisibility::Public; + Ok(noir_fn) +} + +// Generate the serialize_content method as +// +// fn serialize_content(self: {}) -> [Field; NOTE_SERIALIZED_LEN] { +// [self.note_field1 as Field, self.note_field2.to_field()...] +// } +// +// It assumes every struct field can be converted either via the to_field() trait (structs) or cast as Field (integers) +fn generate_note_serialize_content( + note_type: &str, + note_fields: &[(String, String)], + note_serialize_len: &String, + note_header_field_name: &String, + impl_span: Option, +) -> Result { + let function_source = generate_note_serialize_content_source( + note_type, + note_fields, + note_serialize_len, + note_header_field_name, + ); + + let (function_ast, errors) = parse_program(&function_source); + if !errors.is_empty() { + dbg!(errors); + return Err(AztecMacroError::CouldNotImplementNoteInterface { + secondary_message: Some("Failed to parse Noir macro code (fn serialize_content). This is either a bug in the compiler or the Noir macro code".to_string()), + span: impl_span + }); + } + + let mut function_ast = function_ast.into_sorted(); + let mut noir_fn = function_ast.functions.remove(0); + noir_fn.def.span = impl_span.unwrap(); + noir_fn.def.visibility = ItemVisibility::Public; + Ok(noir_fn) +} + +// Automatically generate a function in the Note's impl that returns the note's fields metadata +fn generate_note_properties_fn( + note_type: &str, + note_fields: &[(String, String)], + note_header_field_name: &String, + impl_span: Option, +) -> Result { + let function_source = + generate_note_properties_fn_source(note_type, note_fields, note_header_field_name); + let (function_ast, errors) = parse_program(&function_source); + if !errors.is_empty() { + dbg!(errors); + return Err(AztecMacroError::CouldNotImplementNoteInterface { + secondary_message: Some("Failed to parse Noir macro code (fn properties). This is either a bug in the compiler or the Noir macro code".to_string()), + span: impl_span + }); + } + let mut function_ast = function_ast.into_sorted(); + let mut noir_fn = function_ast.functions.remove(0); + noir_fn.def.span = impl_span.unwrap(); + noir_fn.def.visibility = ItemVisibility::Public; + Ok(noir_fn) +} + +// Automatically generate the method to compute the note's content hash as: +// fn compute_note_content_hash(self: NoteType) -> Field { +// // TODO(#1205) Should use a non-zero generator index. +// dep::aztec::hash::pedersen_hash(self.serialize_content(), 0) +// } +// +fn generate_compute_note_content_hash( + note_type: &String, + impl_span: Option, +) -> Result { + let function_source = format!( + " + fn compute_note_content_hash(self: {}) -> Field {{ + // TODO(#1205) Should use a non-zero generator index. + dep::aztec::hash::pedersen_hash(self.serialize_content(), 0) + }} + ", + note_type + ); + let (function_ast, errors) = parse_program(&function_source); + if !errors.is_empty() { + dbg!(errors); + return Err(AztecMacroError::CouldNotImplementNoteInterface { + secondary_message: Some("Failed to parse Noir macro code (fn compute_note_content_hash). This is either a bug in the compiler or the Noir macro code".to_string()), + span: impl_span + }); + } + let mut function_ast = function_ast.into_sorted(); + let mut noir_fn = function_ast.functions.remove(0); + noir_fn.def.span = impl_span.unwrap(); + noir_fn.def.visibility = ItemVisibility::Public; + Ok(noir_fn) +} + +// Source code generator functions. These utility methods produce Noir code as strings, that are then parsed and added to the AST. + +fn generate_note_properties_struct_source( + note_type: &str, + note_fields: &[(String, String)], + note_header_field_name: &String, +) -> String { + let note_property_selectors = note_fields + .iter() + .filter_map(|(field_name, _)| { + if field_name != note_header_field_name { + Some(format!( + "{}: dep::aztec::note::note_getter_options::PropertySelector", + field_name + )) + } else { + None + } + }) + .collect::>() + .join(",\n"); + format!( + " + struct {}Properties {{ + {} + }}", + note_type, note_property_selectors + ) + .to_string() +} + +fn generate_note_properties_fn_source( + note_type: &str, + note_fields: &[(String, String)], + note_header_field_name: &String, +) -> String { + let note_property_selectors = note_fields + .iter() + .enumerate() + .filter_map(|(index, (field_name, _))| { + if field_name != note_header_field_name { + Some(format!( + "{}: dep::aztec::note::note_getter_options::PropertySelector {{ index: {}, offset: 0, length: 32 }}", + field_name, + index + )) + } else { + None + } + }) + .collect::>() + .join(", "); + format!( + " + pub fn properties() -> {}Properties {{ + {}Properties {{ + {} + }} + }}", + note_type, note_type, note_property_selectors + ) + .to_string() +} + +fn generate_note_serialize_content_source( + note_type: &str, + note_fields: &[(String, String)], + note_serialize_len: &String, + note_header_field_name: &String, +) -> String { + let note_fields = note_fields + .iter() + .filter_map(|(field_name, field_type)| { + if field_name != note_header_field_name { + if field_type == "Field" { + Some(format!("self.{}", field_name)) + } else { + Some(format!("self.{}.to_field()", field_name)) + } + } else { + None + } + }) + .collect::>() + .join(", "); + format!( + " + fn serialize_content(self: {}) -> [Field; {}] {{ + [{}] + }}", + note_type, note_serialize_len, note_fields + ) + .to_string() +} + +fn generate_note_deserialize_content_source( + note_type: &str, + note_fields: &[(String, String)], + note_serialize_len: &String, + note_header_field_name: &String, +) -> String { + let note_fields = note_fields + .iter() + .enumerate() + .map(|(index, (field_name, field_type))| { + if field_name != note_header_field_name { + // TODO: Simplify this when https://github.com/noir-lang/noir/issues/4463 is fixed + if field_type.eq("Field") + || Regex::new(r"u[0-9]+").unwrap().is_match(field_type) + || field_type.eq("bool") + { + format!("{}: serialized_note[{}] as {},", field_name, index, field_type) + } else { + format!( + "{}: {}::from_field(serialized_note[{}]),", + field_name, field_type, index + ) + } + } else { + format!( + "{}: dep::aztec::note::note_header::NoteHeader::empty()", + note_header_field_name + ) + } + }) + .collect::>() + .join("\n"); + format!( + " + fn deserialize_content(serialized_note: [Field; {}]) -> Self {{ + {} {{ + {} + }} + }}", + note_serialize_len, note_type, note_fields + ) + .to_string() +} diff --git a/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs b/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs index 71c6a93f388..bdcbad646c2 100644 --- a/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs +++ b/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs @@ -2,8 +2,9 @@ use noirc_errors::{Span, Spanned}; use noirc_frontend::{ token::SecondaryAttribute, BinaryOpKind, CallExpression, CastExpression, Expression, ExpressionKind, FunctionReturnType, Ident, IndexExpression, InfixExpression, Lambda, - LetStatement, MemberAccessExpression, MethodCallExpression, Path, Pattern, PrefixExpression, - Statement, StatementKind, UnaryOp, UnresolvedType, UnresolvedTypeData, + LetStatement, MemberAccessExpression, MethodCallExpression, NoirTraitImpl, Path, Pattern, + PrefixExpression, Statement, StatementKind, TraitImplItem, UnaryOp, UnresolvedType, + UnresolvedTypeData, }; // @@ -173,6 +174,13 @@ pub fn index_array_variable(array: Expression, index: &str) -> Expression { }))) } +pub fn check_trait_method_implemented(trait_impl: &NoirTraitImpl, method_name: &str) -> bool { + trait_impl.items.iter().any(|item| match item { + TraitImplItem::Function(func) => func.def.name.0.contents == method_name, + _ => false, + }) +} + /// Checks if an attribute is a custom attribute with a specific name pub fn is_custom_attribute(attr: &SecondaryAttribute, attribute_name: &str) -> bool { if let SecondaryAttribute::Custom(custom_attr) = attr { diff --git a/noir/noir-repo/aztec_macros/src/utils/errors.rs b/noir/noir-repo/aztec_macros/src/utils/errors.rs index 199473baec6..48186555eff 100644 --- a/noir/noir-repo/aztec_macros/src/utils/errors.rs +++ b/noir/noir-repo/aztec_macros/src/utils/errors.rs @@ -10,7 +10,7 @@ pub enum AztecMacroError { UnsupportedFunctionArgumentType { span: Span, typ: UnresolvedTypeData }, UnsupportedStorageType { span: Option, typ: UnresolvedTypeData }, CouldNotAssignStorageSlots { secondary_message: Option }, - CouldNotImplementNoteSerialization { span: Option, typ: UnresolvedTypeData }, + CouldNotImplementNoteInterface { span: Option, secondary_message: Option }, EventError { span: Span, message: String }, UnsupportedAttributes { span: Span, secondary_message: Option }, } @@ -43,9 +43,9 @@ impl From for MacroError { secondary_message, span: None, }, - AztecMacroError::CouldNotImplementNoteSerialization { span, typ } => MacroError { - primary_message: format!("Could not implement serialization methods for note `{typ:?}`, please provide a serialize_content and deserialize_content methods"), - secondary_message: None, + AztecMacroError::CouldNotImplementNoteInterface { span, secondary_message } => MacroError { + primary_message: "Could not implement automatic methods for note, please provide an implementation of the NoteInterface trait".to_string(), + secondary_message, span, }, AztecMacroError::EventError { span, message } => MacroError { @@ -53,7 +53,7 @@ impl From for MacroError { secondary_message: None, span: Some(span), }, -AztecMacroError::UnsupportedAttributes { span, secondary_message } => MacroError { + AztecMacroError::UnsupportedAttributes { span, secondary_message } => MacroError { primary_message: "Unsupported attributes in contract function".to_string(), secondary_message, span: Some(span), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index 5adb9eb5b7e..fcb20c740c7 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -577,7 +577,12 @@ impl<'a> ModCollector<'a> { let mut ast = ast.into_sorted(); for macro_processor in macro_processors { - match macro_processor.process_untyped_ast(ast.clone(), &crate_id, context) { + match macro_processor.process_untyped_ast( + ast.clone(), + &crate_id, + child_file_id, + context, + ) { Ok(processed_ast) => { ast = processed_ast; } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs index 1326ffca9f7..157227f763e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -91,7 +91,8 @@ impl CrateDefMap { let mut ast = ast.into_sorted(); for macro_processor in macro_processors { - match macro_processor.process_untyped_ast(ast.clone(), &crate_id, context) { + match macro_processor.process_untyped_ast(ast.clone(), &crate_id, root_file_id, context) + { Ok(processed_ast) => { ast = processed_ast; } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lib.rs b/noir/noir-repo/compiler/noirc_frontend/src/lib.rs index 1871b594ae7..6ce6f4325e4 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lib.rs @@ -72,6 +72,7 @@ pub mod macros_api { &self, ast: SortedModule, crate_id: &CrateId, + file_id: FileId, context: &HirContext, ) -> Result; diff --git a/yarn-project/simulator/src/acvm/oracle/oracle.ts b/yarn-project/simulator/src/acvm/oracle/oracle.ts index 356691b00ce..17510858cfc 100644 --- a/yarn-project/simulator/src/acvm/oracle/oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/oracle.ts @@ -160,10 +160,14 @@ export class Oracle { async getNotes( [storageSlot]: ACVMField[], [numSelects]: ACVMField[], - selectBy: ACVMField[], + selectByIndexes: ACVMField[], + selectByOffsets: ACVMField[], + selectByLengths: ACVMField[], selectValues: ACVMField[], selectComparators: ACVMField[], - sortBy: ACVMField[], + sortByIndexes: ACVMField[], + sortByOffsets: ACVMField[], + sortByLengths: ACVMField[], sortOrder: ACVMField[], [limit]: ACVMField[], [offset]: ACVMField[], @@ -173,10 +177,14 @@ export class Oracle { const noteDatas = await this.typedOracle.getNotes( fromACVMField(storageSlot), +numSelects, - selectBy.map(s => +s), + selectByIndexes.map(s => +s), + selectByOffsets.map(s => +s), + selectByLengths.map(s => +s), selectValues.map(fromACVMField), selectComparators.map(s => +s), - sortBy.map(s => +s), + sortByIndexes.map(s => +s), + sortByOffsets.map(s => +s), + sortByLengths.map(s => +s), sortOrder.map(s => +s), +limit, +offset, diff --git a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts index a9f86711391..83a75f7d339 100644 --- a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts @@ -143,10 +143,14 @@ export abstract class TypedOracle { getNotes( _storageSlot: Fr, _numSelects: number, - _selectBy: number[], + _selectByIndexes: number[], + _selectByOffsets: number[], + _selectByLengths: number[], _selectValues: Fr[], _selectComparators: number[], - _sortBy: number[], + _sortByIndexes: number[], + _sortByOffsets: number[], + _sortByLengths: number[], _sortOrder: number[], _limit: number, _offset: number, diff --git a/yarn-project/simulator/src/client/client_execution_context.ts b/yarn-project/simulator/src/client/client_execution_context.ts index cfb3db3139d..2c1b9571a87 100644 --- a/yarn-project/simulator/src/client/client_execution_context.ts +++ b/yarn-project/simulator/src/client/client_execution_context.ts @@ -199,10 +199,14 @@ export class ClientExecutionContext extends ViewDataOracle { public async getNotes( storageSlot: Fr, numSelects: number, - selectBy: number[], + selectByIndexes: number[], + selectByOffsets: number[], + selectByLengths: number[], selectValues: Fr[], selectComparators: number[], - sortBy: number[], + sortByIndexes: number[], + sortByOffsets: number[], + sortByLengths: number[], sortOrder: number[], limit: number, offset: number, @@ -216,10 +220,15 @@ export class ClientExecutionContext extends ViewDataOracle { const dbNotesFiltered = dbNotes.filter(n => !pendingNullifiers.has((n.siloedNullifier as Fr).value)); const notes = pickNotes([...dbNotesFiltered, ...pendingNotes], { - selects: selectBy - .slice(0, numSelects) - .map((index, i) => ({ index, value: selectValues[i], comparator: selectComparators[i] })), - sorts: sortBy.map((index, i) => ({ index, order: sortOrder[i] })), + selects: selectByIndexes.slice(0, numSelects).map((index, i) => ({ + selector: { index, offset: selectByOffsets[i], length: selectByLengths[i] }, + value: selectValues[i], + comparator: selectComparators[i], + })), + sorts: sortByIndexes.map((index, i) => ({ + selector: { index, offset: sortByOffsets[i], length: sortByLengths[i] }, + order: sortOrder[i], + })), limit, offset, }); diff --git a/yarn-project/simulator/src/client/pick_notes.test.ts b/yarn-project/simulator/src/client/pick_notes.test.ts index ccd73a4a117..50c8312f831 100644 --- a/yarn-project/simulator/src/client/pick_notes.test.ts +++ b/yarn-project/simulator/src/client/pick_notes.test.ts @@ -36,14 +36,14 @@ describe('getNotes', () => { // Sort 1st field in ascending order. { - const options = { sorts: [{ index: 1, order: SortOrder.ASC }] }; + const options = { sorts: [{ selector: { index: 1, offset: 0, length: 32 }, order: SortOrder.ASC }] }; const result = pickNotes(notes, options); expectNotesFields(result, [1, [0n, 1n, 5n, 5n, 5n, 6n]]); } // Sort 1st field in descending order. { - const options = { sorts: [{ index: 1, order: SortOrder.DESC }] }; + const options = { sorts: [{ selector: { index: 1, offset: 0, length: 32 }, order: SortOrder.DESC }] }; const result = pickNotes(notes, options); expectNotesFields(result, [1, [6n, 5n, 5n, 5n, 1n, 0n]], [0, [7n, 4n, 6n, 6n, 2n, 0n]]); } @@ -52,8 +52,8 @@ describe('getNotes', () => { { const options = { sorts: [ - { index: 1, order: SortOrder.DESC }, - { index: 0, order: SortOrder.DESC }, + { selector: { index: 1, offset: 0, length: 32 }, order: SortOrder.DESC }, + { selector: { index: 0, offset: 0, length: 32 }, order: SortOrder.DESC }, ], }; const result = pickNotes(notes, options); @@ -65,8 +65,8 @@ describe('getNotes', () => { { const options = { sorts: [ - { index: 1, order: SortOrder.DESC }, - { index: 0, order: SortOrder.ASC }, + { selector: { index: 1, offset: 0, length: 32 }, order: SortOrder.DESC }, + { selector: { index: 0, offset: 0, length: 32 }, order: SortOrder.ASC }, ], }; const result = pickNotes(notes, options); @@ -84,9 +84,9 @@ describe('getNotes', () => { { const options = { sorts: [ - { index: 1, order: SortOrder.DESC }, - { index: 0, order: SortOrder.ASC }, - { index: 2, order: SortOrder.DESC }, + { selector: { index: 1, offset: 0, length: 32 }, order: SortOrder.DESC }, + { selector: { index: 0, offset: 0, length: 32 }, order: SortOrder.ASC }, + { selector: { index: 2, offset: 0, length: 32 }, order: SortOrder.DESC }, ], }; const result = pickNotes(notes, options); @@ -102,7 +102,7 @@ describe('getNotes', () => { it('should get sorted notes in a range', () => { const notes = [createNote([2n]), createNote([8n]), createNote([6n]), createNote([5n]), createNote([0n])]; - const sorts = [{ index: 0, order: SortOrder.DESC }]; + const sorts = [{ selector: { index: 0, offset: 0, length: 32 }, order: SortOrder.DESC }]; // Sorted values: [8n, 6n, 5n, 2n, 0n] { @@ -126,7 +126,7 @@ describe('getNotes', () => { it('should not change order if sortOrder is NADA', () => { const notes = [createNote([2n]), createNote([8n]), createNote([6n]), createNote([5n]), createNote([0n])]; - const options = { sorts: [{ index: 0, order: SortOrder.NADA }] }; + const options = { sorts: [{ selector: { index: 0, offset: 0, length: 32 }, order: SortOrder.NADA }] }; const result = pickNotes(notes, options); expectNotesFields(result, [0, [2n, 8n, 6n, 5n, 0n]]); }); @@ -141,7 +141,9 @@ describe('getNotes', () => { ]; { - const options = { selects: [{ index: 0, value: new Fr(2n), comparator: Comparator.EQ }] }; + const options = { + selects: [{ selector: { index: 0, offset: 0, length: 32 }, value: new Fr(2n), comparator: Comparator.EQ }], + }; const result = pickNotes(notes, options); expectNotes(result, [ [2n, 1n, 3n], @@ -153,8 +155,8 @@ describe('getNotes', () => { { const options = { selects: [ - { index: 0, value: new Fr(2n), comparator: Comparator.EQ }, - { index: 2, value: new Fr(3n), comparator: Comparator.EQ }, + { selector: { index: 0, offset: 0, length: 32 }, value: new Fr(2n), comparator: Comparator.EQ }, + { selector: { index: 2, offset: 0, length: 32 }, value: new Fr(3n), comparator: Comparator.EQ }, ], }; const result = pickNotes(notes, options); @@ -167,8 +169,8 @@ describe('getNotes', () => { { const options = { selects: [ - { index: 1, value: new Fr(2n), comparator: Comparator.EQ }, - { index: 2, value: new Fr(3n), comparator: Comparator.EQ }, + { selector: { index: 1, offset: 0, length: 32 }, value: new Fr(2n), comparator: Comparator.EQ }, + { selector: { index: 2, offset: 0, length: 32 }, value: new Fr(3n), comparator: Comparator.EQ }, ], }; const result = pickNotes(notes, options); @@ -176,7 +178,9 @@ describe('getNotes', () => { } { - const options = { selects: [{ index: 1, value: new Fr(5n), comparator: Comparator.EQ }] }; + const options = { + selects: [{ selector: { index: 1, offset: 0, length: 32 }, value: new Fr(5n), comparator: Comparator.EQ }], + }; const result = pickNotes(notes, options); expectNotes(result, []); } @@ -184,8 +188,8 @@ describe('getNotes', () => { { const options = { selects: [ - { index: 0, value: new Fr(2n), comparator: Comparator.EQ }, - { index: 1, value: new Fr(5n), comparator: Comparator.EQ }, + { selector: { index: 0, offset: 0, length: 32 }, value: new Fr(2n), comparator: Comparator.EQ }, + { selector: { index: 1, offset: 0, length: 32 }, value: new Fr(5n), comparator: Comparator.EQ }, ], }; const result = pickNotes(notes, options); @@ -204,8 +208,8 @@ describe('getNotes', () => { ]; const options = { - selects: [{ index: 2, value: new Fr(8n), comparator: Comparator.EQ }], - sorts: [{ index: 1, order: SortOrder.ASC }], + selects: [{ selector: { index: 2, offset: 0, length: 32 }, value: new Fr(8n), comparator: Comparator.EQ }], + sorts: [{ selector: { index: 1, offset: 0, length: 32 }, order: SortOrder.ASC }], }; const result = pickNotes(notes, options); expectNotes(result, [ @@ -229,19 +233,19 @@ describe('getNotes', () => { const options = { selects: [ { - index: 2, + selector: { index: 2, offset: 0, length: 32 }, value: new Fr(7n), comparator: Comparator.GTE, }, { - index: 2, + selector: { index: 2, offset: 0, length: 32 }, value: new Fr(8n), comparator: Comparator.LTE, }, ], sorts: [ { - index: 1, + selector: { index: 1, offset: 0, length: 32 }, order: SortOrder.ASC, }, ], @@ -268,14 +272,14 @@ describe('getNotes', () => { const options1 = { selects: [ { - index: 2, + selector: { index: 2, offset: 0, length: 32 }, value: new Fr(3n), comparator: Comparator.GT, }, ], sorts: [ { - index: 1, + selector: { index: 1, offset: 0, length: 32 }, order: SortOrder.ASC, }, ], @@ -292,14 +296,14 @@ describe('getNotes', () => { const options2 = { selects: [ { - index: 2, + selector: { index: 2, offset: 0, length: 32 }, value: new Fr(4n), comparator: Comparator.LT, }, ], sorts: [ { - index: 1, + selector: { index: 1, offset: 0, length: 32 }, order: SortOrder.ASC, }, ], diff --git a/yarn-project/simulator/src/client/pick_notes.ts b/yarn-project/simulator/src/client/pick_notes.ts index d054a226e39..4531a487ef6 100644 --- a/yarn-project/simulator/src/client/pick_notes.ts +++ b/yarn-project/simulator/src/client/pick_notes.ts @@ -1,14 +1,20 @@ import { Comparator, Note } from '@aztec/circuit-types'; import { Fr } from '@aztec/foundation/fields'; +export interface PropertySelector { + index: number; + offset: number; + length: number; +} + /** * Configuration for selecting values. */ export interface Select { /** - * Index of the field to select and match. + * Selector of the field to select and match. */ - index: number; + selector: PropertySelector; /** * Required value of the field. */ @@ -33,9 +39,9 @@ export enum SortOrder { */ export interface Sort { /** - * Index of the field to sort. + * Selector of the field to sort. */ - index: number; + selector: PropertySelector; /** * Order to sort the field. */ @@ -78,16 +84,23 @@ interface ContainsNote { note: Note; } +const selectPropertyFromSerializedNote = (noteData: Fr[], selector: PropertySelector): Fr => { + const noteValueBuffer = noteData[selector.index].toBuffer(); + const noteValue = noteValueBuffer.subarray(selector.offset, selector.offset + selector.length); + return Fr.fromBuffer(noteValue); +}; + const selectNotes = (noteDatas: T[], selects: Select[]): T[] => noteDatas.filter(noteData => - selects.every(({ index, value, comparator }) => { + selects.every(({ selector, value, comparator }) => { + const noteValueFr = selectPropertyFromSerializedNote(noteData.note.items, selector); const comparatorSelector = { - [Comparator.EQ]: () => noteData.note.items[index].equals(value), - [Comparator.NEQ]: () => !noteData.note.items[index].equals(value), - [Comparator.LT]: () => noteData.note.items[index].lt(value), - [Comparator.LTE]: () => noteData.note.items[index].lt(value) || noteData.note.items[index].equals(value), - [Comparator.GT]: () => !noteData.note.items[index].lt(value) && !noteData.note.items[index].equals(value), - [Comparator.GTE]: () => !noteData.note.items[index].lt(value), + [Comparator.EQ]: () => noteValueFr.equals(value), + [Comparator.NEQ]: () => !noteValueFr.equals(value), + [Comparator.LT]: () => noteValueFr.lt(value), + [Comparator.LTE]: () => noteValueFr.lt(value) || noteValueFr.equals(value), + [Comparator.GT]: () => !noteValueFr.lt(value) && !noteValueFr.equals(value), + [Comparator.GTE]: () => !noteValueFr.lt(value), }; return comparatorSelector[comparator](); @@ -99,15 +112,18 @@ const sortNotes = (a: Fr[], b: Fr[], sorts: Sort[], level = 0): number => { return 0; } - const { index, order } = sorts[level]; + const { selector, order } = sorts[level]; if (order === 0) { return 0; } + const aValue = selectPropertyFromSerializedNote(a, selector); + const bValue = selectPropertyFromSerializedNote(b, selector); + const dir = order === 1 ? [-1, 1] : [1, -1]; - return a[index].value === b[index].value + return aValue.toBigInt() === bValue.toBigInt() ? sortNotes(a, b, sorts, level + 1) - : a[index].value > b[index].value + : aValue.toBigInt() > bValue.toBigInt() ? dir[0] : dir[1]; }; diff --git a/yarn-project/simulator/src/client/view_data_oracle.ts b/yarn-project/simulator/src/client/view_data_oracle.ts index 4c953463ef5..40707da52b7 100644 --- a/yarn-project/simulator/src/client/view_data_oracle.ts +++ b/yarn-project/simulator/src/client/view_data_oracle.ts @@ -184,10 +184,14 @@ export class ViewDataOracle extends TypedOracle { public async getNotes( storageSlot: Fr, numSelects: number, - selectBy: number[], + selectByIndexes: number[], + selectByOffsets: number[], + selectByLengths: number[], selectValues: Fr[], selectComparators: number[], - sortBy: number[], + sortByIndexes: number[], + sortByOffsets: number[], + sortByLengths: number[], sortOrder: number[], limit: number, offset: number, @@ -195,10 +199,15 @@ export class ViewDataOracle extends TypedOracle { ): Promise { const dbNotes = await this.db.getNotes(this.contractAddress, storageSlot, status); return pickNotes(dbNotes, { - selects: selectBy - .slice(0, numSelects) - .map((index, i) => ({ index, value: selectValues[i], comparator: selectComparators[i] })), - sorts: sortBy.map((index, i) => ({ index, order: sortOrder[i] })), + selects: selectByIndexes.slice(0, numSelects).map((index, i) => ({ + selector: { index, offset: selectByOffsets[i], length: selectByLengths[i] }, + value: selectValues[i], + comparator: selectComparators[i], + })), + sorts: sortByIndexes.map((index, i) => ({ + selector: { index, offset: sortByOffsets[i], length: sortByLengths[i] }, + order: sortOrder[i], + })), limit, offset, });