From 3b788d61bfdba60e8cca4471954d8689c539e7dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Mon, 30 Mar 2026 20:49:13 +0000 Subject: [PATCH 1/2] feat: move event size check from declaration to private emission The `#[event]` macro previously enforced MAX_EVENT_SERIALIZED_LEN at declaration time, blocking valid use cases like large public-only events. The size constraint only applies to private emission (due to encryption overhead), so the check now lives in `encode_private_event_message` instead. Adds a test contract and e2e test proving large events work when emitted publicly. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../aztec-nr/aztec/src/macros/events.nr | 20 ++------ .../aztec-nr/aztec/src/messages/logs/event.nr | 5 ++ .../contracts/invalid_event/expected_error | 2 +- .../contracts/invalid_event/src/main.nr | 9 ++-- noir-projects/noir-contracts/Nargo.toml | 1 + .../large_public_event_contract/Nargo.toml | 8 +++ .../large_public_event_contract/src/main.nr | 21 ++++++++ .../src/e2e_large_public_event.test.ts | 50 +++++++++++++++++++ 8 files changed, 94 insertions(+), 22 deletions(-) create mode 100644 noir-projects/noir-contracts/contracts/test/large_public_event_contract/Nargo.toml create mode 100644 noir-projects/noir-contracts/contracts/test/large_public_event_contract/src/main.nr create mode 100644 yarn-project/end-to-end/src/e2e_large_public_event.test.ts 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..0c71285c024e 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,14 @@ 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; - // 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 {}); } } 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())); + }); +}); From bc58ec64e26a5a340fb8227bc11a061a85d585ae Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 31 Mar 2026 10:17:58 +0000 Subject: [PATCH 2/2] fix --- .../contracts/invalid_event/src/main.nr | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 0c71285c024e..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 @@ -5,12 +5,15 @@ mod invalid_event; #[aztec] contract InvalidEventContract { use crate::invalid_event::InvalidEvent; - use aztec::macros::functions::external; + use aztec::{macros::functions::external, messages::message_delivery::MessageDelivery}; // 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() { - self.emit(InvalidEvent {}); + self.emit(InvalidEvent {}).deliver_to( + self.msg_sender(), + MessageDelivery.ONCHAIN_UNCONSTRAINED, + ); } }