diff --git a/noir-projects/aztec-nr/aztec/src/prelude.nr b/noir-projects/aztec-nr/aztec/src/prelude.nr index 7dc16c890158..b43c10713cd4 100644 --- a/noir-projects/aztec-nr/aztec/src/prelude.nr +++ b/noir-projects/aztec-nr/aztec/src/prelude.nr @@ -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::{ diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr b/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr index e56a9bb047f2..070c8d6fec0f 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr @@ -11,7 +11,3 @@ struct Storable { typ: str } -struct StorableNote { - id: Field, - typ: str - } 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 de355841cfac..6490b85ff1a3 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 @@ -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 { - leader: Storable, - legendary_card: Storable, - profiles: Storable, - set: Storable, - private_immutable: Storable, - shared_immutable: Storable, - minters: Storable, - public_immutable: Storable, - } - - struct StorageNotes { - card_note: StorableNote, - } - - struct StorageLayout { - fields: StorageFields, - notes: StorageNotes, - } - - #[abi(event)] - struct WithdrawalProcessed { - who: AztecAddress, - amount: u64, - } - - #[abi(storage)] - global STORAGE_LAYOUT = StorageLayout { - fields: StorageFields { - leader: Storable { slot: 1, typ: "PublicMutable" }, - legendary_card: Storable { slot: 3, typ: "PrivateMutable" }, - profiles: Storable { slot: 4, typ: "Map>" }, - set: Storable { slot: 5, typ: "PrivateSet" }, - private_immutable: Storable { slot: 6, typ: "PrivateImmutable" }, - shared_immutable: Storable { slot: 7, typ: "SharedImmutable" }, - minters: Storable { slot: 8, typ: "Map" }, - public_immutable: Storable { slot: 9, typ: "PublicImmutable" } - }, - notes: StorageNotes { - card_note: StorableNote { id: 2, typ: "CardNote" } - } - }; - #[aztec(storage)] struct Storage { // Shows how to create a custom struct in PublicMutable diff --git a/noir/noir-repo/aztec_macros/src/lib.rs b/noir/noir-repo/aztec_macros/src/lib.rs index 03df02a9ffef..785792817143 100644 --- a/noir/noir-repo/aztec_macros/src/lib.rs +++ b/noir/noir-repo/aztec_macros/src/lib.rs @@ -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, }, }; @@ -95,6 +95,7 @@ fn transform_module(module: &mut SortedModule) -> Result 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() { @@ -185,16 +186,11 @@ fn transform_module(module: &mut SortedModule) -> Result 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(()) } diff --git a/noir/noir-repo/aztec_macros/src/transforms/compute_note_hash_and_nullifier.rs b/noir/noir-repo/aztec_macros/src/transforms/compute_note_hash_and_nullifier.rs index fd538dc578bc..ed077fbfd354 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/compute_note_hash_and_nullifier.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/compute_note_hash_and_nullifier.rs @@ -2,7 +2,7 @@ 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}, @@ -10,7 +10,7 @@ use noirc_frontend::{ 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( @@ -53,64 +53,48 @@ 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(¬e_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(¬e_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 @@ -118,12 +102,12 @@ pub fn inject_compute_note_hash_and_nullifier( "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(()) } diff --git a/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs b/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs index 01d0272088bb..de94e5eed2d6 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs @@ -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; @@ -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 @@ -80,6 +80,13 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt )), }), }?; + let note_type_id = note_type_id(¬e_type); + + let (note_exports_struct, note_exports_global) = + generate_note_exports_struct_and_global(¬e_type, ¬e_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) = @@ -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( ¬e_type, ¬e_fields, @@ -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(¬e_type, note_interface_impl_span)?; + generate_note_get_type_id(¬e_type_id, note_interface_impl_span)?; trait_impl.items.push(TraitImplItem::Function(get_note_type_id_fn)); } @@ -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(()) } @@ -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, ) -> 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 + note_type_id ) .to_string(); @@ -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 {{ + id: Field, + typ: str + }} + + #[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( @@ -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::>().join("") +} diff --git a/noir/noir-repo/aztec_macros/src/transforms/storage.rs b/noir/noir-repo/aztec_macros/src/transforms/storage.rs index df865f3d3a26..93ed1c7c96af 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/storage.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/storage.rs @@ -1,4 +1,4 @@ -use std::borrow::{Borrow, BorrowMut}; +use std::borrow::Borrow; use noirc_errors::Span; use noirc_frontend::{ @@ -7,7 +7,9 @@ use noirc_frontend::{ FieldElement, FileId, HirContext, HirExpression, HirLiteral, HirStatement, NodeInterner, }, node_interner::{TraitId, TraitImplKind}, + parse_program, parser::SortedModule, + token::SecondaryAttribute, BlockExpression, Expression, ExpressionKind, FunctionDefinition, Ident, Literal, NoirFunction, NoirStruct, PathKind, Pattern, StatementKind, Type, TypeImpl, UnresolvedType, UnresolvedTypeData, @@ -21,7 +23,7 @@ use crate::{ make_type, pattern, return_type, variable, variable_path, }, errors::AztecMacroError, - hir_utils::{collect_crate_structs, collect_traits}, + hir_utils::{collect_crate_structs, collect_traits, get_contract_module_data}, }, }; @@ -263,117 +265,207 @@ pub fn assign_storage_slots( context: &mut HirContext, ) -> Result<(), (AztecMacroError, FileId)> { let traits: Vec<_> = collect_traits(context); - for struct_id in collect_crate_structs(crate_id, context) { - let interner: &mut NodeInterner = context.def_interner.borrow_mut(); - let r#struct = interner.get_struct(struct_id); - let file_id = r#struct.borrow().location.file; - let attributes = interner.struct_attributes(&struct_id); - if attributes.iter().any(|attr| is_custom_attribute(attr, "aztec(storage)")) - && r#struct.borrow().id.krate().is_root() - { - let init_id = interner - .lookup_method( - &Type::Struct(interner.get_struct(struct_id), vec![]), - struct_id, - "init", - false, - ) - .ok_or(( - AztecMacroError::CouldNotAssignStorageSlots { - secondary_message: Some( - "Storage struct must have an init function".to_string(), - ), - }, - file_id, - ))?; - let init_function = interner.function(&init_id).block(interner); - let init_function_statement_id = init_function.statements().first().ok_or(( + if let Some((_, file_id)) = get_contract_module_data(context, crate_id) { + let storage_struct = collect_crate_structs(crate_id, context) + .iter() + .find_map(|&struct_id| { + let r#struct = context.def_interner.get_struct(struct_id); + let attributes = context.def_interner.struct_attributes(&struct_id); + if attributes.iter().any(|attr| is_custom_attribute(attr, "aztec(storage)")) + && r#struct.borrow().id.krate().is_root() + { + Some(r#struct) + } else { + None + } + }) + .ok_or(( AztecMacroError::CouldNotAssignStorageSlots { - secondary_message: Some("Init storage statement not found".to_string()), + secondary_message: Some("Storage struct not found".to_string()), }, file_id, ))?; - let storage_constructor_statement = interner.statement(init_function_statement_id); - let storage_constructor_expression = match storage_constructor_statement { - HirStatement::Expression(expression_id) => { - match interner.expression(&expression_id) { + let storage_layout = context + .def_interner + .get_all_globals() + .iter() + .find_map(|global_info| { + let statement = context.def_interner.get_global_let_statement(global_info.id); + if statement.clone().is_some_and(|stmt| { + stmt.attributes + .iter() + .any(|attr| *attr == SecondaryAttribute::Abi("storage".to_string())) + }) { + let expr = context.def_interner.expression(&statement.unwrap().expression); + match expr { HirExpression::Constructor(hir_constructor_expression) => { - Ok(hir_constructor_expression) + Some(hir_constructor_expression) } - _ => Err((AztecMacroError::CouldNotAssignStorageSlots { + _ => None, + } + } else { + None + } + }) + .ok_or(( + AztecMacroError::CouldNotAssignStorageSlots { + secondary_message: Some("Storage layout struct not found".to_string()), + }, + file_id, + ))?; + + let init_id = context + .def_interner + .lookup_method( + &Type::Struct(context.def_interner.get_struct(storage_struct.borrow().id), vec![]), + storage_struct.borrow().id, + "init", + false, + ) + .ok_or(( + AztecMacroError::CouldNotAssignStorageSlots { + secondary_message: Some( + "Storage struct must have an init function".to_string(), + ), + }, + file_id, + ))?; + let init_function = context.def_interner.function(&init_id).block(&context.def_interner); + let init_function_statement_id = init_function.statements().first().ok_or(( + AztecMacroError::CouldNotAssignStorageSlots { + secondary_message: Some("Init storage statement not found".to_string()), + }, + file_id, + ))?; + let storage_constructor_statement = + context.def_interner.statement(init_function_statement_id); + + let storage_constructor_expression = match storage_constructor_statement { + HirStatement::Expression(expression_id) => { + match context.def_interner.expression(&expression_id) { + HirExpression::Constructor(hir_constructor_expression) => { + Ok(hir_constructor_expression) + } + _ => Err(( + AztecMacroError::CouldNotAssignStorageSlots { secondary_message: Some( "Storage constructor statement must be a constructor expression" .to_string(), ), - }, file_id)) - } + }, + file_id, + )), } + } + _ => Err(( + AztecMacroError::CouldNotAssignStorageSlots { + secondary_message: Some( + "Storage constructor statement must be an expression".to_string(), + ), + }, + file_id, + )), + }?; + + let mut storage_slot: u64 = 1; + for (index, (_, expr_id)) in storage_constructor_expression.fields.iter().enumerate() { + let fields = storage_struct.borrow().get_fields(&[]); + let (field_name, field_type) = fields.get(index).unwrap(); + let new_call_expression = match context.def_interner.expression(expr_id) { + HirExpression::Call(hir_call_expression) => Ok(hir_call_expression), _ => Err(( AztecMacroError::CouldNotAssignStorageSlots { secondary_message: Some( - "Storage constructor statement must be an expression".to_string(), + "Storage field initialization expression is not a call expression" + .to_string(), ), }, file_id, )), }?; - let mut storage_slot: u64 = 1; - for (index, (_, expr_id)) in storage_constructor_expression.fields.iter().enumerate() { - let fields = r#struct.borrow().get_fields(&[]); - let (_, field_type) = fields.get(index).unwrap(); - let new_call_expression = match interner.expression(expr_id) { - HirExpression::Call(hir_call_expression) => Ok(hir_call_expression), - _ => Err(( - AztecMacroError::CouldNotAssignStorageSlots { - secondary_message: Some( - "Storage field initialization expression is not a call expression" - .to_string(), - ), - }, - file_id, - )), - }?; + let slot_arg_expression = + context.def_interner.expression(&new_call_expression.arguments[1]); - let slot_arg_expression = interner.expression(&new_call_expression.arguments[1]); + let current_storage_slot = match slot_arg_expression { + HirExpression::Literal(HirLiteral::Integer(slot, _)) => Ok(slot.to_u128()), + _ => Err(( + AztecMacroError::CouldNotAssignStorageSlots { + secondary_message: Some( + "Storage slot argument expression must be a literal integer" + .to_string(), + ), + }, + file_id, + )), + }?; - let current_storage_slot = match slot_arg_expression { - HirExpression::Literal(HirLiteral::Integer(slot, _)) => Ok(slot.to_u128()), - _ => Err(( - AztecMacroError::CouldNotAssignStorageSlots { - secondary_message: Some( - "Storage slot argument expression must be a literal integer" - .to_string(), - ), - }, - file_id, + let storage_layout_field = storage_layout + .fields + .iter() + .find(|field| field.0 .0.contents == field_name.to_string()); + + let storage_layout_slot_expr = if let Some((_, expr_id)) = storage_layout_field { + let expr = context.def_interner.expression(expr_id); + if let HirExpression::Constructor(storage_layout_field_storable_expr) = expr { + storage_layout_field_storable_expr.fields.iter().find_map(|(field, expr_id)| { + if field.0.contents == "slot" { + Some(*expr_id) + } else { + None + } + }) + } else { + None + } + } else { + None + } + .ok_or(( + AztecMacroError::CouldNotAssignStorageSlots { + secondary_message: Some(format!( + "Storage layout field ({}) not found or has an incorrect type", + field_name )), - }?; + }, + file_id, + ))?; - if current_storage_slot != 0 { - continue; - } + let new_storage_slot = if current_storage_slot == 0 { + u128::from(storage_slot) + } else { + current_storage_slot + }; - let type_serialized_len = get_serialized_length(&traits, field_type, interner) + let type_serialized_len = + get_serialized_length(&traits, field_type, &context.def_interner) .map_err(|err| (err, file_id))?; - interner.update_expression(new_call_expression.arguments[1], |expr| { - *expr = HirExpression::Literal(HirLiteral::Integer( - FieldElement::from(u128::from(storage_slot)), - false, - )); - }); - - storage_slot += type_serialized_len; - } + + context.def_interner.update_expression(new_call_expression.arguments[1], |expr| { + *expr = HirExpression::Literal(HirLiteral::Integer( + FieldElement::from(new_storage_slot), + false, + )); + }); + + context.def_interner.update_expression(storage_layout_slot_expr, |expr| { + *expr = HirExpression::Literal(HirLiteral::Integer( + FieldElement::from(new_storage_slot), + false, + )); + }); + + storage_slot += type_serialized_len; } } + Ok(()) } pub fn generate_storage_layout( module: &mut SortedModule, - storage_struct_name: &String, + storage_struct_name: String, ) -> Result<(), AztecMacroError> { let definition = module .types @@ -383,21 +475,46 @@ pub fn generate_storage_layout( let mut generic_args = vec![]; let mut storable_fields = vec![]; + let mut storable_fields_impl = vec![]; - definition.fields.iter().enumerate().for_each(|(index, (field_ident, _))| { + definition.fields.iter().enumerate().for_each(|(index, (field_ident, field_type))| { storable_fields.push(format!("{}: Storable", field_ident, index)); generic_args.push(format!("N{}", index)); + storable_fields_impl.push(format!( + "{}: Storable {{ slot: 0, typ: \"{}\" }}", + field_ident, + field_type.to_string().replace("plain::", "") + )); }); let storage_fields_source = format!( " - struct StorageFields<{}> {{ + struct StorageLayout<{}> {{ {} }} + + #[abi(storage)] + global STORAGE_LAYOUT = StorageLayout {{ + {} + }}; ", generic_args.join(", "), - storable_fields.join(",\n") + storable_fields.join(",\n"), + storable_fields_impl.join(",\n") ); + let (struct_ast, errors) = parse_program(&storage_fields_source); + if !errors.is_empty() { + dbg!(errors); + return Err(AztecMacroError::CouldNotImplementNoteInterface { + secondary_message: Some("Failed to parse Noir macro code (struct StorageLayout). This is either a bug in the compiler or the Noir macro code".to_string()), + span: None + }); + } + + let mut struct_ast = struct_ast.into_sorted(); + module.types.push(struct_ast.types.pop().unwrap()); + module.globals.push(struct_ast.globals.pop().unwrap()); + Ok(()) } diff --git a/noir/noir-repo/aztec_macros/src/utils/errors.rs b/noir/noir-repo/aztec_macros/src/utils/errors.rs index 509322b3db2c..9d7043d08eb0 100644 --- a/noir/noir-repo/aztec_macros/src/utils/errors.rs +++ b/noir/noir-repo/aztec_macros/src/utils/errors.rs @@ -12,6 +12,7 @@ pub enum AztecMacroError { CouldNotAssignStorageSlots { secondary_message: Option }, CouldNotImplementNoteInterface { span: Option, secondary_message: Option }, MultipleStorageDefinitions { span: Option }, + CouldNotExportStorageLayout { span: Option, secondary_message: Option }, EventError { span: Span, message: String }, UnsupportedAttributes { span: Span, secondary_message: Option }, } @@ -54,6 +55,11 @@ impl From for MacroError { secondary_message: None, span, }, + AztecMacroError::CouldNotExportStorageLayout { secondary_message, span } => MacroError { + primary_message: "Could not generate and export storage layout".to_string(), + secondary_message: secondary_message, + span, + }, AztecMacroError::EventError { span, message } => MacroError { primary_message: message, secondary_message: None, diff --git a/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs b/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs index f31a05842617..f5a2837fe1f0 100644 --- a/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs +++ b/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs @@ -1,12 +1,14 @@ use iter_extended::vecmap; use noirc_frontend::{ graph::CrateId, - hir::def_collector::dc_crate::UnresolvedTraitImpl, - macros_api::{HirContext, ModuleDefId, StructId}, - node_interner::{TraitId, TraitImplId}, - Signedness, Type, UnresolvedTypeData, + hir::def_map::LocalModuleId, + macros_api::{FileId, HirContext, ModuleDefId, StructId}, + node_interner::TraitId, + Signedness, Type, }; +use super::ast_utils::is_custom_attribute; + pub fn collect_crate_structs(crate_id: &CrateId, context: &HirContext) -> Vec { context .def_map(crate_id) @@ -70,49 +72,39 @@ pub fn signature_of_type(typ: &Type) -> String { } // Fetches the name of all structs that implement trait_name, both in the current crate and all of its dependencies. -pub fn fetch_struct_trait_impls( - context: &mut HirContext, - unresolved_traits_impls: &[UnresolvedTraitImpl], - trait_name: &str, -) -> Vec { - let mut struct_typenames: Vec = Vec::new(); - - // These structs can be declared in either external crates or the current one. External crates that contain - // dependencies have already been processed and resolved, but are available here via the NodeInterner. Note that - // crates on which the current crate does not depend on may not have been processed, and will be ignored. - for trait_impl_id in 0..context.def_interner.next_trait_impl_id().0 { - let trait_impl = &context.def_interner.get_trait_implementation(TraitImplId(trait_impl_id)); - - if trait_impl.borrow().ident.0.contents == *trait_name { - if let Type::Struct(s, _) = &trait_impl.borrow().typ { - struct_typenames.push(s.borrow().name.0.contents.clone()); +pub fn fetch_note_names(context: &mut HirContext, crate_id: &CrateId) -> Vec { + collect_crate_structs(crate_id, context) + .iter() + .filter_map(|&struct_id| { + let r#struct = context.def_interner.get_struct(struct_id); + let attributes = context.def_interner.struct_attributes(&struct_id); + if attributes.iter().any(|attr| is_custom_attribute(attr, "aztec(note)")) { + Some(r#struct.borrow().name.0.contents.clone()) } else { - panic!("Found impl for {} on non-Struct", trait_name); + None } - } - } + }) + .collect() +} - // This crate's traits and impls have not yet been resolved, so we look for impls in unresolved_trait_impls. - struct_typenames.extend( - unresolved_traits_impls - .iter() - .filter(|trait_impl| { - trait_impl - .trait_path - .segments - .last() - .expect("ICE: empty trait_impl path") - .0 - .contents - == *trait_name - }) - .filter_map(|trait_impl| match &trait_impl.object_type.typ { - UnresolvedTypeData::Named(path, _, _) => { - Some(path.segments.last().unwrap().0.contents.clone()) - } - _ => None, - }), - ); +pub fn get_contract_module_data( + context: &mut HirContext, + crate_id: &CrateId, +) -> Option<(LocalModuleId, 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. More than 1 contract in a crate is forbidden by the compiler + if contract_module_file_ids.is_empty() { + return None; + } - struct_typenames + Some(contract_module_file_ids[0]) }