diff --git a/boxes/token/src/contracts/src/main.nr b/boxes/token/src/contracts/src/main.nr index da7b7cb74ca..673ee997663 100644 --- a/boxes/token/src/contracts/src/main.nr +++ b/boxes/token/src/contracts/src/main.nr @@ -34,7 +34,7 @@ contract Token { address::AztecAddress, } }; - + // docs:start:import_authwit use dep::authwit::{ auth::{ @@ -246,9 +246,9 @@ contract Token { fn redeem_shield(to: AztecAddress, amount: Field, secret: Field) { let pending_shields = storage.pending_shields; let secret_hash = compute_secret_hash(secret); - // Get 1 note (set_limit(1)) which has amount stored in field with index 0 (select(0, amount)) and secret_hash - // stored in field with index 1 (select(1, secret_hash)). - let options = NoteGetterOptions::new().select(0, amount).select(1, secret_hash).set_limit(1); + // Get 1 note (set_limit(1)) which has amount stored in field with index 0 (select(0, amount, Option::none())) and secret_hash + // stored in field with index 1 (select(1, secret_hash, Option::none())). + let options = NoteGetterOptions::new().select(0, amount, Option::none()).select(1, 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/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts b/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts index 020e1738776..402bfe97a09 100644 --- a/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts +++ b/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts @@ -167,6 +167,7 @@ export class Oracle { [numSelects]: ACVMField[], selectBy: ACVMField[], selectValues: ACVMField[], + selectComparators: ACVMField[], sortBy: ACVMField[], sortOrder: ACVMField[], [limit]: ACVMField[], @@ -178,6 +179,7 @@ export class Oracle { +numSelects, selectBy.map(s => +s), selectValues.map(fromACVMField), + selectComparators.map(s => +s), sortBy.map(s => +s), sortOrder.map(s => +s), +limit, diff --git a/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts b/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts index 27d9f8d5f30..4fd25983f9b 100644 --- a/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts +++ b/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts @@ -137,6 +137,7 @@ export abstract class TypedOracle { _numSelects: number, _selectBy: number[], _selectValues: Fr[], + _selectComparators: number[], _sortBy: number[], _sortOrder: number[], _limit: number, diff --git a/yarn-project/acir-simulator/src/client/client_execution_context.ts b/yarn-project/acir-simulator/src/client/client_execution_context.ts index 062cfdf4def..78dcc68d139 100644 --- a/yarn-project/acir-simulator/src/client/client_execution_context.ts +++ b/yarn-project/acir-simulator/src/client/client_execution_context.ts @@ -185,6 +185,7 @@ export class ClientExecutionContext extends ViewDataOracle { * @param numSelects - The number of valid selects in selectBy and selectValues. * @param selectBy - An array of indices of the fields to selects. * @param selectValues - The values to match. + * @param selectComparators - The comparators to match by. * @param sortBy - An array of indices of the fields to sort. * @param sortOrder - The order of the corresponding index in sortBy. (1: DESC, 2: ASC, 0: Do nothing) * @param limit - The number of notes to retrieve per query. @@ -196,6 +197,7 @@ export class ClientExecutionContext extends ViewDataOracle { numSelects: number, selectBy: number[], selectValues: Fr[], + selectComparators: number[], sortBy: number[], sortOrder: number[], limit: number, @@ -209,7 +211,9 @@ 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] })), + selects: selectBy + .slice(0, numSelects) + .map((index, i) => ({ index, value: selectValues[i], comparator: selectComparators[i] })), sorts: sortBy.map((index, i) => ({ index, order: sortOrder[i] })), limit, offset, diff --git a/yarn-project/acir-simulator/src/client/pick_notes.test.ts b/yarn-project/acir-simulator/src/client/pick_notes.test.ts index bf2ad610718..ccd73a4a117 100644 --- a/yarn-project/acir-simulator/src/client/pick_notes.test.ts +++ b/yarn-project/acir-simulator/src/client/pick_notes.test.ts @@ -1,4 +1,4 @@ -import { Note } from '@aztec/circuit-types'; +import { Comparator, Note } from '@aztec/circuit-types'; import { Fr } from '@aztec/foundation/fields'; import { SortOrder, pickNotes } from './pick_notes.js'; @@ -141,7 +141,7 @@ describe('getNotes', () => { ]; { - const options = { selects: [{ index: 0, value: new Fr(2n) }] }; + const options = { selects: [{ index: 0, value: new Fr(2n), comparator: Comparator.EQ }] }; const result = pickNotes(notes, options); expectNotes(result, [ [2n, 1n, 3n], @@ -153,8 +153,8 @@ describe('getNotes', () => { { const options = { selects: [ - { index: 0, value: new Fr(2n) }, - { index: 2, value: new Fr(3n) }, + { index: 0, value: new Fr(2n), comparator: Comparator.EQ }, + { index: 2, value: new Fr(3n), comparator: Comparator.EQ }, ], }; const result = pickNotes(notes, options); @@ -167,8 +167,8 @@ describe('getNotes', () => { { const options = { selects: [ - { index: 1, value: new Fr(2n) }, - { index: 2, value: new Fr(3n) }, + { index: 1, value: new Fr(2n), comparator: Comparator.EQ }, + { index: 2, value: new Fr(3n), comparator: Comparator.EQ }, ], }; const result = pickNotes(notes, options); @@ -176,7 +176,7 @@ describe('getNotes', () => { } { - const options = { selects: [{ index: 1, value: new Fr(5n) }] }; + const options = { selects: [{ index: 1, value: new Fr(5n), comparator: Comparator.EQ }] }; const result = pickNotes(notes, options); expectNotes(result, []); } @@ -184,8 +184,8 @@ describe('getNotes', () => { { const options = { selects: [ - { index: 0, value: new Fr(2n) }, - { index: 1, value: new Fr(5n) }, + { index: 0, value: new Fr(2n), comparator: Comparator.EQ }, + { index: 1, value: new Fr(5n), comparator: Comparator.EQ }, ], }; const result = pickNotes(notes, options); @@ -203,7 +203,10 @@ describe('getNotes', () => { createNote([6n, 5n, 8n]), ]; - const options = { selects: [{ index: 2, value: new Fr(8n) }], sorts: [{ index: 1, order: SortOrder.ASC }] }; + const options = { + selects: [{ index: 2, value: new Fr(8n), comparator: Comparator.EQ }], + sorts: [{ index: 1, order: SortOrder.ASC }], + }; const result = pickNotes(notes, options); expectNotes(result, [ [0n, 0n, 8n], @@ -212,4 +215,102 @@ describe('getNotes', () => { [7n, 6n, 8n], ]); }); + + it('should get sorted matching notes with GTE and LTE', () => { + const notes = [ + createNote([2n, 1n, 3n]), + createNote([4n, 5n, 8n]), + createNote([7n, 6n, 8n]), + createNote([6n, 5n, 2n]), + createNote([0n, 0n, 8n]), + createNote([6n, 5n, 8n]), + ]; + + const options = { + selects: [ + { + index: 2, + value: new Fr(7n), + comparator: Comparator.GTE, + }, + { + index: 2, + value: new Fr(8n), + comparator: Comparator.LTE, + }, + ], + sorts: [ + { + index: 1, + order: SortOrder.ASC, + }, + ], + }; + const result = pickNotes(notes, options); + expectNotes(result, [ + [0n, 0n, 8n], + [4n, 5n, 8n], + [6n, 5n, 8n], + [7n, 6n, 8n], + ]); + }); + + it('should get sorted matching notes with GTE and LTE', () => { + const notes = [ + createNote([2n, 1n, 1n]), + createNote([4n, 5n, 2n]), + createNote([7n, 6n, 3n]), + createNote([6n, 5n, 4n]), + createNote([0n, 0n, 5n]), + createNote([6n, 5n, 6n]), + ]; + + const options1 = { + selects: [ + { + index: 2, + value: new Fr(3n), + comparator: Comparator.GT, + }, + ], + sorts: [ + { + index: 1, + order: SortOrder.ASC, + }, + ], + }; + + const result1 = pickNotes(notes, options1); + + expectNotes(result1, [ + [0n, 0n, 5n], + [6n, 5n, 4n], + [6n, 5n, 6n], + ]); + + const options2 = { + selects: [ + { + index: 2, + value: new Fr(4n), + comparator: Comparator.LT, + }, + ], + sorts: [ + { + index: 1, + order: SortOrder.ASC, + }, + ], + }; + + const result2 = pickNotes(notes, options2); + + expectNotes(result2, [ + [2n, 1n, 1n], + [4n, 5n, 2n], + [7n, 6n, 3n], + ]); + }); }); diff --git a/yarn-project/acir-simulator/src/client/pick_notes.ts b/yarn-project/acir-simulator/src/client/pick_notes.ts index a964cf35177..d054a226e39 100644 --- a/yarn-project/acir-simulator/src/client/pick_notes.ts +++ b/yarn-project/acir-simulator/src/client/pick_notes.ts @@ -1,4 +1,4 @@ -import { Note } from '@aztec/circuit-types'; +import { Comparator, Note } from '@aztec/circuit-types'; import { Fr } from '@aztec/foundation/fields'; /** @@ -13,6 +13,10 @@ export interface Select { * Required value of the field. */ value: Fr; + /** + * The comparator to use + */ + comparator: Comparator; } /** @@ -75,7 +79,20 @@ interface ContainsNote { } const selectNotes = (noteDatas: T[], selects: Select[]): T[] => - noteDatas.filter(noteData => selects.every(({ index, value }) => noteData.note.items[index]?.equals(value))); + noteDatas.filter(noteData => + selects.every(({ index, value, comparator }) => { + 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), + }; + + return comparatorSelector[comparator](); + }), + ); const sortNotes = (a: Fr[], b: Fr[], sorts: Sort[], level = 0): number => { if (sorts[level] === undefined) { diff --git a/yarn-project/acir-simulator/src/client/view_data_oracle.ts b/yarn-project/acir-simulator/src/client/view_data_oracle.ts index 3fb79b16b6f..3d2e6d7d16e 100644 --- a/yarn-project/acir-simulator/src/client/view_data_oracle.ts +++ b/yarn-project/acir-simulator/src/client/view_data_oracle.ts @@ -198,6 +198,7 @@ export class ViewDataOracle extends TypedOracle { * @param numSelects - The number of valid selects in selectBy and selectValues. * @param selectBy - An array of indices of the fields to selects. * @param selectValues - The values to match. + * @param selectComparators - The comparators to use to match values. * @param sortBy - An array of indices of the fields to sort. * @param sortOrder - The order of the corresponding index in sortBy. (1: DESC, 2: ASC, 0: Do nothing) * @param limit - The number of notes to retrieve per query. @@ -209,6 +210,7 @@ export class ViewDataOracle extends TypedOracle { numSelects: number, selectBy: number[], selectValues: Fr[], + selectComparators: number[], sortBy: number[], sortOrder: number[], limit: number, @@ -216,7 +218,9 @@ export class ViewDataOracle extends TypedOracle { ): Promise { const dbNotes = await this.db.getNotes(this.contractAddress, storageSlot); return pickNotes(dbNotes, { - selects: selectBy.slice(0, numSelects).map((index, i) => ({ index, value: selectValues[i] })), + selects: selectBy + .slice(0, numSelects) + .map((index, i) => ({ index, value: selectValues[i], comparator: selectComparators[i] })), sorts: sortBy.map((index, i) => ({ index, order: sortOrder[i] })), limit, offset, diff --git a/yarn-project/aztec-nr/aztec/src/note/note_getter.nr b/yarn-project/aztec-nr/aztec/src/note/note_getter.nr index 15b98f89f30..0a5420ff7ba 100644 --- a/yarn-project/aztec-nr/aztec/src/note/note_getter.nr +++ b/yarn-project/aztec-nr/aztec/src/note/note_getter.nr @@ -8,7 +8,7 @@ use dep::protocol_types::constants::{ }; use crate::context::PrivateContext; use crate::note::{ - note_getter_options::{NoteGetterOptions, Select, Sort, SortOrder}, + note_getter_options::{NoteGetterOptions, Select, Sort, SortOrder, Comparator}, note_interface::NoteInterface, note_viewer_options::NoteViewerOptions, utils::compute_note_hash_for_read_or_nullify, @@ -32,7 +32,24 @@ fn check_note_header( fn check_note_fields(fields: [Field; N], selects: BoundedVec, N>) { for i in 0..selects.len { let select = selects.get_unchecked(i).unwrap_unchecked(); - assert(fields[select.field_index] == select.value, "Mismatch return note field."); + + // 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); + + if (select.comparator == Comparator.EQ) { + assert(isEqual, "Mismatch return note field."); + } else if (select.comparator == Comparator.NEQ) { + assert(!isEqual, "Mismatch return note field."); + } else if (select.comparator == Comparator.LT) { + assert(isLt, "Mismatch return note field."); + } else if (select.comparator == Comparator.LTE) { + assert(isLt | isEqual, "Mismatch return note field."); + } else if (select.comparator == Comparator.GT) { + assert(!isLt & !isEqual, "Mismatch return note field."); + } else if (select.comparator == Comparator.GTE) { + assert(!isLt, "Mismatch return note field."); + } } } @@ -115,6 +132,7 @@ unconstrained fn get_note_internal(storage_slot: Field, note_interface: [], [], [], + [], 1, // limit 0, // offset placeholder_note, @@ -127,7 +145,7 @@ unconstrained fn get_notes_internal( note_interface: NoteInterface, options: NoteGetterOptions ) -> [Option; MAX_READ_REQUESTS_PER_CALL] { - let (num_selects, select_by, select_values, sort_by, sort_order) = flatten_options(options.selects, options.sorts); + let (num_selects, select_by, select_values, select_comparators, sort_by, sort_order) = flatten_options(options.selects, options.sorts); let placeholder_opt_notes = [Option::none(); MAX_READ_REQUESTS_PER_CALL]; let placeholder_fields = [0; GET_NOTES_ORACLE_RETURN_LENGTH]; let opt_notes = oracle::notes::get_notes( @@ -136,6 +154,7 @@ unconstrained fn get_notes_internal( num_selects, select_by, select_values, + select_comparators, sort_by, sort_order, options.limit, @@ -154,7 +173,7 @@ unconstrained pub fn view_notes( note_interface: NoteInterface, options: NoteViewerOptions ) -> [Option; MAX_NOTES_PER_PAGE] { - let (num_selects, select_by, select_values, sort_by, sort_order) = flatten_options(options.selects, options.sorts); + let (num_selects, select_by, select_values, select_comparators, sort_by, 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]; oracle::notes::get_notes( @@ -163,6 +182,7 @@ unconstrained pub fn view_notes( num_selects, select_by, select_values, + select_comparators, sort_by, sort_order, options.limit, @@ -175,15 +195,18 @@ unconstrained pub fn view_notes( unconstrained fn flatten_options( selects: BoundedVec, N>, sorts: BoundedVec, N> -) -> (u8, [u8; N], [Field; N], [u8; N], [u2; N]) { +) -> (u8, [u8; N], [Field; N], [u3; N], [u8; N], [u2; N]) { let mut num_selects = 0; let mut select_by = [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_values[num_selects] = select.unwrap_unchecked().value; + select_comparators[num_selects] = select.unwrap_unchecked().comparator; num_selects += 1; }; } @@ -198,5 +221,5 @@ unconstrained fn flatten_options( }; } - (num_selects, select_by, select_values, sort_by, sort_order) + (num_selects, select_by, select_values, select_comparators, sort_by, sort_order) } diff --git a/yarn-project/aztec-nr/aztec/src/note/note_getter_options.nr b/yarn-project/aztec-nr/aztec/src/note/note_getter_options.nr index 8ceca5eb03e..78fbeffd553 100644 --- a/yarn-project/aztec-nr/aztec/src/note/note_getter_options.nr +++ b/yarn-project/aztec-nr/aztec/src/note/note_getter_options.nr @@ -2,14 +2,33 @@ use dep::std::option::Option; use crate::types::vec::BoundedVec; use dep::protocol_types::constants::MAX_READ_REQUESTS_PER_CALL; +struct ComparatorEnum { + EQ: u3, + NEQ: u3, + LT: u3, + LTE: u3, + GT: u3, + GTE: u3, +} + +global Comparator = ComparatorEnum { + EQ: 1, + NEQ: 2, + LT: 3, + LTE: 4, + GT: 5, + GTE: 6, +}; + struct Select { field_index: u8, value: Field, + comparator: u3, } impl Select { - pub fn new(field_index: u8, value: Field) -> Self { - Select { field_index, value } + pub fn new(field_index: u8, value: Field, comparator: u3) -> Self { + Select { field_index, value, comparator } } } @@ -86,9 +105,11 @@ impl NoteGetterOptions { } // This method adds a `Select` criterion to the options. - // It takes a field_index indicating which field to select and a value representing the specific value to match in that field. - pub fn select(&mut self, field_index: u8, value: Field) -> Self { - self.selects.push(Option::some(Select::new(field_index, value))); + // 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 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)))); *self } diff --git a/yarn-project/aztec-nr/aztec/src/note/note_viewer_options.nr b/yarn-project/aztec-nr/aztec/src/note/note_viewer_options.nr index 15d445d2c02..34c7271bfeb 100644 --- a/yarn-project/aztec-nr/aztec/src/note/note_viewer_options.nr +++ b/yarn-project/aztec-nr/aztec/src/note/note_viewer_options.nr @@ -1,6 +1,6 @@ use dep::std::option::Option; use dep::protocol_types::constants::MAX_NOTES_PER_PAGE; -use crate::note::note_getter_options::{Select, Sort}; +use crate::note::note_getter_options::{Select, Sort, Comparator}; use crate::types::vec::BoundedVec; // docs:start:NoteViewerOptions @@ -21,9 +21,13 @@ impl NoteViewerOptions { offset: 0, } } - - pub fn select(&mut self, field_index: u8, value: Field) -> Self { - self.selects.push(Option::some(Select::new(field_index, value))); + + // This method adds a `Select` criterion to the options. + // 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)))); *self } diff --git a/yarn-project/aztec-nr/aztec/src/oracle/notes.nr b/yarn-project/aztec-nr/aztec/src/oracle/notes.nr index 88664e64dc8..4176e647578 100644 --- a/yarn-project/aztec-nr/aztec/src/oracle/notes.nr +++ b/yarn-project/aztec-nr/aztec/src/oracle/notes.nr @@ -27,6 +27,7 @@ fn get_notes_oracle( _num_selects: u8, _select_by: [u8; N], _select_values: [Field; N], + _select_comparators: [u3; N], _sort_by: [u8; N], _sort_order: [u2; N], _limit: u32, @@ -40,6 +41,7 @@ unconstrained fn get_notes_oracle_wrapper( num_selects: u8, select_by: [u8; N], select_values: [Field; N], + select_comparators: [u3; N], sort_by: [u8; N], sort_order: [u2; N], limit: u32, @@ -52,6 +54,7 @@ unconstrained fn get_notes_oracle_wrapper( num_selects, select_by, select_values, + select_comparators, sort_by, sort_order, limit, @@ -67,6 +70,7 @@ unconstrained pub fn get_notes( num_selects: u8, select_by: [u8; M], select_values: [Field; M], + select_comparators: [u3; M], sort_by: [u8; M], sort_order: [u2; M], limit: u32, @@ -79,6 +83,7 @@ unconstrained pub fn get_notes( num_selects, select_by, select_values, + select_comparators, sort_by, sort_order, limit, diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index ff2378a23bf..bc5558c8d9f 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -106,6 +106,7 @@ export { emptyFunctionCall, merkleTreeIds, mockTx, + Comparator, } from '@aztec/circuit-types'; export { NodeInfo } from '@aztec/types/interfaces'; diff --git a/yarn-project/circuit-types/src/notes/note_filter.ts b/yarn-project/circuit-types/src/notes/note_filter.ts index 515dabb7c35..362526d13ff 100644 --- a/yarn-project/circuit-types/src/notes/note_filter.ts +++ b/yarn-project/circuit-types/src/notes/note_filter.ts @@ -16,3 +16,15 @@ export type NoteFilter = { /** The owner of the note (whose public key was used to encrypt the note). */ owner?: AztecAddress; }; + +/** + * The comparator to use to compare. + */ +export enum Comparator { + EQ = 1, + NEQ = 2, + LT = 3, + LTE = 4, + GT = 5, + GTE = 6, +} diff --git a/yarn-project/end-to-end/src/e2e_note_getter.test.ts b/yarn-project/end-to-end/src/e2e_note_getter.test.ts new file mode 100644 index 00000000000..26e11b55aa9 --- /dev/null +++ b/yarn-project/end-to-end/src/e2e_note_getter.test.ts @@ -0,0 +1,126 @@ +import { Comparator, Fr, Wallet } from '@aztec/aztec.js'; +import { DocsExampleContract } from '@aztec/noir-contracts'; + +import { setup } from './fixtures/utils.js'; + +interface NoirOption { + _is_some: boolean; + _value: T; +} + +function unwrapOptions(options: NoirOption[]): T[] { + return options.filter((option: any) => option._is_some).map((option: any) => option._value); +} + +describe('e2e_singleton', () => { + let wallet: Wallet; + + let teardown: () => Promise; + let contract: DocsExampleContract; + + beforeAll(async () => { + ({ teardown, wallet } = await setup()); + contract = await DocsExampleContract.deploy(wallet).send().deployed(); + // sets card value to 1 and leader to sender. + await contract.methods.initialize_private(Fr.random(), 1).send().wait(); + }, 25_000); + + afterAll(() => teardown()); + + // Singleton tests: + it('a test that inserts note and checks if ', async () => { + const numbers = [...Array(10).keys()]; + await Promise.all(numbers.map(number => contract.methods.insert_note(number).send().wait())); + await contract.methods.insert_note(5).send().wait(); + + const [returnEq, returnNeq, returnLt, returnGt, returnLte, returnGte] = await Promise.all([ + contract.methods.read_note(5, Comparator.EQ).view(), + contract.methods.read_note(5, Comparator.NEQ).view(), + contract.methods.read_note(5, Comparator.LT).view(), + contract.methods.read_note(5, Comparator.GT).view(), + contract.methods.read_note(5, Comparator.LTE).view(), + contract.methods.read_note(5, Comparator.GTE).view(), + ]); + + expect(unwrapOptions(returnEq).map(({ points, randomness }: any) => ({ points, randomness }))).toStrictEqual([ + { points: 5n, randomness: 1n }, + { points: 5n, randomness: 1n }, + ]); + + expect( + unwrapOptions(returnNeq) + .map(({ points, randomness }: any) => ({ points, randomness })) + .sort((a: any, b: any) => (a.points > b.points ? 1 : -1)), + ).toStrictEqual( + [ + { points: 0n, randomness: 1n }, + { points: 1n, randomness: 1n }, + { points: 7n, randomness: 1n }, + { points: 9n, randomness: 1n }, + { points: 2n, randomness: 1n }, + { points: 6n, randomness: 1n }, + { points: 8n, randomness: 1n }, + { points: 4n, randomness: 1n }, + { points: 3n, randomness: 1n }, + ].sort((a: any, b: any) => (a.points > b.points ? 1 : -1)), + ); + + expect( + unwrapOptions(returnLt) + .map(({ points, randomness }: any) => ({ points, randomness })) + .sort((a: any, b: any) => (a.points > b.points ? 1 : -1)), + ).toStrictEqual( + [ + { points: 0n, randomness: 1n }, + { points: 1n, randomness: 1n }, + { points: 2n, randomness: 1n }, + { points: 4n, randomness: 1n }, + { points: 3n, randomness: 1n }, + ].sort((a: any, b: any) => (a.points > b.points ? 1 : -1)), + ); + + expect( + unwrapOptions(returnGt) + .map(({ points, randomness }: any) => ({ points, randomness })) + .sort((a: any, b: any) => (a.points > b.points ? 1 : -1)), + ).toStrictEqual( + [ + { points: 7n, randomness: 1n }, + { points: 9n, randomness: 1n }, + { points: 6n, randomness: 1n }, + { points: 8n, randomness: 1n }, + ].sort((a: any, b: any) => (a.points > b.points ? 1 : -1)), + ); + + expect( + unwrapOptions(returnLte) + .map(({ points, randomness }: any) => ({ points, randomness })) + .sort((a: any, b: any) => (a.points > b.points ? 1 : -1)), + ).toStrictEqual( + [ + { points: 5n, randomness: 1n }, + { points: 5n, randomness: 1n }, + { points: 0n, randomness: 1n }, + { points: 1n, randomness: 1n }, + { points: 2n, randomness: 1n }, + { points: 4n, randomness: 1n }, + { points: 3n, randomness: 1n }, + ].sort((a: any, b: any) => (a.points > b.points ? 1 : -1)), + ); + + expect( + unwrapOptions(returnGte) + .map(({ points, randomness }: any) => ({ points, randomness })) + .sort((a: any, b: any) => (a.points > b.points ? 1 : -1)), + ).toStrictEqual( + [ + { points: 5n, randomness: 1n }, + { points: 5n, randomness: 1n }, + { points: 7n, randomness: 1n }, + { points: 9n, randomness: 1n }, + { points: 6n, randomness: 1n }, + { points: 8n, randomness: 1n }, + ].sort((a: any, b: any) => (a.points > b.points ? 1 : -1)), + ); + }, 300_000); +}); diff --git a/yarn-project/foundation/.eslintrc.docs.cjs b/yarn-project/foundation/.eslintrc.docs.cjs index cac8e4f9095..ef0ba45aede 100644 --- a/yarn-project/foundation/.eslintrc.docs.cjs +++ b/yarn-project/foundation/.eslintrc.docs.cjs @@ -40,10 +40,7 @@ const JSDOC_RULES_LEVEL = 'error'; module.exports = { ...base, plugins: [...base.plugins, 'jsdoc'], - overrides: [ - ...base.overrides, - { files: '*.test.ts', rules: { 'jsdoc/require-jsdoc': 'off' } }, - ], + overrides: [...base.overrides, { files: '*.test.ts', rules: { 'jsdoc/require-jsdoc': 'off' } }], rules: { ...base.rules, 'tsdoc/syntax': JSDOC_RULES_LEVEL, @@ -65,5 +62,5 @@ module.exports = { 'jsdoc/require-property-description': [JSDOC_RULES_LEVEL, { contexts }], 'jsdoc/require-property-name': [JSDOC_RULES_LEVEL, { contexts }], 'jsdoc/require-returns': 'off', - } + }, }; diff --git a/yarn-project/foundation/src/fields/fields.ts b/yarn-project/foundation/src/fields/fields.ts index bbf2f3b9a20..0e622cd4fde 100644 --- a/yarn-project/foundation/src/fields/fields.ts +++ b/yarn-project/foundation/src/fields/fields.ts @@ -104,6 +104,10 @@ abstract class BaseField { return this.toBuffer().equals(rhs.toBuffer()); } + lt(rhs: BaseField): boolean { + return this.toBigInt() < rhs.toBigInt(); + } + isZero(): boolean { return this.toBuffer().equals(ZERO_BUFFER); } diff --git a/yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr b/yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr index e7c9befa319..0e9ce6369fa 100644 --- a/yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr +++ b/yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr @@ -18,12 +18,17 @@ contract DocsExample { address::AztecAddress, }; use dep::aztec::{ + oracle::{ + debug_log::debug_log_format, + }, note::{ note_header::NoteHeader, + note_getter_options::{NoteGetterOptions, Comparator}, + note_viewer_options::{NoteViewerOptions}, utils as note_utils, }, context::{PrivateContext, PublicContext, Context}, - state_vars::{map::Map, public_state::PublicState,singleton::Singleton, immutable_singleton::ImmutableSingleton}, + state_vars::{map::Map, public_state::PublicState,singleton::Singleton, immutable_singleton::ImmutableSingleton, set::Set}, }; // how to import methods from other files/folders within your workspace use crate::options::create_account_card_getter_options; @@ -42,6 +47,7 @@ contract DocsExample { // docs:start:storage-map-singleton-declaration profiles: Map>, // docs:end:storage-map-singleton-declaration + test: Set, imm_singleton: ImmutableSingleton, } @@ -66,6 +72,7 @@ contract DocsExample { }, ), // docs:end:state_vars-MapSingleton + test: Set::new(context, 4, CardNoteMethods), imm_singleton: ImmutableSingleton::new(context, 4, CardNoteMethods), } } @@ -88,6 +95,28 @@ contract DocsExample { storage.legendary_card.initialize(&mut legendary_card, true); } + #[aztec(private)] + fn insert_note(amount: u8) { + let mut note = CardNote::new(amount, 1, context.msg_sender()); + storage.test.insert(&mut note, true); + } + + unconstrained fn read_note(amount: Field, comparator: u3) -> pub [Option; 10] { + let options = NoteViewerOptions::new().select(0, amount, Option::some(comparator)); + let notes = storage.test.view_notes(options); + + for i in 0..notes.len() { + if notes[i].is_some() { + debug_log_format( + "NOTES THAT MATCH: {0}", + [notes[i].unwrap_unchecked().points as Field] + ); + } + } + + notes + } + #[aztec(private)] fn update_legendary_card(randomness: Field, points: u8) { let mut new_card = CardNote::new(points, randomness, context.msg_sender()); diff --git a/yarn-project/noir-contracts/contracts/docs_example_contract/src/options.nr b/yarn-project/noir-contracts/contracts/docs_example_contract/src/options.nr index 9742345f8ca..9f1173f7588 100644 --- a/yarn-project/noir-contracts/contracts/docs_example_contract/src/options.nr +++ b/yarn-project/noir-contracts/contracts/docs_example_contract/src/options.nr @@ -13,7 +13,7 @@ pub fn create_account_card_getter_options( account: AztecAddress, offset: u32 ) -> NoteGetterOptions { - NoteGetterOptions::new().select(2, account.to_field()).sort(0, SortOrder.DESC).set_offset(offset) + NoteGetterOptions::new().select(2, account.to_field(), Option::none()).sort(0, SortOrder.DESC).set_offset(offset) } // docs:end:state_vars-NoteGetterOptionsSelectSortOffset @@ -23,7 +23,7 @@ pub fn create_exact_card_getter_options( secret: Field, account: AztecAddress ) -> NoteGetterOptions { - NoteGetterOptions::new().select(0, points as Field).select(1, secret).select(2, account.to_field()) + NoteGetterOptions::new().select(0, points as Field, Option::none()).select(1, secret, Option::none()).select(2, account.to_field(), Option::none()) } // docs:end:state_vars-NoteGetterOptionsMultiSelects @@ -46,12 +46,12 @@ 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()).sort(0, SortOrder.ASC) + NoteGetterOptions::with_filter(filter_min_points, min_points).select(2, account.to_field(), Option::none()).sort(0, 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()).sort(0, SortOrder.DESC).set_limit(1) + NoteGetterOptions::new().select(2, account.to_field(), Option::none()).sort(0, SortOrder.DESC).set_limit(1) } // docs:end:state_vars-NoteGetterOptionsPickOne diff --git a/yarn-project/noir-contracts/contracts/escrow_contract/src/main.nr b/yarn-project/noir-contracts/contracts/escrow_contract/src/main.nr index cf7500c7fa6..09b92c5131f 100644 --- a/yarn-project/noir-contracts/contracts/escrow_contract/src/main.nr +++ b/yarn-project/noir-contracts/contracts/escrow_contract/src/main.nr @@ -57,7 +57,7 @@ contract Escrow { let sender = context.msg_sender(); // We don't remove note from the owners set. If a note exists, the owner and recipient are legit. - let options = NoteGetterOptions::new().select(0, sender.to_field()).select(1, this.to_field()).set_limit(1); + let options = NoteGetterOptions::new().select(0, sender.to_field(), Option::none()).select(1, this.to_field(), Option::none()).set_limit(1); let notes = storage.owners.get_notes(options); assert(notes[0].is_some(), "Sender is not an owner."); diff --git a/yarn-project/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr b/yarn-project/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr index 5b99fdd60bb..4ad5b8c81e5 100644 --- a/yarn-project/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr +++ b/yarn-project/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr @@ -109,7 +109,7 @@ contract InclusionProofs { // docs:start:get_note_from_pxe // 1) Get the note from PXE. let private_values = storage.private_values.at(owner); - let options = NoteGetterOptions::new().select(1, owner.to_field()).set_limit(1); + let options = NoteGetterOptions::new().select(1, owner.to_field(), Option::none()).set_limit(1); let notes = private_values.get_notes(options); let maybe_note = notes[0]; // docs:end:get_note_from_pxe @@ -142,7 +142,7 @@ contract InclusionProofs { ) { // 2) Get the note from PXE let private_values = storage.private_values.at(owner); - let options = NoteGetterOptions::new().select(1, owner.to_field()).set_limit(1); + let options = NoteGetterOptions::new().select(1, owner.to_field(), Option::none()).set_limit(1); let notes = private_values.get_notes(options); let maybe_note = notes[0]; @@ -171,7 +171,7 @@ contract InclusionProofs { ) { // 1) Get the note from PXE. let private_values = storage.private_values.at(owner); - let options = NoteGetterOptions::new().select(1, owner.to_field()).set_limit(1); + let options = NoteGetterOptions::new().select(1, owner.to_field(), Option::none()).set_limit(1); let notes = private_values.get_notes(options); let note = notes[0].unwrap(); @@ -185,7 +185,7 @@ 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()).set_limit(1); + let options = NoteGetterOptions::new().select(1, owner.to_field(), Option::none()).set_limit(1); let notes = private_values.get_notes(options); let note = notes[0].unwrap(); diff --git a/yarn-project/noir-contracts/contracts/token_blacklist_contract/src/main.nr b/yarn-project/noir-contracts/contracts/token_blacklist_contract/src/main.nr index 46ff827b00c..d0fd433342a 100644 --- a/yarn-project/noir-contracts/contracts/token_blacklist_contract/src/main.nr +++ b/yarn-project/noir-contracts/contracts/token_blacklist_contract/src/main.nr @@ -281,7 +281,7 @@ 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).select(1, secret_hash).set_limit(1); + let options = NoteGetterOptions::new().select(0, amount, Option::none()).select(1, 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/yarn-project/noir-contracts/contracts/token_contract/src/main.nr b/yarn-project/noir-contracts/contracts/token_contract/src/main.nr index 6aa5c0f88d3..080f252969b 100644 --- a/yarn-project/noir-contracts/contracts/token_contract/src/main.nr +++ b/yarn-project/noir-contracts/contracts/token_contract/src/main.nr @@ -301,7 +301,7 @@ 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).select(1, secret_hash).set_limit(1); + let options = NoteGetterOptions::new().select(0, amount, Option::none()).select(1, 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