diff --git a/avm-transpiler/src/transpile_contract.rs b/avm-transpiler/src/transpile_contract.rs index 5fa671f30a16..661c823f6c65 100644 --- a/avm-transpiler/src/transpile_contract.rs +++ b/avm-transpiler/src/transpile_contract.rs @@ -92,7 +92,7 @@ impl From for TranspiledContractArtifact { let mut has_public_dispatch = false; for function in contract.functions { - if function.custom_attributes.contains(&"public".to_string()) { + if function.custom_attributes.contains(&"abi_public".to_string()) { if function.name == "public_dispatch" { has_public_dispatch = true; } @@ -192,7 +192,7 @@ fn create_revert_dispatch_fn() -> AvmOrAcirContractFunctionArtifact { let empty_dispatch_fn = AvmContractFunctionArtifact { name: "public_dispatch".to_string(), is_unconstrained: true, - custom_attributes: vec!["public".to_string()], + custom_attributes: vec!["abi_public".to_string()], abi: Abi { parameters: vec![AbiParameter { name: "selector".to_string(), diff --git a/aztec-up/bin/aztec b/aztec-up/bin/aztec index 667a77bdccb2..a9774f8c2e98 100755 --- a/aztec-up/bin/aztec +++ b/aztec-up/bin/aztec @@ -249,6 +249,15 @@ EOF --entrypoint=/usr/src/barretenberg/cpp/build/bin/bb-avm \ $DOCKER_REPO:$VERSION aztec_process + # Strip internal prefixes from all compiled contract JSONs in target directory + docker run ${TTY_FLAGS:-} \ + --user $(id -u):$(id -g) \ + -v $HOME:$HOME \ + -e HOME=$HOME \ + --workdir="$PWD" \ + --entrypoint=/bin/bash \ + $DOCKER_REPO:$VERSION -c 'for json in target/*.json; do [ -f "$json" ] && /usr/src/noir-projects/noir-contracts/scripts/strip_aztec_nr_prefix.sh "$json"; done' + echo "Compilation complete!" ;; test) diff --git a/cspell.json b/cspell.json index 59bf9cd1acf9..d4c88ebe535d 100644 --- a/cspell.json +++ b/cspell.json @@ -63,10 +63,10 @@ "checkpointed", "checksummed", "chiplet", + "chonk", "cimg", "ciphertext", "ciphertexts", - "chonk", "clonedeep", "clonedeepwith", "cmd", @@ -342,6 +342,7 @@ "typecheck", "typegen", "typeparam", + "uncallable", "undeployed", "undici", "unexclude", diff --git a/docs/examples/bootstrap.sh b/docs/examples/bootstrap.sh index 824966af6c5e..3edcfb0a923f 100755 --- a/docs/examples/bootstrap.sh +++ b/docs/examples/bootstrap.sh @@ -10,6 +10,7 @@ REPO_ROOT=$(git rev-parse --show-toplevel) export BB=${BB:-"$REPO_ROOT/barretenberg/cpp/build/bin/bb"} export NARGO=${NARGO:-"$REPO_ROOT/noir/noir-repo/target/release/nargo"} export TRANSPILER=${TRANSPILER:-"$REPO_ROOT/avm-transpiler/target/release/avm-transpiler"} +export STRIP_AZTEC_NR_PREFIX=${STRIP_AZTEC_NR_PREFIX:-"$REPO_ROOT/noir-projects/noir-contracts/scripts/strip_aztec_nr_prefix.sh"} export BB_HASH=${BB_HASH:-$("$REPO_ROOT/barretenberg/cpp/bootstrap.sh" hash)} export NOIR_HASH=${NOIR_HASH:-$("$REPO_ROOT/noir/bootstrap.sh" hash)} diff --git a/noir-projects/aztec-nr/aztec/src/macros/aztec.nr b/noir-projects/aztec-nr/aztec/src/macros/aztec.nr index ed4ff63124d2..69f0309e397b 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/aztec.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/aztec.nr @@ -1,6 +1,7 @@ use crate::macros::{ dispatch::generate_public_dispatch, functions::{self_call_registry, stub_registry, utils::check_each_fn_macroified}, + internals_functions_generation::{create_fn_abi_exports, process_functions}, notes::NOTES, storage::STORAGE_LAYOUT_NAME, utils::{get_trait_impl_method, module_has_storage}, @@ -10,11 +11,18 @@ use crate::macros::{ /// the `sync_private_state` utility function. /// Note: This is a module annotation, so the returned quote gets injected inside the module (contract) itself. pub comptime fn aztec(m: Module) -> Quoted { + // Functions that don't have #[external(...)], #[contract_library_method], or #[test] are not allowed in contracts. + check_each_fn_macroified(m); + + // We generate new functions prefixed with `__aztec_nr_internals__` and we replace the original functions' bodies + // with `static_assert(false, ...)` to prevent them from being called directly from within the contract. + let functions = process_functions(m); + let interface = generate_contract_interface(m); let self_call_structs = generate_self_call_structs(m); - // Functions that don't have #[external(...)], #[contract_library_method], or #[test] are not allowed in contracts. - check_each_fn_macroified(m); + // We generate ABI exports for all the external functions in the contract. + let fn_abi_exports = create_fn_abi_exports(m); // We generate `_compute_note_hash_and_nullifier`, `sync_private_state` and `process_message` // functions only if they are not already implemented. If they are implemented we just insert empty @@ -26,12 +34,17 @@ pub comptime fn aztec(m: Module) -> Quoted { } else { quote {} }; - let sync_private_state = if !m.functions().any(|f| f.name() == quote { sync_private_state }) { + let sync_private_state_fn_and_abi_export = if !m.functions().any(|f| { + f.name() == quote { sync_private_state } + }) { generate_sync_private_state() } else { quote {} }; - let process_message = if !m.functions().any(|f| f.name() == quote { process_message }) { + + let process_message_fn_and_abi_export = if !m.functions().any(|f| { + f.name() == quote { process_message } + }) { generate_process_message() } else { quote {} @@ -41,10 +54,12 @@ pub comptime fn aztec(m: Module) -> Quoted { quote { $interface $self_call_structs + $functions + $fn_abi_exports $contract_library_method_compute_note_hash_and_nullifier $public_dispatch - $sync_private_state - $process_message + $sync_private_state_fn_and_abi_export + $process_message_fn_and_abi_export } } @@ -249,36 +264,45 @@ comptime fn generate_contract_library_method_compute_note_hash_and_nullifier() - } comptime fn generate_sync_private_state() -> Quoted { - // We obtain the `external` function on the next line instead of directly doing - // `#[aztec::macros::functions::external("utility")]` in the returned quote because the latter would result in - // the function attribute having the full path in the ABI. This is undesirable because we use the information in - // the ABI only to determine whether a function is `external("private")`, `external("public")`, or `external("utility")`. - let external = crate::macros::functions::external; - - // All we need to do here is trigger message discovery, but this is already done by the #[external("utility")] macro - we don't - // need to do anything extra. quote { - #[$external("utility")] + pub struct sync_private_state_parameters {} + + #[abi(functions)] + pub struct sync_private_state_abi { + parameters: sync_private_state_parameters, + } + + #[aztec::macros::internals_functions_generation::abi_attributes::abi_utility] unconstrained fn sync_private_state() { + let address = aztec::context::utility_context::UtilityContext::new().this_address(); + + aztec::messages::discovery::discover_new_messages(address, _compute_note_hash_and_nullifier); } } } comptime fn generate_process_message() -> Quoted { - // We obtain the `external` function on the next line instead of directly doing - // `#[aztec::macros::functions::external("utility")]` in the returned quote because the latter would result in - // the function attribute having the full path in the ABI. This is undesirable because we use the information in - // the ABI only to determine whether a function is `external("private")`, `external("public")`, or `external("utility")`. - let external = crate::macros::functions::external; - quote { - #[$external("utility")] + pub struct process_message_parameters { + pub message_ciphertext: BoundedVec, + pub message_context: aztec::messages::processing::message_context::MessageContext, + } + + #[abi(functions)] + pub struct process_message_abi { + parameters: process_message_parameters, + } + + #[aztec::macros::internals_functions_generation::abi_attributes::abi_utility] unconstrained fn process_message( message_ciphertext: BoundedVec, message_context: aztec::messages::processing::message_context::MessageContext, ) { + let address = aztec::context::utility_context::UtilityContext::new().this_address(); + + aztec::messages::discovery::discover_new_messages(address, _compute_note_hash_and_nullifier); aztec::messages::discovery::process_message::process_message_ciphertext( - self.address, + address, _compute_note_hash_and_nullifier, message_ciphertext, message_context, diff --git a/noir-projects/aztec-nr/aztec/src/macros/dispatch.nr b/noir-projects/aztec-nr/aztec/src/macros/dispatch.nr index 2eaced239955..24bd44d74ea8 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/dispatch.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/dispatch.nr @@ -1,3 +1,4 @@ +use crate::macros::internals_functions_generation::external_functions_registry::get_public_functions; use super::utils::compute_fn_selector; use poseidon::poseidon2::Poseidon2Hasher; use protocol_types::meta::utils::get_params_len_quote; @@ -5,9 +6,7 @@ use std::{collections::umap::UHashMap, hash::BuildHasherDefault, panic}; /// Returns an `fn public_dispatch(...)` function for the given module that's assumed to be an Aztec contract. pub comptime fn generate_public_dispatch(m: Module) -> Quoted { - let functions = m.functions(); - let functions = - functions.filter(|function: FunctionDefinition| function.has_named_attribute("public")); + let functions = get_public_functions(m); let unit = get_type::<()>(); @@ -68,9 +67,12 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted { args = args.push_back(quote { $param_name }); } + // We call a function whose name is prefixed with `__aztec_nr_internals__`. This is necessary because the + // original function is intentionally made uncallable, preventing direct invocation within the contract. + // Instead, a new function with the same name, but prefixed by `__aztec_nr_internals__`, has been generated to + // be called here. For more details see the `process_functions` function. + let name = f"__aztec_nr_internals__{fn_name}".quoted_contents(); let args = args.join(quote { , }); - // name of the function is assigned just before the call so debug metadata doesn't span most of this macro when figuring out where the call comes from. - let name = function.name(); let call = quote { $name($args) }; let return_code = if return_type == unit { @@ -104,10 +106,8 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted { let dispatch = ifs.join(quote { }); let body = quote { - // We mark this as public because our whole system depends on public - // functions having this attribute. However, the public MACRO will - // handle the public_dispatch function specially and do nothing. - #[external("public")] + // We mark this as public because our whole system depends on public functions having this attribute. + #[aztec::macros::internals_functions_generation::abi_attributes::abi_public] pub unconstrained fn public_dispatch(selector: Field) { $dispatch } 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 daed53d6650e..c5dbe62e5476 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr @@ -1,4 +1,3 @@ -pub(crate) mod abi_export; pub(crate) mod call_interface_stubs; // TODO: Move initialization_utils out of this crate // See https://github.com/AztecProtocol/aztec-packages/pull/15856#discussion_r2229134689 for more details @@ -9,10 +8,7 @@ pub(crate) mod auth_registry; pub(crate) mod utils; use crate::macros::{ - functions::{ - abi_export::create_fn_abi_export, - utils::{transform_private, transform_public, transform_utility}, - }, + internals_functions_generation::external_functions_registry, utils::{is_fn_external, module_has_initializer}, }; use super::utils::{fn_has_noinitcheck, is_fn_initializer, is_fn_only_self, is_fn_view}; @@ -134,30 +130,36 @@ pub comptime fn authorize_once( AUTHORIZE_ONCE_REGISTRY.insert(f, (from_arg_name, nonce_arg_name)); } -/// Same as in Solidity external functions are functions that our callable from outside the contract. External -/// functions can be either private, public, or utility. -pub comptime fn external(f: FunctionDefinition, f_type: CtString) -> Quoted { +/// Same as in Solidity external functions are functions that our callable from outside the contract. +/// +/// There are 3 types of external functions: private, public, and utility. +/// - private functions are executed client-side and preserve privacy. +/// - public functions are executed sequencer-side and do not preserve privacy, similar to the EVM. +/// - utility functions are standalone unconstrained functions that cannot be called from another function in a +/// contract. They are typically used either to obtain some information from the contract (e.g. token balance of a +/// user) or to modify internal contract-related state of PXE (e.g. processing logs in Aztec.nr during sync). +/// +/// In this function we perform basic checks on the function to ensure it is valid and then we add the function to the +/// external functions registry for later processing by the `#[aztec]` macro. +pub comptime fn external(f: FunctionDefinition, f_type: CtString) { if f_type.eq("private") { - private(f) + assert_valid_private(f); + external_functions_registry::add_private(f); } else if f_type.eq("public") { - public(f) + assert_valid_public(f); + external_functions_registry::add_public(f); } else if f_type.eq("utility") { - utility(f) + assert_valid_utility(f); + external_functions_registry::add_utility(f); } else { let function_name = f.name(); panic( f"Function '{function_name}' is marked as #[external(\"{f_type}\")], but '{f_type}' is not a valid external function type. External functions must be one of 'private', 'public' or 'utility'", ); - quote {} } } -/// Private functions are executed client-side and preserve privacy. -comptime fn private(f: FunctionDefinition) -> Quoted { - // We need to add this attribute to be able to identify if the function is external private when constructing - // the contract artifact. - f.add_attribute("private"); - +comptime fn assert_valid_private(f: FunctionDefinition) { let visibility = f.visibility(); if visibility != quote {} { let name = f.name(); @@ -172,57 +174,26 @@ comptime fn private(f: FunctionDefinition) -> Quoted { f"#[external(\"private\")] functions must not be unconstrained - {name} is", ); } - - // The abi export function is expected to be executed before the function is transformed. - let fn_abi_export = create_fn_abi_export(f); - - transform_private(f); - - fn_abi_export } -/// Public functions are executed sequencer-side and do not preserve privacy, similar to the EVM. -comptime fn public(f: FunctionDefinition) -> Quoted { - // We need to add this attribute to be able to identify if the function is external public when constructing - // the contract artifact. - f.add_attribute("public"); - - // We don't want to transform the public_dispatch function. - if f.name() == quote { public_dispatch } { - quote {} - } else { - let visibility = f.visibility(); - if visibility != quote {} { - let name = f.name(); - panic( - f"A function marked as #[external(\"public\")] must not have public Noir visibility - {name}'s visibility is '{visibility}'", - ); - } - - if f.is_unconstrained() { - let name = f.name(); - panic( - f"#[external(\"public\")] functions must not be unconstrained - {name} is", - ); - } - - // The abi export function is expected to be executed before the function is transformed. - let fn_abi_export = create_fn_abi_export(f); - - transform_public(f); +comptime fn assert_valid_public(f: FunctionDefinition) { + let visibility = f.visibility(); + if visibility != quote {} { + let name = f.name(); + panic( + f"A function marked as #[external(\"public\")] must not have public Noir visibility - {name}'s visibility is '{visibility}'", + ); + } - fn_abi_export + if f.is_unconstrained() { + let name = f.name(); + panic( + f"#[external(\"public\")] functions must not be unconstrained - {name} is", + ); } } -/// Utility functions are standalone unconstrained functions that cannot be called from another function in a contract. -/// They are typically used either to obtain some information from the contract (e.g. token balance of a user) or to -/// modify internal contract-related state of PXE (e.g. processing logs in Aztec.nr during sync). -comptime fn utility(f: FunctionDefinition) -> Quoted { - // We need to add this attribute to be able to identify if the function is external utility when constructing - // the contract artifact. - f.add_attribute("utility"); - +comptime fn assert_valid_utility(f: FunctionDefinition) { let visibility = f.visibility(); if visibility != quote {} { let name = f.name(); @@ -238,22 +209,11 @@ comptime fn utility(f: FunctionDefinition) -> Quoted { ); } - post_external_utility_checks(f); - - // The abi export function is expected to be executed before the function is transformed. - let fn_abi_export = create_fn_abi_export(f); - - transform_utility(f); - - fn_abi_export -} - -/// Utility functions cannot be used with the following modifiers: #[authorize_once], #[only_self], #[view], -/// #[initializer], and #[noinitcheck]. Since we cannot enforce a specific ordering between these modifiers and -/// #[external(...)], and we cannot access the #[external(...)] argument from within these modifiers' implementations -/// (as accessing EXTERNAL_REGISTRY would require enforcing ordering), we perform these compatibility checks here in -/// the utility macro. -comptime fn post_external_utility_checks(f: FunctionDefinition) { + // Utility functions cannot be used with the following modifiers: #[authorize_once], #[only_self], #[view], + // #[initializer], and #[noinitcheck]. Since we cannot enforce a specific ordering between these modifiers and + // #[external(...)], and we cannot access the #[external(...)] argument from within these modifiers' implementations + // (as accessing EXTERNAL_REGISTRY would require enforcing ordering), we perform these compatibility checks here in + // the utility macro. if AUTHORIZE_ONCE_REGISTRY.get(f).is_some() { let name = f.name(); panic( diff --git a/noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr b/noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr index 0b3e883d245a..318cf3b0f8bc 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr @@ -1,3 +1,5 @@ +// TODO(benesjan): Move this whole file to `noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation`. +// Not done yet in order to not murder the PR diff. use crate::macros::{ functions::{ auth_registry::AUTHORIZE_ONCE_REGISTRY, @@ -8,14 +10,37 @@ use crate::macros::{ notes::NOTES, utils::{ fn_has_authorize_once, fn_has_noinitcheck, is_fn_contract_library_method, is_fn_external, - is_fn_initializer, is_fn_only_self, is_fn_test, is_fn_view, modify_fn_body, - module_has_initializer, module_has_storage, + is_fn_initializer, is_fn_only_self, is_fn_test, is_fn_view, module_has_initializer, + module_has_storage, }, }; use dep::protocol_types::meta::utils::derive_serialization_quotes; use std::meta::{ctstring::AsCtString, type_of}; -pub(crate) comptime fn transform_private(f: FunctionDefinition) { +/// Gathers all attributes relevant to the function's ABI and returns a quote that can be applied to the newly generated +/// function. We apply the abi marker attributes instead of the original ones (e.g. abi_view instead of view) to avoid +/// the relevant attribute's functionality from getting triggered. +comptime fn get_abi_relevant_attributes(f: FunctionDefinition) -> Quoted { + let mut attributes = quote {}; + + if is_fn_view(f) { + attributes = quote { $attributes #[aztec::macros::internals_functions_generation::abi_attributes::abi_view] }; + } + + if is_fn_only_self(f) { + attributes = quote { $attributes #[aztec::macros::internals_functions_generation::abi_attributes::abi_only_self] }; + } + + if is_fn_initializer(f) { + attributes = quote { $attributes #[aztec::macros::internals_functions_generation::abi_attributes::abi_initializer] }; + } + + attributes +} + +pub(crate) comptime fn transform_private(f: FunctionDefinition) -> Quoted { + // TODO(benesjan): This function should only generate the new function's quote. Move the following to a different + // place. register_private_fn_stubs(f); let module_has_initializer = module_has_initializer(f.module()); @@ -26,11 +51,12 @@ pub(crate) comptime fn transform_private(f: FunctionDefinition) { // 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 original_params_quotes = original_params + .map(|(param_name, param_type)| quote { $param_name: $param_type }) + .join(quote {, }); + + let params = quote { inputs: aztec::context::inputs::private_context_inputs::PrivateContextInputs, $original_params_quotes }; let mut body = f.body().as_block().unwrap(); @@ -69,19 +95,21 @@ pub(crate) comptime fn transform_private(f: FunctionDefinition) { }; }; - let function_name = f.name(); + let original_function_name = f.name(); // Modifications introduced by the different marker attributes. let internal_check = if is_fn_only_self(f) { - let assertion_message = f"Function {function_name} can only be called by the same contract"; + let assertion_message = + f"Function {original_function_name} can only be called by the same contract"; quote { assert(self.msg_sender().unwrap() == self.address, $assertion_message); } } else { quote {} }; let view_check = if is_fn_view(f) { - let assertion_message = - f"Function {function_name} can only be called statically".as_ctstring().as_quoted_str(); + let assertion_message = f"Function {original_function_name} can only be called statically" + .as_ctstring() + .as_quoted_str(); quote { assert(self.context.inputs.call_context.is_static_call, $assertion_message); } } else { quote {} @@ -162,7 +190,11 @@ pub(crate) comptime fn transform_private(f: FunctionDefinition) { let context_finish = quote { self.context.finish() }; - // A quote to be injected at the beginning of the function body. + // Preserve all attributes that are relevant to the function's ABI. + let abi_relevant_attributes = get_abi_relevant_attributes(f); + + let fn_name = f"__aztec_nr_internals__{original_function_name}".quoted_contents(); + let to_prepend = quote { dep::aztec::oracle::version::assert_compatible_oracle_version(); $contract_self_creation @@ -174,21 +206,28 @@ pub(crate) comptime fn transform_private(f: FunctionDefinition) { $authorize_once_check }; + let body_quote = body.map(|expr| expr.quoted()).join(quote { }); + 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(); + + quote { + #[aztec::macros::internals_functions_generation::abi_attributes::abi_private] + $abi_relevant_attributes + fn $fn_name($params) -> return_data aztec::protocol_types::abis::private_circuit_public_inputs::PrivateCircuitPublicInputs { + $to_prepend + $body_quote + $to_append + } + } } -pub(crate) comptime fn transform_public(f: FunctionDefinition) { +pub(crate) comptime fn transform_public(f: FunctionDefinition) -> Quoted { + // TODO(benesjan): This function should only generate the new function's quote. Move the following to a different + // place. register_public_fn_stubs(f); let module_has_initializer = module_has_initializer(f.module()); @@ -241,19 +280,21 @@ pub(crate) comptime fn transform_public(f: FunctionDefinition) { }; }; - let function_name = f.name(); + let original_function_name = f.name(); // Modifications introduced by the different marker attributes. let internal_check = if is_fn_only_self(f) { - let assertion_message = f"Function {function_name} can only be called by the same contract"; + let assertion_message = + f"Function {original_function_name} can only be called by the same contract"; quote { assert(self.msg_sender().unwrap() == self.address, $assertion_message); } } else { quote {} }; let view_check = if is_fn_view(f) { - let assertion_message = - f"Function {function_name} can only be called statically".as_ctstring().as_quoted_str(); + let assertion_message = f"Function {original_function_name} can only be called statically" + .as_ctstring() + .as_quoted_str(); quote { assert(self.context.is_static_call(), $assertion_message); } } else { quote {} @@ -295,18 +336,35 @@ pub(crate) comptime fn transform_public(f: FunctionDefinition) { $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); + let fn_name = f"__aztec_nr_internals__{original_function_name}".quoted_contents(); + let body = f.body(); + let return_type = f.return_type(); + + // New function parameters are the same as the original function's ones. + let params = original_params + .map(|(param_name, param_type)| quote { $param_name: $param_type }) + .join(quote {, }); + + // Preserve all attributes that are relevant to the function's ABI. + let abi_relevant_attributes = get_abi_relevant_attributes(f); // 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); + quote { + #[aztec::macros::internals_functions_generation::abi_attributes::abi_public] + $abi_relevant_attributes + unconstrained fn $fn_name($params) -> pub $return_type { + $to_prepend + $body + $to_append + } + } } -pub(crate) comptime fn transform_utility(f: FunctionDefinition) { +pub(crate) comptime fn transform_utility(f: FunctionDefinition) -> Quoted { + // TODO(benesjan): This function should only generate the new function's quote. Move the following to a different + // place. register_utility_fn_stub(f); // Initialize Storage if module has storage @@ -344,11 +402,23 @@ pub(crate) comptime fn transform_utility(f: FunctionDefinition) { $contract_self_creation $message_discovery_call }; - let body = f.body().as_block().unwrap(); - let modified_body = modify_fn_body(body, to_prepend, quote {}); - f.set_body(modified_body); - f.set_return_public(true); + let original_function_name = f.name(); + let fn_name = f"__aztec_nr_internals__{original_function_name}".quoted_contents(); + let body = f.body(); + let params = f + .parameters() + .map(|(param_name, param_type)| quote { $param_name: $param_type }) + .join(quote {, }); + let return_type = f.return_type(); + + quote { + #[aztec::macros::internals_functions_generation::abi_attributes::abi_utility] + unconstrained fn $fn_name($params) -> pub $return_type { + $to_prepend + $body + } + } } /// Injects a call to `aztec::messages::discovery::discover_new_messages`, causing for new notes to be added to PXE and made @@ -436,9 +506,7 @@ pub(crate) comptime fn create_authorize_once_check( let nonce_check_quote = f"{nonce_arg_name_quoted} == 0".quoted_contents(); let fn_call = if is_private { - // At this point, the original args of the fn have already been altered by the macro - // to include PrivateContextInputs, so we need to adjust the args_len accordingly. - let args_len = f.parameters().len() - 1; + let args_len = f.parameters().len(); quote { dep::aztec::authwit::auth::assert_current_call_valid_authwit::<$args_len> } } else { quote { dep::aztec::authwit::auth::assert_current_call_valid_authwit_public } diff --git a/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/abi_attributes.nr b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/abi_attributes.nr new file mode 100644 index 000000000000..66b70bc7d8bd --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/abi_attributes.nr @@ -0,0 +1,27 @@ +//! ABI attributes that are applied by the Aztec.nr macros to the generated functions with the `__aztec_nr_internals___` +//! prefix in the name. The attributes are prefixed with `abi_` as they are used only for ABI purposes and artifact +//! generation. + +// Applied to macro-generated functions with the `__aztec_nr_internals___` prefix, which are created from functions +// annotated with `#[external("private")]`. +pub comptime fn abi_private(_f: FunctionDefinition) {} + +// Applied to macro-generated functions with the `__aztec_nr_internals___` prefix, which are created from functions +// annotated with `#[external("public")]`. +pub comptime fn abi_public(_f: FunctionDefinition) {} + +// Applied to macro-generated functions with the `__aztec_nr_internals___` prefix, which are created from functions +// annotated with `#[external("utility")]`. +pub comptime fn abi_utility(_f: FunctionDefinition) {} + +// Applied to macro-generated functions with the `__aztec_nr_internals___` prefix, which are created from functions +// annotated with `#[view]`. +pub comptime fn abi_view(_f: FunctionDefinition) {} + +// Applied to macro-generated functions with the `__aztec_nr_internals___` prefix, which are created from functions +// annotated with `#[only_self]`. +pub comptime fn abi_only_self(_f: FunctionDefinition) {} + +// Applied to macro-generated functions with the `__aztec_nr_internals___` prefix, which are created from functions +// annotated with `#[initializer]`. +pub comptime fn abi_initializer(_f: FunctionDefinition) {} diff --git a/noir-projects/aztec-nr/aztec/src/macros/functions/abi_export.nr b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/abi_export.nr similarity index 92% rename from noir-projects/aztec-nr/aztec/src/macros/functions/abi_export.nr rename to noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/abi_export.nr index 1c05d15a92c2..156d060dbf8e 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/functions/abi_export.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/abi_export.nr @@ -2,10 +2,6 @@ use std::meta::type_of; /// Creates ABI structs for a function to expose its interface. /// -/// # Important -/// This function needs to be run before transformations modifying the signature of the target function `f` are applied -/// (e.g. transform_private). -/// /// # Overview /// This function takes a FunctionDefinition and generates two structs: /// 1. A parameters struct containing all the function parameters diff --git a/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external_functions_registry.nr b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external_functions_registry.nr new file mode 100644 index 000000000000..9285783d4f19 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external_functions_registry.nr @@ -0,0 +1,56 @@ +//! Registry for external functions that are added to the corresponding global variable when the `#[external(...)]` +//! macro is applied. This registry is then used by the `#[aztec]` macro to generate the functions prefixed with +//! `__aztec_nr_internals___` and the `public_dispatch` function. +//! +//! Note that we need to use this registry approach instead of directly iterating over the functions in the `#[aztec]` +//! macro, because at that point we do not have the information about whether a given external function is private or +//! public. We only see the internal attribute itself, not the "private" or "public" argument. + +use poseidon::poseidon2::Poseidon2Hasher; +use std::{collections::umap::UHashMap, hash::BuildHasherDefault}; + +// Key is a contract module, value is an array of function definitions. +comptime mut global PRIVATE_REGISTRY: UHashMap> = + UHashMap::default(); +comptime mut global PUBLIC_REGISTRY: UHashMap> = + UHashMap::default(); +comptime mut global UTILITY_REGISTRY: UHashMap> = + UHashMap::default(); + +comptime fn add_to_registry( + registry: &mut UHashMap>, + f: FunctionDefinition, +) { + let module = f.module(); + let current_functions = registry.get(module); + let functions_to_insert = if current_functions.is_some() { + current_functions.unwrap().push_back(f) + } else { + &[f] + }; + registry.insert(module, functions_to_insert); +} + +pub(crate) comptime fn add_private(f: FunctionDefinition) { + add_to_registry(&mut PRIVATE_REGISTRY, f); +} + +pub(crate) comptime fn add_public(f: FunctionDefinition) { + add_to_registry(&mut PUBLIC_REGISTRY, f); +} + +pub(crate) comptime fn add_utility(f: FunctionDefinition) { + add_to_registry(&mut UTILITY_REGISTRY, f); +} + +pub(crate) comptime fn get_private_functions(m: Module) -> [FunctionDefinition] { + PRIVATE_REGISTRY.get(m).unwrap_or(&[]) +} + +pub(crate) comptime fn get_public_functions(m: Module) -> [FunctionDefinition] { + PUBLIC_REGISTRY.get(m).unwrap_or(&[]) +} + +pub(crate) comptime fn get_utility_functions(m: Module) -> [FunctionDefinition] { + UTILITY_REGISTRY.get(m).unwrap_or(&[]) +} diff --git a/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/mod.nr new file mode 100644 index 000000000000..7b81452b262a --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/mod.nr @@ -0,0 +1,104 @@ +//! The functionality in this module is triggered by the `#[aztec]` macro. It generates new functions, prefixed with +//! `__aztec_nr_internals___`, from the ones marked with `#[external(...)]` attributes. The original functions are then +//! modified to be uncallable. This prevents developers from inadvertently calling a function directly, instead of +//! performing a proper contract call. + +pub mod abi_attributes; +pub(crate) mod external_functions_registry; +mod abi_export; + +use crate::macros::functions::utils::{transform_private, transform_public, transform_utility}; +use abi_export::create_fn_abi_export; +use external_functions_registry::{ + get_private_functions, get_public_functions, get_utility_functions, +}; + +/// Modifies a function such that when it's called directly, it will result in a compilation error with a reasonable +/// error message. +comptime fn make_functions_uncallable( + functions: [FunctionDefinition], + error_message_template: str, +) { + functions.for_each(|function| { + let name = function.name(); + // The body of the function will contain a static_assert(false, ...) to prevent the function from being called + // directly and a std::mem::zeroed() to make the compilation fail on the static_assert and not on a missing + // return value. + // TODO(benesjan): Add a link to the documentation for instructions on the proper invocation of functions. + let error_message = f"{error_message_template}{name}. See documentation for instructions on the proper invocation of functions."; + let body = f"{{ std::static_assert(false, \"{error_message}\"); std::mem::zeroed() }}" + .quoted_contents(); + let body_expr = body.as_expr().expect(f"Body is not an expression: {body}"); + + // Prefix all parameter names with "_" to suppress unused variable warnings + let params = function.parameters(); + let prefixed_params = params.map(|(param_name, param_type)| { + let prefixed_name = f"_{param_name}".quoted_contents(); + (prefixed_name, param_type) + }); + + function.set_body(body_expr); + function.set_parameters(prefixed_params); + // We need to add the `contract_library_method` attribute to the function to prevent this function from being + // compiled as an entrypoint function (function that's compiled as its own circuit). + function.add_attribute("contract_library_method"); + + // Contract functions need to have a public return type so we mark it as such to avoid undesired compilation + // errors. + function.set_return_public(true); + }); +} + +/// Produces new functions for all external contract functions, prefixing them with `__aztec_nr_internals___`, and then +/// renders the original functions uncallable. +pub(crate) comptime fn process_functions(m: Module) -> Quoted { + let private_functions = get_private_functions(m); + let public_functions = get_public_functions(m); + let utility_functions = get_utility_functions(m); + + let transformed_private_functions = + private_functions.map(|function| transform_private(function)).join(quote {}); + let transformed_public_functions = + public_functions.map(|function| transform_public(function)).join(quote {}); + let transformed_utility_functions = + utility_functions.map(|function| transform_utility(function)).join(quote {}); + + // Now that we have generated quotes of the new functions based on the original function definitions, we replace + // the original functions' bodies with `static_assert(false, ...)` to prevent them from being called directly + // from within the contract. We also need to set the return type to `()` to avoid compilation errors. + make_functions_uncallable( + private_functions, + "Direct invocation of private functions is not supported. You attempted to call ", + ); + make_functions_uncallable( + public_functions, + "Direct invocation of public functions is not supported. You attempted to call ", + ); + make_functions_uncallable( + utility_functions, + "Calling utility functions directly from within the contract is not supported. You attempted to call ", + ); + + // We return the new functions' quotes to be injected into the contract. + quote { + $transformed_private_functions + $transformed_public_functions + $transformed_utility_functions + } +} + +// See docs of create_fn_abi_export for information on what this does. +pub(crate) comptime fn create_fn_abi_exports(m: Module) -> Quoted { + let private_functions_exports = + get_private_functions(m).map(|function| create_fn_abi_export(function)).join(quote {}); + let public_functions_exports = + get_public_functions(m).map(|function| create_fn_abi_export(function)).join(quote {}); + let utility_functions_exports = + get_utility_functions(m).map(|function| create_fn_abi_export(function)).join(quote {}); + + quote { + $private_functions_exports + $public_functions_exports + $utility_functions_exports + } +} diff --git a/noir-projects/aztec-nr/aztec/src/macros/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/mod.nr index 8978c8673b01..f0821c9f3b9c 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/mod.nr @@ -1,5 +1,6 @@ pub mod aztec; pub mod dispatch; +pub mod internals_functions_generation; pub mod functions; pub mod utils; pub mod notes; diff --git a/noir-projects/aztec-nr/aztec/src/macros/utils.nr b/noir-projects/aztec-nr/aztec/src/macros/utils.nr index ab7139390e39..e48e2db13aa8 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/utils.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/utils.nr @@ -33,27 +33,6 @@ pub(crate) comptime fn fn_has_authorize_once(f: FunctionDefinition) -> bool { f.has_named_attribute("authorize_once") } -/// Takes a function body as a collection of expressions, and alters it by prepending and appending quoted values. -pub(crate) comptime fn modify_fn_body(body: [Expr], prepend: Quoted, append: Quoted) -> Expr { - // We need to quote the body before we can alter its contents, so we fold it by quoting each expression. - let mut body_quote = body.fold(quote {}, |full_quote: Quoted, expr: Expr| { - let expr_quote = expr.quoted(); - quote { - $full_quote - $expr_quote - } - }); - body_quote = quote { - { - $prepend - $body_quote - $append - } - }; - let body_expr = body_quote.as_expr(); - body_expr.expect(f"Body is not an expression: {body_quote}") -} - comptime fn signature_of_type(typ: Type) -> Quoted { if typ.is_field() { quote {Field} diff --git a/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_private_external_fn_call/Nargo.toml b/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_private_external_fn_call/Nargo.toml new file mode 100644 index 000000000000..35ab8ec6157d --- /dev/null +++ b/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_private_external_fn_call/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "panic_on_direct_private_external_fn_call" +authors = [""] +compiler_version = ">=0.25.0" +type = "contract" + +[dependencies] +aztec = { path = "../../../aztec-nr/aztec" } diff --git a/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_private_external_fn_call/expected_error b/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_private_external_fn_call/expected_error new file mode 100644 index 000000000000..10f96dc14596 --- /dev/null +++ b/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_private_external_fn_call/expected_error @@ -0,0 +1 @@ +Direct invocation of private functions is not supported. You attempted to call arbitrary_external_function. See documentation for instructions on the proper invocation of functions. diff --git a/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_private_external_fn_call/src/main.nr b/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_private_external_fn_call/src/main.nr new file mode 100644 index 000000000000..67d1737c6fc4 --- /dev/null +++ b/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_private_external_fn_call/src/main.nr @@ -0,0 +1,14 @@ +use aztec::macros::aztec; + +#[aztec] +pub contract PanicOnDirectExternalPrivateFnCall { + use aztec::macros::functions::external; + + #[external("private")] + fn arbitrary_external_function() {} + + #[external("private")] + fn function_calling_external_function_directly() { + arbitrary_external_function(); + } +} diff --git a/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_public_external_fn_call/Nargo.toml b/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_public_external_fn_call/Nargo.toml new file mode 100644 index 000000000000..a7ce32c176e5 --- /dev/null +++ b/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_public_external_fn_call/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "panic_on_direct_public_external_fn_call" +authors = [""] +compiler_version = ">=0.25.0" +type = "contract" + +[dependencies] +aztec = { path = "../../../aztec-nr/aztec" } diff --git a/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_public_external_fn_call/expected_error b/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_public_external_fn_call/expected_error new file mode 100644 index 000000000000..21f3f0f154ee --- /dev/null +++ b/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_public_external_fn_call/expected_error @@ -0,0 +1 @@ +Direct invocation of public functions is not supported. You attempted to call arbitrary_external_function. See documentation for instructions on the proper invocation of functions. diff --git a/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_public_external_fn_call/src/main.nr b/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_public_external_fn_call/src/main.nr new file mode 100644 index 000000000000..69555e893806 --- /dev/null +++ b/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_public_external_fn_call/src/main.nr @@ -0,0 +1,14 @@ +use aztec::macros::aztec; + +#[aztec] +pub contract PanicOnDirectExternalPublicFnCall { + use aztec::macros::functions::external; + + #[external("public")] + fn arbitrary_external_function() {} + + #[external("public")] + fn function_calling_external_function_directly() { + arbitrary_external_function(); + } +} diff --git a/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_utility_external_fn_call/Nargo.toml b/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_utility_external_fn_call/Nargo.toml new file mode 100644 index 000000000000..261086647ccd --- /dev/null +++ b/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_utility_external_fn_call/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "panic_on_direct_utility_external_fn_call" +authors = [""] +compiler_version = ">=0.25.0" +type = "contract" + +[dependencies] +aztec = { path = "../../../aztec-nr/aztec" } diff --git a/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_utility_external_fn_call/expected_error b/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_utility_external_fn_call/expected_error new file mode 100644 index 000000000000..1181354a9530 --- /dev/null +++ b/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_utility_external_fn_call/expected_error @@ -0,0 +1 @@ +Calling utility functions directly from within the contract is not supported. You attempted to call arbitrary_external_function. See documentation for instructions on the proper invocation of functions. diff --git a/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_utility_external_fn_call/src/main.nr b/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_utility_external_fn_call/src/main.nr new file mode 100644 index 000000000000..33ed7dbdde77 --- /dev/null +++ b/noir-projects/noir-contracts-comp-failures/contracts/panic_on_direct_utility_external_fn_call/src/main.nr @@ -0,0 +1,14 @@ +use aztec::macros::aztec; + +#[aztec] +pub contract PanicOnDirectExternalUtilityFnCall { + use aztec::macros::functions::external; + + #[external("utility")] + unconstrained fn arbitrary_external_function() {} + + #[external("utility")] + unconstrained fn function_calling_external_function_directly() { + arbitrary_external_function(); + } +} diff --git a/noir-projects/noir-contracts/bootstrap.sh b/noir-projects/noir-contracts/bootstrap.sh index 6aa518e0c19c..e52e2217709b 100755 --- a/noir-projects/noir-contracts/bootstrap.sh +++ b/noir-projects/noir-contracts/bootstrap.sh @@ -30,6 +30,7 @@ export PLATFORM_TAG=any export BB=${BB:-../../barretenberg/cpp/build/bin/bb} export NARGO=${NARGO:-../../noir/noir-repo/target/release/nargo} export TRANSPILER=${TRANSPILER:-../../avm-transpiler/target/release/avm-transpiler} +export STRIP_AZTEC_NR_PREFIX=${STRIP_AZTEC_NR_PREFIX:-./scripts/strip_aztec_nr_prefix.sh} export BB_HASH=${BB_HASH:-$(../../barretenberg/cpp/bootstrap.sh hash)} export NOIR_HASH=${NOIR_HASH:-$(../../noir/bootstrap.sh hash)} @@ -170,6 +171,7 @@ function compile { if ! cache_download contract-$contract_hash.tar.gz; then $NARGO compile --package $contract --inliner-aggressiveness 0 --pedantic-solving --deny-warnings $TRANSPILER $json_path $json_path + $STRIP_AZTEC_NR_PREFIX $json_path cache_upload contract-$contract_hash.tar.gz $json_path fi diff --git a/noir-projects/noir-contracts/contracts/test/avm_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/avm_test_contract/src/main.nr index 6447159019a8..7ae47f7c1938 100644 --- a/noir-projects/noir-contracts/contracts/test/avm_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test/avm_test_contract/src/main.nr @@ -45,6 +45,7 @@ pub contract AvmTest { use std::embedded_curve_ops::{EmbeddedCurvePoint, multi_scalar_mul}; use dep::auth_contract::AuthRegistry; + use dep::aztec::context::public_context::PublicContext; use dep::fee_juice::FeeJuice; use dep::instance_contract::ContractInstanceRegistry; use std::ops::Add; @@ -61,12 +62,22 @@ pub contract AvmTest { ************************************************************************/ #[external("public")] fn set_storage_single(a: Field) { - self.storage.single.write(a); + _set_storage_single(self.storage, a); + } + + #[contract_library_method] + fn _set_storage_single(storage: Storage, a: Field) { + storage.single.write(a); } #[external("public")] fn read_storage_single() -> Field { - self.storage.single.read() + _read_storage_single(self.storage) + } + + #[contract_library_method] + fn _read_storage_single(storage: Storage) -> Field { + storage.single.read() } #[external("public")] @@ -83,33 +94,58 @@ pub contract AvmTest { #[external("public")] fn set_storage_list(a: Field, b: Field) { - self.storage.list.write(Note { a, b }); + _set_storage_list(self.storage, a, b); + } + + #[contract_library_method] + fn _set_storage_list(storage: Storage, a: Field, b: Field) { + storage.list.write(Note { a, b }); } #[external("public")] fn read_storage_list() -> [Field; 2] { - let note: Note = self.storage.list.read(); + _read_storage_list(self.storage) + } + + #[contract_library_method] + fn _read_storage_list(storage: Storage) -> [Field; 2] { + let note: Note = storage.list.read(); note.serialize() } #[external("public")] fn set_storage_map(to: AztecAddress, amount: u32) -> Field { - self.storage.map.at(to).write(amount); + _set_storage_map(self.storage, to, amount) + } + + #[contract_library_method] + fn _set_storage_map(storage: Storage, to: AztecAddress, amount: u32) -> Field { + storage.map.at(to).write(amount); // returns storage slot for key - derive_storage_slot_in_map(self.storage.map.get_storage_slot(), to) + derive_storage_slot_in_map(storage.map.get_storage_slot(), to) } #[external("public")] fn add_storage_map(to: AztecAddress, amount: u32) -> Field { - let new_balance = self.storage.map.at(to).read().add(amount); - self.storage.map.at(to).write(new_balance); + _add_storage_map(self.storage, to, amount) + } + + #[contract_library_method] + fn _add_storage_map(storage: Storage, to: AztecAddress, amount: u32) -> Field { + let new_balance = storage.map.at(to).read().add(amount); + storage.map.at(to).write(new_balance); // returns storage slot for key - derive_storage_slot_in_map(self.storage.map.get_storage_slot(), to) + derive_storage_slot_in_map(storage.map.get_storage_slot(), to) } #[external("public")] fn read_storage_map(address: AztecAddress) -> u32 { - self.storage.map.at(address).read() + _read_storage_map(self.storage, address) + } + + #[contract_library_method] + fn _read_storage_map(storage: Storage, address: AztecAddress) -> u32 { + storage.map.at(address).read() } #[external("public")] @@ -211,7 +247,7 @@ pub contract AvmTest { } } - #[external("public")] + #[contract_library_method] fn bitwise_ops(x: u32, y: u32) -> u32 { let mut result = x & y; result = result | x; @@ -231,23 +267,33 @@ pub contract AvmTest { max_u128 + one } - #[external("public")] + #[contract_library_method] fn integer_division(x: u8, y: u8) -> u8 { x / y } - #[external("public")] + #[contract_library_method] fn field_division(x: Field, y: Field) -> Field { x / y } #[external("public")] fn to_le_bytes(input: Field) -> [u8; 10] { + _to_le_bytes(input) + } + + #[contract_library_method] + fn _to_le_bytes(input: Field) -> [u8; 10] { input.to_le_bytes() } #[external("public")] fn to_le_bits(input: Field) -> [u1; 16] { + _to_le_bits(input) + } + + #[contract_library_method] + fn _to_le_bits(input: Field) -> [u1; 16] { input.to_le_bits() } @@ -398,6 +444,21 @@ pub contract AvmTest { expected_deployer: AztecAddress, expected_class_id: ContractClassId, expected_initialization_hash: Field, + ) { + _test_get_contract_instance_matches( + address, + expected_deployer, + expected_class_id, + expected_initialization_hash, + ); + } + + #[contract_library_method] + fn _test_get_contract_instance_matches( + address: AztecAddress, + expected_deployer: AztecAddress, + expected_class_id: ContractClassId, + expected_initialization_hash: Field, ) { let deployer = get_contract_instance_deployer_avm(address); let class_id = get_contract_instance_class_id_avm(address); @@ -431,57 +492,112 @@ pub contract AvmTest { ************************************************************************/ #[external("public")] fn get_address() -> AztecAddress { - self.address + _get_address(self.address) + } + + #[contract_library_method] + fn _get_address(address: AztecAddress) -> AztecAddress { + address } #[external("public")] fn get_sender() -> AztecAddress { - self.context.msg_sender_unsafe() + _get_sender(self.context) + } + + #[contract_library_method] + fn _get_sender(context: PublicContext) -> AztecAddress { + context.msg_sender_unsafe() } #[external("public")] fn get_transaction_fee() -> Field { - self.context.transaction_fee() + _get_transaction_fee(self.context) + } + + #[contract_library_method] + fn _get_transaction_fee(context: PublicContext) -> Field { + context.transaction_fee() } #[external("public")] fn get_chain_id() -> Field { - self.context.chain_id() + _get_chain_id(self.context) + } + + #[contract_library_method] + fn _get_chain_id(context: PublicContext) -> Field { + context.chain_id() } #[external("public")] fn get_version() -> Field { - self.context.version() + _get_version(self.context) + } + + #[contract_library_method] + fn _get_version(context: PublicContext) -> Field { + context.version() } #[external("public")] fn get_block_number() -> u32 { - self.context.block_number() + _get_block_number(self.context) + } + + #[contract_library_method] + fn _get_block_number(context: PublicContext) -> u32 { + context.block_number() } #[external("public")] fn get_timestamp() -> u64 { - self.context.timestamp() + _get_timestamp(self.context) + } + + #[contract_library_method] + fn _get_timestamp(context: PublicContext) -> u64 { + context.timestamp() } #[external("public")] fn get_fee_per_l2_gas() -> u128 { - self.context.base_fee_per_l2_gas() + _get_fee_per_l2_gas(self.context) + } + + #[contract_library_method] + fn _get_fee_per_l2_gas(context: PublicContext) -> u128 { + context.base_fee_per_l2_gas() } #[external("public")] fn get_fee_per_da_gas() -> u128 { - self.context.base_fee_per_da_gas() + _get_fee_per_da_gas(self.context) + } + + #[contract_library_method] + fn _get_fee_per_da_gas(context: PublicContext) -> u128 { + context.base_fee_per_da_gas() } #[external("public")] fn get_l2_gas_left() -> u32 { - self.context.l2_gas_left() + _get_l2_gas_left(self.context) + } + + #[contract_library_method] + fn _get_l2_gas_left(context: PublicContext) -> u32 { + context.l2_gas_left() } #[external("public")] fn get_da_gas_left() -> u32 { - self.context.da_gas_left() + _get_da_gas_left(self.context) + } + + #[contract_library_method] + fn _get_da_gas_left(context: PublicContext) -> u32 { + context.da_gas_left() } #[external("public")] @@ -491,12 +607,17 @@ pub contract AvmTest { #[external("public")] fn emit_public_log() { - self.context.emit_public_log(/*message=*/ [10, 20, 30]); - self.context.emit_public_log(/*message=*/ "Hello, world!"); + _emit_public_log(self.context); + } + + #[contract_library_method] + fn _emit_public_log(context: PublicContext) { + context.emit_public_log(/*message=*/ [10, 20, 30]); + context.emit_public_log(/*message=*/ "Hello, world!"); let s: CompressedString<2, 44> = CompressedString::from_string("A long time ago, in a galaxy far far away..."); - self.context.emit_public_log(/*message=*/ s); - self.context.emit_public_log(/*message=*/ [ + context.emit_public_log(/*message=*/ s); + context.emit_public_log(/*message=*/ [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, ]); // Large log @@ -504,19 +625,34 @@ pub contract AvmTest { #[external("public")] fn note_hash_exists(note_hash: Field, leaf_index: u64) -> bool { - self.context.note_hash_exists(note_hash, leaf_index) + _note_hash_exists(self.context, note_hash, leaf_index) + } + + #[contract_library_method] + fn _note_hash_exists(context: PublicContext, note_hash: Field, leaf_index: u64) -> bool { + context.note_hash_exists(note_hash, leaf_index) } // Use the standard context interface to emit a new note hash #[external("public")] fn new_note_hash(note_hash: Field) { - self.context.push_note_hash(note_hash); + _new_note_hash(self.context, note_hash); + } + + #[contract_library_method] + fn _new_note_hash(context: PublicContext, note_hash: Field) { + context.push_note_hash(note_hash); } // Use the standard context interface to emit a new nullifier #[external("public")] fn new_nullifier(nullifier: Field) { - self.context.push_nullifier(nullifier); + _new_nullifier(self.context, nullifier); + } + + #[contract_library_method] + fn _new_nullifier(context: PublicContext, nullifier: Field) { + context.push_nullifier(nullifier); } #[external("public")] @@ -557,7 +693,12 @@ pub contract AvmTest { // Use the standard context interface to check for a nullifier #[external("public")] fn nullifier_exists(nullifier: Field) -> bool { - self.context.nullifier_exists(nullifier, self.address) + _nullifier_exists(self.context, self.address, nullifier) + } + + #[contract_library_method] + fn _nullifier_exists(context: PublicContext, address: AztecAddress, nullifier: Field) -> bool { + context.nullifier_exists(nullifier, address) } #[external("public")] @@ -583,12 +724,26 @@ pub contract AvmTest { #[external("public")] fn l1_to_l2_msg_exists(msg_hash: Field, msg_leaf_index: Field) -> bool { - self.context.l1_to_l2_msg_exists(msg_hash, msg_leaf_index) + _l1_to_l2_msg_exists(self.context, msg_hash, msg_leaf_index) + } + + #[contract_library_method] + fn _l1_to_l2_msg_exists( + context: PublicContext, + msg_hash: Field, + msg_leaf_index: Field, + ) -> bool { + context.l1_to_l2_msg_exists(msg_hash, msg_leaf_index) } #[external("public")] fn send_l2_to_l1_msg(recipient: EthAddress, content: Field) { - self.context.message_portal(recipient, content) + _send_l2_to_l1_msg(self.context, recipient, content); + } + + #[contract_library_method] + fn _send_l2_to_l1_msg(context: PublicContext, recipient: EthAddress, content: Field) { + context.message_portal(recipient, content) } /************************************************************************ @@ -628,7 +783,17 @@ pub contract AvmTest { // Use the `call_public_function` wrapper to initiate a nested call to the add function #[external("public")] fn nested_call_to_add(arg_a: Field, arg_b: Field) -> pub Field { - self.call_self.add_args_return(arg_a, arg_b) + _nested_call_to_add(self.context, self.address, arg_a, arg_b) + } + + #[contract_library_method] + unconstrained fn _nested_call_to_add( + context: PublicContext, + address: AztecAddress, + arg_a: Field, + arg_b: Field, + ) -> Field { + AvmTest::at(address).add_args_return(arg_a, arg_b).call(context) } #[external("public")] @@ -646,7 +811,17 @@ pub contract AvmTest { // Indirectly call_static the external call opcode to initiate a nested call to the add function #[external("public")] fn nested_static_call_to_add(arg_a: Field, arg_b: Field) -> pub Field { - AvmTest::at(self.address).add_args_return(arg_a, arg_b).view(self.context) + _nested_static_call_to_add(self.context, self.address, arg_a, arg_b) + } + + #[contract_library_method] + unconstrained fn _nested_static_call_to_add( + context: PublicContext, + address: AztecAddress, + arg_a: Field, + arg_b: Field, + ) -> Field { + AvmTest::at(address).add_args_return(arg_a, arg_b).view(context) } // Indirectly call_static `set_storage_single`. Should revert since it's accessing self.storage. @@ -697,7 +872,12 @@ pub contract AvmTest { ************************************************************************/ #[external("public")] fn call_fee_juice() { - let _ = FeeJuice::at(FEE_JUICE_ADDRESS).balance_of_public(self.address).view(self.context); + _call_fee_juice(self.context, self.address); + } + + #[contract_library_method] + unconstrained fn _call_fee_juice(context: PublicContext, address: AztecAddress) { + let _ = FeeJuice::at(FEE_JUICE_ADDRESS).balance_of_public(address).view(context); } #[external("public")] @@ -731,18 +911,18 @@ pub contract AvmTest { skip_strictly_limited_side_effects: bool, ) { dep::aztec::oracle::debug_log::debug_log("biwise_ops"); - let num = get_block_number(); + let num = self.context.block_number(); let _ = bitwise_ops(num, num); dep::aztec::oracle::debug_log::debug_log("set_storage_single"); - set_storage_single(30); + _set_storage_single(self.storage, 30); dep::aztec::oracle::debug_log::debug_log("set_storage_list"); - set_storage_list(40, 50); + _set_storage_list(self.storage, 40, 50); dep::aztec::oracle::debug_log::debug_log("read_storage_list"); - let _ = set_storage_map(self.address, 60); + let _ = _set_storage_map(self.storage, self.address, 60); dep::aztec::oracle::debug_log::debug_log("add_storage_map"); - let _ = add_storage_map(self.address, 10); + let _ = _add_storage_map(self.storage, self.address, 10); dep::aztec::oracle::debug_log::debug_log("read_storage_map"); - let _ = read_storage_map(self.address); + let _ = _read_storage_map(self.storage, self.address); dep::aztec::oracle::debug_log::debug_log("keccak_hash"); let _ = keccak256::keccak256(args_u8, args_u8.len()); dep::aztec::oracle::debug_log::debug_log("sha256_hash"); @@ -758,65 +938,65 @@ pub contract AvmTest { dep::aztec::oracle::debug_log::debug_log("field_division"); let _ = field_division(args_field[0], args_field[1]); dep::aztec::oracle::debug_log::debug_log("test_get_contract_instance"); - test_get_contract_instance_matches( + _test_get_contract_instance_matches( get_instance_for_address, expected_deployer, expected_class_id, expected_initialization_hash, ); dep::aztec::oracle::debug_log::debug_log("get_address"); - let _ = get_address(); + let _ = _get_address(self.address); dep::aztec::oracle::debug_log::debug_log("get_sender"); - let _ = get_sender(); + let _ = _get_sender(self.context); dep::aztec::oracle::debug_log::debug_log("get_transaction_fee"); - let _ = get_transaction_fee(); + let _ = _get_transaction_fee(self.context); dep::aztec::oracle::debug_log::debug_log("get_chain_id"); - let _ = get_chain_id(); + let _ = _get_chain_id(self.context); dep::aztec::oracle::debug_log::debug_log("get_version"); - let _ = get_version(); + let _ = _get_version(self.context); dep::aztec::oracle::debug_log::debug_log("get_block_number"); - let _ = get_block_number(); + let _ = _get_block_number(self.context); dep::aztec::oracle::debug_log::debug_log("get_timestamp"); - let _ = get_timestamp(); + let _ = _get_timestamp(self.context); dep::aztec::oracle::debug_log::debug_log("get_fee_per_l2_gas"); - let _ = get_fee_per_l2_gas(); + let _ = _get_fee_per_l2_gas(self.context); dep::aztec::oracle::debug_log::debug_log("get_fee_per_da_gas"); - let _ = get_fee_per_da_gas(); + let _ = _get_fee_per_da_gas(self.context); dep::aztec::oracle::debug_log::debug_log("get_l2_gas_left"); - let _ = get_l2_gas_left(); + let _ = _get_l2_gas_left(self.context); dep::aztec::oracle::debug_log::debug_log("get_da_gas_left"); - let _ = get_da_gas_left(); + let _ = _get_da_gas_left(self.context); if !skip_strictly_limited_side_effects { dep::aztec::oracle::debug_log::debug_log("emit_public_log"); - let _ = emit_public_log(); + _emit_public_log(self.context); } dep::aztec::oracle::debug_log::debug_log("note_hash_exists"); - let _ = note_hash_exists(1, 2); + let _ = _note_hash_exists(self.context, 1, 2); dep::aztec::oracle::debug_log::debug_log("new_note_hash"); - let _ = new_note_hash(1); + _new_note_hash(self.context, 1); dep::aztec::oracle::debug_log::debug_log("new_nullifier"); - let _ = new_nullifier(args_field[0]); + _new_nullifier(self.context, args_field[0]); dep::aztec::oracle::debug_log::debug_log("nullifier_exists"); - let _ = nullifier_exists(1); + let _ = _nullifier_exists(self.context, self.address, 1); dep::aztec::oracle::debug_log::debug_log("l1_to_l2_msg_exists"); - let _ = l1_to_l2_msg_exists(1, 2); + let _ = _l1_to_l2_msg_exists(self.context, 1, 2); if !skip_strictly_limited_side_effects { dep::aztec::oracle::debug_log::debug_log("send_l2_to_l1_msg"); - let _ = send_l2_to_l1_msg(EthAddress::from_field(0x2020), 1); + _send_l2_to_l1_msg(self.context, EthAddress::from_field(0x2020), 1); } dep::aztec::oracle::debug_log::debug_log("storage_read_and_write"); - set_storage_single(read_storage_single()); + _set_storage_single(self.storage, _read_storage_single(self.storage)); dep::aztec::oracle::debug_log::debug_log("nested_call_to_add"); - let _ = nested_call_to_add(1, 2); + let _ = _nested_call_to_add(self.context, self.address, 1, 2); dep::aztec::oracle::debug_log::debug_log("nested_static_call_to_add"); - let _ = nested_static_call_to_add(1, 2); + let _ = _nested_static_call_to_add(self.context, self.address, 1, 2); dep::aztec::oracle::debug_log::debug_log("nested_call_to_protocol_contract"); - let _ = call_fee_juice(); + _call_fee_juice(self.context, self.address); dep::aztec::oracle::debug_log::debug_log("to_le_bytes"); - let first_byte = to_le_bytes(args_field[0])[0]; + let first_byte = _to_le_bytes(args_field[0])[0]; assert(first_byte != 0); dep::aztec::oracle::debug_log::debug_log("to_le_bits"); - let first_bit = to_le_bits(args_field[0])[0]; + let first_bit = _to_le_bits(args_field[0])[0]; assert(first_bit != 0); //let _ = nested_call_to_nothing_recovers(); } diff --git a/noir-projects/noir-contracts/scripts/strip_aztec_nr_prefix.sh b/noir-projects/noir-contracts/scripts/strip_aztec_nr_prefix.sh new file mode 100755 index 000000000000..e0466361421d --- /dev/null +++ b/noir-projects/noir-contracts/scripts/strip_aztec_nr_prefix.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +# This script strips the `__aztec_nr_internals__` prefix from function names in the exported contract ABI JSON. +# +# Background: +# The #[aztec] macro generates new functions prefixed with `__aztec_nr_internals__` from the original external contract +# functions (see aztec.nr and internals_functions_generation/mod.nr). The original functions are then modified to be +# uncallable (replaced with static_assert(false, ...)) to prevent developers from inadvertently calling them directly +# instead of performing proper contract calls. +# +# Why this script is needed: +# During compilation, the transformed functions with the `__aztec_nr_internals__` prefix are what actually get +# compiled into circuits. However, in the exported ABI JSON that external tools and developers use, we want to +# expose the original function names without the internal prefix. This makes the ABI cleaner and matches what +# developers originally wrote in their contracts. + +json_path=$1 +temp_file="${json_path}.tmp" + +jq '.functions |= map(.name |= sub("^__aztec_nr_internals__"; ""))' "$json_path" > "$temp_file" +mv "$temp_file" "$json_path" diff --git a/release-image/Dockerfile.dockerignore b/release-image/Dockerfile.dockerignore index 6c21355c9174..9b23567ad68f 100644 --- a/release-image/Dockerfile.dockerignore +++ b/release-image/Dockerfile.dockerignore @@ -9,6 +9,7 @@ !/noir/noir-repo/target/release/noir-profiler !/noir-projects/noir-contracts/scripts/flamegraph.sh !/noir-projects/noir-contracts/scripts/extractFunctionAsNoirArtifact.js +!/noir-projects/noir-contracts/scripts/strip_aztec_nr_prefix.sh !/noir/packages/ !/yarn-project/package.json !/yarn-project/yarn.lock diff --git a/yarn-project/stdlib/src/noir/index.ts b/yarn-project/stdlib/src/noir/index.ts index 8ce3a6f591c7..6d8ea5a55fe3 100644 --- a/yarn-project/stdlib/src/noir/index.ts +++ b/yarn-project/stdlib/src/noir/index.ts @@ -8,12 +8,13 @@ import type { DebugInfo, } from '../abi/abi.js'; -export const AZTEC_PRIVATE_ATTRIBUTE = 'private'; -export const AZTEC_PUBLIC_ATTRIBUTE = 'public'; -export const AZTEC_UTILITY_ATTRIBUTE = 'utility'; -export const AZTEC_INTERNAL_ATTRIBUTE = 'internal'; -export const AZTEC_INITIALIZER_ATTRIBUTE = 'initializer'; -export const AZTEC_VIEW_ATTRIBUTE = 'view'; +export const AZTEC_PRIVATE_ATTRIBUTE = 'abi_private'; +export const AZTEC_PUBLIC_ATTRIBUTE = 'abi_public'; +export const AZTEC_UTILITY_ATTRIBUTE = 'abi_utility'; +// TODO(F-142): Fix the naming here. +export const AZTEC_INTERNAL_ATTRIBUTE = 'abi_only_self'; +export const AZTEC_INITIALIZER_ATTRIBUTE = 'abi_initializer'; +export const AZTEC_VIEW_ATTRIBUTE = 'abi_view'; /** The ABI of an Aztec.nr function. */ export interface NoirFunctionAbi {