diff --git a/noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr b/noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr index 32999071ee0b..10e8ce4da9bc 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr @@ -99,7 +99,9 @@ pub(crate) comptime fn transform_private(f: FunctionDefinition) -> Quoted { }; // All private functions perform message discovery, since they may need to access notes. This is slightly - // inefficient and could be improved by only doing it once we actually attempt to read any. + // inefficient and could be improved by only doing it once we actually attempt to read any. Note that the message + // discovery call syncs private events as well. We do not sync those here if there are no notes because we don't + // have an API that would access events from private functions. let message_discovery_call = if NOTES.len() > 0 { create_message_discovery_call() } else { @@ -303,13 +305,9 @@ pub(crate) comptime fn transform_utility(f: FunctionDefinition) -> Quoted { }; // All utility functions perform message discovery, since they may need to access private notes that would be - // found during this process. This is slightly inefficient and could be improved by only doing it once we actually - // attempt to read any. - let message_discovery_call = if NOTES.len() > 0 { - create_message_discovery_call() - } else { - quote {} - }; + // found during this process or they may be used to sync private events from TypeScript + // (`sync_private_state` function gets invoked by PXE::getPrivateEvents function). + let message_discovery_call = create_message_discovery_call(); // Inject context creation, storage initialization, and message discovery call at the beginning of the function // body. diff --git a/noir-projects/noir-contracts/Nargo.toml b/noir-projects/noir-contracts/Nargo.toml index a5e148fd5e5e..9e6c33825e5a 100644 --- a/noir-projects/noir-contracts/Nargo.toml +++ b/noir-projects/noir-contracts/Nargo.toml @@ -38,6 +38,7 @@ members = [ "contracts/test/benchmarking_contract", "contracts/test/child_contract", "contracts/test/counter_contract", + "contracts/test/event_only_contract", "contracts/test/import_test_contract", "contracts/test/invalid_account_contract", "contracts/test/no_constructor_contract", diff --git a/noir-projects/noir-contracts/contracts/test/event_only_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/test/event_only_contract/Nargo.toml new file mode 100644 index 000000000000..77ac50bef3ae --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test/event_only_contract/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "event_only_contract" +authors = [""] +compiler_version = ">=0.25.0" +type = "contract" + +[dependencies] +aztec = { path = "../../../../aztec-nr/aztec" } diff --git a/noir-projects/noir-contracts/contracts/test/event_only_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/event_only_contract/src/main.nr new file mode 100644 index 000000000000..81ab4def8c50 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test/event_only_contract/src/main.nr @@ -0,0 +1,30 @@ +use aztec::macros::aztec; + +/// This contract is used to test that the private event synchronization is working correctly even when the contract +/// doesn't work with notes. +#[aztec] +contract EventOnly { + use aztec::{ + event::event_interface::EventInterface, + macros::{events::event, functions::private}, + messages::logs::event::encode_and_encrypt_event_unconstrained, + protocol_types::traits::Serialize, + }; + use std::meta::derive; + + #[derive(Serialize)] + #[event] + struct TestEvent { + value: Field, + } + + #[private] + fn emit_event_for_msg_sender(value: Field) { + let sender = context.msg_sender(); + TestEvent { value }.emit(encode_and_encrypt_event_unconstrained( + &mut context, + sender, + sender, + )); + } +} diff --git a/yarn-project/end-to-end/src/e2e_event_only.test.ts b/yarn-project/end-to-end/src/e2e_event_only.test.ts new file mode 100644 index 000000000000..d4f2168348b1 --- /dev/null +++ b/yarn-project/end-to-end/src/e2e_event_only.test.ts @@ -0,0 +1,41 @@ +import { type AccountWalletWithSecretKey, Fr } from '@aztec/aztec.js'; +import { EventOnlyContract, type TestEvent } from '@aztec/noir-test-contracts.js/EventOnly'; + +import { jest } from '@jest/globals'; + +import { ensureAccountsPubliclyDeployed, setup } from './fixtures/utils.js'; + +const TIMEOUT = 120_000; + +/// Tests that a private event can be obtained for a contract that does not work with notes. +describe('EventOnly', () => { + let eventOnlyContract: EventOnlyContract; + jest.setTimeout(TIMEOUT); + + let wallets: AccountWalletWithSecretKey[]; + let teardown: () => Promise; + + beforeAll(async () => { + ({ teardown, wallets } = await setup(2)); + await ensureAccountsPubliclyDeployed(wallets[0], wallets.slice(0, 2)); + eventOnlyContract = await EventOnlyContract.deploy(wallets[0]).send().deployed(); + }); + + afterAll(() => teardown()); + + it('emits and retrieves a private event for a contract with no notes', async () => { + const value = Fr.random(); + const tx = await eventOnlyContract.methods.emit_event_for_msg_sender(value).send().wait(); + + const events = await wallets[0].getPrivateEvents( + eventOnlyContract.address, + EventOnlyContract.events.TestEvent, + tx.blockNumber!, + 1, + [wallets[0].getAddress()], + ); + + expect(events.length).toBe(1); + expect(events[0].value).toBe(value.toBigInt()); + }); +});