Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions docs/netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,37 @@
# Add new error codes sequentially (1, 2, 3, ...).
# =============================================================================

# Example (uncomment and modify when adding error codes):
# [[redirects]]
# from = "/errors/1"
# to = "/developers/docs/aztec-nr/framework-description/functions/how_to_define_functions"
[[redirects]]
from = "/errors/1"
# A warning that is shown when `aztec compile` is run and the contract crate contains tests
to = "/developers/docs/aztec-nr/testing_contracts#keep-tests-in-the-test-crate"

[[redirects]]
# Aztec-nr: custom message was received but no handler was configured
from = "/errors/2"
to = "/aztec-nr-api/nightly/noir_aztec/macros/struct.AztecConfig.html"

[[redirects]]
# Aztec-nr: message received with message type ID in the range reserved for future aztec.nr built-in message types
from = "/errors/3"
to = "/aztec-nr-api/nightly/noir_aztec/messages/msg_type/index.html"

[[redirects]]
# Aztec-nr: note packed length exceeds MAX_NOTE_PACKED_LEN
from = "/errors/4"
to = "/aztec-nr-api/nightly/noir_aztec/macros/notes/fn.note.html"

[[redirects]]
# Aztec-nr: event serialized length exceeds MAX_EVENT_SERIALIZED_LEN
from = "/errors/5"
to = "/aztec-nr-api/nightly/noir_aztec/macros/events/fn.event.html"

[[redirects]]
# Aztec-nr: direct invocation of contract functions is not supported
from = "/errors/6"
to = "/developers/docs/aztec-nr/framework-description/calling_contracts"

[[redirects]]
# Aztec-nr: user-defined 'offchain_receive' is not allowed
from = "/errors/7"
to = "/aztec-nr-api/nightly/noir_aztec/messages/processing/offchain/fn.receive.html"
138 changes: 132 additions & 6 deletions noir-projects/aztec-nr/aztec/src/macros/aztec.nr
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,77 @@ use crate::macros::{
},
};

use crate::messages::discovery::CustomMessageHandler;

/// Marks a contract as an Aztec contract, generating the interfaces for its functions and notes, as well as injecting
/// the `sync_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 {
/// This type lets users override different parts of the default aztec-nr contract behavior, such
/// as message handling. These are advanced features that require careful understanding of
/// the behavior of these systems.
///
/// ## Examples
///
/// ```noir
/// #[aztec(aztec::macros::AztecConfig::new().custom_message_handler(my_handler))]
/// contract MyContract { ... }
/// ```
pub struct AztecConfig {
custom_message_handler: Option<CustomMessageHandler<()>>,
}

impl AztecConfig {
/// Creates a new `AztecConfig` with default values.
///
/// Calling `new` is equivalent to invoking the [`aztec`] macro with no parameters. The different methods
/// (e.g. [`AztecConfig::custom_message_handler`]) can then be used to change the default behavior.
pub comptime fn new() -> Self {
Self { custom_message_handler: Option::none() }
}

/// Sets a handler for custom messages.
///
/// This enables contracts to process non-standard messages (i.e. any with a message type that is not in
/// [`crate::messages::msg_type`]).
///
/// `handler` must be a function that conforms to the
/// [`crate::messages::discovery::CustomMessageHandler`] type signature.
pub comptime fn custom_message_handler(_self: Self, handler: CustomMessageHandler<()>) -> Self {
Self { custom_message_handler: Option::some(handler) }
}
}

/// Enables aztec-nr features on a `contract`.
///
/// All aztec-nr contracts should have this macro invoked on them, as it is the one that processes all contract
/// functions, notes, storage, generates interfaces for external calls, and creates the message processing
/// boilerplate.
///
/// ## Examples
///
/// Most contracts can simply invoke the macro with no parameters, resulting in default aztec-nr behavior:
/// ```noir
/// #[aztec]
/// contract MyContract { ... }
/// ```
///
/// Advanced contracts can use [`AztecConfig`] to customize parts of its behavior, such as message
/// processing.
/// ```noir
/// #[aztec(aztec::macros::AztecConfig::new().custom_message_handler(my_handler))]
/// contract MyAdvancedContract { ... }
/// ```
#[varargs]
pub comptime fn aztec(m: Module, args: [AztecConfig]) -> Quoted {
let num_args = args.len();
let config = if num_args == 0 {
AztecConfig::new()
} else if num_args == 1 {
args[0]
} else {
panic(f"#[aztec] expects 0 or 1 arguments, got {num_args}")
};

// Functions that don't have #[external(...)], #[contract_library_method], or #[test] are not allowed in contracts.
check_each_fn_macroified(m);

Expand All @@ -42,12 +108,30 @@ pub comptime fn aztec(m: Module) -> Quoted {
} else {
quote {}
};
let process_custom_message_option = if config.custom_message_handler.is_some() {
let handler = config.custom_message_handler.unwrap();
quote { Option::some($handler) }
} else {
quote { Option::<aztec::messages::discovery::CustomMessageHandler<()>>::none() }
};

let offchain_inbox_sync_option = quote {
Option::some(aztec::messages::processing::offchain::sync_inbox)
};

let sync_state_fn_and_abi_export = if !m.functions().any(|f| f.name() == quote { sync_state }) {
generate_sync_state()
generate_sync_state(process_custom_message_option, offchain_inbox_sync_option)
} else {
quote {}
};

if m.functions().any(|f| f.name() == quote { offchain_receive }) {
panic(
"User-defined 'offchain_receive' is not allowed. The function is auto-injected by the #[aztec] macro. See https://docs.aztec.network/errors/7",
);
}
let offchain_receive_fn_and_abi_export = generate_offchain_receive();

let process_message_fn_and_abi_export = if !m.functions().any(|f| f.name() == quote { process_message }) {
generate_process_message()
} else {
Expand All @@ -65,6 +149,7 @@ pub comptime fn aztec(m: Module) -> Quoted {
$public_dispatch
$sync_state_fn_and_abi_export
$process_message_fn_and_abi_export
$offchain_receive_fn_and_abi_export
}
}

Expand Down Expand Up @@ -263,7 +348,8 @@ comptime fn generate_contract_library_method_compute_note_hash_and_nullifier() -
}
}

comptime fn generate_sync_state() -> Quoted {
/// Generates the `sync_state` utility function that performs message discovery.
comptime fn generate_sync_state(process_custom_message_option: Quoted, offchain_inbox_sync_option: Quoted) -> Quoted {
quote {
pub struct sync_state_parameters {}

Expand All @@ -275,8 +361,12 @@ comptime fn generate_sync_state() -> Quoted {
#[aztec::macros::internals_functions_generation::abi_attributes::abi_utility]
unconstrained fn sync_state() {
let address = aztec::context::UtilityContext::new().this_address();

aztec::messages::discovery::do_sync_state(address, _compute_note_hash_and_nullifier);
aztec::messages::discovery::do_sync_state(
address,
_compute_note_hash_and_nullifier,
$process_custom_message_option,
$offchain_inbox_sync_option,
);
}
}
}
Expand All @@ -303,6 +393,7 @@ comptime fn generate_process_message() -> Quoted {
aztec::messages::discovery::process_message::process_message_ciphertext(
address,
_compute_note_hash_and_nullifier,
Option::<aztec::messages::discovery::CustomMessageHandler<()>>::none(),
message_ciphertext,
message_context,
);
Expand All @@ -314,6 +405,41 @@ comptime fn generate_process_message() -> Quoted {
}
}

/// Generates an `offchain_receive` utility function that lets callers add messages to the offchain message inbox.
///
/// For more details, see `aztec::messages::processing::offchain::receive`.
comptime fn generate_offchain_receive() -> Quoted {
quote {
pub struct offchain_receive_parameters {
pub messages: BoundedVec<
aztec::messages::processing::offchain::OffchainMessage,
aztec::messages::processing::offchain::MAX_OFFCHAIN_MESSAGES_PER_RECEIVE_CALL,
>,
}

#[abi(functions)]
pub struct offchain_receive_abi {
parameters: offchain_receive_parameters,
}

/// Receives offchain messages into this contract's offchain inbox for subsequent processing.
///
/// For more details, see `aztec::messages::processing::offchain::receive`.
///
/// This function is automatically injected by the `#[aztec]` macro.
#[aztec::macros::internals_functions_generation::abi_attributes::abi_utility]
unconstrained fn offchain_receive(
messages: BoundedVec<
aztec::messages::processing::offchain::OffchainMessage,
aztec::messages::processing::offchain::MAX_OFFCHAIN_MESSAGES_PER_RECEIVE_CALL,
>,
) {
let address = aztec::context::UtilityContext::new().this_address();
aztec::messages::processing::offchain::receive(address, messages);
}
}
}

/// Checks that all functions in the module have a context macro applied.
///
/// Non-macroified functions are not allowed in contracts. They must all be one of
Expand Down
39 changes: 36 additions & 3 deletions noir-projects/aztec-nr/aztec/src/messages/discovery/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ pub mod private_notes;
pub mod process_message;

use crate::{
capsules::CapsuleArray,
messages::{
discovery::process_message::process_message_ciphertext,
logs::note::MAX_NOTE_PACKED_LEN,
processing::{
get_private_logs, pending_tagged_log::PendingTaggedLog, validate_and_store_enqueued_notes_and_events,
get_private_logs, MessageContext, offchain::OffchainInboxSync, OffchainMessageWithContext,
pending_tagged_log::PendingTaggedLog, validate_and_store_enqueued_notes_and_events,
},
},
utils::array,
Expand Down Expand Up @@ -64,6 +66,18 @@ pub type ComputeNoteHashAndNullifier<Env> = unconstrained fn[Env](/* packed_note
owner */ AztecAddress, /* storage_slot */ Field, /* note_type_id */ Field, /* contract_address */ AztecAddress, /*
randomness */ Field, /* note nonce */ Field) -> Option<NoteHashAndNullifier>;

/// A handler for custom messages.
///
/// Contracts that emit custom messages (i.e. any with a message type that is not in [`crate::messages::msg_type`])
/// need to use [`crate::macros::aztec::AztecConfig::custom_message_handler`] with a function of this type in order to
/// process them. They will otherwise be **silently ignored**.
pub type CustomMessageHandler<Env> = unconstrained fn[Env](
/* contract_address */AztecAddress,
/* msg_type_id */ u64,
/* msg_metadata */ u64,
/* msg_content */ BoundedVec<Field, crate::messages::encoding::MAX_MESSAGE_CONTENT_LEN>,
/* message_context */ MessageContext);

/// Performs the state synchronization process, in which private logs are downloaded and inspected to find new private
/// notes, partial notes and events, etc., and pending partial notes are processed to search for their completion logs.
/// This is the mechanism via which a contract updates its knowledge of its private state.
Expand All @@ -74,9 +88,11 @@ randomness */ Field, /* note nonce */ Field) -> Option<NoteHashAndNullifier>;
///
/// Receives the address of the contract on which discovery is performed along with its
/// `compute_note_hash_and_nullifier` function.
pub unconstrained fn do_sync_state<Env>(
pub unconstrained fn do_sync_state<ComputeNoteHashAndNullifierEnv, CustomMessageHandlerEnv>(
contract_address: AztecAddress,
compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier<Env>,
compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier<ComputeNoteHashAndNullifierEnv>,
process_custom_message: Option<CustomMessageHandler<CustomMessageHandlerEnv>>,
offchain_inbox_sync: Option<OffchainInboxSync<()>>,
) {
debug_log("Performing state synchronization");

Expand All @@ -95,12 +111,29 @@ pub unconstrained fn do_sync_state<Env>(
process_message_ciphertext(
contract_address,
compute_note_hash_and_nullifier,
process_custom_message,
message_ciphertext,
pending_tagged_log.context,
);
logs.remove(i);
});

if offchain_inbox_sync.is_some() {
let msgs: CapsuleArray<OffchainMessageWithContext> = offchain_inbox_sync.unwrap()(contract_address);
msgs.for_each(|i, msg| {
process_message_ciphertext(
contract_address,
compute_note_hash_and_nullifier,
process_custom_message,
msg.message_ciphertext,
msg.message_context,
);
// The inbox sync returns _a copy_ of messages to process, so we clear them as we do so. This is a
// volatile array with the to-process message, not the actual persistent storage of them.
msgs.remove(i);
});
}

// Then we process all pending partial notes, regardless of whether they were found in the current or previous
// executions.
partial_notes::fetch_and_process_partial_note_completion_logs(contract_address, compute_note_hash_and_nullifier);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use crate::messages::{
discovery::{
ComputeNoteHashAndNullifier, partial_notes::process_partial_note_private_msg,
ComputeNoteHashAndNullifier, CustomMessageHandler, partial_notes::process_partial_note_private_msg,
private_events::process_private_event_msg, private_notes::process_private_note_msg,
},
encoding::{decode_message, MESSAGE_CIPHERTEXT_LEN, MESSAGE_PLAINTEXT_LEN},
encryption::{aes128::AES128, message_encryption::MessageEncryption},
msg_type::{PARTIAL_NOTE_PRIVATE_MSG_TYPE_ID, PRIVATE_EVENT_MSG_TYPE_ID, PRIVATE_NOTE_MSG_TYPE_ID},
msg_type::{
MIN_CUSTOM_MSG_TYPE_ID, PARTIAL_NOTE_PRIVATE_MSG_TYPE_ID, PRIVATE_EVENT_MSG_TYPE_ID, PRIVATE_NOTE_MSG_TYPE_ID,
},
processing::MessageContext,
};

Expand All @@ -23,9 +25,10 @@ use crate::protocol::{address::AztecAddress, logging::{debug_log, debug_log_form
///
/// Events are processed by computing an event commitment from the serialized event data and its randomness field, then
/// enqueueing the event data and commitment for validation.
pub unconstrained fn process_message_ciphertext<Env>(
pub unconstrained fn process_message_ciphertext<ComputeNoteHashAndNullifierEnv, CustomMessageHandlerEnv>(
contract_address: AztecAddress,
compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier<Env>,
compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier<ComputeNoteHashAndNullifierEnv>,
process_custom_message: Option<CustomMessageHandler<CustomMessageHandlerEnv>>,
message_ciphertext: BoundedVec<Field, MESSAGE_CIPHERTEXT_LEN>,
message_context: MessageContext,
) {
Expand All @@ -35,6 +38,7 @@ pub unconstrained fn process_message_ciphertext<Env>(
process_message_plaintext(
contract_address,
compute_note_hash_and_nullifier,
process_custom_message,
message_plaintext_option.unwrap(),
message_context,
);
Expand All @@ -46,9 +50,10 @@ pub unconstrained fn process_message_ciphertext<Env>(
}
}

pub unconstrained fn process_message_plaintext<Env>(
pub unconstrained fn process_message_plaintext<ComputeNoteHashAndNullifierEnv, CustomMessageHandlerEnv>(
contract_address: AztecAddress,
compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier<Env>,
compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier<ComputeNoteHashAndNullifierEnv>,
process_custom_message: Option<CustomMessageHandler<CustomMessageHandlerEnv>>,
message_plaintext: BoundedVec<Field, MESSAGE_PLAINTEXT_LEN>,
message_context: MessageContext,
) {
Expand Down Expand Up @@ -90,7 +95,23 @@ pub unconstrained fn process_message_plaintext<Env>(
msg_content,
message_context.tx_hash,
);
} else if msg_type_id < MIN_CUSTOM_MSG_TYPE_ID {
debug_log_format(
"Message type ID {0} is in the reserved range but is not recognized, ignoring. See https://docs.aztec.network/errors/3",
[msg_type_id as Field],
);
} else if process_custom_message.is_some() {
process_custom_message.unwrap()(
contract_address,
msg_type_id,
msg_metadata,
msg_content,
message_context,
);
} else {
debug_log_format("Unknown msg type id {0}", [msg_type_id as Field]);
debug_log_format(
"Received custom message with type id {0} but no handler is configured, ignoring. See https://docs.aztec.network/errors/2",
[msg_type_id as Field],
);
}
}
Loading
Loading