diff --git a/noir-projects/aztec-nr/aztec/src/macros/events.nr b/noir-projects/aztec-nr/aztec/src/macros/events.nr index a2ff2afabcef..1c60937bdbf9 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/events.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/events.nr @@ -10,9 +10,6 @@ pub comptime mut global EVENT_SELECTORS: CHashMap = CHashMap::new comptime fn generate_event_interface_and_get_selector(s: TypeDefinition) -> (Quoted, Field) { let name = s.name(); - let typ = s.as_type(); - let event_type_name: str<_> = f"{name}".as_quoted_str!(); - let max_event_serialized_len = crate::messages::logs::event::MAX_EVENT_SERIALIZED_LEN; let event_selector = compute_struct_selector(s, quote { crate::event::EventSelector::from_signature }); @@ -26,18 +23,6 @@ comptime fn generate_event_interface_and_get_selector(s: TypeDefinition) -> (Quo quote { impl aztec::event::event_interface::EventInterface for $name { fn get_event_type_id() -> aztec::event::EventSelector { - // This static assertion ensures the event's serialized length doesn't exceed the maximum - // allowed size. While this check would ideally live in the Serialize trait implementation, we - // place it here since this function is always generated by our macros and the Serialize trait - // implementation is not. - // - // Note: We set the event type name and max serialized length as local variables because - // injecting them directly into the error message doesn't work. - let event_type_name = $event_type_name; - let max_event_serialized_len: u32 = $max_event_serialized_len; - let event_serialized_len = <$typ as aztec::protocol::traits::Serialize>::N; - std::static_assert(event_serialized_len <= $max_event_serialized_len, f"{event_type_name} has a serialized length of {event_serialized_len} fields, which exceeds the maximum allowed length of {max_event_serialized_len} fields. See https://docs.aztec.network/errors/5"); - $from_field($event_selector) } } @@ -63,8 +48,9 @@ comptime fn register_event_selector(event_selector: Field, event_name: Quoted) { /// /// ## Requirements /// -/// The event struct must not exceed -/// [`MAX_EVENT_SERIALIZED_LEN`](crate::messages::logs::event::MAX_EVENT_SERIALIZED_LEN) when serialized. +/// Events can be emitted both privately and publicly. Public emission imposes no requirements, but for an event to be +/// emittable privately its serialization length must not exceeed +/// [`MAX_EVENT_SERIALIZED_LEN`](crate::messages::logs::event::MAX_EVENT_SERIALIZED_LEN). pub comptime fn event(s: TypeDefinition) -> Quoted { let (event_interface_impl, event_selector) = generate_event_interface_and_get_selector(s); register_event_selector(event_selector, s.name()); diff --git a/noir-projects/aztec-nr/aztec/src/messages/logs/event.nr b/noir-projects/aztec-nr/aztec/src/messages/logs/event.nr index 101bff9a3f68..21e0bfa45d78 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/logs/event.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/logs/event.nr @@ -27,6 +27,11 @@ pub fn encode_private_event_message( where Event: EventInterface + Serialize, { + std::static_assert( + ::N <= MAX_EVENT_SERIALIZED_LEN, + "event's serialized length exceeds the maximum allowed for private events", + ); + // We use `Serialize` because we want for events to be processable by off-chain actors, e.g. block explorers, // wallets and apps, without having to rely on contract invocation. If we used `Packable` we'd need to call utility // functions in order to unpack events, which would introduce a level of complexity we don't currently think is diff --git a/noir-projects/noir-contracts-comp-failures/contracts/invalid_event/expected_error b/noir-projects/noir-contracts-comp-failures/contracts/invalid_event/expected_error index 4f092dbf4803..52ea26ac60ee 100644 --- a/noir-projects/noir-contracts-comp-failures/contracts/invalid_event/expected_error +++ b/noir-projects/noir-contracts-comp-failures/contracts/invalid_event/expected_error @@ -1 +1 @@ -InvalidEvent has a serialized length of 11 fields, which exceeds the maximum allowed length of 10 fields. See https://docs.aztec.network/errors/5 +event's serialized length exceeds the maximum allowed for private events diff --git a/noir-projects/noir-contracts-comp-failures/contracts/invalid_event/src/main.nr b/noir-projects/noir-contracts-comp-failures/contracts/invalid_event/src/main.nr index 03fdfb49b062..7bfb2bc47c26 100644 --- a/noir-projects/noir-contracts-comp-failures/contracts/invalid_event/src/main.nr +++ b/noir-projects/noir-contracts-comp-failures/contracts/invalid_event/src/main.nr @@ -3,13 +3,17 @@ use aztec::macros::aztec; mod invalid_event; #[aztec] -pub contract InvalidEventContract { +contract InvalidEventContract { use crate::invalid_event::InvalidEvent; - use aztec::{event::event_interface::EventInterface, macros::functions::external}; + use aztec::{macros::functions::external, messages::message_delivery::MessageDelivery}; - // We have here this function in order for the static_assert in `get_event_type_id` to get triggered. + // Emitting an oversized event privately should fail at compile time because its serialized length exceeds + // MAX_EVENT_SERIALIZED_LEN. #[external("private")] fn trigger_event_check() { - let _ = InvalidEvent::get_event_type_id(); + self.emit(InvalidEvent {}).deliver_to( + self.msg_sender(), + MessageDelivery.ONCHAIN_UNCONSTRAINED, + ); } } diff --git a/noir-projects/noir-contracts/Nargo.toml b/noir-projects/noir-contracts/Nargo.toml index 6f1cb287b370..8bc900840e8a 100644 --- a/noir-projects/noir-contracts/Nargo.toml +++ b/noir-projects/noir-contracts/Nargo.toml @@ -44,6 +44,7 @@ members = [ "contracts/test/counter/counter_contract", "contracts/test/custom_message_contract", "contracts/test/event_only_contract", + "contracts/test/large_public_event_contract", "contracts/test/import_test_contract", "contracts/test/init_test_contract", "contracts/test/invalid_account_contract", diff --git a/noir-projects/noir-contracts/contracts/test/large_public_event_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/test/large_public_event_contract/Nargo.toml new file mode 100644 index 000000000000..e3a21abba448 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test/large_public_event_contract/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "large_public_event_contract" +authors = [""] +compiler_version = ">=0.25.0" +type = "contract" + +[dependencies] +aztec = { path = "../../../../aztec-nr/aztec" } diff --git a/noir-projects/noir-contracts/contracts/test/large_public_event_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/large_public_event_contract/src/main.nr new file mode 100644 index 000000000000..2f99c9103b45 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test/large_public_event_contract/src/main.nr @@ -0,0 +1,21 @@ +use aztec::macros::aztec; + +/// Tests that events with a serialized length exceeding [`MAX_EVENT_SERIALIZED_LEN`] can be emitted publicly. +/// The private emission size limit does not apply to public logs. +#[aztec] +contract LargePublicEvent { + use aztec::{ + macros::{events::event, functions::external}, + messages::logs::event::MAX_EVENT_SERIALIZED_LEN, + }; + + #[event] + pub struct LargeEvent { + data: [Field; MAX_EVENT_SERIALIZED_LEN + 1], + } + + #[external("public")] + fn emit_large_event(data: [Field; MAX_EVENT_SERIALIZED_LEN + 1]) { + self.emit(LargeEvent { data }); + } +} diff --git a/yarn-project/end-to-end/src/e2e_large_public_event.test.ts b/yarn-project/end-to-end/src/e2e_large_public_event.test.ts new file mode 100644 index 000000000000..81997ac4cf6b --- /dev/null +++ b/yarn-project/end-to-end/src/e2e_large_public_event.test.ts @@ -0,0 +1,50 @@ +import { getPublicEvents } from '@aztec/aztec.js/events'; +import { Fr } from '@aztec/aztec.js/fields'; +import type { AztecNode } from '@aztec/aztec.js/node'; +import type { Wallet } from '@aztec/aztec.js/wallet'; +import { BlockNumber } from '@aztec/foundation/branded-types'; +import { type LargeEvent, LargePublicEventContract } from '@aztec/noir-test-contracts.js/LargePublicEvent'; +import type { AztecAddress } from '@aztec/stdlib/aztec-address'; + +import { jest } from '@jest/globals'; + +import { setup } from './fixtures/utils.js'; + +const TIMEOUT = 120_000; + +/// Tests that events exceeding MAX_EVENT_SERIALIZED_LEN can be emitted publicly. +describe('LargePublicEvent', () => { + let contract: LargePublicEventContract; + jest.setTimeout(TIMEOUT); + + let wallet: Wallet; + let aztecNode: AztecNode; + let accountAddress: AztecAddress; + let teardown: () => Promise; + + beforeAll(async () => { + ({ + teardown, + wallet, + aztecNode, + accounts: [accountAddress], + } = await setup(1)); + ({ contract } = await LargePublicEventContract.deploy(wallet).send({ from: accountAddress })); + }); + + afterAll(() => teardown()); + + it('emits and retrieves a public event with more than MAX_EVENT_SERIALIZED_LEN fields', async () => { + const data = Array.from({ length: 11 }, () => Fr.random()); + + const { receipt: tx } = await contract.methods.emit_large_event(data).send({ from: accountAddress }); + + const { events } = await getPublicEvents(aztecNode, LargePublicEventContract.events.LargeEvent, { + fromBlock: BlockNumber(tx.blockNumber!), + toBlock: BlockNumber(tx.blockNumber! + 1), + }); + + expect(events.length).toBe(1); + expect(events[0].event.data).toEqual(data.map(f => f.toBigInt())); + }); +});