From cbbac1b3d1188e48e96c37e1c6e8ce9292b03e87 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 18 Feb 2025 13:48:02 +0000 Subject: [PATCH] moving stuff around --- .../aztec/src/macros/functions/mod.nr | 340 +----------------- .../aztec/src/macros/functions/utils.nr | 320 +++++++++++++++++ .../aztec-nr/aztec/src/macros/mod.nr | 2 - 3 files changed, 333 insertions(+), 329 deletions(-) create mode 100644 noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr diff --git a/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr index 4bc0c2061e41..d9abaa124ff9 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr @@ -1,17 +1,10 @@ -mod abi_export; -mod call_interface_stubs; -pub mod initialization_utils; -pub mod stub_registry; +pub(crate) mod abi_export; +pub(crate) mod call_interface_stubs; +pub(crate) mod initialization_utils; +pub(crate) mod stub_registry; +pub(crate) mod utils; -use super::utils::{ - add_to_hasher, fn_has_noinitcheck, get_fn_visibility, is_fn_initializer, is_fn_internal, - is_fn_private, is_fn_view, modify_fn_body, module_has_initializer, module_has_storage, -}; -use protocol_types::meta::generate_serialize_to_fields; -use std::meta::type_of; - -use abi_export::create_fn_abi_export; -use call_interface_stubs::stub_fn; +use utils::{transform_private, transform_public}; // Functions can have multiple attributes applied to them, e.g. a single function can have #[public], #[view] and // #[internal]. However. the order in which this will be evaluated is unknown, which makes combining them tricky. @@ -25,34 +18,6 @@ use call_interface_stubs::stub_fn; // what we call a 'marker' attribute, that only exists for `#[private]` or `#[public]` to check if it's been applied. // Therefore, the execution order of `#[internal]` and `#[private]` is irrelevant. -/// Internal functions can only be called by the contract itself, typically from private into public. -pub comptime fn internal(_f: FunctionDefinition) { - // Marker attribute -} - -comptime fn create_internal_check(f: FunctionDefinition) -> Quoted { - let name = f.name(); - let assertion_message = f"Function {name} can only be called internally"; - quote { assert(context.msg_sender() == context.this_address(), $assertion_message); } -} - -/// View functions can only be called in a static execution context. -pub comptime fn view(_f: FunctionDefinition) { - // Marker attribute -} - -comptime fn create_view_check(f: FunctionDefinition) -> Quoted { - let name = f.name(); - let assertion_message = f"Function {name} can only be called statically"; - if is_fn_private(f) { - // Here `context` is of type context::PrivateContext - quote { assert(context.inputs.call_context.is_static_call == true, $assertion_message); } - } else { - // Here `context` is of type context::PublicContext - quote { assert(context.is_static_call(), $assertion_message); } - } -} - /// An initializer function is similar to a constructor: /// - it can only be called once /// - if there are multiple initializer functions, only one of them can be called @@ -66,178 +31,19 @@ pub comptime fn noinitcheck(_f: FunctionDefinition) { // Marker attribute } -comptime fn create_assert_correct_initializer_args(f: FunctionDefinition) -> Quoted { - let fn_visibility = get_fn_visibility(f); - f"dep::aztec::macros::functions::initialization_utils::assert_initialization_matches_address_preimage_{fn_visibility}(context);" - .quoted_contents() -} - -comptime fn create_mark_as_initialized(f: FunctionDefinition) -> Quoted { - let fn_visibility = get_fn_visibility(f); - f"dep::aztec::macros::functions::initialization_utils::mark_as_initialized_{fn_visibility}(&mut context);" - .quoted_contents() +/// Internal functions can only be called by the contract itself, typically from private into public. +pub comptime fn internal(_f: FunctionDefinition) { + // Marker attribute } -comptime fn create_init_check(f: FunctionDefinition) -> Quoted { - let fn_visibility = get_fn_visibility(f); - f"dep::aztec::macros::functions::initialization_utils::assert_is_initialized_{fn_visibility}(&mut context);" - .quoted_contents() +/// View functions can only be called in a static execution context. +pub comptime fn view(_f: FunctionDefinition) { + // Marker attribute } /// Private functions are executed client-side and preserve privacy. pub comptime fn private(f: FunctionDefinition) -> Quoted { - let fn_abi = create_fn_abi_export(f); - let fn_stub = stub_fn(f); - stub_registry::register(f.module(), fn_stub); - - // If a function is further modified as unconstrained, we throw an error - if f.is_unconstrained() { - let name = f.name(); - panic( - f"Function {name} is annotated with #[private] but marked as unconstrained, remove unconstrained keyword", - ); - } - - let module_has_initializer = module_has_initializer(f.module()); - let module_has_storage = module_has_storage(f.module()); - - // Private functions undergo a lot of transformations from their Aztec.nr form into a circuit that can be fed to the - // Private Kernel Circuit. - // First we change the function signature so that it also receives `PrivateContextInputs`, which contain information - // about the execution context (e.g. the caller). - let original_params = f.parameters(); - f.set_parameters(&[( - quote { inputs }, - quote { crate::context::inputs::private_context_inputs::PrivateContextInputs }.as_type(), - )] - .append(original_params)); - - let mut body = f.body().as_block().unwrap(); - - // The original params are hashed and passed to the `context` object, so that the kernel can verify we've received - // the correct values. - // TODO: Optimize args_hasher for small number of arguments - let args_hasher_name = quote { args_hasher }; - let args_hasher = original_params.fold( - quote { - let mut $args_hasher_name = dep::aztec::hash::ArgsHasher::new(); - }, - |args_hasher, param: (Quoted, Type)| { - let (name, typ) = param; - let appended_arg = add_to_hasher(args_hasher_name, name, typ); - quote { - $args_hasher - $appended_arg - } - }, - ); - - let context_creation = quote { let mut context = dep::aztec::context::private_context::PrivateContext::new(inputs, args_hasher.hash()); }; - - // Modifications introduced by the different marker attributes. - let internal_check = if is_fn_internal(f) { - create_internal_check(f) - } else { - quote {} - }; - - let view_check = if is_fn_view(f) { - create_view_check(f) - } else { - quote {} - }; - - let (assert_initializer, mark_as_initialized) = if is_fn_initializer(f) { - (create_assert_correct_initializer_args(f), create_mark_as_initialized(f)) - } else { - (quote {}, quote {}) - }; - - let storage_init = if module_has_storage { - quote { - // Some functions don't access storage, but it'd be quite difficult to only inject this variable if it is - // referenced. We instead ignore 'unused variable' warnings for it. - #[allow(unused_variables)] - let storage = Storage::init(&mut context); - } - } else { - quote {} - }; - - // Initialization checks are not included in contracts that don't have initializers. - let init_check = if module_has_initializer & !is_fn_initializer(f) & !fn_has_noinitcheck(f) { - create_init_check(f) - } else { - quote {} - }; - - // Finally, we need to change the return type to be `PrivateCircuitPublicInputs`, which is what the Private Kernel - // circuit expects. - let return_value_var_name = quote { macro__returned__values }; - - let return_value_type = f.return_type(); - let return_value = if body.len() == 0 { - quote {} - } else if return_value_type != type_of(()) { - // The original return value is passed to a second args hasher which the context receives. - let (body_without_return, last_body_expr) = body.pop_back(); - let return_value = last_body_expr.quoted(); - let return_value_assignment = - quote { let $return_value_var_name: $return_value_type = $return_value; }; - let return_hasher_name = quote { return_hasher }; - let return_value_into_hasher = - add_to_hasher(return_hasher_name, return_value_var_name, return_value_type); - - body = body_without_return; - - quote { - let mut $return_hasher_name = dep::aztec::hash::ArgsHasher::new(); - $return_value_assignment - $return_value_into_hasher - context.set_return_hash($return_hasher_name); - } - } else { - let (body_without_return, last_body_expr) = body.pop_back(); - if !last_body_expr.has_semicolon() - & last_body_expr.as_for().is_none() - & last_body_expr.as_assert().is_none() - & last_body_expr.as_for_range().is_none() - & last_body_expr.as_assert_eq().is_none() - & last_body_expr.as_let().is_none() { - let unused_return_value_name = f"_{return_value_var_name}".quoted_contents(); - body = body_without_return.push_back( - quote { let $unused_return_value_name = $last_body_expr; }.as_expr().unwrap(), - ); - } - quote {} - }; - - let context_finish = quote { context.finish() }; - - let to_prepend = quote { - $args_hasher - $context_creation - $assert_initializer - $init_check - $internal_check - $view_check - $storage_init - }; - - let to_append = quote { - $return_value - $mark_as_initialized - $context_finish - }; - let modified_body = modify_fn_body(body, to_prepend, to_append); - f.set_body(modified_body); - f.set_return_type( - quote { dep::protocol_types::abis::private_circuit_public_inputs::PrivateCircuitPublicInputs } - .as_type(), - ); - f.set_return_data(); - - fn_abi + transform_private(f) } /// Public functions are executed sequencer-side and do not preserve privacy, similar to the EVM. @@ -249,123 +55,3 @@ pub comptime fn public(f: FunctionDefinition) -> Quoted { transform_public(f) } } - -comptime fn transform_public(f: FunctionDefinition) -> Quoted { - let fn_abi = create_fn_abi_export(f); - let fn_stub = stub_fn(f); - stub_registry::register(f.module(), fn_stub); - - // If a function is further modified as unconstrained, we throw an error - if f.is_unconstrained() { - let name = f.name(); - panic( - f"Function {name} is annotated with #[public] but marked as unconstrained, remove unconstrained keyword", - ); - } - - let module_has_initializer = module_has_initializer(f.module()); - let module_has_storage = module_has_storage(f.module()); - - // Public functions undergo a lot of transformations from their Aztec.nr form. - let original_params = f.parameters(); - let args_len = original_params - .map(|(name, typ): (Quoted, Type)| { - generate_serialize_to_fields(name, typ, &[], false).0.len() - }) - .fold(0, |acc: u32, val: u32| acc + val); - - // Unlike in the private case, in public the `context` does not need to receive the hash of the original params. - let context_creation = quote { - let mut context = dep::aztec::context::public_context::PublicContext::new(|| { - // We start from 1 because we skip the selector for the dispatch function. - let serialized_args : [Field; $args_len] = dep::aztec::context::public_context::calldata_copy(1, $args_len); - dep::aztec::hash::hash_args_array(serialized_args) - }); - }; - - // Modifications introduced by the different marker attributes. - let internal_check = if is_fn_internal(f) { - create_internal_check(f) - } else { - quote {} - }; - - let view_check = if is_fn_view(f) { - create_view_check(f) - } else { - quote {} - }; - - let (assert_initializer, mark_as_initialized) = if is_fn_initializer(f) { - (create_assert_correct_initializer_args(f), create_mark_as_initialized(f)) - } else { - (quote {}, quote {}) - }; - - let storage_init = if module_has_storage { - // Some functions don't access storage, but it'd be quite difficult to only inject this variable if it is - // referenced. We instead ignore 'unused variable' warnings for it. - quote { - #[allow(unused_variables)] - let storage = Storage::init(&mut context); - } - } else { - quote {} - }; - - // Initialization checks are not included in contracts that don't have initializers. - let init_check = if module_has_initializer & !fn_has_noinitcheck(f) & !is_fn_initializer(f) { - create_init_check(f) - } else { - quote {} - }; - - let to_prepend = quote { - $context_creation - $assert_initializer - $init_check - $internal_check - $view_check - $storage_init - }; - - let to_append = quote { - $mark_as_initialized - }; - - let body = f.body().as_block().unwrap(); - let modified_body = modify_fn_body(body, to_prepend, to_append); - f.set_body(modified_body); - - // All public functions are automatically made unconstrained, even if they were not marked as such. This is because - // instead of compiling into a circuit, they will compile to bytecode that will be later transpiled into AVM - // bytecode. - f.set_unconstrained(true); - f.set_return_public(true); - - fn_abi -} - -pub comptime fn transform_unconstrained(f: FunctionDefinition) { - let context_creation = quote { let mut context = dep::aztec::context::unconstrained_context::UnconstrainedContext::new(); }; - let module_has_storage = module_has_storage(f.module()); - - let storage_init = if module_has_storage { - quote { - // Some functions don't access storage, but it'd be quite difficult to only inject this variable if it is - // referenced. We instead ignore 'unused variable' warnings for it. - #[allow(unused_variables)] - let storage = Storage::init(context); - } - } else { - quote {} - }; - let to_prepend = quote { - $context_creation - $storage_init - }; - let body = f.body().as_block().unwrap(); - let modified_body = modify_fn_body(body, to_prepend, quote {}); - f.set_return_public(true); - f.set_body(modified_body); -} diff --git a/noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr b/noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr new file mode 100644 index 000000000000..b7ab1a005707 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr @@ -0,0 +1,320 @@ +use crate::macros::{ + functions::{abi_export::create_fn_abi_export, call_interface_stubs::stub_fn, stub_registry}, + utils::{ + add_to_hasher, fn_has_noinitcheck, get_fn_visibility, is_fn_initializer, is_fn_internal, + is_fn_private, is_fn_view, modify_fn_body, module_has_initializer, module_has_storage, + }, +}; +use protocol_types::meta::generate_serialize_to_fields; +use std::meta::type_of; + +pub(crate) comptime fn transform_private(f: FunctionDefinition) -> Quoted { + let fn_abi = create_fn_abi_export(f); + let fn_stub = stub_fn(f); + stub_registry::register(f.module(), fn_stub); + + // If a function is further modified as unconstrained, we throw an error + if f.is_unconstrained() { + let name = f.name(); + panic( + f"Function {name} is annotated with #[private] but marked as unconstrained, remove unconstrained keyword", + ); + } + + let module_has_initializer = module_has_initializer(f.module()); + let module_has_storage = module_has_storage(f.module()); + + // Private functions undergo a lot of transformations from their Aztec.nr form into a circuit that can be fed to the + // Private Kernel Circuit. + // First we change the function signature so that it also receives `PrivateContextInputs`, which contain information + // about the execution context (e.g. the caller). + let original_params = f.parameters(); + f.set_parameters(&[( + quote { inputs }, + quote { crate::context::inputs::private_context_inputs::PrivateContextInputs }.as_type(), + )] + .append(original_params)); + + let mut body = f.body().as_block().unwrap(); + + // The original params are hashed and passed to the `context` object, so that the kernel can verify we've received + // the correct values. + // TODO: Optimize args_hasher for small number of arguments + let args_hasher_name = quote { args_hasher }; + let args_hasher = original_params.fold( + quote { + let mut $args_hasher_name = dep::aztec::hash::ArgsHasher::new(); + }, + |args_hasher, param: (Quoted, Type)| { + let (name, typ) = param; + let appended_arg = add_to_hasher(args_hasher_name, name, typ); + quote { + $args_hasher + $appended_arg + } + }, + ); + + let context_creation = quote { let mut context = dep::aztec::context::private_context::PrivateContext::new(inputs, args_hasher.hash()); }; + + // Modifications introduced by the different marker attributes. + let internal_check = if is_fn_internal(f) { + create_internal_check(f) + } else { + quote {} + }; + + let view_check = if is_fn_view(f) { + create_view_check(f) + } else { + quote {} + }; + + let (assert_initializer, mark_as_initialized) = if is_fn_initializer(f) { + (create_assert_correct_initializer_args(f), create_mark_as_initialized(f)) + } else { + (quote {}, quote {}) + }; + + let storage_init = if module_has_storage { + quote { + // Some functions don't access storage, but it'd be quite difficult to only inject this variable if it is + // referenced. We instead ignore 'unused variable' warnings for it. + #[allow(unused_variables)] + let storage = Storage::init(&mut context); + } + } else { + quote {} + }; + + // Initialization checks are not included in contracts that don't have initializers. + let init_check = if module_has_initializer & !is_fn_initializer(f) & !fn_has_noinitcheck(f) { + create_init_check(f) + } else { + quote {} + }; + + // Finally, we need to change the return type to be `PrivateCircuitPublicInputs`, which is what the Private Kernel + // circuit expects. + let return_value_var_name = quote { macro__returned__values }; + + let return_value_type = f.return_type(); + let return_value = if body.len() == 0 { + quote {} + } else if return_value_type != type_of(()) { + // The original return value is passed to a second args hasher which the context receives. + let (body_without_return, last_body_expr) = body.pop_back(); + let return_value = last_body_expr.quoted(); + let return_value_assignment = + quote { let $return_value_var_name: $return_value_type = $return_value; }; + let return_hasher_name = quote { return_hasher }; + let return_value_into_hasher = + add_to_hasher(return_hasher_name, return_value_var_name, return_value_type); + + body = body_without_return; + + quote { + let mut $return_hasher_name = dep::aztec::hash::ArgsHasher::new(); + $return_value_assignment + $return_value_into_hasher + context.set_return_hash($return_hasher_name); + } + } else { + let (body_without_return, last_body_expr) = body.pop_back(); + if !last_body_expr.has_semicolon() + & last_body_expr.as_for().is_none() + & last_body_expr.as_assert().is_none() + & last_body_expr.as_for_range().is_none() + & last_body_expr.as_assert_eq().is_none() + & last_body_expr.as_let().is_none() { + let unused_return_value_name = f"_{return_value_var_name}".quoted_contents(); + body = body_without_return.push_back( + quote { let $unused_return_value_name = $last_body_expr; }.as_expr().unwrap(), + ); + } + quote {} + }; + + let context_finish = quote { context.finish() }; + + let to_prepend = quote { + $args_hasher + $context_creation + $assert_initializer + $init_check + $internal_check + $view_check + $storage_init + }; + + let to_append = quote { + $return_value + $mark_as_initialized + $context_finish + }; + let modified_body = modify_fn_body(body, to_prepend, to_append); + f.set_body(modified_body); + f.set_return_type( + quote { dep::protocol_types::abis::private_circuit_public_inputs::PrivateCircuitPublicInputs } + .as_type(), + ); + f.set_return_data(); + + fn_abi +} + +pub(crate) comptime fn transform_public(f: FunctionDefinition) -> Quoted { + let fn_abi = create_fn_abi_export(f); + let fn_stub = stub_fn(f); + stub_registry::register(f.module(), fn_stub); + + // If a function is further modified as unconstrained, we throw an error + if f.is_unconstrained() { + let name = f.name(); + panic( + f"Function {name} is annotated with #[public] but marked as unconstrained, remove unconstrained keyword", + ); + } + + let module_has_initializer = module_has_initializer(f.module()); + let module_has_storage = module_has_storage(f.module()); + + // Public functions undergo a lot of transformations from their Aztec.nr form. + let original_params = f.parameters(); + let args_len = original_params + .map(|(name, typ): (Quoted, Type)| { + generate_serialize_to_fields(name, typ, &[], false).0.len() + }) + .fold(0, |acc: u32, val: u32| acc + val); + + // Unlike in the private case, in public the `context` does not need to receive the hash of the original params. + let context_creation = quote { + let mut context = dep::aztec::context::public_context::PublicContext::new(|| { + // We start from 1 because we skip the selector for the dispatch function. + let serialized_args : [Field; $args_len] = dep::aztec::context::public_context::calldata_copy(1, $args_len); + dep::aztec::hash::hash_args_array(serialized_args) + }); + }; + + // Modifications introduced by the different marker attributes. + let internal_check = if is_fn_internal(f) { + create_internal_check(f) + } else { + quote {} + }; + + let view_check = if is_fn_view(f) { + create_view_check(f) + } else { + quote {} + }; + + let (assert_initializer, mark_as_initialized) = if is_fn_initializer(f) { + (create_assert_correct_initializer_args(f), create_mark_as_initialized(f)) + } else { + (quote {}, quote {}) + }; + + let storage_init = if module_has_storage { + // Some functions don't access storage, but it'd be quite difficult to only inject this variable if it is + // referenced. We instead ignore 'unused variable' warnings for it. + quote { + #[allow(unused_variables)] + let storage = Storage::init(&mut context); + } + } else { + quote {} + }; + + // Initialization checks are not included in contracts that don't have initializers. + let init_check = if module_has_initializer & !fn_has_noinitcheck(f) & !is_fn_initializer(f) { + create_init_check(f) + } else { + quote {} + }; + + let to_prepend = quote { + $context_creation + $assert_initializer + $init_check + $internal_check + $view_check + $storage_init + }; + + let to_append = quote { + $mark_as_initialized + }; + + let body = f.body().as_block().unwrap(); + let modified_body = modify_fn_body(body, to_prepend, to_append); + f.set_body(modified_body); + + // All public functions are automatically made unconstrained, even if they were not marked as such. This is because + // instead of compiling into a circuit, they will compile to bytecode that will be later transpiled into AVM + // bytecode. + f.set_unconstrained(true); + f.set_return_public(true); + + fn_abi +} + +pub(crate) comptime fn transform_unconstrained(f: FunctionDefinition) { + let context_creation = quote { let mut context = dep::aztec::context::unconstrained_context::UnconstrainedContext::new(); }; + let module_has_storage = module_has_storage(f.module()); + + let storage_init = if module_has_storage { + quote { + // Some functions don't access storage, but it'd be quite difficult to only inject this variable if it is + // referenced. We instead ignore 'unused variable' warnings for it. + #[allow(unused_variables)] + let storage = Storage::init(context); + } + } else { + quote {} + }; + let to_prepend = quote { + $context_creation + $storage_init + }; + let body = f.body().as_block().unwrap(); + let modified_body = modify_fn_body(body, to_prepend, quote {}); + f.set_return_public(true); + f.set_body(modified_body); +} + +comptime fn create_internal_check(f: FunctionDefinition) -> Quoted { + let name = f.name(); + let assertion_message = f"Function {name} can only be called internally"; + quote { assert(context.msg_sender() == context.this_address(), $assertion_message); } +} + +comptime fn create_view_check(f: FunctionDefinition) -> Quoted { + let name = f.name(); + let assertion_message = f"Function {name} can only be called statically"; + if is_fn_private(f) { + // Here `context` is of type context::PrivateContext + quote { assert(context.inputs.call_context.is_static_call == true, $assertion_message); } + } else { + // Here `context` is of type context::PublicContext + quote { assert(context.is_static_call(), $assertion_message); } + } +} + +comptime fn create_assert_correct_initializer_args(f: FunctionDefinition) -> Quoted { + let fn_visibility = get_fn_visibility(f); + f"dep::aztec::macros::functions::initialization_utils::assert_initialization_matches_address_preimage_{fn_visibility}(context);" + .quoted_contents() +} + +comptime fn create_mark_as_initialized(f: FunctionDefinition) -> Quoted { + let fn_visibility = get_fn_visibility(f); + f"dep::aztec::macros::functions::initialization_utils::mark_as_initialized_{fn_visibility}(&mut context);" + .quoted_contents() +} + +comptime fn create_init_check(f: FunctionDefinition) -> Quoted { + let fn_visibility = get_fn_visibility(f); + f"dep::aztec::macros::functions::initialization_utils::assert_is_initialized_{fn_visibility}(&mut context);" + .quoted_contents() +} diff --git a/noir-projects/aztec-nr/aztec/src/macros/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/mod.nr index 25f15c7ec207..0ca5d6874851 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/mod.nr @@ -10,12 +10,10 @@ use notes::{generate_note_export, NOTES}; use storage::STORAGE_LAYOUT_NAME; use dispatch::generate_public_dispatch; -use functions::transform_unconstrained; use utils::{get_trait_impl_method, module_has_storage}; /// Marks a contract as an Aztec contract, generating the interfaces for its functions and notes, as well as injecting /// the `compute_note_hash_and_optionally_a_nullifier` function PXE requires in order to validate notes. -/// /// Note: This is a module annotation, so the returned quote gets injected inside the module (contract) itself. pub comptime fn aztec(m: Module) -> Quoted { let interface = generate_contract_interface(m);