@@ -3,6 +3,8 @@ use std::vec;
33
44use convert_case:: { Case , Casing } ;
55use iter_extended:: vecmap;
6+ use noirc_frontend:: hir:: def_collector:: dc_crate:: { UnresolvedFunctions , UnresolvedTraitImpl } ;
7+ use noirc_frontend:: hir:: def_map:: { LocalModuleId , ModuleId } ;
68use noirc_frontend:: macros_api:: FieldElement ;
79use noirc_frontend:: macros_api:: {
810 BlockExpression , CallExpression , CastExpression , Distinctness , Expression , ExpressionKind ,
@@ -16,9 +18,10 @@ use noirc_frontend::macros_api::{
1618use noirc_frontend:: macros_api:: { CrateId , FileId } ;
1719use noirc_frontend:: macros_api:: { MacroError , MacroProcessor } ;
1820use noirc_frontend:: macros_api:: { ModuleDefId , NodeInterner , SortedModule , StructId } ;
19- use noirc_frontend:: node_interner:: { TraitId , TraitImplKind } ;
21+ use noirc_frontend:: node_interner:: { FuncId , TraitId , TraitImplId , TraitImplKind } ;
2022use noirc_frontend:: Lambda ;
21-
23+ use noirc_frontend:: macros_api:: parse_program;
24+ use noirc_errors:: Location ;
2225pub struct AztecMacro ;
2326
2427impl MacroProcessor for AztecMacro {
@@ -31,6 +34,20 @@ impl MacroProcessor for AztecMacro {
3134 transform ( ast, crate_id, context)
3235 }
3336
37+ fn process_unresolved_traits_impls (
38+ & self ,
39+ crate_id : & CrateId ,
40+ context : & mut HirContext ,
41+ unresolved_traits_impls : & Vec < UnresolvedTraitImpl > ,
42+ collected_functions : & mut Vec < UnresolvedFunctions > ,
43+ ) -> Result < ( ) , ( MacroError , FileId ) > {
44+ if has_aztec_dependency ( crate_id, context) {
45+ inject_compute_note_hash_and_nullifier ( crate_id, context, unresolved_traits_impls, collected_functions)
46+ } else {
47+ Ok ( ( ) )
48+ }
49+ }
50+
3451 fn process_typed_ast (
3552 & self ,
3653 crate_id : & CrateId ,
@@ -46,7 +63,6 @@ const MAX_CONTRACT_FUNCTIONS: usize = 2_usize.pow(FUNCTION_TREE_HEIGHT);
4663#[ derive( Debug , Clone ) ]
4764pub enum AztecMacroError {
4865 AztecDepNotFound ,
49- ComputeNoteHashAndNullifierNotFound { span : Span } ,
5066 ContractHasTooManyFunctions { span : Span } ,
5167 ContractConstructorMissing { span : Span } ,
5268 UnsupportedFunctionArgumentType { span : Span , typ : UnresolvedTypeData } ,
@@ -63,11 +79,6 @@ impl From<AztecMacroError> for MacroError {
6379 secondary_message : None ,
6480 span : None ,
6581 } ,
66- AztecMacroError :: ComputeNoteHashAndNullifierNotFound { span } => MacroError {
67- primary_message : "compute_note_hash_and_nullifier function not found. Define it in your contract. For more information go to https://docs.aztec.network/developers/debugging/aztecnr-errors#compute_note_hash_and_nullifier-function-not-found-define-it-in-your-contract" . to_owned ( ) ,
68- secondary_message : None ,
69- span : Some ( span) ,
70- } ,
7182 AztecMacroError :: ContractHasTooManyFunctions { span } => MacroError {
7283 primary_message : format ! ( "Contract can only have a maximum of {} functions" , MAX_CONTRACT_FUNCTIONS ) ,
7384 secondary_message : None ,
@@ -313,15 +324,17 @@ fn check_for_aztec_dependency(
313324 crate_id : & CrateId ,
314325 context : & HirContext ,
315326) -> Result < ( ) , ( MacroError , FileId ) > {
316- let crate_graph = & context. crate_graph [ crate_id] ;
317- let has_aztec_dependency = crate_graph. dependencies . iter ( ) . any ( |dep| dep. as_name ( ) == "aztec" ) ;
318- if has_aztec_dependency {
327+ if has_aztec_dependency ( crate_id, context) {
319328 Ok ( ( ) )
320329 } else {
321- Err ( ( AztecMacroError :: AztecDepNotFound . into ( ) , crate_graph. root_file_id ) )
330+ Err ( ( AztecMacroError :: AztecDepNotFound . into ( ) , context . crate_graph [ crate_id ] . root_file_id ) )
322331 }
323332}
324333
334+ fn has_aztec_dependency ( crate_id : & CrateId , context : & HirContext ) -> bool {
335+ context. crate_graph [ crate_id] . dependencies . iter ( ) . any ( |dep| dep. as_name ( ) == "aztec" )
336+ }
337+
325338// Check to see if the user has defined a storage struct
326339fn check_for_storage_definition ( module : & SortedModule ) -> bool {
327340 module. types . iter ( ) . any ( |r#struct| r#struct. name . 0 . contents == "Storage" )
@@ -338,27 +351,27 @@ fn check_for_storage_implementation(module: &SortedModule) -> bool {
338351}
339352
340353// Check if "compute_note_hash_and_nullifier(AztecAddress,Field,Field,Field,[Field; N]) -> [Field; 4]" is defined
341- fn check_for_compute_note_hash_and_nullifier_definition ( module : & SortedModule ) -> bool {
342- module . functions . iter ( ) . any ( |func | {
343- func . def . name . 0 . contents == "compute_note_hash_and_nullifier"
344- && func . def . parameters . len ( ) == 5
345- && match & func . def . parameters [ 0 ] . typ . typ {
354+ fn check_for_compute_note_hash_and_nullifier_definition ( functions_data : & Vec < ( LocalModuleId , FuncId , NoirFunction ) > , module_id : LocalModuleId ) -> bool {
355+ functions_data . iter ( ) . filter ( |func_data| func_data . 0 == module_id ) . any ( |func_data | {
356+ func_data . 2 . def . name . 0 . contents == "compute_note_hash_and_nullifier"
357+ && func_data . 2 . def . parameters . len ( ) == 5
358+ && match & func_data . 2 . def . parameters [ 0 ] . typ . typ {
346359 UnresolvedTypeData :: Named ( path, _, _) => path. segments . last ( ) . unwrap ( ) . 0 . contents == "AztecAddress" ,
347360 _ => false ,
348361 }
349- && func . def . parameters [ 1 ] . typ . typ == UnresolvedTypeData :: FieldElement
350- && func . def . parameters [ 2 ] . typ . typ == UnresolvedTypeData :: FieldElement
351- && func . def . parameters [ 3 ] . typ . typ == UnresolvedTypeData :: FieldElement
362+ && func_data . 2 . def . parameters [ 1 ] . typ . typ == UnresolvedTypeData :: FieldElement
363+ && func_data . 2 . def . parameters [ 2 ] . typ . typ == UnresolvedTypeData :: FieldElement
364+ && func_data . 2 . def . parameters [ 3 ] . typ . typ == UnresolvedTypeData :: FieldElement
352365 // checks if the 5th parameter is an array and the Box<UnresolvedType> in
353366 // Array(Option<UnresolvedTypeExpression>, Box<UnresolvedType>) contains only fields
354- && match & func . def . parameters [ 4 ] . typ . typ {
367+ && match & func_data . 2 . def . parameters [ 4 ] . typ . typ {
355368 UnresolvedTypeData :: Array ( _, inner_type) => {
356369 matches ! ( inner_type. typ, UnresolvedTypeData :: FieldElement )
357370 } ,
358371 _ => false ,
359372 }
360373 // We check the return type the same way as we did the 5th parameter
361- && match & func . def . return_type {
374+ && match & func_data . 2 . def . return_type {
362375 FunctionReturnType :: Default ( _) => false ,
363376 FunctionReturnType :: Ty ( unresolved_type) => {
364377 match & unresolved_type. typ {
@@ -401,13 +414,6 @@ fn transform_module(
401414 generate_storage_implementation ( module) . map_err ( |err| ( err, crate_graph. root_file_id ) ) ?;
402415 }
403416
404- if storage_defined && !check_for_compute_note_hash_and_nullifier_definition ( module) {
405- return Err ( (
406- AztecMacroError :: ComputeNoteHashAndNullifierNotFound { span : Span :: default ( ) } ,
407- crate_graph. root_file_id ,
408- ) ) ;
409- }
410-
411417 for structure in module. types . iter ( ) {
412418 if structure. attributes . iter ( ) . any ( |attr| matches ! ( attr, SecondaryAttribute :: Event ) ) {
413419 module. impls . push ( generate_selector_impl ( structure) ) ;
@@ -596,7 +602,7 @@ fn generate_storage_implementation(module: &mut SortedModule) -> Result<(), Azte
596602/// If it does, it will insert the following things:
597603/// - A new Input that is provided for a kernel app circuit, named: {Public/Private}ContextInputs
598604/// - Hashes all of the function input variables
599- /// - This instantiates a helper function
605+ /// - This instantiates a helper function
600606fn transform_function (
601607 ty : & str ,
602608 func : & mut NoirFunction ,
@@ -1601,3 +1607,165 @@ fn event_signature(event: &StructType) -> String {
16011607 let fields = vecmap ( event. get_fields ( & [ ] ) , |( _, typ) | signature_of_type ( & typ) ) ;
16021608 format ! ( "{}({})" , event. name. 0 . contents, fields. join( "," ) )
16031609}
1610+
1611+ fn inject_compute_note_hash_and_nullifier (
1612+ crate_id : & CrateId ,
1613+ context : & mut HirContext ,
1614+ unresolved_traits_impls : & Vec < UnresolvedTraitImpl > ,
1615+ collected_functions : & mut Vec < UnresolvedFunctions > ,
1616+ ) -> Result < ( ) , ( MacroError , FileId ) > {
1617+ // We first fetch modules in this crate which correspond to contracts, along with their file id.
1618+ let contract_module_file_ids: Vec < ( LocalModuleId , FileId ) > = context. def_map ( crate_id) . expect ( "ICE: Missing crate in def_map" )
1619+ . modules ( ) . iter ( )
1620+ . filter ( |( _, module) | module. is_contract )
1621+ . map ( |( idx, module) | ( LocalModuleId ( idx) , module. location . file ) )
1622+ . collect ( ) ;
1623+
1624+ // If the current crate does not contain a contract module we simply skip it.
1625+ if contract_module_file_ids. len ( ) == 0 {
1626+ return Ok ( ( ) ) ;
1627+ } else if contract_module_file_ids. len ( ) != 1 {
1628+ panic ! ( "Found multiple contracts in the same crate" ) ;
1629+ }
1630+
1631+ let ( module_id, file_id) = contract_module_file_ids[ 0 ] ;
1632+
1633+ // If compute_note_hash_and_nullifier is already defined by the user, we skip auto-generation in order to provide an
1634+ // escape hatch for this mechanism.
1635+ // TODO(#4647): improve this diagnosis and error messaging.
1636+ if collected_functions. iter ( ) . any ( |coll_funcs_data| check_for_compute_note_hash_and_nullifier_definition ( & coll_funcs_data. functions , module_id) ) {
1637+ return Ok ( ( ) ) ;
1638+ }
1639+
1640+ // In order to implement compute_note_hash_and_nullifier, we need to know all of the different note types the
1641+ // contract might use. These are the types that implement the NoteInterface trait, which provides the
1642+ // get_note_type_id function.
1643+ let note_types = fetch_struct_trait_impls ( context, unresolved_traits_impls, "NoteInterface" ) ;
1644+
1645+ // We can now generate a version of compute_note_hash_and_nullifier tailored for the contract in this crate.
1646+ let func = generate_compute_note_hash_and_nullifier ( & note_types) ;
1647+
1648+ // And inject the newly created function into the contract.
1649+
1650+ // TODO(#4373): We don't have a reasonable location for the source code of this autogenerated function, so we simply
1651+ // pass an empty span. This function should not produce errors anyway so this should not matter.
1652+ let location = Location :: new ( Span :: empty ( 0 ) , file_id) ;
1653+
1654+ // These are the same things the ModCollector does when collecting functions: we push the function to the
1655+ // NodeInterner, declare it in the module (which checks for duplicate definitions), and finally add it to the list
1656+ // on collected but unresolved functions.
1657+
1658+ let func_id = context. def_interner . push_empty_fn ( ) ;
1659+ context. def_interner . push_function ( func_id, & func. def , ModuleId { krate : * crate_id, local_id : module_id } , location) ;
1660+
1661+ context. def_map_mut ( crate_id) . unwrap ( )
1662+ . modules_mut ( ) [ module_id. 0 ]
1663+ . declare_function (
1664+ func. name_ident ( ) . clone ( ) , func_id
1665+ ) . expect (
1666+ "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."
1667+ ) ;
1668+
1669+ collected_functions. iter_mut ( )
1670+ . find ( |fns| fns. file_id == file_id) . expect ( "ICE: no functions found in contract file" )
1671+ . push_fn ( module_id, func_id, func. clone ( ) ) ;
1672+
1673+ Ok ( ( ) )
1674+ }
1675+
1676+ // Fetches the name of all structs that implement trait_name, both in the current crate and all of its dependencies.
1677+ fn fetch_struct_trait_impls (
1678+ context : & mut HirContext ,
1679+ unresolved_traits_impls : & Vec < UnresolvedTraitImpl > ,
1680+ trait_name : & str
1681+ ) -> Vec < String > {
1682+ let mut struct_typenames: Vec < String > = Vec :: new ( ) ;
1683+
1684+ // These structs can be declared in either external crates or the current one. External crates that contain
1685+ // dependencies have already been processed and resolved, but are available here via the NodeInterner. Note that
1686+ // crates on which the current crate does not depend on may not have been processed, and will be ignored.
1687+ for trait_impl_id in 0 ..( & context. def_interner . next_trait_impl_id ( ) ) . 0 {
1688+ let trait_impl = & context. def_interner . get_trait_implementation ( TraitImplId ( trait_impl_id) ) ;
1689+
1690+ if trait_impl. borrow ( ) . ident . 0 . contents == * trait_name {
1691+ if let Type :: Struct ( s, _) = & trait_impl. borrow ( ) . typ {
1692+ struct_typenames. push ( s. borrow ( ) . name . 0 . contents . clone ( ) ) ;
1693+ } else {
1694+ panic ! ( "Found impl for {} on non-Struct" , trait_name) ;
1695+ }
1696+ }
1697+ }
1698+
1699+ // This crate's traits and impls have not yet been resolved, so we look for impls in unresolved_trait_impls.
1700+ struct_typenames. extend ( unresolved_traits_impls. iter ( )
1701+ . filter ( |trait_impl|
1702+ trait_impl. trait_path . segments . last ( ) . expect ( "ICE: empty trait_impl path" ) . 0 . contents == * trait_name)
1703+ . filter_map ( |trait_impl| match & trait_impl. object_type . typ {
1704+ UnresolvedTypeData :: Named ( path, _, _) => Some ( path. segments . last ( ) . unwrap ( ) . 0 . contents . clone ( ) ) ,
1705+ _ => None ,
1706+ } ) ) ;
1707+
1708+ struct_typenames
1709+ }
1710+
1711+ fn generate_compute_note_hash_and_nullifier ( note_types : & Vec < String > ) -> NoirFunction {
1712+ let function_source = generate_compute_note_hash_and_nullifier_source ( note_types) ;
1713+
1714+ let ( function_ast, errors) = parse_program ( & function_source) ;
1715+ if !errors. is_empty ( ) {
1716+ dbg ! ( errors. clone( ) ) ;
1717+ }
1718+ assert_eq ! ( errors. len( ) , 0 , "Failed to parse Noir macro code. This is either a bug in the compiler or the Noir macro code" ) ;
1719+
1720+ let mut function_ast = function_ast. into_sorted ( ) ;
1721+ function_ast. functions . remove ( 0 )
1722+ }
1723+
1724+ fn generate_compute_note_hash_and_nullifier_source ( note_types : & Vec < String > ) -> String {
1725+ // TODO(#4649): The serialized_note parameter is a fixed-size array, but we don't know what length it should have.
1726+ // For now we hardcode it to 20, which is the same as MAX_NOTE_FIELDS_LENGTH.
1727+
1728+ if note_types. len ( ) == 0 {
1729+ // TODO(#4520): Even if the contract does not include any notes, other parts of the stack expect for this
1730+ // function to exist, so we include a dummy version. We likely should error out here instead.
1731+ "
1732+ unconstrained fn compute_note_hash_and_nullifier(
1733+ contract_address: AztecAddress,
1734+ nonce: Field,
1735+ storage_slot: Field,
1736+ note_type_id: Field,
1737+ serialized_note: [Field; 20]
1738+ ) -> pub [Field; 4] {
1739+ [0, 0, 0, 0]
1740+ }" . to_string ( )
1741+ } else {
1742+ // For contracts that include notes we do a simple if-else chain comparing note_type_id with the different
1743+ // get_note_type_id of each of the note types.
1744+
1745+ let if_statements: Vec < String > = note_types. iter ( ) . map ( |note_type| format ! (
1746+ "if (note_type_id == {0}::get_note_type_id()) {{
1747+ note_utils::compute_note_hash_and_nullifier({0}::deserialize_content, note_header, serialized_note)
1748+ }}"
1749+ , note_type) ) . collect ( ) ;
1750+
1751+ // TODO(#4520): error out on the else instead of returning a zero array
1752+ let full_if_statement = if_statements. join ( " else " ) + "
1753+ else {
1754+ [0, 0, 0, 0]
1755+ }" ;
1756+
1757+ format ! ( "
1758+ unconstrained fn compute_note_hash_and_nullifier(
1759+ contract_address: AztecAddress,
1760+ nonce: Field,
1761+ storage_slot: Field,
1762+ note_type_id: Field,
1763+ serialized_note: [Field; 20]
1764+ ) -> pub [Field; 4] {{
1765+ let note_header = NoteHeader::new(contract_address, nonce, storage_slot);
1766+
1767+ {}
1768+ }}" , full_if_statement)
1769+ }
1770+
1771+ }
0 commit comments