Skip to content

Commit

Permalink
feat!: automatic NoteInterface and NoteGetterOptions auto select (#4508)
Browse files Browse the repository at this point in the history
Partially addresses:
AztecProtocol/aztec-packages#4519 (moved
autogeneration to the macro, even if not incremental)
Closes: AztecProtocol/aztec-packages#3011

Added the `#[aztec(note)]` attribute, which automatically implements
most of the `NoteInterface` trait in a struct marked as such, plus
several utilities. Even if this adds a fair share of "magic" to the note
implementation logic, it is structured in a way that it's hopefully easy
to follow, including meaningful errors attached to the correct span
during the process.

![Screenshot 2024-03-14 at 14 59
07](https://github.com/AztecProtocol/aztec-packages/assets/5404052/84a3d6e4-e346-4cfe-93eb-ec317632f344)

Hey you! Implement the trait!

![Screenshot 2024-03-14 at 14 46
39](https://github.com/AztecProtocol/aztec-packages/assets/5404052/bebfb3dd-c178-44d0-b9bc-005b5c9f0f38)
But only the meat and potatoes though.

As long as the user doesn't want to do any custom stuff, `get_header`,
`set_header`, `compute_note_content_hash`, `get_note_type_id`,
`serialize_content` and `deserialize_content` get automatically
implemented. Any combination of them can be overridden by the developer
though.

A metadata struct is also added, which takes the following form:

```rust 
struct CardNote {
    points: FieldSelector,
    randomness: FieldSelector,
    owner: FieldSelector,
}
```

This is used to implement a `properties()` function, which in turn can
be used in conjunction with the `NoteGetterOptions.select` and `.sort`

<img width="697" alt="Screenshot 2024-03-18 at 15 27 27"
src="https://github.com/AztecProtocol/aztec-packages/assets/5404052/5da531b3-0b7f-4cf9-9908-300ff8d98c6d">
  • Loading branch information
superstar0402 committed Mar 20, 2024
1 parent 029259c commit 73e362f
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 148 deletions.
33 changes: 1 addition & 32 deletions address-note/src/address_note.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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<ADDRESS_NOTE_LEN> 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);
Expand All @@ -60,14 +43,6 @@ impl NoteInterface<ADDRESS_NOTE_LEN> 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);
Expand All @@ -82,12 +57,6 @@ impl NoteInterface<ADDRESS_NOTE_LEN> 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 {
Expand Down
79 changes: 61 additions & 18 deletions aztec/src/note/note_getter.nr
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,45 @@ 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<N>(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<Note, N>(context: PrivateContext, storage_slot: Field, note: Note) where Note: NoteInterface<N> {
let header = note.get_header();
let contract_address = context.this_address();
assert(header.contract_address.eq(contract_address));
assert(header.storage_slot == storage_slot);
}

fn check_note_fields<N>(fields: [Field; N], selects: BoundedVec<Option<Select>, N>) {
fn check_note_fields<N>(serialized_note: [Field; N], selects: BoundedVec<Option<Select>, 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.");
Expand All @@ -50,8 +69,10 @@ fn check_notes_order<N>(
) {
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 {
Expand Down Expand Up @@ -120,6 +141,10 @@ unconstrained fn get_note_internal<Note, N>(storage_slot: Field) -> Note where N
[],
[],
[],
[],
[],
[],
[],
1, // limit
0, // offset
NoteStatus.ACTIVE,
Expand All @@ -133,17 +158,21 @@ unconstrained fn get_notes_internal<Note, N, FILTER_ARGS>(
storage_slot: Field,
options: NoteGetterOptions<Note, N, FILTER_ARGS>
) -> [Option<Note>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] where Note: NoteInterface<N> {
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,
Expand All @@ -162,17 +191,21 @@ unconstrained pub fn view_notes<Note, N>(
storage_slot: Field,
options: NoteViewerOptions<Note, N>
) -> [Option<Note>; MAX_NOTES_PER_PAGE] where Note: NoteInterface<N> {
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,
Expand All @@ -186,31 +219,41 @@ unconstrained pub fn view_notes<Note, N>(
unconstrained fn flatten_options<Note, N>(
selects: BoundedVec<Option<Select>, N>,
sorts: BoundedVec<Option<Sort>, 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
)
}
44 changes: 32 additions & 12 deletions aztec/src/note/note_getter_options.nr
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 }
}
}

Expand All @@ -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 }
}
}

Expand Down Expand Up @@ -118,18 +125,31 @@ impl<Note, N, FILTER_ARGS> NoteGetterOptions<Note, N, FILTER_ARGS> {
}

// 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<u8>) -> Self {
self.selects.push(Option::some(Select::new(field_index, value, comparator.unwrap_or(Comparator.EQ))));
pub fn select<T>(
&mut self,
property_selector: PropertySelector,
value: T,
comparator: Option<u8>
) -> 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
}

Expand Down
18 changes: 12 additions & 6 deletions aztec/src/note/note_interface.nr
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,28 @@ use crate::note::note_header::NoteHeader;

// docs:start:note_interface
trait NoteInterface<N> {
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
Expand Down
26 changes: 20 additions & 6 deletions aztec/src/note/note_viewer_options.nr
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -27,13 +28,26 @@ impl<Note, N> NoteViewerOptions<Note, N> {
// 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<u8>) -> Self {
self.selects.push(Option::some(Select::new(field_index, value, comparator.unwrap_or(Comparator.EQ))));
pub fn select<T>(
&mut self,
property_selector: PropertySelector,
value: T,
comparator: Option<u8>
) -> 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
}

Expand Down
Loading

0 comments on commit 73e362f

Please sign in to comment.