Skip to content

Commit 12a2391

Browse files
authored
feat: add noir tests for events (#19128)
This started out as me wanting to remove the commitment from an event message, as it is unused, and then realizing that testing this was quite sad because we had no infra to do it. We now do. I did not publicly expose it yet as there are some open questions (what to do if we get too many events? how do we handle events emitted by contracts?), but those are more API questions and not related to the internals. With this we can at least use the capabilities ourselves for internal testing.
2 parents 4cec7ae + fb5ce3a commit 12a2391

File tree

13 files changed

+473
-52
lines changed

13 files changed

+473
-52
lines changed

noir-projects/aztec-nr/aztec/src/event/event_emission.nr

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use protocol_types::traits::{Serialize, ToField};
1212
pub struct NewEvent<Event> {
1313
pub(crate) event: Event,
1414
pub(crate) randomness: Field,
15-
pub(crate) commitment: Field,
1615
}
1716

1817
/// Equivalent to `self.emit(event)`: see [crate::contract_self::ContractSelf::emit].
@@ -38,7 +37,7 @@ where
3837
let commitment = compute_private_event_commitment(event, randomness);
3938
context.push_nullifier(commitment);
4039

41-
EventMessage::new(NewEvent { event, randomness, commitment }, context)
40+
EventMessage::new(NewEvent { event, randomness }, context)
4241
}
4342

4443
/// Equivalent to `self.emit(event)`: see [crate::contract_self::ContractSelf::emit].

noir-projects/aztec-nr/aztec/src/event/event_message.nr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use protocol_types::{address::AztecAddress, traits::Serialize};
1717
/// Use [EventMessage::deliver_to] to select a delivery mechanism.
1818
#[must_use = "Unused EventMessage result - use the `deliver_to` function to prevent the event information from being lost forever"]
1919
pub struct EventMessage<Event> {
20-
new_event: NewEvent<Event>,
20+
pub(crate) new_event: NewEvent<Event>,
2121

2222
// EventMessage is constructed when an event is emitted, which means that the `context` object will be in scope. By
2323
// storing a reference to it inside this object we remove the need for its methods to receive it, resulting in a

noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr

Lines changed: 181 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
use protocol_types::{
22
abis::function_selector::FunctionSelector,
33
address::AztecAddress,
4-
traits::{Deserialize, Packable},
4+
traits::{Deserialize, Packable, Serialize},
55
};
66

77
use crate::{
88
context::{
99
PrivateCall, PrivateContext, PrivateStaticCall, PublicCall, PublicContext, PublicStaticCall,
1010
UtilityCall, UtilityContext,
1111
},
12+
event::{event_interface::EventInterface, event_message::EventMessage},
1213
hash::hash_args,
1314
messages::{
1415
discovery::{
1516
ComputeNoteHashAndNullifier, NoteHashAndNullifier,
1617
process_message::process_message_plaintext,
1718
},
18-
logs::note::private_note_to_message_plaintext,
19+
encoding::MESSAGE_PLAINTEXT_LEN,
20+
logs::{event::private_event_to_message_plaintext, note::private_note_to_message_plaintext},
1921
processing::{message_context::MessageContext, validate_enqueued_notes_and_events},
2022
},
2123
note::{
@@ -128,6 +130,10 @@ struct NoteDiscoveryOptions {
128130
contract_address: Option<AztecAddress>,
129131
}
130132

133+
struct EventDiscoveryOptions {
134+
contract_address: Option<AztecAddress>,
135+
}
136+
131137
impl TestEnvironment {
132138
/// Creates a new `TestEnvironment`. This function should only be called once per test.
133139
pub unconstrained fn new() -> Self {
@@ -139,6 +145,12 @@ impl TestEnvironment {
139145
}
140146
}
141147

148+
/// Returns the default address used by [TestEnvironment::private_context] and other functions that do not take a
149+
/// contract address.
150+
pub(crate) unconstrained fn get_default_address() -> AztecAddress {
151+
txe_oracles::get_default_address()
152+
}
153+
142154
/// Creates a `PublicContext`, which allows using aztec-nr features as if inside a public contract function. Useful
143155
/// for low-level testing of public state variables and utilities.
144156
///
@@ -667,13 +679,13 @@ impl TestEnvironment {
667679
T::deserialize(serialized_return_values)
668680
}
669681

670-
/// Discovers a note from a [NewNoteMessage], which is expected to have been created in the last transaction
671-
/// (typically via `private_context()`) so that it can be retrieved in later transactions. This mimics the normal
672-
/// note discovery process that takes place automatically in contracts.
682+
/// Discovers a note from a [NoteMessage], which is expected to have been created in the last transaction
683+
/// (typically via [TestEnvironment::private_context]) so that it can be retrieved in later transactions. This
684+
/// mimics the normal message processing that takes place automatically in contracts.
673685
///
674-
/// [NewNoteMessage] values are typically returned by aztec-nr state variables that create notes and need to notify
686+
/// [NoteMessage] values are typically returned by aztec-nr state variables that create notes and need to notify
675687
/// a recipient of their existence. Instead of going through the message encoding, encryption and delivery that
676-
/// would regularly take place in a contract, this function simply takes the [NewNoteMessage] and processes it as
688+
/// would regularly take place in a contract, this function simply takes the [NoteMessage] and processes it as
677689
/// needed to discover the underlying note.
678690
///
679691
/// See [TestEnvironment::discover_note_at] for a variant that allows specifying the contract address the note
@@ -682,7 +694,7 @@ impl TestEnvironment {
682694
/// ### Sample usage
683695
///
684696
/// The most common way to invoke this function is to obtain a note message created during the execution of a
685-
/// `private_context`:
697+
/// [TestEnvironment::private_context]:
686698
///
687699
/// ```noir
688700
/// let note_message = env.private_context(|context| create_note(context, storage_slot, note));
@@ -701,8 +713,9 @@ impl TestEnvironment {
701713
);
702714
}
703715

704-
/// Variant of `discover_note` which allows specifying the contract address the note belongs to, which is required
705-
/// when the note was not emitted in the default address used by `private_context`.
716+
/// Variant of [TestEnvironment::discover_note] which allows specifying the contract address the note belongs to,
717+
/// which is required when the note was not emitted in the default address used by
718+
/// [TestEnvironment::private_context].
706719
///
707720
/// ```noir
708721
/// let note_message = env.private_context_at(contract_address, |context| {
@@ -740,7 +753,7 @@ impl TestEnvironment {
740753
// This function will emulate the message discovery and processing that would happen in a real contract, based
741754
// on a message created from the received note message.
742755

743-
// First we produce the message itself. We skip encryption/decryption since we're not interested in that here.
756+
// First we produce the message plaintext. We skip encryption/decryption since we're not interested in that here.
744757
// Note that we need to convert the fixed-size array produced by the encoding functions into a BoundedVec, which
745758
// is what the decoding functions expect (because they are built to manage protocol logs, which are arbitrarily
746759
// sized).
@@ -751,24 +764,6 @@ impl TestEnvironment {
751764
note_message.new_note.randomness,
752765
));
753766

754-
// Then we fetch the transaction effects from the latest transaction, in which the note from the message is
755-
// expected to have been created. In a real contract this data would have either been supplied by the
756-
// `process_message` caller, of retrieved along with the message from a tagged log.
757-
let (tx_hash, unique_note_hashes_in_tx, nullifiers_in_tx) =
758-
txe_oracles::get_last_tx_effects();
759-
760-
// Real messages would also have a recipient, a concept which does not translate to anything in
761-
// `TestEnvironment` since it works with a single PXE with global scopes. We therefore simply set the zero
762-
// address as the recipient, which PXE accepts as a note scope despite this not being a registered account.
763-
let recipient = AztecAddress::zero();
764-
765-
let message_context = MessageContext {
766-
tx_hash,
767-
unique_note_hashes_in_tx,
768-
first_nullifier_in_tx: nullifiers_in_tx.get(0),
769-
recipient,
770-
};
771-
772767
// We also need to provide an implementation for the `compute_note_hash_and_nullifier` function, which would
773768
// typically be created by the macros for the different notes in a given contract. Here we build one specialized
774769
// for `Note`.
@@ -796,21 +791,163 @@ impl TestEnvironment {
796791
Option::some(NoteHashAndNullifier { note_hash, inner_nullifier })
797792
};
798793

799-
// Both private and utility functions perform message processing and note discovery. We do it in an utility
800-
// context here as that one is more lightweight and does not create new blocks, which also allows for
801-
// `discover_notes` to be called repeatedly with multiple messages from the same transaction.
802-
self.utility_context_opts(
803-
UtilityContextOptions { contract_address: opts.contract_address },
804-
|context| {
805-
process_message_plaintext(
806-
context.this_address(),
807-
compute_note_hash_and_nullifier,
808-
message_plaintext,
809-
message_context,
810-
);
811-
812-
validate_enqueued_notes_and_events(context.this_address());
813-
},
794+
self.discover_data_in_message_plaintext(
795+
message_plaintext,
796+
opts.contract_address,
797+
Option::none(),
798+
compute_note_hash_and_nullifier,
799+
);
800+
}
801+
802+
/// EXPERIMENTAL - not yet part of the public API
803+
///
804+
/// Discovers an event from an [EventMessage], which is expected to have been created in the last transaction
805+
/// (typically via [TestEnvironment::private_context]) so that it can be retrieved in queries. This mimics the
806+
/// normal message processing that takes place automatically in contracts.
807+
///
808+
/// [EventMessage] values are returned by aztec-nr private event emitting functions, like
809+
/// [crate::contract_self::ContractSelf::emit], which need to notify a `recipient` of their existence. Instead of
810+
/// going through the message encoding, encryption and delivery that would regularly take place in a contract, this
811+
/// function simply takes the [EventMessage] and processes it as needed to discover the underlying event.
812+
///
813+
/// As this mimics regular event processing, only `recipient` will have knowledge of the event: other accounts will
814+
/// not be able to read it.
815+
///
816+
/// See [TestEnvironment::discover_event_at] for a variant that allows specifying the contract address the event
817+
/// belongs to.
818+
///
819+
/// ### Sample usage
820+
///
821+
/// The most common way to invoke this function is to obtain a note message created during the execution of a
822+
/// [TestEnvironment::private_context]:
823+
///
824+
/// ```noir
825+
/// let event_message = env.private_context(|context| emit_event_in_private(context, event));
826+
///
827+
/// env.discover_event(event_message, recipient);
828+
/// ```
829+
pub(crate) unconstrained fn discover_event<Event>(
830+
self: Self,
831+
event_message: EventMessage<Event>,
832+
recipient: AztecAddress,
833+
)
834+
where
835+
Event: Serialize + EventInterface,
836+
{
837+
self.discover_event_opts(
838+
EventDiscoveryOptions { contract_address: Option::none() },
839+
event_message,
840+
recipient,
841+
);
842+
}
843+
844+
/// EXPERIMENTAL - not yet part of the public API
845+
///
846+
/// Variant of [TestEnvironment::discover_event] which allows specifying the contract address the event belongs to,
847+
/// which is required when the event was not emitted in the default address used by
848+
/// [TestEnvironment::private_context].
849+
///
850+
/// ```noir
851+
/// let event_message = env.private_context_at(contract_address, |context| {
852+
/// emit_event_in_private(context, event)
853+
/// });
854+
///
855+
/// env.discover_event_at(contract_address, event_message);
856+
/// ```
857+
pub(crate) unconstrained fn discover_event_at<Event>(
858+
self: Self,
859+
addr: AztecAddress,
860+
event_message: EventMessage<Event>,
861+
recipient: AztecAddress,
862+
)
863+
where
864+
Event: Serialize + EventInterface,
865+
{
866+
self.discover_event_opts(
867+
EventDiscoveryOptions { contract_address: Option::some(addr) },
868+
event_message,
869+
recipient,
814870
);
815871
}
872+
873+
unconstrained fn discover_event_opts<Event>(
874+
self: Self,
875+
opts: EventDiscoveryOptions,
876+
event_message: EventMessage<Event>,
877+
recipient: AztecAddress,
878+
)
879+
where
880+
Event: Serialize + EventInterface,
881+
{
882+
// This function will emulate the message discovery and processing that would happen in a real contract, based
883+
// on a message created from the received event message.
884+
885+
// First we produce the message plaintext. We skip encryption/decryption since we're not interested in that here.
886+
// Note that we need to convert the fixed-size array produced by the encoding functions into a BoundedVec, which
887+
// is what the decoding functions expect (because they are built to manage protocol logs, which are arbitrarily
888+
// sized).
889+
let message_plaintext = BoundedVec::from_array(private_event_to_message_plaintext(
890+
event_message.new_event.event,
891+
event_message.new_event.randomness,
892+
));
893+
894+
// We also need to provide an implementation for the `compute_note_hash_and_nullifier` function, which would
895+
// typically be created by the macros for the different notes in a given contract. Here we build an empty one,
896+
// since it will never be invoked as this is an event and not a note message.
897+
let compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier<_> = |_packed_note, _owner, _storage_slot, _note_type_id, _contract_address, _randomness, _note_nonce| {
898+
panic(
899+
f"Unexpected compute_note_hash_and_nullifier invocation in TestEnvironment::discover_event",
900+
)
901+
};
902+
903+
self.discover_data_in_message_plaintext(
904+
message_plaintext,
905+
opts.contract_address,
906+
Option::some(recipient),
907+
compute_note_hash_and_nullifier,
908+
);
909+
}
910+
911+
unconstrained fn discover_data_in_message_plaintext<Env>(
912+
self,
913+
message_plaintext: BoundedVec<Field, MESSAGE_PLAINTEXT_LEN>,
914+
contract_address: Option<AztecAddress>,
915+
recipient: Option<AztecAddress>,
916+
compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier<Env>,
917+
) {
918+
// This function will emulate the message discovery and processing that would happen in a real contract, based
919+
// on a message plaintext.
920+
921+
// First we fetch the transaction effects from the latest transaction, which are required to create the
922+
// `MessageContext`, and might contain information about data (e.g. notes, events) contained in the message. In
923+
// a real contract this data would have either been supplied by the `process_message` caller, of retrieved
924+
// along with the message from a tagged log.
925+
let (tx_hash, unique_note_hashes_in_tx, nullifiers_in_tx) =
926+
txe_oracles::get_last_tx_effects();
927+
928+
// Real messages would also have a recipient, a concept which is currently half-supported in `TestEnvironment`
929+
// as events are properly scoped by recipient, but notes use a global scope instead. We therefore simply set the
930+
// zero address as the recipient if one is not supplied, which PXE accepts as a scope despite this not being a
931+
// registered account.
932+
let message_context = MessageContext {
933+
tx_hash,
934+
unique_note_hashes_in_tx,
935+
first_nullifier_in_tx: nullifiers_in_tx.get(0),
936+
recipient: recipient.unwrap_or(AztecAddress::zero()),
937+
};
938+
939+
// Both private and utility functions perform message processing . We do it in an utility context here as that
940+
// one is more lightweight and does not create new blocks, which also allows for `discover_note` and
941+
// `discover_event` to be called repeatedly with multiple messages from the same transaction.
942+
self.utility_context_opts(UtilityContextOptions { contract_address }, |context| {
943+
process_message_plaintext(
944+
context.this_address(),
945+
compute_note_hash_and_nullifier,
946+
message_plaintext,
947+
message_context,
948+
);
949+
950+
validate_enqueued_notes_and_events(context.this_address());
951+
});
952+
}
816953
}

noir-projects/aztec-nr/aztec/src/test/helpers/test_environment/test.nr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod accounts;
22
mod deployment;
3+
mod events;
34
mod private_context;
45
mod public_context;
56
mod utility_context;

0 commit comments

Comments
 (0)