Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
26 changes: 26 additions & 0 deletions docs/docs-developers/docs/aztec-nr/testing_contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,32 @@ env.advance_next_block_timestamp_by(duration);
env.mine_block_at(block_timestamp);
````

## Testing private events [experimental]

> Testing events API is currently experimental as we don't yet support filtering or pagination and there will be breaking changes.

You can verify that private events were emitted correctly in the last call using `get_private_events`:

```rust
#[test]
unconstrained fn test_transfer_emits_event() {
let (env, token_address, owner, recipient) = setup(false);

env.call_private(owner, Token::at(token_address).transfer(recipient, 100));

let events = env.get_private_events::<Token::Transfer>(token_address, recipient);
assert_eq(events.len(), 1);
let event = events.get(0);
assert_eq(event.from, owner);
assert_eq(event.to, recipient);
assert_eq(event.amount, 100 as u128);
}
```

:::info
The `#[event]` macro automatically applies `pub` visibility to the struct, making it accessible from the test crate.
:::

## Testing failure cases

Test functions that should fail using annotations:
Expand Down
10 changes: 9 additions & 1 deletion noir-projects/aztec-nr/aztec/src/macros/events.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::{
macros::utils::{compute_struct_selector, derive_serialize_if_not_implemented, get_trait_impl_method},
macros::utils::{
compute_struct_selector, derive_deserialize_if_not_implemented, derive_serialize_if_not_implemented,
get_trait_impl_method,
},
utils::cmap::CHashMap,
};
use std::panic;
Expand Down Expand Up @@ -61,6 +64,8 @@ comptime fn register_event_selector(event_selector: Field, event_name: Quoted) {
/// [`EventInterface`](crate::event::event_interface::EventInterface) implementation (which provides the event type
/// id) and a [`Serialize`](crate::protocol::traits::Serialize) implementation if one is not already provided.
///
/// The macro automatically applies `pub` visibility to the struct.
///
/// ## Requirements
///
/// The event struct must not exceed
Expand All @@ -70,11 +75,14 @@ pub comptime fn event(s: TypeDefinition) -> Quoted {
register_event_selector(event_selector, s.name());

let serialize_impl = derive_serialize_if_not_implemented(s);
let deserialize_impl = derive_deserialize_if_not_implemented(s);

s.add_attribute("abi(events)");
s.set_visibility(quote { pub });

quote {
$event_interface_impl
$serialize_impl
$deserialize_impl
}
}
13 changes: 12 additions & 1 deletion noir-projects/aztec-nr/aztec/src/macros/utils.nr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::protocol::meta::derive_serialize;
use crate::protocol::meta::{derive_deserialize, derive_serialize};
use std::meta::{ctstring::AsCtString, unquote};

pub(crate) comptime fn is_fn_external(f: FunctionDefinition) -> bool {
Expand Down Expand Up @@ -164,3 +164,14 @@ pub comptime fn derive_serialize_if_not_implemented(s: TypeDefinition) -> Quoted
derive_serialize(s)
}
}

/// Returns an `impl` for `Deserialize` if not already implemented.
///
/// The returned quote will be empty if the type `s` already implements `Deserialize`.
pub comptime fn derive_deserialize_if_not_implemented(s: TypeDefinition) -> Quoted {
if s.as_type().implements(quote { crate::protocol::traits::Deserialize }.as_trait_constraint()) {
quote {}
} else {
derive_deserialize(s)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,36 @@ impl TestEnvironment {
);
}

/// Returns private events emitted as of last block.
///
/// Queries up to `MAX_PRIVATE_EVENTS_PER_TXE_QUERY` private events of type `Event` that were emitted by
/// `contract_address` and are readable by `scope` (i.e. the address under which the event was added to pxe, this
/// is typically the event recipient).
///
/// **Experimental** - there will be breaking API changes.
///
/// ---
///
/// ## Examples
///
/// Emitting a single event in a contract call and reading it back (transfer function emits the event):
/// ```noir
/// env.call_private(owner, Token::at(token_address).transfer(recipient, 100));
///
/// let events = env.get_private_events::<Token::Transfer>(token_address, recipient);
/// assert_eq(events.get(0).amount, 100 as u128);
/// ```
pub unconstrained fn get_private_events<Event>(
_self: Self,
contract_address: AztecAddress,
scope: AztecAddress,
) -> BoundedVec<Event, crate::test::helpers::txe_oracles::MAX_PRIVATE_EVENTS_PER_TXE_QUERY>
where
Event: EventInterface + Deserialize,
{
txe_oracles::get_private_events(contract_address, scope)
}

unconstrained fn discover_event_opts<Event>(
self: Self,
opts: EventDiscoveryOptions,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::event::{event_emission::emit_event_in_private, event_interface::EventInterface};
use crate::protocol::traits::Serialize;
use crate::event::event_emission::emit_event_in_private;
use crate::test::{helpers::test_environment::TestEnvironment, mocks::mock_event::MockEvent};

global VALUE: Field = 7;
Expand All @@ -16,14 +15,10 @@ unconstrained fn emit_and_discover_event() {

env.discover_event(event_message, recipient);

let events = crate::test::helpers::txe_oracles::get_private_events(
MockEvent::get_event_type_id(),
TestEnvironment::get_default_address(),
recipient,
);
let events: BoundedVec<MockEvent, _> = env.get_private_events(TestEnvironment::get_default_address(), recipient);

assert_eq(events.len(), 1);
assert_eq(events.get(0), BoundedVec::from_array(event.serialize()));
assert_eq(events.get(0), event);
}

#[test]
Expand All @@ -42,15 +37,11 @@ unconstrained fn emit_and_discover_multiple_events() {
env.discover_event(event_messages.0, recipient);
env.discover_event(event_messages.1, recipient);

let events = crate::test::helpers::txe_oracles::get_private_events(
MockEvent::get_event_type_id(),
TestEnvironment::get_default_address(),
recipient,
);
let events: BoundedVec<MockEvent, _> = env.get_private_events(TestEnvironment::get_default_address(), recipient);

assert_eq(events.len(), 2);
assert(events.any(|ev| ev == BoundedVec::from_array(event.serialize())));
assert(events.any(|ev| ev == BoundedVec::from_array(other_event.serialize())));
assert(events.any(|ev| ev == event));
assert(events.any(|ev| ev == other_event));
}

#[test]
Expand All @@ -66,14 +57,10 @@ unconstrained fn emit_and_discover_same_event_multiple_times() {
env.discover_event(event_message, recipient);
env.discover_event(event_message, recipient);

let events = crate::test::helpers::txe_oracles::get_private_events(
MockEvent::get_event_type_id(),
TestEnvironment::get_default_address(),
recipient,
);
let events: BoundedVec<MockEvent, _> = env.get_private_events(TestEnvironment::get_default_address(), recipient);

assert_eq(events.len(), 1);
assert_eq(events.get(0), BoundedVec::from_array(event.serialize()));
assert_eq(events.get(0), event);
}

#[test]
Expand All @@ -89,11 +76,8 @@ unconstrained fn emit_and_fail_to_discover_event_as_other_recipient() {

env.discover_event(event_message, recipient);

let events = crate::test::helpers::txe_oracles::get_private_events(
MockEvent::get_event_type_id(),
TestEnvironment::get_default_address(),
other_recipient,
);
let events: BoundedVec<MockEvent, _> =
env.get_private_events(TestEnvironment::get_default_address(), other_recipient);

assert_eq(events.len(), 0);
}
Expand All @@ -112,12 +96,8 @@ unconstrained fn emit_and_discover_event_multiple_recipients() {
env.discover_event(event_message, recipient);
env.discover_event(event_message, other_recipient);

let events = crate::test::helpers::txe_oracles::get_private_events(
MockEvent::get_event_type_id(),
TestEnvironment::get_default_address(),
recipient,
);
let events: BoundedVec<MockEvent, _> = env.get_private_events(TestEnvironment::get_default_address(), recipient);

assert_eq(events.len(), 1);
assert_eq(events.get(0), BoundedVec::from_array(event.serialize()));
assert_eq(events.get(0), event);
}
28 changes: 20 additions & 8 deletions noir-projects/aztec-nr/aztec/src/test/helpers/txe_oracles.nr
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use crate::{context::inputs::PrivateContextInputs, event::EventSelector, test::helpers::utils::TestAccount};
use crate::{
context::inputs::PrivateContextInputs,
event::{event_interface::EventInterface, EventSelector},
test::helpers::utils::TestAccount,
};

use crate::protocol::{
abis::function_selector::FunctionSelector,
Expand All @@ -8,9 +12,10 @@ use crate::protocol::{
},
contract_instance::ContractInstance,
traits::{Deserialize, ToField},
utils::reader::Reader,
};

global MAX_PRIVATE_EVENTS_PER_TXE_QUERY: u32 = 5;
pub(crate) global MAX_PRIVATE_EVENTS_PER_TXE_QUERY: u32 = 5;
global MAX_EVENT_SERIALIZATION_LENGTH: u32 = 10;

pub unconstrained fn deploy<let M: u32, let N: u32, let P: u32>(
Expand Down Expand Up @@ -78,20 +83,27 @@ pub unconstrained fn get_last_tx_effects() -> (Field, BoundedVec<Field, MAX_NOTE
#[oracle(aztec_txe_getDefaultAddress)]
pub unconstrained fn get_default_address() -> AztecAddress {}

// experimental
pub(crate) unconstrained fn get_private_events(
selector: EventSelector,
pub(crate) unconstrained fn get_private_events<Event>(
contract_address: AztecAddress,
scope: AztecAddress,
) -> BoundedVec<BoundedVec<Field, MAX_EVENT_SERIALIZATION_LENGTH>, MAX_PRIVATE_EVENTS_PER_TXE_QUERY> {
) -> BoundedVec<Event, MAX_PRIVATE_EVENTS_PER_TXE_QUERY>
where
Event: EventInterface + Deserialize,
{
// This is a workaround as Noir does not currently let us return nested structs with arrays. We instead return a
// raw multidimensional array in get_private_events_oracle and create the BoundedVecs here.

let selector = Event::get_event_type_id();
let (raw_array_storage, event_lengths, query_length) = get_private_events_oracle(selector, contract_address, scope);

let mut events = BoundedVec::new();
for i in 0..query_length {
events.push(BoundedVec::from_parts(raw_array_storage[i], event_lengths[i]));
assert(
<Event as Deserialize>::N == event_lengths[i],
"Unexpected event length obtained from txeGetPrivateEvents",
);

let mut reader = Reader::new(raw_array_storage[i]);
events.push(Deserialize::stream_deserialize(&mut reader));
}

events
Expand Down
4 changes: 2 additions & 2 deletions noir-projects/aztec-nr/aztec/src/test/mocks/mock_event.nr
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

use crate::{event::{event_emission::NewEvent, event_interface::EventInterface, EventSelector}, oracle::random::random};

use crate::protocol::traits::{FromField, Serialize};
use crate::protocol::traits::{Deserialize, FromField, Serialize};

#[derive(Eq, Serialize)]
#[derive(Eq, Serialize, Deserialize)]
pub(crate) struct MockEvent {
pub(crate) value: Field,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use aztec::{
};

#[event]
pub struct InvalidEvent {}
struct InvalidEvent {}

impl Serialize for InvalidEvent {
let N: u32 = MAX_EVENT_SERIALIZED_LEN + 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ pub contract Token {

#[event]
struct Transfer {
from: AztecAddress,
to: AztecAddress,
amount: u128,
pub from: AztecAddress,
pub to: AztecAddress,
pub amount: u128,
}

// docs:start:storage_struct
Expand Down Expand Up @@ -243,7 +243,7 @@ pub contract Token {
self.storage.balances.at(from).add(change).deliver(MessageDelivery.ONCHAIN_UNCONSTRAINED);
self.storage.balances.at(to).add(amount).deliver(MessageDelivery.ONCHAIN_UNCONSTRAINED);

// We don't constrain encryption of the note log in `transfer` (unlike in `transfer_in_private`) because the transfer
// We don't constrain encryption of the event log here (unlike in `transfer_in_private`) because the transfer
// function is only designed to be used in situations where the event is not strictly necessary (e.g. payment to
// another person where the payment is considered to be successful when the other party successfully decrypts a
// note).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ unconstrained fn transfer_private() {
mint_amount - transfer_amount,
);
utils::check_private_balance(env, token_contract_address, recipient, transfer_amount);

utils::check_private_transfer_event(
env,
token_contract_address,
owner,
recipient,
transfer_amount,
);
}

#[test]
Expand All @@ -32,6 +40,8 @@ unconstrained fn transfer_private_to_self() {

// Check balances
utils::check_private_balance(env, token_contract_address, owner, mint_amount);

utils::check_private_transfer_event(env, token_contract_address, owner, owner, transfer_amount);
}

#[test]
Expand All @@ -56,6 +66,14 @@ unconstrained fn transfer_private_to_non_deployed_account() {
mint_amount - transfer_amount,
);
utils::check_private_balance(env, token_contract_address, not_deployed, transfer_amount);

utils::check_private_transfer_event(
env,
token_contract_address,
owner,
not_deployed,
transfer_amount,
);
}

#[test(should_fail_with = "Balance too low")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,24 @@ pub unconstrained fn check_private_balance(
);
}

pub unconstrained fn check_private_transfer_event(
env: TestEnvironment,
token_contract_address: AztecAddress,
from: AztecAddress,
to: AztecAddress,
amount: u128,
) {
let events = env.get_private_events::<Token::Transfer>(token_contract_address, to);

assert_eq(events.len(), 1);

let event = events.get(0);

assert_eq(event.from, from);
assert_eq(event.to, to);
assert_eq(event.amount, amount);
}

// While authwits authorize a contract to perform an action, spending private notes still requires
// knowledge of the note secrets. For private tokens, the note owner must be involved in the
// transaction -- they cannot simply give another user an authwit and have that user spend the notes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub contract TestLog {
macros::{events::event, functions::external, storage::storage},
messages::message_delivery::MessageDelivery,
oracle::random::random,
protocol::{address::AztecAddress, traits::{FromField, Serialize}},
protocol::{address::AztecAddress, traits::{Deserialize, FromField, Serialize}},
state_vars::{Owned, PrivateSet},
};
use field_note::FieldNote;
Expand All @@ -23,7 +23,7 @@ pub contract TestLog {
value3: u8,
}

#[derive(Serialize)]
#[derive(Deserialize, Serialize)]
struct NestedStruct {
a: Field,
b: Field,
Expand Down
Loading
Loading