Skip to content

Commit

Permalink
storage layout export and note ids
Browse files Browse the repository at this point in the history
  • Loading branch information
Thunkar committed Mar 26, 2024
1 parent 359961b commit ced6bcf
Show file tree
Hide file tree
Showing 9 changed files with 350 additions and 258 deletions.
2 changes: 1 addition & 1 deletion noir-projects/aztec-nr/aztec/src/prelude.nr
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
state_vars::{
map::Map, private_immutable::PrivateImmutable, private_mutable::PrivateMutable,
public_immutable::PublicImmutable, public_mutable::PublicMutable, private_set::PrivateSet,
shared_immutable::SharedImmutable, storage::Storable, storage::StorableNote
shared_immutable::SharedImmutable, storage::Storable
},
log::{emit_unencrypted_log, emit_encrypted_log}, context::PrivateContext,
note::{
Expand Down
4 changes: 0 additions & 4 deletions noir-projects/aztec-nr/aztec/src/state_vars/storage.nr
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,3 @@ struct Storable<N> {
typ: str<N>
}

struct StorableNote<N> {
id: Field,
typ: str<N>
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,56 +16,13 @@ contract DocsExample {
use dep::aztec::prelude::{
AztecAddress, FunctionSelector, NoteHeader, NoteGetterOptions, NoteViewerOptions,
PrivateContext, Map, PublicMutable, PublicImmutable, PrivateMutable, PrivateImmutable,
PrivateSet, SharedImmutable, Storable, StorableNote
PrivateSet, SharedImmutable, Storable
};
use dep::aztec::{note::note_getter_options::Comparator, context::{PublicContext, Context}};
// how to import methods from other files/folders within your workspace
use crate::options::create_account_card_getter_options;
use crate::types::{card_note::{CardNote, CARD_NOTE_LEN}, leader::Leader};

struct StorageFields<N1, N2, N3, N4, N5, N6, N7, N8> {
leader: Storable<N1>,
legendary_card: Storable<N2>,
profiles: Storable<N3>,
set: Storable<N4>,
private_immutable: Storable<N5>,
shared_immutable: Storable<N6>,
minters: Storable<N7>,
public_immutable: Storable<N8>,
}

struct StorageNotes<N1> {
card_note: StorableNote<N1>,
}

struct StorageLayout<N1, N2, N3, N4, N5, N6, N7, N8, M1> {
fields: StorageFields<N1, N2, N3, N4, N5, N6, N7, N8>,
notes: StorageNotes<M1>,
}

#[abi(event)]
struct WithdrawalProcessed {
who: AztecAddress,
amount: u64,
}

#[abi(storage)]
global STORAGE_LAYOUT = StorageLayout {
fields: StorageFields {
leader: Storable { slot: 1, typ: "PublicMutable<Leader>" },
legendary_card: Storable { slot: 3, typ: "PrivateMutable<CardNote>" },
profiles: Storable { slot: 4, typ: "Map<AztecAddress, PrivateMutable<CardNote>>" },
set: Storable { slot: 5, typ: "PrivateSet<CardNote>" },
private_immutable: Storable { slot: 6, typ: "PrivateImmutable<CardNote>" },
shared_immutable: Storable { slot: 7, typ: "SharedImmutable<Leader>" },
minters: Storable { slot: 8, typ: "Map<AztecAddress, PublicMutable<bool>" },
public_immutable: Storable { slot: 9, typ: "PublicImmutable<Leader>" }
},
notes: StorageNotes {
card_note: StorableNote { id: 2, typ: "CardNote" }
}
};

#[aztec(storage)]
struct Storage {
// Shows how to create a custom struct in PublicMutable
Expand Down
12 changes: 4 additions & 8 deletions noir/noir-repo/aztec_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use transforms::{
note_interface::generate_note_interface_impl,
storage::{
assign_storage_slots, check_for_storage_definition, check_for_storage_implementation,
generate_storage_implementation,
generate_storage_implementation, generate_storage_layout,
},
};

Expand Down Expand Up @@ -95,6 +95,7 @@ fn transform_module(module: &mut SortedModule) -> Result<bool, AztecMacroError>
if !check_for_storage_implementation(module, &storage_struct_name) {
generate_storage_implementation(module, &storage_struct_name)?;
}
generate_storage_layout(module, storage_struct_name)?;
}

for structure in module.types.iter_mut() {
Expand Down Expand Up @@ -185,16 +186,11 @@ fn transform_module(module: &mut SortedModule) -> Result<bool, AztecMacroError>
fn transform_collected_defs(
crate_id: &CrateId,
context: &mut HirContext,
collected_trait_impls: &[UnresolvedTraitImpl],
_collected_trait_impls: &[UnresolvedTraitImpl],
collected_functions: &mut [UnresolvedFunctions],
) -> Result<(), (MacroError, FileId)> {
if has_aztec_dependency(crate_id, context) {
inject_compute_note_hash_and_nullifier(
crate_id,
context,
collected_trait_impls,
collected_functions,
)
inject_compute_note_hash_and_nullifier(crate_id, context, collected_functions)
} else {
Ok(())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ use noirc_errors::{Location, Span};
use noirc_frontend::{
graph::CrateId,
hir::{
def_collector::dc_crate::{UnresolvedFunctions, UnresolvedTraitImpl},
def_collector::dc_crate::UnresolvedFunctions,
def_map::{LocalModuleId, ModuleId},
},
macros_api::{FileId, HirContext, MacroError},
node_interner::FuncId,
parse_program, FunctionReturnType, ItemVisibility, NoirFunction, UnresolvedTypeData,
};

use crate::utils::hir_utils::fetch_struct_trait_impls;
use crate::utils::hir_utils::{fetch_note_names, get_contract_module_data};

// Check if "compute_note_hash_and_nullifier(AztecAddress,Field,Field,Field,[Field; N]) -> [Field; 4]" is defined
fn check_for_compute_note_hash_and_nullifier_definition(
Expand Down Expand Up @@ -53,77 +53,61 @@ fn check_for_compute_note_hash_and_nullifier_definition(
pub fn inject_compute_note_hash_and_nullifier(
crate_id: &CrateId,
context: &mut HirContext,
unresolved_traits_impls: &[UnresolvedTraitImpl],
collected_functions: &mut [UnresolvedFunctions],
) -> Result<(), (MacroError, FileId)> {
// We first fetch modules in this crate which correspond to contracts, along with their file id.
let contract_module_file_ids: Vec<(LocalModuleId, FileId)> = context
.def_map(crate_id)
.expect("ICE: Missing crate in def_map")
.modules()
.iter()
.filter(|(_, module)| module.is_contract)
.map(|(idx, module)| (LocalModuleId(idx), module.location.file))
.collect();

// If the current crate does not contain a contract module we simply skip it.
if contract_module_file_ids.is_empty() {
return Ok(());
} else if contract_module_file_ids.len() != 1 {
panic!("Found multiple contracts in the same crate");
}

let (module_id, file_id) = contract_module_file_ids[0];

// If compute_note_hash_and_nullifier is already defined by the user, we skip auto-generation in order to provide an
// escape hatch for this mechanism.
// TODO(#4647): improve this diagnosis and error messaging.
if collected_functions.iter().any(|coll_funcs_data| {
check_for_compute_note_hash_and_nullifier_definition(&coll_funcs_data.functions, module_id)
}) {
return Ok(());
}

// In order to implement compute_note_hash_and_nullifier, we need to know all of the different note types the
// contract might use. These are the types that implement the NoteInterface trait, which provides the
// get_note_type_id function.
let note_types = fetch_struct_trait_impls(context, unresolved_traits_impls, "NoteInterface");

// We can now generate a version of compute_note_hash_and_nullifier tailored for the contract in this crate.
let func = generate_compute_note_hash_and_nullifier(&note_types);

// And inject the newly created function into the contract.

// TODO(#4373): We don't have a reasonable location for the source code of this autogenerated function, so we simply
// pass an empty span. This function should not produce errors anyway so this should not matter.
let location = Location::new(Span::empty(0), file_id);

// These are the same things the ModCollector does when collecting functions: we push the function to the
// NodeInterner, declare it in the module (which checks for duplicate definitions), and finally add it to the list
// on collected but unresolved functions.

let func_id = context.def_interner.push_empty_fn();
context.def_interner.push_function(
func_id,
&func.def,
ModuleId { krate: *crate_id, local_id: module_id },
location,
);
if let Some((module_id, file_id)) = get_contract_module_data(context, crate_id) {
// If compute_note_hash_and_nullifier is already defined by the user, we skip auto-generation in order to provide an
// escape hatch for this mechanism.
// TODO(#4647): improve this diagnosis and error messaging.
if collected_functions.iter().any(|coll_funcs_data| {
check_for_compute_note_hash_and_nullifier_definition(
&coll_funcs_data.functions,
module_id,
)
}) {
return Ok(());
}

// In order to implement compute_note_hash_and_nullifier, we need to know all of the different note types the
// contract might use. These are the types that implement the NoteInterface trait, which provides the
// get_note_type_id function.
let note_types = fetch_note_names(context, crate_id);

// We can now generate a version of compute_note_hash_and_nullifier tailored for the contract in this crate.
let func = generate_compute_note_hash_and_nullifier(&note_types);

// And inject the newly created function into the contract.

// TODO(#4373): We don't have a reasonable location for the source code of this autogenerated function, so we simply
// pass an empty span. This function should not produce errors anyway so this should not matter.
let location = Location::new(Span::empty(0), file_id);

// These are the same things the ModCollector does when collecting functions: we push the function to the
// NodeInterner, declare it in the module (which checks for duplicate definitions), and finally add it to the list
// on collected but unresolved functions.

let func_id = context.def_interner.push_empty_fn();
context.def_interner.push_function(
func_id,
&func.def,
ModuleId { krate: *crate_id, local_id: module_id },
location,
);

context.def_map_mut(crate_id).unwrap()
context.def_map_mut(crate_id).unwrap()
.modules_mut()[module_id.0]
.declare_function(
func.name_ident().clone(), ItemVisibility::Public, func_id
).expect(
"Failed to declare the autogenerated compute_note_hash_and_nullifier function, likely due to a duplicate definition. See https://github.com/AztecProtocol/aztec-packages/issues/4647."
);

collected_functions
.iter_mut()
.find(|fns| fns.file_id == file_id)
.expect("ICE: no functions found in contract file")
.push_fn(module_id, func_id, func.clone());

collected_functions
.iter_mut()
.find(|fns| fns.file_id == file_id)
.expect("ICE: no functions found in contract file")
.push_fn(module_id, func_id, func.clone());
}
Ok(())
}

Expand Down
66 changes: 55 additions & 11 deletions noir/noir-repo/aztec_macros/src/transforms/note_interface.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use noirc_errors::Span;
use noirc_frontend::{
parse_program, parser::SortedModule, ItemVisibility, NoirFunction, NoirStruct, PathKind,
TraitImplItem, TypeImpl, UnresolvedTypeData, UnresolvedTypeExpression,
parse_program, parser::SortedModule, ItemVisibility, LetStatement, NoirFunction, NoirStruct,
PathKind, TraitImplItem, TypeImpl, UnresolvedTypeData, UnresolvedTypeExpression,
};
use regex::Regex;

Expand All @@ -24,7 +24,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt
.iter_mut()
.filter(|typ| typ.attributes.iter().any(|attr| is_custom_attribute(attr, "aztec(note)")));

let mut note_properties_structs = vec![];
let mut structs_to_inject = vec![];

for note_struct in annotated_note_structs {
// Look for the NoteInterface trait implementation for the note
Expand Down Expand Up @@ -80,6 +80,13 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt
)),
}),
}?;
let note_type_id = note_type_id(&note_type);

let (note_exports_struct, note_exports_global) =
generate_note_exports_struct_and_global(&note_type, &note_type_id)?;

structs_to_inject.push(note_exports_struct);
module.globals.push(note_exports_global);

// Automatically inject the header field if it's not present
let (header_field_name, _) = if let Some(existing_header) =
Expand Down Expand Up @@ -138,7 +145,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt
&header_field_name.0.contents,
note_interface_impl_span,
)?;
note_properties_structs.push(note_properties_struct);
structs_to_inject.push(note_properties_struct);
let note_properties_fn = generate_note_properties_fn(
&note_type,
&note_fields,
Expand Down Expand Up @@ -167,7 +174,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt

if !check_trait_method_implemented(trait_impl, "get_note_type_id") {
let get_note_type_id_fn =
generate_note_get_type_id(&note_type, note_interface_impl_span)?;
generate_note_get_type_id(&note_type_id, note_interface_impl_span)?;
trait_impl.items.push(TraitImplItem::Function(get_note_type_id_fn));
}

Expand All @@ -178,7 +185,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt
}
}

module.types.extend(note_properties_structs);
module.types.extend(structs_to_inject);
Ok(())
}

Expand Down Expand Up @@ -245,19 +252,16 @@ fn generate_note_set_header(
// 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,
note_type_id: &str,
impl_span: Option<Span>,
) -> Result<NoirFunction, AztecMacroError> {
// TODO(#4519) Improve automatic note id generation and assignment
let note_id =
note_type.chars().map(|c| (c as u32).to_string()).collect::<Vec<String>>().join("");
let function_source = format!(
"
fn get_note_type_id() -> Field {{
{}
}}
",
note_id
note_type_id
)
.to_string();

Expand Down Expand Up @@ -443,6 +447,40 @@ fn generate_compute_note_content_hash(
Ok(noir_fn)
}

fn generate_note_exports_struct_and_global(
note_type: &str,
note_type_id: &str,
) -> Result<(NoirStruct, LetStatement), AztecMacroError> {
let struct_source = format!(
"
struct {}Exports<N> {{
id: Field,
typ: str<N>
}}
#[abi(notes)]
global {}_EXPORTS = {}Exports {{
id: {},
typ: \"{}\",
}};
",
note_type, note_type, note_type, note_type_id, note_type
)
.to_string();

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 {}Exports). This is either a bug in the compiler or the Noir macro code", note_type)),
span: None
});
}

let mut struct_ast = struct_ast.into_sorted();
Ok((struct_ast.types.pop().unwrap(), struct_ast.globals.pop().unwrap()))
}

// 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(
Expand Down Expand Up @@ -581,3 +619,9 @@ fn generate_note_deserialize_content_source(
)
.to_string()
}

// Utility function to generate the note type id as a Field
fn note_type_id(note_type: &str) -> String {
// TODO(#4519) Improve automatic note id generation and assignment
note_type.chars().map(|c| (c as u32).to_string()).collect::<Vec<String>>().join("")
}
Loading

0 comments on commit ced6bcf

Please sign in to comment.