From 60fcc5f145c5f87bd41055911fbb850dde92214b Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 2 Jul 2026 22:58:07 -0300 Subject: [PATCH 1/2] test(e2e): consolidate automine contracts and effects suites Round-2 consolidation of the automine contracts/effects/accounts tests. - Merge effects/{event_only,custom_message.parallel,large_public_event} into effects/events.test.ts (one node, per-contract describes). - Merge contracts/nested/{manual_private_call,manual_public.parallel, manual_private_enqueue.parallel} into contracts/nested/manual_calls.test.ts (single beforeAll, fresh child per test only where storage is asserted). - Add real assertions to the previously zero-assertion nested-call and importer tests (child state / return values / duplicate-nullifier effects). - Convert contracts/static_calls.test.ts to an it.each table, collapse accounts/scope_isolation.test.ts via describe.each, and de-parallel contracts/{option_params,nested_utility_calls} into plain files with tables. - Extract a file-local expectInitialized helper in contracts/state_vars.test.ts. - Drop the no-op proverTestVerificationDelayMs option in mempool_limit.test.ts. --- .../automine/accounts/scope_isolation.test.ts | 70 ++-- .../nested/importer.parallel.test.ts | 23 +- .../contracts/nested/manual_calls.test.ts | 131 ++++++ .../nested/manual_private_call.test.ts | 25 -- .../manual_private_enqueue.parallel.test.ts | 67 --- .../nested/manual_public.parallel.test.ts | 63 --- ...l.test.ts => nested_utility_calls.test.ts} | 185 ++++----- .../contracts/option_params.parallel.test.ts | 99 ----- .../automine/contracts/option_params.test.ts | 66 +++ .../src/automine/contracts/state_vars.test.ts | 128 +----- .../automine/contracts/static_calls.test.ts | 385 ++++++++++-------- .../effects/custom_message.parallel.test.ts | 93 ----- .../src/automine/effects/event_only.test.ts | 52 --- .../src/automine/effects/events.test.ts | 149 +++++++ .../effects/large_public_event.test.ts | 54 --- .../src/automine/mempool_limit.test.ts | 7 +- 16 files changed, 693 insertions(+), 904 deletions(-) create mode 100644 yarn-project/end-to-end/src/automine/contracts/nested/manual_calls.test.ts delete mode 100644 yarn-project/end-to-end/src/automine/contracts/nested/manual_private_call.test.ts delete mode 100644 yarn-project/end-to-end/src/automine/contracts/nested/manual_private_enqueue.parallel.test.ts delete mode 100644 yarn-project/end-to-end/src/automine/contracts/nested/manual_public.parallel.test.ts rename yarn-project/end-to-end/src/automine/contracts/{nested_utility_calls.parallel.test.ts => nested_utility_calls.test.ts} (53%) delete mode 100644 yarn-project/end-to-end/src/automine/contracts/option_params.parallel.test.ts create mode 100644 yarn-project/end-to-end/src/automine/contracts/option_params.test.ts delete mode 100644 yarn-project/end-to-end/src/automine/effects/custom_message.parallel.test.ts delete mode 100644 yarn-project/end-to-end/src/automine/effects/event_only.test.ts create mode 100644 yarn-project/end-to-end/src/automine/effects/events.test.ts delete mode 100644 yarn-project/end-to-end/src/automine/effects/large_public_event.test.ts diff --git a/yarn-project/end-to-end/src/automine/accounts/scope_isolation.test.ts b/yarn-project/end-to-end/src/automine/accounts/scope_isolation.test.ts index 67bc78be7aa5..6d28950e9c0b 100644 --- a/yarn-project/end-to-end/src/automine/accounts/scope_isolation.test.ts +++ b/yarn-project/end-to-end/src/automine/accounts/scope_isolation.test.ts @@ -1,4 +1,5 @@ import type { AztecAddress } from '@aztec/aztec.js/addresses'; +import type { ContractFunctionInteraction } from '@aztec/aztec.js/contracts'; import type { Wallet } from '@aztec/aztec.js/wallet'; import { ScopeTestContract } from '@aztec/noir-test-contracts.js/ScopeTest'; @@ -6,7 +7,8 @@ import { AutomineTestContext } from '../automine_test_context.js'; // Verifies that PXE note access and key-derivation are scoped per account: a different account // cannot read another's notes or derive their nullifier hiding key. Uses a single node with -// AutomineSequencer and three accounts (alice, bob, charlie). +// AutomineSequencer and three accounts (alice, bob, charlie). The same isolation checks run for both +// the external-private and external-utility function variants, which differ only by a `_utility` suffix. describe('automine/accounts/scope_isolation', () => { let wallet: Wallet; let accounts: AztecAddress[]; @@ -33,62 +35,44 @@ describe('automine/accounts/scope_isolation', () => { afterAll(() => teardown()); - // Tests for external private functions: read_note (scoped to owner) and get_nhk (scoped to key holder). - describe('external private', () => { - // Alice simulates read_note from her own scope; asserts the correct stored value is returned. + const variants: { + context: string; + readNote: (owner: AztecAddress) => ContractFunctionInteraction; + getNhk: (owner: AztecAddress) => ContractFunctionInteraction; + }[] = [ + { + context: 'external private', + readNote: owner => contract.methods.read_note(owner), + getNhk: owner => contract.methods.get_nhk(owner), + }, + { + context: 'external utility', + readNote: owner => contract.methods.read_note_utility(owner), + getNhk: owner => contract.methods.get_nhk_utility(owner), + }, + ]; + + describe.each(variants)('$context', ({ readNote, getNhk }) => { + // Alice reads her own note from her own scope; asserts the correct stored value is returned. it('owner can read own notes', async () => { - const { result: value } = await contract.methods.read_note(alice).simulate({ from: alice }); + const { result: value } = await readNote(alice).simulate({ from: alice }); expect(value).toEqual(ALICE_NOTE_VALUE); }); // Bob attempts to read Alice's note from his scope; asserts simulation throws 'Failed to get a note'. it('cannot read notes belonging to a different account', async () => { - await expect(contract.methods.read_note(alice).simulate({ from: bob })).rejects.toThrow('Failed to get a note'); + await expect(readNote(alice).simulate({ from: bob })).rejects.toThrow('Failed to get a note'); }); // Bob attempts to derive Charlie's nullifier hiding key; asserts 'Key validation request denied'. it('cannot access nullifier hiding key of a different account', async () => { - await expect(contract.methods.get_nhk(charlie).simulate({ from: bob })).rejects.toThrow( - 'Key validation request denied', - ); + await expect(getNhk(charlie).simulate({ from: bob })).rejects.toThrow('Key validation request denied'); }); // Both Alice and Bob read their own notes on the shared wallet; asserts each sees only their value. it('each account can access their isolated state on a shared wallet', async () => { - const { result: aliceValue } = await contract.methods.read_note(alice).simulate({ from: alice }); - const { result: bobValue } = await contract.methods.read_note(bob).simulate({ from: bob }); - - expect(aliceValue).toEqual(ALICE_NOTE_VALUE); - expect(bobValue).toEqual(BOB_NOTE_VALUE); - }); - }); - - // Same isolation checks repeated for external utility functions (read_note_utility, get_nhk_utility). - describe('external utility', () => { - // Alice simulates read_note_utility from her own scope; asserts the correct stored value is returned. - it('owner can read own notes', async () => { - const { result: value } = await contract.methods.read_note_utility(alice).simulate({ from: alice }); - expect(value).toEqual(ALICE_NOTE_VALUE); - }); - - // Bob attempts to read Alice's note via utility scope; asserts simulation throws 'Failed to get a note'. - it('cannot read notes belonging to a different account', async () => { - await expect(contract.methods.read_note_utility(alice).simulate({ from: bob })).rejects.toThrow( - 'Failed to get a note', - ); - }); - - // Bob attempts to derive Charlie's NHK via utility scope; asserts 'Key validation request denied'. - it('cannot access nullifier hiding key of a different account', async () => { - await expect(contract.methods.get_nhk_utility(charlie).simulate({ from: bob })).rejects.toThrow( - 'Key validation request denied', - ); - }); - - // Both Alice and Bob read via utility on the shared wallet; asserts each sees only their value. - it('each account can access their isolated state on a shared wallet', async () => { - const { result: aliceValue } = await contract.methods.read_note_utility(alice).simulate({ from: alice }); - const { result: bobValue } = await contract.methods.read_note_utility(bob).simulate({ from: bob }); + const { result: aliceValue } = await readNote(alice).simulate({ from: alice }); + const { result: bobValue } = await readNote(bob).simulate({ from: bob }); expect(aliceValue).toEqual(ALICE_NOTE_VALUE); expect(bobValue).toEqual(BOB_NOTE_VALUE); diff --git a/yarn-project/end-to-end/src/automine/contracts/nested/importer.parallel.test.ts b/yarn-project/end-to-end/src/automine/contracts/nested/importer.parallel.test.ts index eabb2b861f4b..526c8d497aa1 100644 --- a/yarn-project/end-to-end/src/automine/contracts/nested/importer.parallel.test.ts +++ b/yarn-project/end-to-end/src/automine/contracts/nested/importer.parallel.test.ts @@ -1,3 +1,4 @@ +import { Fr } from '@aztec/aztec.js/fields'; import { ImportTestContract } from '@aztec/noir-test-contracts.js/ImportTest'; import { TestContract } from '@aztec/noir-test-contracts.js/Test'; @@ -25,21 +26,37 @@ describe('automine/contracts/nested/importer', () => { await t.teardown(); }); - // Calls importerContract.call_no_args(testContract.address) and awaits inclusion. + // call_no_args routes a private call into Test.get_this_address; asserts the imported call returns the + // Test contract's own address, and that the tx is included on-chain. it('calls a method no arguments', async () => { logger.info(`Calling noargs on importer contract`); + const { result } = await importerContract.methods + .call_no_args(testContract.address) + .simulate({ from: defaultAccountAddress }); + expect(result).toEqual(testContract.address); + await importerContract.methods.call_no_args(testContract.address).send({ from: defaultAccountAddress }); }); - // Calls importerContract.call_public_fn(testContract.address) and awaits inclusion. + // call_public_fn enqueues Test.emit_nullifier_public(1); asserts the nullifier landed by checking that + // re-emitting the same nullifier on the Test contract is rejected as a duplicate. it('calls a public function', async () => { logger.info(`Calling public_fn on importer contract`); await importerContract.methods.call_public_fn(testContract.address).send({ from: defaultAccountAddress }); + + await expect( + testContract.methods.emit_nullifier_public(new Fr(1)).simulate({ from: defaultAccountAddress }), + ).rejects.toThrow(/duplicate nullifier/); }); - // Calls importerContract.pub_call_public_fn(testContract.address) and awaits inclusion. + // pub_call_public_fn calls Test.emit_nullifier_public(1) from a public function; asserts the nullifier + // landed by checking that re-emitting the same nullifier on the Test contract is rejected as a duplicate. it('calls a public function from a public function', async () => { logger.info(`Calling pub_public_fn on importer contract`); await importerContract.methods.pub_call_public_fn(testContract.address).send({ from: defaultAccountAddress }); + + await expect( + testContract.methods.emit_nullifier_public(new Fr(1)).simulate({ from: defaultAccountAddress }), + ).rejects.toThrow(/duplicate nullifier/); }); }); diff --git a/yarn-project/end-to-end/src/automine/contracts/nested/manual_calls.test.ts b/yarn-project/end-to-end/src/automine/contracts/nested/manual_calls.test.ts new file mode 100644 index 000000000000..c61610b3c75e --- /dev/null +++ b/yarn-project/end-to-end/src/automine/contracts/nested/manual_calls.test.ts @@ -0,0 +1,131 @@ +import type { AztecAddress } from '@aztec/aztec.js/addresses'; +import { BatchCall } from '@aztec/aztec.js/contracts'; +import { Fr } from '@aztec/aztec.js/fields'; +import { toBigIntBE } from '@aztec/foundation/bigint-buffer'; +import { serializeToBuffer } from '@aztec/foundation/serialize'; +import { ChildContract } from '@aztec/noir-test-contracts.js/Child'; + +import { AutomineTestContext } from '../../automine_test_context.js'; + +// Nested contract calls between Parent and Child. A single account runs a shared Parent/Child deployed in +// beforeAll via applyManualParentChild(); the public and enqueued-call suites redeploy a fresh Child per +// test because each asserts an absolute child storage value that only holds if the child starts at zero. +describe('automine/contracts/nested/manual_calls', () => { + const t = new AutomineTestContext(); + let { wallet, parentContract, childContract, defaultAccountAddress, aztecNode } = t; + + const getChildStoredValue = (child: { address: AztecAddress }) => + aztecNode.getPublicStorageAt('latest', child.address, new Fr(1)); + + beforeAll(async () => { + await t.setup(); + await t.applyManualParentChild(); + ({ wallet, parentContract, childContract, defaultAccountAddress, aztecNode } = t); + }); + + afterAll(async () => { + await t.teardown(); + }); + + describe('private calls', () => { + // Routes a private call through the parent into child.value(0). Asserts the nested call returns the + // same preimage as calling child.value(0) directly, and that the tx is included on-chain. + it('performs a nested private call returning the child value', async () => { + const selector = await childContract.methods.value.selector(); + + const { result } = await parentContract.methods + .entry_point(childContract.address, selector) + .simulate({ from: defaultAccountAddress }); + const { result: direct } = await childContract.methods.value(0n).simulate({ from: defaultAccountAddress }); + expect(result).toEqual(direct); + + await parentContract.methods.entry_point(childContract.address, selector).send({ from: defaultAccountAddress }); + }); + }); + + describe('public calls', () => { + let child: ChildContract; + + beforeEach(async () => { + ({ contract: child } = await ChildContract.deploy(wallet).send({ from: defaultAccountAddress })); + }); + + // Routes a public call through the parent into child.pub_inc_value(42); asserts the child's storage + // slot holds 42 afterwards, confirming the nested public call executed and wrote state. + it('performs public nested calls', async () => { + await parentContract.methods + .pub_entry_point(child.address, await child.methods.pub_inc_value.selector(), 42n) + .send({ from: defaultAccountAddress }); + expect(await getChildStoredValue(child)).toEqual(new Fr(42n)); + }); + + // Regression for https://github.com/AztecProtocol/aztec-packages/issues/640 + // Calls pub_entry_point_twice so pub_inc_value runs twice in one tx; asserts storage is 84 (not 42). + it('reads fresh value after write within the same tx', async () => { + await parentContract.methods + .pub_entry_point_twice(child.address, await child.methods.pub_inc_value.selector(), 42n) + .send({ from: defaultAccountAddress }); + expect(await getChildStoredValue(child)).toEqual(new Fr(84n)); + }); + + // Regression for https://github.com/AztecProtocol/aztec-packages/issues/1645 + // Executes a public call first and then a private call (which enqueues another public call) + // through the account contract, if the account entrypoint behaves properly, it will honor + // this order and not run the private call first which results in the public calls being inverted. + // Batches pub_set_value(20) and parent.enqueue(pub_set_value(40)); reads public logs to assert [20, 40]. + it('executes public calls in expected order', async () => { + const pubSetValueSelector = await child.methods.pub_set_value.selector(); + const actions = [ + child.methods.pub_set_value(20n), + parentContract.methods.enqueue_call_to_child(child.address, pubSetValueSelector, 40n), + ]; + + const { receipt: tx } = await new BatchCall(wallet, actions).send({ from: defaultAccountAddress }); + const block = (await aztecNode.getBlock({ number: tx.blockNumber! }, { includeTransactions: true }))!; + const allPublicLogs = block.body.txEffects.flatMap(effect => effect.publicLogs); + const processedLogs = allPublicLogs.map(log => toBigIntBE(serializeToBuffer(log.getEmittedFields()))); + expect(processedLogs).toEqual([20n, 40n]); + expect(await getChildStoredValue(child)).toEqual(new Fr(40n)); + }); + }); + + describe('enqueued public calls', () => { + let child: ChildContract; + + beforeEach(async () => { + ({ contract: child } = await ChildContract.deploy(wallet).send({ from: defaultAccountAddress })); + }); + + // Enqueues one pub_inc_value(42) call via the parent and asserts child storage equals 42. + it('enqueues a single public call', async () => { + await parentContract.methods + .enqueue_call_to_child(child.address, await child.methods.pub_inc_value.selector(), 42n) + .send({ from: defaultAccountAddress }); + expect(await getChildStoredValue(child)).toEqual(new Fr(42n)); + }); + + // Enqueues pub_inc_value(42) then pub_inc_value(43) via enqueue_call_to_child_twice; asserts 85. + it('enqueues multiple public calls', async () => { + await parentContract.methods + .enqueue_call_to_child_twice(child.address, await child.methods.pub_inc_value.selector(), 42n) + .send({ from: defaultAccountAddress }); + expect(await getChildStoredValue(child)).toEqual(new Fr(85n)); + }); + + // Calls enqueue_call_to_pub_entry_point which enqueues pub_entry_point → pub_inc_value; asserts 42. + it('enqueues a public call with nested public calls', async () => { + await parentContract.methods + .enqueue_call_to_pub_entry_point(child.address, await child.methods.pub_inc_value.selector(), 42n) + .send({ from: defaultAccountAddress }); + expect(await getChildStoredValue(child)).toEqual(new Fr(42n)); + }); + + // Calls enqueue_calls_to_pub_entry_point which enqueues pub_entry_point twice; asserts 85. + it('enqueues multiple public calls with nested public calls', async () => { + await parentContract.methods + .enqueue_calls_to_pub_entry_point(child.address, await child.methods.pub_inc_value.selector(), 42n) + .send({ from: defaultAccountAddress }); + expect(await getChildStoredValue(child)).toEqual(new Fr(85n)); + }); + }); +}); diff --git a/yarn-project/end-to-end/src/automine/contracts/nested/manual_private_call.test.ts b/yarn-project/end-to-end/src/automine/contracts/nested/manual_private_call.test.ts deleted file mode 100644 index 618d3819d261..000000000000 --- a/yarn-project/end-to-end/src/automine/contracts/nested/manual_private_call.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { AutomineTestContext } from '../../automine_test_context.js'; - -// Tests a nested private call from ParentContract into ChildContract's value() function. -// Runs on a single account. applyManualParentChild() deploys Parent and Child contracts in beforeAll. -describe('automine/contracts/nested/manual_private_call', () => { - const t = new AutomineTestContext(); - let { parentContract, childContract, defaultAccountAddress } = t; - - beforeAll(async () => { - await t.setup(); - await t.applyManualParentChild(); - ({ parentContract, childContract, defaultAccountAddress } = t); - }); - - afterAll(async () => { - await t.teardown(); - }); - - // Calls parent.entry_point(child.address, child.value.selector()) and awaits inclusion. - it('performs nested calls', async () => { - await parentContract.methods - .entry_point(childContract.address, await childContract.methods.value.selector()) - .send({ from: defaultAccountAddress }); - }); -}); diff --git a/yarn-project/end-to-end/src/automine/contracts/nested/manual_private_enqueue.parallel.test.ts b/yarn-project/end-to-end/src/automine/contracts/nested/manual_private_enqueue.parallel.test.ts deleted file mode 100644 index c243fc5ec51b..000000000000 --- a/yarn-project/end-to-end/src/automine/contracts/nested/manual_private_enqueue.parallel.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { AztecAddress } from '@aztec/aztec.js/addresses'; -import { Fr } from '@aztec/aztec.js/fields'; -import { ChildContract } from '@aztec/noir-test-contracts.js/Child'; -import { ParentContract } from '@aztec/noir-test-contracts.js/Parent'; - -import { AutomineTestContext } from '../../automine_test_context.js'; - -// Tests parent contracts enqueuing public calls on a child contract via various call patterns. -// Runs on a single account. Parent and Child are deployed fresh per test in beforeEach. -describe('automine/contracts/nested/manual_private_enqueue', () => { - const t = new AutomineTestContext(); - let { wallet, parentContract, childContract, defaultAccountAddress, aztecNode } = t; - - const getChildStoredValue = (child: { address: AztecAddress }) => - aztecNode.getPublicStorageAt('latest', child.address, new Fr(1)); - - beforeAll(async () => { - // We don't deploy contracts in beforeAll because every test requires a fresh setup - await t.setup(); - ({ wallet, defaultAccountAddress, aztecNode } = t); - }); - - beforeEach(async () => { - ({ contract: parentContract } = await ParentContract.deploy(wallet).send({ from: defaultAccountAddress })); - ({ contract: childContract } = await ChildContract.deploy(wallet).send({ from: defaultAccountAddress })); - }); - - afterAll(async () => { - await t.teardown(); - }); - - // Enqueues one pub_inc_value(42) call via the parent and asserts child storage equals 42. - it('enqueues a single public call', async () => { - await parentContract.methods - .enqueue_call_to_child(childContract.address, await childContract.methods.pub_inc_value.selector(), 42n) - .send({ from: defaultAccountAddress }); - expect(await getChildStoredValue(childContract)).toEqual(new Fr(42n)); - }); - - // Enqueues pub_inc_value(42) twice via enqueue_call_to_child_twice and asserts child storage is 85. - it('enqueues multiple public calls', async () => { - await parentContract.methods - .enqueue_call_to_child_twice(childContract.address, await childContract.methods.pub_inc_value.selector(), 42n) - .send({ from: defaultAccountAddress }); - expect(await getChildStoredValue(childContract)).toEqual(new Fr(85n)); - }); - - // Calls enqueue_call_to_pub_entry_point which enqueues pub_entry_point → pub_inc_value; asserts 42. - it('enqueues a public call with nested public calls', async () => { - await parentContract.methods - .enqueue_call_to_pub_entry_point(childContract.address, await childContract.methods.pub_inc_value.selector(), 42n) - .send({ from: defaultAccountAddress }); - expect(await getChildStoredValue(childContract)).toEqual(new Fr(42n)); - }); - - // Calls enqueue_calls_to_pub_entry_point which enqueues pub_entry_point twice; asserts 85. - it('enqueues multiple public calls with nested public calls', async () => { - await parentContract.methods - .enqueue_calls_to_pub_entry_point( - childContract.address, - await childContract.methods.pub_inc_value.selector(), - 42n, - ) - .send({ from: defaultAccountAddress }); - expect(await getChildStoredValue(childContract)).toEqual(new Fr(85n)); - }); -}); diff --git a/yarn-project/end-to-end/src/automine/contracts/nested/manual_public.parallel.test.ts b/yarn-project/end-to-end/src/automine/contracts/nested/manual_public.parallel.test.ts deleted file mode 100644 index bb163922df50..000000000000 --- a/yarn-project/end-to-end/src/automine/contracts/nested/manual_public.parallel.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { AztecAddress } from '@aztec/aztec.js/addresses'; -import { BatchCall } from '@aztec/aztec.js/contracts'; -import { Fr } from '@aztec/aztec.js/fields'; -import { toBigIntBE } from '@aztec/foundation/bigint-buffer'; -import { serializeToBuffer } from '@aztec/foundation/serialize'; - -import { AutomineTestContext } from '../../automine_test_context.js'; - -// Tests public-to-public nested calls and ordering guarantees (public before private enqueue). -// Runs on a single account. applyManualParentChild() deploys Parent and Child contracts in beforeAll. -describe('automine/contracts/nested/manual_public', () => { - const t = new AutomineTestContext(); - let { wallet, parentContract, childContract, defaultAccountAddress, aztecNode } = t; - - const getChildStoredValue = (child: { address: AztecAddress }) => - aztecNode.getPublicStorageAt('latest', child.address, new Fr(1)); - - beforeAll(async () => { - await t.setup(); - await t.applyManualParentChild(); - ({ wallet, parentContract, childContract, defaultAccountAddress, aztecNode } = t); - }); - - afterAll(async () => { - await t.teardown(); - }); - - // Calls parent.pub_entry_point(child, pub_get_value, 42) and awaits inclusion. - it('performs public nested calls', async () => { - await parentContract.methods - .pub_entry_point(childContract.address, await childContract.methods.pub_get_value.selector(), 42n) - .send({ from: defaultAccountAddress }); - }); - - // Regression for https://github.com/AztecProtocol/aztec-packages/issues/640 - // Calls pub_entry_point_twice so pub_inc_value runs twice in one tx; asserts storage is 84 (not 42). - it('reads fresh value after write within the same tx', async () => { - await parentContract.methods - .pub_entry_point_twice(childContract.address, await childContract.methods.pub_inc_value.selector(), 42n) - .send({ from: defaultAccountAddress }); - expect(await getChildStoredValue(childContract)).toEqual(new Fr(84n)); - }); - - // Regression for https://github.com/AztecProtocol/aztec-packages/issues/1645 - // Executes a public call first and then a private call (which enqueues another public call) - // through the account contract, if the account entrypoint behaves properly, it will honor - // this order and not run the private call first which results in the public calls being inverted. - // Batches pub_set_value(20) and parent.enqueue(pub_set_value(40)); reads public logs to assert [20, 40]. - it('executes public calls in expected order', async () => { - const pubSetValueSelector = await childContract.methods.pub_set_value.selector(); - const actions = [ - childContract.methods.pub_set_value(20n), - parentContract.methods.enqueue_call_to_child(childContract.address, pubSetValueSelector, 40n), - ]; - - const { receipt: tx } = await new BatchCall(wallet, actions).send({ from: defaultAccountAddress }); - const block = (await aztecNode.getBlock({ number: tx.blockNumber! }, { includeTransactions: true }))!; - const allPublicLogs = block.body.txEffects.flatMap(tx => tx.publicLogs); - const processedLogs = allPublicLogs.map(log => toBigIntBE(serializeToBuffer(log.getEmittedFields()))); - expect(processedLogs).toEqual([20n, 40n]); - expect(await getChildStoredValue(childContract)).toEqual(new Fr(40n)); - }); -}); diff --git a/yarn-project/end-to-end/src/automine/contracts/nested_utility_calls.parallel.test.ts b/yarn-project/end-to-end/src/automine/contracts/nested_utility_calls.test.ts similarity index 53% rename from yarn-project/end-to-end/src/automine/contracts/nested_utility_calls.parallel.test.ts rename to yarn-project/end-to-end/src/automine/contracts/nested_utility_calls.test.ts index 6dd1ccbc2c20..6c164725bc4b 100644 --- a/yarn-project/end-to-end/src/automine/contracts/nested_utility_calls.parallel.test.ts +++ b/yarn-project/end-to-end/src/automine/contracts/nested_utility_calls.test.ts @@ -1,5 +1,6 @@ import { NO_FROM } from '@aztec/aztec.js/account'; import type { AztecAddress } from '@aztec/aztec.js/addresses'; +import type { ContractFunctionInteraction } from '@aztec/aztec.js/contracts'; import type { Wallet } from '@aztec/aztec.js/wallet'; import type { Fr } from '@aztec/foundation/curves/bn254'; import { NestedUtilityContract } from '@aztec/noir-test-contracts.js/NestedUtility'; @@ -48,27 +49,27 @@ describe('automine/contracts/nested_utility_calls', () => { expect(result).toEqual(2n ** 10n); }); - // Simulates pow_private(2, 10) which calls pow_utility from a private function context; expects - // 1024. + // Simulates pow_private(2, 10) which calls pow_utility from a private function context; expects 1024. it('pow_private 2 to the 10 returns 1024 - private function calling utility', async () => { const { result } = await contractA.methods.pow_private(2n, 10).simulate({ from: defaultAccountAddress }); expect(result).toEqual(2n ** 10n); }); - // Simulates contractA.delegate_pow_utility(contractB, 2, 3) with no hook registered; expects - // 'Cross-contract utility call denied'. - it('denies cross-contract utility call from utility context by default', async () => { - await expect( - contractA.methods.delegate_pow_utility(contractB.address, 2n, 3n).simulate({ from: defaultAccountAddress }), - ).rejects.toThrow('Cross-contract utility call denied'); - }); - - // Simulates contractA.delegate_pow_private(contractB, 2, 3) with no hook; expects 'Cross-contract - // utility call denied'. - it('denies cross-contract utility call from private function by default', async () => { - await expect( - contractA.methods.delegate_pow_private(contractB.address, 2n, 3n).simulate({ from: defaultAccountAddress }), - ).rejects.toThrow('Cross-contract utility call denied'); + // With no authorizeUtilityCall hook registered, a cross-contract utility call is denied by default + // whether it originates from a utility or a private function. + it.each([ + { + context: 'utility context', + getInteraction: () => contractA.methods.delegate_pow_utility(contractB.address, 2n, 3n), + }, + { + context: 'private function', + getInteraction: () => contractA.methods.delegate_pow_private(contractB.address, 2n, 3n), + }, + ])('denies cross-contract utility call from $context by default', async ({ getInteraction }) => { + await expect(getInteraction().simulate({ from: defaultAccountAddress })).rejects.toThrow( + 'Cross-contract utility call denied', + ); }); it('top-level utility has no caller, so its msg_sender is none', async () => { @@ -128,39 +129,58 @@ describe('authorizeUtilityCall hook', () => { lastRequest = undefined; }); - // Calls delegate_pow_utility with hookAllows=false; expects denial and checks lastRequest fields. - it('denies cross-contract utility call from utility context when hook returns false', async () => { - await expect( - contractA.methods.delegate_pow_utility(contractB.address, 2n, 3n).simulate({ from: defaultAccountAddress }), - ).rejects.toThrow('Cross-contract utility call denied'); - expect(lastRequest).toMatchObject({ - caller: contractA.address, - callerClassId: contractClassId, - target: contractB.address, - targetClassId: contractClassId, - functionSelector: await contractB.methods.pow_utility.selector(), - functionName: 'pow_utility', - callerContext: 'utility', - }); - }); - - // Sets hookAllows=true, calls delegate_pow_utility, and asserts result=8 and lastRequest fields. - it('allows cross-contract utility call from utility context when hook returns true', async () => { - hookAllows = true; - const { result } = await contractA.methods - .delegate_pow_utility(contractB.address, 2n, 3n) - .simulate({ from: defaultAccountAddress }); - expect(result).toEqual(8n); // 2^3 - expect(lastRequest).toMatchObject({ - caller: contractA.address, - callerClassId: contractClassId, - target: contractB.address, - targetClassId: contractClassId, - functionSelector: await contractB.methods.pow_utility.selector(), - functionName: 'pow_utility', + const hookRows: { + context: string; + callerContext: string; + allow: boolean; + getInteraction: () => ContractFunctionInteraction; + }[] = [ + { + context: 'utility', callerContext: 'utility', - }); - }); + getInteraction: () => contractA.methods.delegate_pow_utility(contractB.address, 2n, 3n), + }, + { + context: 'private function', + callerContext: 'private', + getInteraction: () => contractA.methods.delegate_pow_private(contractB.address, 2n, 3n), + }, + { + context: 'view function', + callerContext: 'private view', + getInteraction: () => contractA.methods.delegate_pow_view(contractB.address, 2n, 3n), + }, + ].flatMap(row => [ + { ...row, allow: false }, + { ...row, allow: true }, + ]); + + // The hook is consulted for every cross-contract utility call; its boolean return governs access, and + // the request it receives carries the caller/target class ids, the target selector, and the caller + // context (utility / private / private view). A denied call throws; an allowed one returns 2^3 = 8. + it.each(hookRows)( + '$context context: hook returning $allow governs the cross-contract utility call', + async ({ getInteraction, callerContext, allow }) => { + hookAllows = allow; + if (allow) { + const { result } = await getInteraction().simulate({ from: defaultAccountAddress }); + expect(result).toEqual(8n); + } else { + await expect(getInteraction().simulate({ from: defaultAccountAddress })).rejects.toThrow( + 'Cross-contract utility call denied', + ); + } + expect(lastRequest).toMatchObject({ + caller: contractA.address, + callerClassId: contractClassId, + target: contractB.address, + targetClassId: contractClassId, + functionSelector: await contractB.methods.pow_utility.selector(), + functionName: 'pow_utility', + callerContext, + }); + }, + ); it('nested utility call sees the calling contract as its msg_sender', async () => { hookAllows = true; @@ -170,75 +190,6 @@ describe('authorizeUtilityCall hook', () => { expect(result).toEqual(contractA.address); }); - // Calls delegate_pow_private with hookAllows=false; expects denial and checks lastRequest - // callerContext is 'private'. - it('denies cross-contract utility call from private function when hook returns false', async () => { - await expect( - contractA.methods.delegate_pow_private(contractB.address, 2n, 3n).simulate({ from: defaultAccountAddress }), - ).rejects.toThrow('Cross-contract utility call denied'); - expect(lastRequest).toMatchObject({ - caller: contractA.address, - callerClassId: contractClassId, - target: contractB.address, - targetClassId: contractClassId, - functionSelector: await contractB.methods.pow_utility.selector(), - functionName: 'pow_utility', - callerContext: 'private', - }); - }); - - // Sets hookAllows=true, calls delegate_pow_private, and asserts result=8 with 'private' context. - it('allows cross-contract utility call from private function when hook returns true', async () => { - hookAllows = true; - const { result } = await contractA.methods - .delegate_pow_private(contractB.address, 2n, 3n) - .simulate({ from: defaultAccountAddress }); - expect(result).toEqual(8n); // 2^3 - expect(lastRequest).toMatchObject({ - caller: contractA.address, - callerClassId: contractClassId, - target: contractB.address, - targetClassId: contractClassId, - functionSelector: await contractB.methods.pow_utility.selector(), - functionName: 'pow_utility', - callerContext: 'private', - }); - }); - - // Calls delegate_pow_view with hookAllows=false; expects denial with 'private view' context. - it('denies cross-contract utility call from view function when hook returns false', async () => { - await expect( - contractA.methods.delegate_pow_view(contractB.address, 2n, 3n).simulate({ from: defaultAccountAddress }), - ).rejects.toThrow('Cross-contract utility call denied'); - expect(lastRequest).toMatchObject({ - caller: contractA.address, - callerClassId: contractClassId, - target: contractB.address, - targetClassId: contractClassId, - functionSelector: await contractB.methods.pow_utility.selector(), - functionName: 'pow_utility', - callerContext: 'private view', - }); - }); - - // Sets hookAllows=true, calls delegate_pow_view, and asserts result=8 with 'private view' context. - it('allows cross-contract utility call from view function when hook returns true', async () => { - hookAllows = true; - const { result } = await contractA.methods - .delegate_pow_view(contractB.address, 2n, 3n) - .simulate({ from: defaultAccountAddress }); - expect(result).toEqual(8n); - expect(lastRequest).toMatchObject({ - caller: contractA.address, - callerClassId: contractClassId, - target: contractB.address, - targetClassId: contractClassId, - functionSelector: await contractB.methods.pow_utility.selector(), - functionName: 'pow_utility', - callerContext: 'private view', - }); - }); - // Stores pow args as notes on contractB, then calls delegate_pow_from_storage from contractA // (cross-contract). Asserts that contractB's notes are synced before the utility call so that // the stored values are discoverable. diff --git a/yarn-project/end-to-end/src/automine/contracts/option_params.parallel.test.ts b/yarn-project/end-to-end/src/automine/contracts/option_params.parallel.test.ts deleted file mode 100644 index b73ce33dcf74..000000000000 --- a/yarn-project/end-to-end/src/automine/contracts/option_params.parallel.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { AztecAddress } from '@aztec/aztec.js/addresses'; -import type { Wallet } from '@aztec/aztec.js/wallet'; -import { MAX_FIELD_VALUE } from '@aztec/constants'; -import { OptionParamContract } from '@aztec/noir-test-contracts.js/OptionParam'; - -import { jest } from '@jest/globals'; - -import { AutomineTestContext } from '../automine_test_context.js'; - -const TIMEOUT = 300_000; - -const U64_MAX = 2n ** 64n - 1n; -const I64_MIN = -(2n ** 63n); - -// Verifies that the Aztec.js ABI layer correctly serialises/deserialises Noir Option parameters -// for public, utility, and private functions. Single node with AutomineSequencer; all calls are -// simulate()-only (no on-chain state changes). -describe('automine/contracts/option_params', () => { - let contract: OptionParamContract; - let wallet: Wallet; - let defaultAccountAddress: AztecAddress; - let teardown: () => Promise; - - const someValue = { - w: MAX_FIELD_VALUE, - x: true, - y: U64_MAX, - z: I64_MIN, - }; - - jest.setTimeout(TIMEOUT); - - beforeAll(async () => { - ({ - teardown, - wallet, - accounts: [defaultAccountAddress], - } = (await AutomineTestContext.setup({ numberOfAccounts: 1 })).context); - - contract = (await OptionParamContract.deploy(wallet).send({ from: defaultAccountAddress })).contract; - }); - - afterAll(() => teardown()); - - // Simulates a public function accepting Option with undefined, null, and a real value, - // asserting each maps to None / None / Some correctly. - it('accepts ergonomic Option params for public functions', async () => { - const { result } = await contract.methods - .return_public_optional_struct(undefined) - .simulate({ from: defaultAccountAddress }); - expect(result).toBeUndefined(); - - const { result: nullResult } = await contract.methods - .return_public_optional_struct(null) - .simulate({ from: defaultAccountAddress }); - expect(nullResult).toBeUndefined(); - - const { result: someResult } = await contract.methods - .return_public_optional_struct(someValue) - .simulate({ from: defaultAccountAddress }); - expect(someResult).toEqual(someValue); - }); - - // Same Option round-trip check for a Noir utility function via simulate(). - it('accepts ergonomic Option params for utility functions', async () => { - const { result: undefinedResult } = await contract.methods - .return_utility_optional_struct(undefined) - .simulate({ from: defaultAccountAddress }); - expect(undefinedResult).toBeUndefined(); - - const { result: nullResult } = await contract.methods - .return_utility_optional_struct(null) - .simulate({ from: defaultAccountAddress }); - expect(nullResult).toBeUndefined(); - - const { result: someResult } = await contract.methods - .return_utility_optional_struct(someValue) - .simulate({ from: defaultAccountAddress }); - expect(someResult).toEqual(someValue); - }); - - // Same Option round-trip check for a Noir private function via simulate(). - it('accepts ergonomic Option params for private functions', async () => { - const { result: undefinedResult } = await contract.methods - .return_private_optional_struct(undefined) - .simulate({ from: defaultAccountAddress }); - expect(undefinedResult).toBeUndefined(); - - const { result: nullResult } = await contract.methods - .return_private_optional_struct(null) - .simulate({ from: defaultAccountAddress }); - expect(nullResult).toBeUndefined(); - - const { result: someResult } = await contract.methods - .return_private_optional_struct(someValue) - .simulate({ from: defaultAccountAddress }); - expect(someResult).toEqual(someValue); - }); -}); diff --git a/yarn-project/end-to-end/src/automine/contracts/option_params.test.ts b/yarn-project/end-to-end/src/automine/contracts/option_params.test.ts new file mode 100644 index 000000000000..26679ce0761d --- /dev/null +++ b/yarn-project/end-to-end/src/automine/contracts/option_params.test.ts @@ -0,0 +1,66 @@ +import type { AztecAddress } from '@aztec/aztec.js/addresses'; +import type { ContractFunctionInteraction } from '@aztec/aztec.js/contracts'; +import type { Wallet } from '@aztec/aztec.js/wallet'; +import { MAX_FIELD_VALUE } from '@aztec/constants'; +import { OptionParamContract } from '@aztec/noir-test-contracts.js/OptionParam'; + +import { jest } from '@jest/globals'; + +import { AutomineTestContext } from '../automine_test_context.js'; + +const TIMEOUT = 300_000; + +const U64_MAX = 2n ** 64n - 1n; +const I64_MIN = -(2n ** 63n); + +// Verifies that the Aztec.js ABI layer correctly serialises/deserialises Noir Option parameters +// for public, utility, and private functions. Single node with AutomineSequencer; all calls are +// simulate()-only (no on-chain state changes). +describe('automine/contracts/option_params', () => { + let contract: OptionParamContract; + let wallet: Wallet; + let defaultAccountAddress: AztecAddress; + let teardown: () => Promise; + + const someValue = { + w: MAX_FIELD_VALUE, + x: true, + y: U64_MAX, + z: I64_MIN, + }; + + type OptionalStructArg = Parameters[0]; + + jest.setTimeout(TIMEOUT); + + beforeAll(async () => { + ({ + teardown, + wallet, + accounts: [defaultAccountAddress], + } = (await AutomineTestContext.setup({ numberOfAccounts: 1 })).context); + + contract = (await OptionParamContract.deploy(wallet).send({ from: defaultAccountAddress })).contract; + }); + + afterAll(() => teardown()); + + const variants: { kind: string; call: (arg: OptionalStructArg) => ContractFunctionInteraction }[] = [ + { kind: 'public', call: arg => contract.methods.return_public_optional_struct(arg) }, + { kind: 'utility', call: arg => contract.methods.return_utility_optional_struct(arg) }, + { kind: 'private', call: arg => contract.methods.return_private_optional_struct(arg) }, + ]; + + // For each function kind, an Option param round-trips: undefined and null both map to None, + // and a real value maps to Some. + it.each(variants)('accepts ergonomic Option params for $kind functions', async ({ call }) => { + const { result: undefinedResult } = await call(undefined).simulate({ from: defaultAccountAddress }); + expect(undefinedResult).toBeUndefined(); + + const { result: nullResult } = await call(null).simulate({ from: defaultAccountAddress }); + expect(nullResult).toBeUndefined(); + + const { result: someResult } = await call(someValue).simulate({ from: defaultAccountAddress }); + expect(someResult).toEqual(someValue); + }); +}); diff --git a/yarn-project/end-to-end/src/automine/contracts/state_vars.test.ts b/yarn-project/end-to-end/src/automine/contracts/state_vars.test.ts index 6695d135d630..c5765a035569 100644 --- a/yarn-project/end-to-end/src/automine/contracts/state_vars.test.ts +++ b/yarn-project/end-to-end/src/automine/contracts/state_vars.test.ts @@ -1,5 +1,5 @@ import { AztecAddress } from '@aztec/aztec.js/addresses'; -import { BatchCall } from '@aztec/aztec.js/contracts'; +import { BatchCall, type ContractFunctionInteraction } from '@aztec/aztec.js/contracts'; import type { AztecNode } from '@aztec/aztec.js/node'; import { DefaultL1ContractsConfig } from '@aztec/ethereum/config'; import { AuthContract } from '@aztec/noir-contracts.js/Auth'; @@ -42,6 +42,12 @@ describe('automine/contracts/state_vars', () => { afterAll(() => teardown()); + // Simulates an `is_*_initialized` view and asserts its boolean result, collapsing the repeated + // initialization-status checks across the PrivateMutable and PrivateImmutable suites. + const expectInitialized = async (isInitialized: ContractFunctionInteraction, expected: boolean) => { + expect((await isInitialized.simulate({ from: defaultAccountAddress })).result).toEqual(expected); + }; + // Tests for PublicImmutable: initialize-once semantics, reading from private/public/utility contexts, // and rejection of double-initialization. describe('PublicImmutable', () => { @@ -139,13 +145,7 @@ describe('automine/contracts/state_vars', () => { // Asserts is_private_mutable_initialized returns false before initialization, then confirms // get_private_mutable throws on an uninitialized slot. it('fail to read uninitialized PrivateMutable', async () => { - expect( - ( - await contract.methods - .is_private_mutable_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }) - ).result, - ).toEqual(false); + await expectInitialized(contract.methods.is_private_mutable_initialized(defaultAccountAddress), false); await expect( contract.methods.get_private_mutable(defaultAccountAddress).simulate({ from: defaultAccountAddress }), ).rejects.toThrow(); @@ -154,13 +154,7 @@ describe('automine/contracts/state_vars', () => { // Sends initialize_private(RANDOMNESS, VALUE), verifies the tx produces 2 nullifiers (one for the // tx and one for the initializer), and asserts is_private_mutable_initialized returns true after. it('initialize PrivateMutable', async () => { - expect( - ( - await contract.methods - .is_private_mutable_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }) - ).result, - ).toEqual(false); + await expectInitialized(contract.methods.is_private_mutable_initialized(defaultAccountAddress), false); // Send the transaction and wait for it to be mined (wait function throws if the tx is not mined) const { receipt: txReceipt } = await contract.methods .initialize_private(RANDOMNESS, VALUE) @@ -170,46 +164,22 @@ describe('automine/contracts/state_vars', () => { // 1 for the tx, another for the initializer expect(txEffects?.data.nullifiers.length).toEqual(2); - expect( - ( - await contract.methods - .is_private_mutable_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }) - ).result, - ).toEqual(true); + await expectInitialized(contract.methods.is_private_mutable_initialized(defaultAccountAddress), true); }); // Attempts to call initialize_private a second time; asserts it throws and the initialized flag // remains true. it('fail to reinitialize', async () => { - expect( - ( - await contract.methods - .is_private_mutable_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }) - ).result, - ).toEqual(true); + await expectInitialized(contract.methods.is_private_mutable_initialized(defaultAccountAddress), true); await expect( contract.methods.initialize_private(RANDOMNESS, VALUE).send({ from: defaultAccountAddress }), ).rejects.toThrow(); - expect( - ( - await contract.methods - .is_private_mutable_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }) - ).result, - ).toEqual(true); + await expectInitialized(contract.methods.is_private_mutable_initialized(defaultAccountAddress), true); }); // Reads the PrivateMutable after initialization; asserts the stored value matches VALUE. it('read initialized PrivateMutable', async () => { - expect( - ( - await contract.methods - .is_private_mutable_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }) - ).result, - ).toEqual(true); + await expectInitialized(contract.methods.is_private_mutable_initialized(defaultAccountAddress), true); const { result: { value }, } = await contract.methods.get_private_mutable(defaultAccountAddress).simulate({ from: defaultAccountAddress }); @@ -219,13 +189,7 @@ describe('automine/contracts/state_vars', () => { // Calls update_private_mutable with the same RANDOMNESS and VALUE; asserts one new note hash and // 2 nullifiers (tx + old note), and the stored value is unchanged. it('replace with same value', async () => { - expect( - ( - await contract.methods - .is_private_mutable_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }) - ).result, - ).toEqual(true); + await expectInitialized(contract.methods.is_private_mutable_initialized(defaultAccountAddress), true); const { result: noteBefore } = await contract.methods .get_private_mutable(defaultAccountAddress) .simulate({ from: defaultAccountAddress }); @@ -249,13 +213,7 @@ describe('automine/contracts/state_vars', () => { // Calls update_private_mutable with different RANDOMNESS and VALUE; asserts one new note hash, // 2 nullifiers, and the stored value matches the new VALUE. it('replace PrivateMutable with other values', async () => { - expect( - ( - await contract.methods - .is_private_mutable_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }) - ).result, - ).toEqual(true); + await expectInitialized(contract.methods.is_private_mutable_initialized(defaultAccountAddress), true); const { receipt: txReceipt } = await contract.methods .update_private_mutable(RANDOMNESS + 2n, VALUE + 1n) .send({ from: defaultAccountAddress }); @@ -275,13 +233,7 @@ describe('automine/contracts/state_vars', () => { // Calls increase_private_value (reads then updates in private); asserts the new value is exactly // the prior value + 1, verifying read-then-write consistency. it('replace PrivateMutable dependent on prior value', async () => { - expect( - ( - await contract.methods - .is_private_mutable_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }) - ).result, - ).toEqual(true); + await expectInitialized(contract.methods.is_private_mutable_initialized(defaultAccountAddress), true); const { result: noteBefore } = await contract.methods .get_private_mutable(defaultAccountAddress) .simulate({ from: defaultAccountAddress }); @@ -307,13 +259,7 @@ describe('automine/contracts/state_vars', () => { describe('PrivateImmutable', () => { // Asserts is_priv_imm_initialized is false before initialization and that view_private_immutable throws. it('fail to read uninitialized PrivateImmutable', async () => { - expect( - ( - await contract.methods - .is_priv_imm_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }) - ).result, - ).toEqual(false); + await expectInitialized(contract.methods.is_priv_imm_initialized(defaultAccountAddress), false); await expect( contract.methods.view_private_immutable(defaultAccountAddress).simulate({ from: defaultAccountAddress }), ).rejects.toThrow(); @@ -322,13 +268,7 @@ describe('automine/contracts/state_vars', () => { // Calls initialize_private_immutable(RANDOMNESS, VALUE); asserts 1 note hash and 2 nullifiers are // emitted, and is_priv_imm_initialized becomes true. it('initialize PrivateImmutable', async () => { - expect( - ( - await contract.methods - .is_priv_imm_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }) - ).result, - ).toEqual(false); + await expectInitialized(contract.methods.is_priv_imm_initialized(defaultAccountAddress), false); const { receipt: txReceipt } = await contract.methods .initialize_private_immutable(RANDOMNESS, VALUE) .send({ from: defaultAccountAddress }); @@ -338,45 +278,21 @@ describe('automine/contracts/state_vars', () => { expect(txEffects?.data.noteHashes.length).toEqual(1); // 1 for the tx, another for the initializer expect(txEffects?.data.nullifiers.length).toEqual(2); - expect( - ( - await contract.methods - .is_priv_imm_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }) - ).result, - ).toEqual(true); + await expectInitialized(contract.methods.is_priv_imm_initialized(defaultAccountAddress), true); }); // Calls initialize_private_immutable a second time; asserts it throws and the flag remains true. it('fail to reinitialize', async () => { - expect( - ( - await contract.methods - .is_priv_imm_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }) - ).result, - ).toEqual(true); + await expectInitialized(contract.methods.is_priv_imm_initialized(defaultAccountAddress), true); await expect( contract.methods.initialize_private_immutable(RANDOMNESS, VALUE).send({ from: defaultAccountAddress }), ).rejects.toThrow(); - expect( - ( - await contract.methods - .is_priv_imm_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }) - ).result, - ).toEqual(true); + await expectInitialized(contract.methods.is_priv_imm_initialized(defaultAccountAddress), true); }); // Reads the PrivateImmutable after initialization; asserts the stored value matches VALUE. it('read initialized PrivateImmutable', async () => { - expect( - ( - await contract.methods - .is_priv_imm_initialized(defaultAccountAddress) - .simulate({ from: defaultAccountAddress }) - ).result, - ).toEqual(true); + await expectInitialized(contract.methods.is_priv_imm_initialized(defaultAccountAddress), true); const { result: { value }, } = await contract.methods diff --git a/yarn-project/end-to-end/src/automine/contracts/static_calls.test.ts b/yarn-project/end-to-end/src/automine/contracts/static_calls.test.ts index d8298d186d0b..8dfdcc203cc8 100644 --- a/yarn-project/end-to-end/src/automine/contracts/static_calls.test.ts +++ b/yarn-project/end-to-end/src/automine/contracts/static_calls.test.ts @@ -1,4 +1,5 @@ import { AztecAddress } from '@aztec/aztec.js/addresses'; +import type { ContractFunctionInteraction } from '@aztec/aztec.js/contracts'; import type { Wallet } from '@aztec/aztec.js/wallet'; import { StaticChildContract } from '@aztec/noir-test-contracts.js/StaticChild'; import { StaticParentContract } from '@aztec/noir-test-contracts.js/StaticParent'; @@ -33,197 +34,229 @@ describe('automine/contracts/static_calls', () => { afterAll(() => teardown()); - // Tests calling StaticChild methods directly: legal reads succeed, illegal state-modifying calls fail. - describe('direct view calls to child', () => { - // Calls a read-only private function via a direct send; asserts the tx is mined without error. - it('performs legal private static calls', async () => { - await childContract.methods.private_get_value(42n, owner).send({ from: owner }); - }); + // A single static-call scenario: one or more interactions that must all either succeed or be rejected + // with `error`. `mode` picks whether each interaction is submitted (`send`) or only simulated. + type StaticCallCase = { + description: string; + getInteractions: () => ContractFunctionInteraction[] | Promise; + mode: 'send' | 'simulate'; + /** Expected thrown-error matcher; undefined means the interactions must succeed. */ + error?: string | RegExp; + }; - // Calls a private function that illegally sets state; asserts STATIC_CALL_STATE_MODIFICATION_ERROR. - it('fails when performing non-static calls to poorly written static private functions', async () => { - await expect(childContract.methods.private_illegal_set_value(42n, owner).send({ from: owner })).rejects.toThrow( - STATIC_CALL_STATE_MODIFICATION_ERROR, - ); - }); + const runStaticCallCase = async ({ getInteractions, mode, error }: StaticCallCase) => { + for (const interaction of await getInteractions()) { + const run = mode === 'send' ? interaction.send({ from: owner }) : interaction.simulate({ from: owner }); + if (error !== undefined) { + await expect(run).rejects.toThrow(error); + } else { + await run; + } + } + }; - // Calls a read-only public function; asserts it is mined without error. - it('performs legal public static calls', async () => { - await childContract.methods.pub_get_value(42n).send({ from: owner }); - }); + // Calling StaticChild methods directly: legal reads succeed, illegal state-modifying calls fail. + describe('direct view calls to child', () => { + const cases: StaticCallCase[] = [ + { + description: 'performs legal private static calls', + getInteractions: () => [childContract.methods.private_get_value(42n, owner)], + mode: 'send', + }, + { + description: 'fails when performing non-static calls to poorly written static private functions', + getInteractions: () => [childContract.methods.private_illegal_set_value(42n, owner)], + mode: 'send', + error: STATIC_CALL_STATE_MODIFICATION_ERROR, + }, + { + description: 'performs legal public static calls', + getInteractions: () => [childContract.methods.pub_get_value(42n)], + mode: 'send', + }, + { + description: 'fails when performing non-static calls to poorly written static public functions', + getInteractions: () => [childContract.methods.pub_illegal_inc_value(42n)], + mode: 'simulate', + error: STATIC_CALL_STATE_MODIFICATION_ERROR, + }, + ]; - // Simulates a public function that illegally increments state; asserts STATIC_CALL_STATE_MODIFICATION_ERROR. - it('fails when performing non-static calls to poorly written static public functions', async () => { - await expect(childContract.methods.pub_illegal_inc_value(42n).simulate({ from: owner })).rejects.toThrow( - STATIC_CALL_STATE_MODIFICATION_ERROR, - ); - }); + it.each(cases)('$description', testCase => runStaticCallCase(testCase)); }); - // Tests StaticParent routing calls to StaticChild: legal static calls succeed, illegal calls (state - // modification or assertion violations) throw the expected error strings. + // StaticParent routing calls to StaticChild: legal static calls succeed, illegal calls (state + // modification or assertion violations) throw the expected error strings. Legal cases that list two + // interactions cover both the low-level call and the typed contract interface. describe('parent calls child', () => { - // Parent calls child's private read-only function via low-level call and via typed interface; both succeed. - it('performs legal private to private static calls', async () => { - // Using low level calls - await parentContract.methods - .private_static_call(childContract.address, await childContract.methods.private_get_value.selector(), [ - 42n, - owner, - ]) - .send({ from: owner }); - - // Using the contract interface - await parentContract.methods - .private_get_value_from_child(childContract.address, 42n, owner) - .send({ from: owner }); - }); - - // Parent routes through a second level of nesting before calling child's private read; asserts success. - it('performs legal (nested) private to private static calls', async () => { - await parentContract.methods - .private_nested_static_call(childContract.address, await childContract.methods.private_get_value.selector(), [ - 42n, - owner, - ]) - .send({ from: owner }); - }); - - // Parent calls child's public read-only function via low-level and typed interface; both succeed. - it('performs legal public to public static calls', async () => { - // Using low level calls - await parentContract.methods - .public_static_call(childContract.address, await childContract.methods.pub_get_value.selector(), [42n]) - .send({ from: owner }); - - // Using contract interface - await parentContract.methods.public_get_value_from_child(childContract.address, 42n).send({ from: owner }); - }); - - // Parent routes through a second nesting level before calling child's public read; asserts success. - it('performs legal (nested) public to public static calls', async () => { - await parentContract.methods - .public_nested_static_call(childContract.address, await childContract.methods.pub_get_value.selector(), [42n]) - .send({ from: owner }); - }); - - // Parent enqueues a static public call to child's read function; asserts both low-level and typed - // interface variants succeed. - it('performs legal enqueued public static calls', async () => { - // Using low level calls - await parentContract.methods - .enqueue_static_call_to_pub_function( - childContract.address, - await childContract.methods.pub_get_value.selector(), - [42n], - ) - .send({ from: owner }); - - // Using contract interface - await parentContract.methods.enqueue_public_get_value_from_child(childContract.address, 42).send({ from: owner }); - }); - - // Enqueues a nested static public call through parent → child; asserts the tx succeeds. - it('performs legal (nested) enqueued public static calls', async () => { - await parentContract.methods - .enqueue_static_nested_call_to_pub_function( - childContract.address, - await childContract.methods.pub_get_value.selector(), - [42n], - ) - .send({ from: owner }); - }); - - // Parent makes a private static call to a state-mutating child function; asserts STATIC_CALL_STATE_MODIFICATION_ERROR. - it('fails when performing illegal private to private static calls', async () => { - await expect( - parentContract.methods - .private_static_call_3_args(childContract.address, await childContract.methods.private_set_value.selector(), [ - 42n, - owner, - sender, - ]) - .send({ from: owner }), - ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); - }); - - // Parent makes a non-static private call to a function that asserts static context; asserts STATIC_CONTEXT_ASSERTION_ERROR. - it('fails when performing non-static calls to poorly written private static functions', async () => { - await expect( - parentContract.methods - .private_call(childContract.address, await childContract.methods.private_illegal_set_value.selector(), [ - 42n, - owner, - ]) - .send({ from: owner }), - ).rejects.toThrow(STATIC_CONTEXT_ASSERTION_ERROR); - }); - - // Nested private static call from parent to a state-mutating child; asserts STATIC_CALL_STATE_MODIFICATION_ERROR. - it('fails when performing illegal (nested) private to private static calls', async () => { - await expect( - parentContract.methods - .private_nested_static_call_3_args( + const cases: StaticCallCase[] = [ + { + description: 'performs legal private to private static calls', + getInteractions: async () => [ + parentContract.methods.private_static_call( + childContract.address, + await childContract.methods.private_get_value.selector(), + [42n, owner], + ), + parentContract.methods.private_get_value_from_child(childContract.address, 42n, owner), + ], + mode: 'send', + }, + { + description: 'performs legal (nested) private to private static calls', + getInteractions: async () => [ + parentContract.methods.private_nested_static_call( + childContract.address, + await childContract.methods.private_get_value.selector(), + [42n, owner], + ), + ], + mode: 'send', + }, + { + description: 'performs legal public to public static calls', + getInteractions: async () => [ + parentContract.methods.public_static_call( + childContract.address, + await childContract.methods.pub_get_value.selector(), + [42n], + ), + parentContract.methods.public_get_value_from_child(childContract.address, 42n), + ], + mode: 'send', + }, + { + description: 'performs legal (nested) public to public static calls', + getInteractions: async () => [ + parentContract.methods.public_nested_static_call( + childContract.address, + await childContract.methods.pub_get_value.selector(), + [42n], + ), + ], + mode: 'send', + }, + { + description: 'performs legal enqueued public static calls', + getInteractions: async () => [ + parentContract.methods.enqueue_static_call_to_pub_function( + childContract.address, + await childContract.methods.pub_get_value.selector(), + [42n], + ), + parentContract.methods.enqueue_public_get_value_from_child(childContract.address, 42), + ], + mode: 'send', + }, + { + description: 'performs legal (nested) enqueued public static calls', + getInteractions: async () => [ + parentContract.methods.enqueue_static_nested_call_to_pub_function( + childContract.address, + await childContract.methods.pub_get_value.selector(), + [42n], + ), + ], + mode: 'send', + }, + { + description: 'fails when performing illegal private to private static calls', + getInteractions: async () => [ + parentContract.methods.private_static_call_3_args( childContract.address, await childContract.methods.private_set_value.selector(), [42n, owner, sender], - ) - .send({ from: owner }), - ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); - }); - - // Parent makes a public static call to a state-mutating child function; asserts STATIC_CALL_STATE_MODIFICATION_ERROR. - it('fails when performing illegal public to public static calls', async () => { - await expect( - parentContract.methods - .public_static_call(childContract.address, await childContract.methods.pub_set_value.selector(), [42n]) - .simulate({ from: owner }), - ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); - }); - - // Nested public static call from parent to a state-mutating child; asserts STATIC_CALL_STATE_MODIFICATION_ERROR. - it('fails when performing illegal (nested) public to public static calls', async () => { - await expect( - parentContract.methods - .public_nested_static_call(childContract.address, await childContract.methods.pub_set_value.selector(), [42n]) - .simulate({ from: owner }), - ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); - }); - - // Parent enqueues a static public call to a state-mutating child function; asserts STATIC_CALL_STATE_MODIFICATION_ERROR. - it('fails when performing illegal enqueued public static calls', async () => { - await expect( - parentContract.methods - .enqueue_static_call_to_pub_function( + ), + ], + mode: 'send', + error: STATIC_CALL_STATE_MODIFICATION_ERROR, + }, + { + description: 'fails when performing non-static calls to poorly written private static functions', + getInteractions: async () => [ + parentContract.methods.private_call( + childContract.address, + await childContract.methods.private_illegal_set_value.selector(), + [42n, owner], + ), + ], + mode: 'send', + error: STATIC_CONTEXT_ASSERTION_ERROR, + }, + { + description: 'fails when performing illegal (nested) private to private static calls', + getInteractions: async () => [ + parentContract.methods.private_nested_static_call_3_args( + childContract.address, + await childContract.methods.private_set_value.selector(), + [42n, owner, sender], + ), + ], + mode: 'send', + error: STATIC_CALL_STATE_MODIFICATION_ERROR, + }, + { + description: 'fails when performing illegal public to public static calls', + getInteractions: async () => [ + parentContract.methods.public_static_call( childContract.address, await childContract.methods.pub_set_value.selector(), [42n], - ) - .simulate({ from: owner }), - ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); - }); - - // Nested enqueued static call to a state-mutating function; asserts STATIC_CALL_STATE_MODIFICATION_ERROR. - it('fails when performing illegal (nested) enqueued public static calls', async () => { - await expect( - parentContract.methods - .enqueue_static_nested_call_to_pub_function( + ), + ], + mode: 'simulate', + error: STATIC_CALL_STATE_MODIFICATION_ERROR, + }, + { + description: 'fails when performing illegal (nested) public to public static calls', + getInteractions: async () => [ + parentContract.methods.public_nested_static_call( childContract.address, await childContract.methods.pub_set_value.selector(), [42n], - ) - .simulate({ from: owner }), - ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); - }); + ), + ], + mode: 'simulate', + error: STATIC_CALL_STATE_MODIFICATION_ERROR, + }, + { + description: 'fails when performing illegal enqueued public static calls', + getInteractions: async () => [ + parentContract.methods.enqueue_static_call_to_pub_function( + childContract.address, + await childContract.methods.pub_set_value.selector(), + [42n], + ), + ], + mode: 'simulate', + error: STATIC_CALL_STATE_MODIFICATION_ERROR, + }, + { + description: 'fails when performing illegal (nested) enqueued public static calls', + getInteractions: async () => [ + parentContract.methods.enqueue_static_nested_call_to_pub_function( + childContract.address, + await childContract.methods.pub_set_value.selector(), + [42n], + ), + ], + mode: 'simulate', + error: STATIC_CALL_STATE_MODIFICATION_ERROR, + }, + { + description: 'fails when performing non-static enqueue calls to poorly written public static functions', + getInteractions: async () => [ + parentContract.methods.enqueue_call( + childContract.address, + await childContract.methods.pub_illegal_inc_value.selector(), + [42n], + ), + ], + mode: 'simulate', + error: STATIC_CONTEXT_ASSERTION_ERROR, + }, + ]; - // Parent enqueues a non-static call to a function that asserts it is called statically; asserts - // STATIC_CONTEXT_ASSERTION_ERROR. - it('fails when performing non-static enqueue calls to poorly written public static functions', async () => { - await expect( - parentContract.methods - .enqueue_call(childContract.address, await childContract.methods.pub_illegal_inc_value.selector(), [42n]) - .simulate({ from: owner }), - ).rejects.toThrow(STATIC_CONTEXT_ASSERTION_ERROR); - }); + it.each(cases)('$description', testCase => runStaticCallCase(testCase)); }); }); diff --git a/yarn-project/end-to-end/src/automine/effects/custom_message.parallel.test.ts b/yarn-project/end-to-end/src/automine/effects/custom_message.parallel.test.ts deleted file mode 100644 index 8be39d89cfd2..000000000000 --- a/yarn-project/end-to-end/src/automine/effects/custom_message.parallel.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import type { AztecAddress } from '@aztec/aztec.js/addresses'; -import { BatchCall } from '@aztec/aztec.js/contracts'; -import { Fr } from '@aztec/aztec.js/fields'; -import type { Wallet } from '@aztec/aztec.js/wallet'; -import { BlockNumber } from '@aztec/foundation/branded-types'; -import { CustomMessageContract, type MultiLogEvent } from '@aztec/noir-test-contracts.js/CustomMessage'; - -import { jest } from '@jest/globals'; - -import { AutomineTestContext } from '../automine_test_context.js'; - -const TIMEOUT = 300_000; - -// Tests the CustomMessage contract's multi-log event pattern: emitting a single event split across -// multiple private logs and reassembling it via wallet.getPrivateEvents. -// Uses setup(1, AUTOMINE_E2E_OPTS) with one node, automine sequencer, one account. -describe('automine/effects/custom_message', () => { - let contract: CustomMessageContract; - jest.setTimeout(TIMEOUT); - - let wallet: Wallet; - let account: AztecAddress; - let teardown: () => Promise; - - beforeAll(async () => { - ({ - teardown, - wallet, - accounts: [account], - } = (await AutomineTestContext.setup({ numberOfAccounts: 1 })).context); - ({ contract } = await CustomMessageContract.deploy(wallet).send({ from: account })); - }); - - afterAll(() => teardown()); - - // Emits one MultiLogEvent via emit_multi_log_event, retrieves it via getPrivateEvents, and - // asserts all four field values match. - it('reassembles a multi-log event from multiple private logs', async () => { - const values = [Fr.random(), Fr.random(), Fr.random(), Fr.random()]; - - const { receipt: tx } = await contract.methods - .emit_multi_log_event(values[0], values[1], values[2], values[3], account) - .send({ from: account }); - - const events = await wallet.getPrivateEvents(CustomMessageContract.events.MultiLogEvent, { - contractAddress: contract.address, - fromBlock: BlockNumber(tx.blockNumber!), - toBlock: BlockNumber(tx.blockNumber! + 1), - scopes: [account], - }); - - expect(events.length).toBe(1); - expect(events[0].event.value0).toBe(values[0].toBigInt()); - expect(events[0].event.value1).toBe(values[1].toBigInt()); - expect(events[0].event.value2).toBe(values[2].toBigInt()); - expect(events[0].event.value3).toBe(values[3].toBigInt()); - }); - - // Emits two MultiLogEvents in a single BatchCall, retrieves both, and asserts all eight field - // values match by matching on value0. - it('reassembles multiple multi-log events from the same transaction', async () => { - const valuesA = [Fr.random(), Fr.random(), Fr.random(), Fr.random()]; - const valuesB = [Fr.random(), Fr.random(), Fr.random(), Fr.random()]; - - const { receipt: tx } = await new BatchCall(wallet, [ - contract.methods.emit_multi_log_event(valuesA[0], valuesA[1], valuesA[2], valuesA[3], account), - contract.methods.emit_multi_log_event(valuesB[0], valuesB[1], valuesB[2], valuesB[3], account), - ]).send({ from: account }); - - const events = await wallet.getPrivateEvents(CustomMessageContract.events.MultiLogEvent, { - contractAddress: contract.address, - fromBlock: BlockNumber(tx.blockNumber!), - toBlock: BlockNumber(tx.blockNumber! + 1), - scopes: [account], - }); - - expect(events.length).toBe(2); - - // Events may arrive in any order, so match by value0 - const eventA = events.find(e => e.event.value0 === valuesA[0].toBigInt())!; - const eventB = events.find(e => e.event.value0 === valuesB[0].toBigInt())!; - - expect(eventA).toBeDefined(); - expect(eventA.event.value1).toBe(valuesA[1].toBigInt()); - expect(eventA.event.value2).toBe(valuesA[2].toBigInt()); - expect(eventA.event.value3).toBe(valuesA[3].toBigInt()); - - expect(eventB).toBeDefined(); - expect(eventB.event.value1).toBe(valuesB[1].toBigInt()); - expect(eventB.event.value2).toBe(valuesB[2].toBigInt()); - expect(eventB.event.value3).toBe(valuesB[3].toBigInt()); - }); -}); diff --git a/yarn-project/end-to-end/src/automine/effects/event_only.test.ts b/yarn-project/end-to-end/src/automine/effects/event_only.test.ts deleted file mode 100644 index 7ce0e5d05772..000000000000 --- a/yarn-project/end-to-end/src/automine/effects/event_only.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { AztecAddress } from '@aztec/aztec.js/addresses'; -import { Fr } from '@aztec/aztec.js/fields'; -import type { Wallet } from '@aztec/aztec.js/wallet'; -import { BlockNumber } from '@aztec/foundation/branded-types'; -import { EventOnlyContract, type TestEvent } from '@aztec/noir-test-contracts.js/EventOnly'; - -import { jest } from '@jest/globals'; - -import { AutomineTestContext } from '../automine_test_context.js'; - -const TIMEOUT = 300_000; - -/// Tests that a private event can be obtained for a contract that does not work with notes. -// Single automine node, one genesis-funded account, EventOnlyContract deployed in beforeAll. -describe('automine/effects/event_only', () => { - let eventOnlyContract: EventOnlyContract; - jest.setTimeout(TIMEOUT); - - let wallet: Wallet; - let defaultAccountAddress: AztecAddress; - let teardown: () => Promise; - - beforeAll(async () => { - ({ - teardown, - wallet, - accounts: [defaultAccountAddress], - } = (await AutomineTestContext.setup({ numberOfAccounts: 1 })).context); - ({ contract: eventOnlyContract } = await EventOnlyContract.deploy(wallet).send({ from: defaultAccountAddress })); - }); - - afterAll(() => teardown()); - - // Sends emit_event_for_msg_sender, then calls getPrivateEvents for TestEvent and asserts that - // exactly one event is returned with the correct value field. - it('emits and retrieves a private event for a contract with no notes', async () => { - const value = Fr.random(); - const { receipt: tx } = await eventOnlyContract.methods - .emit_event_for_msg_sender(value) - .send({ from: defaultAccountAddress }); - - const events = await wallet.getPrivateEvents(EventOnlyContract.events.TestEvent, { - contractAddress: eventOnlyContract.address, - fromBlock: BlockNumber(tx.blockNumber!), - toBlock: BlockNumber(tx.blockNumber! + 1), - scopes: [defaultAccountAddress], - }); - - expect(events.length).toBe(1); - expect(events[0].event.value).toBe(value.toBigInt()); - }); -}); diff --git a/yarn-project/end-to-end/src/automine/effects/events.test.ts b/yarn-project/end-to-end/src/automine/effects/events.test.ts new file mode 100644 index 000000000000..aa8455be59c2 --- /dev/null +++ b/yarn-project/end-to-end/src/automine/effects/events.test.ts @@ -0,0 +1,149 @@ +import type { AztecAddress } from '@aztec/aztec.js/addresses'; +import { BatchCall } from '@aztec/aztec.js/contracts'; +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 { CustomMessageContract, type MultiLogEvent } from '@aztec/noir-test-contracts.js/CustomMessage'; +import { EventOnlyContract, type TestEvent } from '@aztec/noir-test-contracts.js/EventOnly'; +import { type LargeEvent, LargePublicEventContract } from '@aztec/noir-test-contracts.js/LargePublicEvent'; + +import { jest } from '@jest/globals'; + +import { AutomineTestContext } from '../automine_test_context.js'; + +const TIMEOUT = 300_000; + +// Consolidated event emission/retrieval tests. A single automine node with one funded account deploys the +// EventOnly, CustomMessage, and LargePublicEvent contracts in beforeAll; each per-contract describe below +// exercises one event round-trip via wallet.getPrivateEvents / getPublicEvents. +describe('automine/effects/events', () => { + jest.setTimeout(TIMEOUT); + + let wallet: Wallet; + let aztecNode: AztecNode; + let account: AztecAddress; + let teardown: () => Promise; + + let eventOnlyContract: EventOnlyContract; + let customMessageContract: CustomMessageContract; + let largePublicEventContract: LargePublicEventContract; + + beforeAll(async () => { + ({ + teardown, + wallet, + aztecNode, + accounts: [account], + } = (await AutomineTestContext.setup({ numberOfAccounts: 1 })).context); + + ({ contract: eventOnlyContract } = await EventOnlyContract.deploy(wallet).send({ from: account })); + ({ contract: customMessageContract } = await CustomMessageContract.deploy(wallet).send({ from: account })); + ({ contract: largePublicEventContract } = await LargePublicEventContract.deploy(wallet).send({ from: account })); + }); + + afterAll(() => teardown()); + + // A private event can be obtained for a contract that does not work with notes. + describe('EventOnly', () => { + // Sends emit_event_for_msg_sender, then calls getPrivateEvents for TestEvent and asserts that + // exactly one event is returned with the correct value field. + it('emits and retrieves a private event for a contract with no notes', async () => { + const value = Fr.random(); + const { receipt: tx } = await eventOnlyContract.methods.emit_event_for_msg_sender(value).send({ from: account }); + + const events = await wallet.getPrivateEvents(EventOnlyContract.events.TestEvent, { + contractAddress: eventOnlyContract.address, + fromBlock: BlockNumber(tx.blockNumber!), + toBlock: BlockNumber(tx.blockNumber! + 1), + scopes: [account], + }); + + expect(events.length).toBe(1); + expect(events[0].event.value).toBe(value.toBigInt()); + }); + }); + + // The CustomMessage contract's multi-log event pattern: emitting a single event split across multiple + // private logs and reassembling it via wallet.getPrivateEvents. + describe('CustomMessage', () => { + // Emits one MultiLogEvent via emit_multi_log_event, retrieves it via getPrivateEvents, and + // asserts all four field values match. + it('reassembles a multi-log event from multiple private logs', async () => { + const values = [Fr.random(), Fr.random(), Fr.random(), Fr.random()]; + + const { receipt: tx } = await customMessageContract.methods + .emit_multi_log_event(values[0], values[1], values[2], values[3], account) + .send({ from: account }); + + const events = await wallet.getPrivateEvents(CustomMessageContract.events.MultiLogEvent, { + contractAddress: customMessageContract.address, + fromBlock: BlockNumber(tx.blockNumber!), + toBlock: BlockNumber(tx.blockNumber! + 1), + scopes: [account], + }); + + expect(events.length).toBe(1); + expect(events[0].event.value0).toBe(values[0].toBigInt()); + expect(events[0].event.value1).toBe(values[1].toBigInt()); + expect(events[0].event.value2).toBe(values[2].toBigInt()); + expect(events[0].event.value3).toBe(values[3].toBigInt()); + }); + + // Emits two MultiLogEvents in a single BatchCall, retrieves both, and asserts all eight field + // values match by matching on value0. + it('reassembles multiple multi-log events from the same transaction', async () => { + const valuesA = [Fr.random(), Fr.random(), Fr.random(), Fr.random()]; + const valuesB = [Fr.random(), Fr.random(), Fr.random(), Fr.random()]; + + const { receipt: tx } = await new BatchCall(wallet, [ + customMessageContract.methods.emit_multi_log_event(valuesA[0], valuesA[1], valuesA[2], valuesA[3], account), + customMessageContract.methods.emit_multi_log_event(valuesB[0], valuesB[1], valuesB[2], valuesB[3], account), + ]).send({ from: account }); + + const events = await wallet.getPrivateEvents(CustomMessageContract.events.MultiLogEvent, { + contractAddress: customMessageContract.address, + fromBlock: BlockNumber(tx.blockNumber!), + toBlock: BlockNumber(tx.blockNumber! + 1), + scopes: [account], + }); + + expect(events.length).toBe(2); + + // Events may arrive in any order, so match by value0 + const eventA = events.find(e => e.event.value0 === valuesA[0].toBigInt())!; + const eventB = events.find(e => e.event.value0 === valuesB[0].toBigInt())!; + + expect(eventA).toBeDefined(); + expect(eventA.event.value1).toBe(valuesA[1].toBigInt()); + expect(eventA.event.value2).toBe(valuesA[2].toBigInt()); + expect(eventA.event.value3).toBe(valuesA[3].toBigInt()); + + expect(eventB).toBeDefined(); + expect(eventB.event.value1).toBe(valuesB[1].toBigInt()); + expect(eventB.event.value2).toBe(valuesB[2].toBigInt()); + expect(eventB.event.value3).toBe(valuesB[3].toBigInt()); + }); + }); + + // Events exceeding MAX_EVENT_SERIALIZED_LEN can be emitted publicly and reassembled on retrieval. + describe('LargePublicEvent', () => { + // Sends emit_large_event with 11 random Fr fields, retrieves via getPublicEvents, and asserts + // the returned event's data array matches. + 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 largePublicEventContract.methods.emit_large_event(data).send({ from: account }); + + const { events } = await getPublicEvents(aztecNode, LargePublicEventContract.events.LargeEvent, { + contractAddress: largePublicEventContract.address, + 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())); + }); + }); +}); diff --git a/yarn-project/end-to-end/src/automine/effects/large_public_event.test.ts b/yarn-project/end-to-end/src/automine/effects/large_public_event.test.ts deleted file mode 100644 index a31ab8cd55d8..000000000000 --- a/yarn-project/end-to-end/src/automine/effects/large_public_event.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -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 { AutomineTestContext } from '../automine_test_context.js'; - -const TIMEOUT = 300_000; - -/// Tests that events exceeding MAX_EVENT_SERIALIZED_LEN can be emitted publicly. -// Single automine node, one funded account, LargePublicEventContract deployed in beforeAll. -describe('automine/effects/large_public_event', () => { - 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 AutomineTestContext.setup({ numberOfAccounts: 1 })).context); - ({ contract } = await LargePublicEventContract.deploy(wallet).send({ from: accountAddress })); - }); - - afterAll(() => teardown()); - - // Sends emit_large_event with 11 random Fr fields, retrieves via getPublicEvents, and asserts - // the returned event's data array matches. - 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, { - contractAddress: contract.address, - 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())); - }); -}); diff --git a/yarn-project/end-to-end/src/automine/mempool_limit.test.ts b/yarn-project/end-to-end/src/automine/mempool_limit.test.ts index 28f8a5dc3258..a69525e0b89b 100644 --- a/yarn-project/end-to-end/src/automine/mempool_limit.test.ts +++ b/yarn-project/end-to-end/src/automine/mempool_limit.test.ts @@ -26,12 +26,7 @@ describe('automine/mempool_limit', () => { aztecNodeAdmin, wallet, accounts: [defaultAccountAddress], - } = ( - await AutomineTestContext.setup({ - numberOfAccounts: 1, - proverTestVerificationDelayMs: undefined, - }) - ).context); + } = (await AutomineTestContext.setup({ numberOfAccounts: 1 })).context); if (!aztecNodeAdmin) { throw new Error('Aztec node admin API must be available for this test'); From 12af57193742b98bf4346ca2adba528967926227 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 3 Jul 2026 10:59:46 -0300 Subject: [PATCH 2/2] test: restore explicit test cases in static_calls.test.ts --- .../automine/contracts/static_calls.test.ts | 385 ++++++++---------- 1 file changed, 176 insertions(+), 209 deletions(-) diff --git a/yarn-project/end-to-end/src/automine/contracts/static_calls.test.ts b/yarn-project/end-to-end/src/automine/contracts/static_calls.test.ts index 8dfdcc203cc8..d8298d186d0b 100644 --- a/yarn-project/end-to-end/src/automine/contracts/static_calls.test.ts +++ b/yarn-project/end-to-end/src/automine/contracts/static_calls.test.ts @@ -1,5 +1,4 @@ import { AztecAddress } from '@aztec/aztec.js/addresses'; -import type { ContractFunctionInteraction } from '@aztec/aztec.js/contracts'; import type { Wallet } from '@aztec/aztec.js/wallet'; import { StaticChildContract } from '@aztec/noir-test-contracts.js/StaticChild'; import { StaticParentContract } from '@aztec/noir-test-contracts.js/StaticParent'; @@ -34,229 +33,197 @@ describe('automine/contracts/static_calls', () => { afterAll(() => teardown()); - // A single static-call scenario: one or more interactions that must all either succeed or be rejected - // with `error`. `mode` picks whether each interaction is submitted (`send`) or only simulated. - type StaticCallCase = { - description: string; - getInteractions: () => ContractFunctionInteraction[] | Promise; - mode: 'send' | 'simulate'; - /** Expected thrown-error matcher; undefined means the interactions must succeed. */ - error?: string | RegExp; - }; + // Tests calling StaticChild methods directly: legal reads succeed, illegal state-modifying calls fail. + describe('direct view calls to child', () => { + // Calls a read-only private function via a direct send; asserts the tx is mined without error. + it('performs legal private static calls', async () => { + await childContract.methods.private_get_value(42n, owner).send({ from: owner }); + }); - const runStaticCallCase = async ({ getInteractions, mode, error }: StaticCallCase) => { - for (const interaction of await getInteractions()) { - const run = mode === 'send' ? interaction.send({ from: owner }) : interaction.simulate({ from: owner }); - if (error !== undefined) { - await expect(run).rejects.toThrow(error); - } else { - await run; - } - } - }; + // Calls a private function that illegally sets state; asserts STATIC_CALL_STATE_MODIFICATION_ERROR. + it('fails when performing non-static calls to poorly written static private functions', async () => { + await expect(childContract.methods.private_illegal_set_value(42n, owner).send({ from: owner })).rejects.toThrow( + STATIC_CALL_STATE_MODIFICATION_ERROR, + ); + }); - // Calling StaticChild methods directly: legal reads succeed, illegal state-modifying calls fail. - describe('direct view calls to child', () => { - const cases: StaticCallCase[] = [ - { - description: 'performs legal private static calls', - getInteractions: () => [childContract.methods.private_get_value(42n, owner)], - mode: 'send', - }, - { - description: 'fails when performing non-static calls to poorly written static private functions', - getInteractions: () => [childContract.methods.private_illegal_set_value(42n, owner)], - mode: 'send', - error: STATIC_CALL_STATE_MODIFICATION_ERROR, - }, - { - description: 'performs legal public static calls', - getInteractions: () => [childContract.methods.pub_get_value(42n)], - mode: 'send', - }, - { - description: 'fails when performing non-static calls to poorly written static public functions', - getInteractions: () => [childContract.methods.pub_illegal_inc_value(42n)], - mode: 'simulate', - error: STATIC_CALL_STATE_MODIFICATION_ERROR, - }, - ]; + // Calls a read-only public function; asserts it is mined without error. + it('performs legal public static calls', async () => { + await childContract.methods.pub_get_value(42n).send({ from: owner }); + }); - it.each(cases)('$description', testCase => runStaticCallCase(testCase)); + // Simulates a public function that illegally increments state; asserts STATIC_CALL_STATE_MODIFICATION_ERROR. + it('fails when performing non-static calls to poorly written static public functions', async () => { + await expect(childContract.methods.pub_illegal_inc_value(42n).simulate({ from: owner })).rejects.toThrow( + STATIC_CALL_STATE_MODIFICATION_ERROR, + ); + }); }); - // StaticParent routing calls to StaticChild: legal static calls succeed, illegal calls (state - // modification or assertion violations) throw the expected error strings. Legal cases that list two - // interactions cover both the low-level call and the typed contract interface. + // Tests StaticParent routing calls to StaticChild: legal static calls succeed, illegal calls (state + // modification or assertion violations) throw the expected error strings. describe('parent calls child', () => { - const cases: StaticCallCase[] = [ - { - description: 'performs legal private to private static calls', - getInteractions: async () => [ - parentContract.methods.private_static_call( - childContract.address, - await childContract.methods.private_get_value.selector(), - [42n, owner], - ), - parentContract.methods.private_get_value_from_child(childContract.address, 42n, owner), - ], - mode: 'send', - }, - { - description: 'performs legal (nested) private to private static calls', - getInteractions: async () => [ - parentContract.methods.private_nested_static_call( - childContract.address, - await childContract.methods.private_get_value.selector(), - [42n, owner], - ), - ], - mode: 'send', - }, - { - description: 'performs legal public to public static calls', - getInteractions: async () => [ - parentContract.methods.public_static_call( - childContract.address, - await childContract.methods.pub_get_value.selector(), - [42n], - ), - parentContract.methods.public_get_value_from_child(childContract.address, 42n), - ], - mode: 'send', - }, - { - description: 'performs legal (nested) public to public static calls', - getInteractions: async () => [ - parentContract.methods.public_nested_static_call( - childContract.address, - await childContract.methods.pub_get_value.selector(), - [42n], - ), - ], - mode: 'send', - }, - { - description: 'performs legal enqueued public static calls', - getInteractions: async () => [ - parentContract.methods.enqueue_static_call_to_pub_function( - childContract.address, - await childContract.methods.pub_get_value.selector(), - [42n], - ), - parentContract.methods.enqueue_public_get_value_from_child(childContract.address, 42), - ], - mode: 'send', - }, - { - description: 'performs legal (nested) enqueued public static calls', - getInteractions: async () => [ - parentContract.methods.enqueue_static_nested_call_to_pub_function( - childContract.address, - await childContract.methods.pub_get_value.selector(), - [42n], - ), - ], - mode: 'send', - }, - { - description: 'fails when performing illegal private to private static calls', - getInteractions: async () => [ - parentContract.methods.private_static_call_3_args( - childContract.address, - await childContract.methods.private_set_value.selector(), - [42n, owner, sender], - ), - ], - mode: 'send', - error: STATIC_CALL_STATE_MODIFICATION_ERROR, - }, - { - description: 'fails when performing non-static calls to poorly written private static functions', - getInteractions: async () => [ - parentContract.methods.private_call( - childContract.address, - await childContract.methods.private_illegal_set_value.selector(), - [42n, owner], - ), - ], - mode: 'send', - error: STATIC_CONTEXT_ASSERTION_ERROR, - }, - { - description: 'fails when performing illegal (nested) private to private static calls', - getInteractions: async () => [ - parentContract.methods.private_nested_static_call_3_args( + // Parent calls child's private read-only function via low-level call and via typed interface; both succeed. + it('performs legal private to private static calls', async () => { + // Using low level calls + await parentContract.methods + .private_static_call(childContract.address, await childContract.methods.private_get_value.selector(), [ + 42n, + owner, + ]) + .send({ from: owner }); + + // Using the contract interface + await parentContract.methods + .private_get_value_from_child(childContract.address, 42n, owner) + .send({ from: owner }); + }); + + // Parent routes through a second level of nesting before calling child's private read; asserts success. + it('performs legal (nested) private to private static calls', async () => { + await parentContract.methods + .private_nested_static_call(childContract.address, await childContract.methods.private_get_value.selector(), [ + 42n, + owner, + ]) + .send({ from: owner }); + }); + + // Parent calls child's public read-only function via low-level and typed interface; both succeed. + it('performs legal public to public static calls', async () => { + // Using low level calls + await parentContract.methods + .public_static_call(childContract.address, await childContract.methods.pub_get_value.selector(), [42n]) + .send({ from: owner }); + + // Using contract interface + await parentContract.methods.public_get_value_from_child(childContract.address, 42n).send({ from: owner }); + }); + + // Parent routes through a second nesting level before calling child's public read; asserts success. + it('performs legal (nested) public to public static calls', async () => { + await parentContract.methods + .public_nested_static_call(childContract.address, await childContract.methods.pub_get_value.selector(), [42n]) + .send({ from: owner }); + }); + + // Parent enqueues a static public call to child's read function; asserts both low-level and typed + // interface variants succeed. + it('performs legal enqueued public static calls', async () => { + // Using low level calls + await parentContract.methods + .enqueue_static_call_to_pub_function( + childContract.address, + await childContract.methods.pub_get_value.selector(), + [42n], + ) + .send({ from: owner }); + + // Using contract interface + await parentContract.methods.enqueue_public_get_value_from_child(childContract.address, 42).send({ from: owner }); + }); + + // Enqueues a nested static public call through parent → child; asserts the tx succeeds. + it('performs legal (nested) enqueued public static calls', async () => { + await parentContract.methods + .enqueue_static_nested_call_to_pub_function( + childContract.address, + await childContract.methods.pub_get_value.selector(), + [42n], + ) + .send({ from: owner }); + }); + + // Parent makes a private static call to a state-mutating child function; asserts STATIC_CALL_STATE_MODIFICATION_ERROR. + it('fails when performing illegal private to private static calls', async () => { + await expect( + parentContract.methods + .private_static_call_3_args(childContract.address, await childContract.methods.private_set_value.selector(), [ + 42n, + owner, + sender, + ]) + .send({ from: owner }), + ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); + }); + + // Parent makes a non-static private call to a function that asserts static context; asserts STATIC_CONTEXT_ASSERTION_ERROR. + it('fails when performing non-static calls to poorly written private static functions', async () => { + await expect( + parentContract.methods + .private_call(childContract.address, await childContract.methods.private_illegal_set_value.selector(), [ + 42n, + owner, + ]) + .send({ from: owner }), + ).rejects.toThrow(STATIC_CONTEXT_ASSERTION_ERROR); + }); + + // Nested private static call from parent to a state-mutating child; asserts STATIC_CALL_STATE_MODIFICATION_ERROR. + it('fails when performing illegal (nested) private to private static calls', async () => { + await expect( + parentContract.methods + .private_nested_static_call_3_args( childContract.address, await childContract.methods.private_set_value.selector(), [42n, owner, sender], - ), - ], - mode: 'send', - error: STATIC_CALL_STATE_MODIFICATION_ERROR, - }, - { - description: 'fails when performing illegal public to public static calls', - getInteractions: async () => [ - parentContract.methods.public_static_call( - childContract.address, - await childContract.methods.pub_set_value.selector(), - [42n], - ), - ], - mode: 'simulate', - error: STATIC_CALL_STATE_MODIFICATION_ERROR, - }, - { - description: 'fails when performing illegal (nested) public to public static calls', - getInteractions: async () => [ - parentContract.methods.public_nested_static_call( - childContract.address, - await childContract.methods.pub_set_value.selector(), - [42n], - ), - ], - mode: 'simulate', - error: STATIC_CALL_STATE_MODIFICATION_ERROR, - }, - { - description: 'fails when performing illegal enqueued public static calls', - getInteractions: async () => [ - parentContract.methods.enqueue_static_call_to_pub_function( + ) + .send({ from: owner }), + ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); + }); + + // Parent makes a public static call to a state-mutating child function; asserts STATIC_CALL_STATE_MODIFICATION_ERROR. + it('fails when performing illegal public to public static calls', async () => { + await expect( + parentContract.methods + .public_static_call(childContract.address, await childContract.methods.pub_set_value.selector(), [42n]) + .simulate({ from: owner }), + ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); + }); + + // Nested public static call from parent to a state-mutating child; asserts STATIC_CALL_STATE_MODIFICATION_ERROR. + it('fails when performing illegal (nested) public to public static calls', async () => { + await expect( + parentContract.methods + .public_nested_static_call(childContract.address, await childContract.methods.pub_set_value.selector(), [42n]) + .simulate({ from: owner }), + ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); + }); + + // Parent enqueues a static public call to a state-mutating child function; asserts STATIC_CALL_STATE_MODIFICATION_ERROR. + it('fails when performing illegal enqueued public static calls', async () => { + await expect( + parentContract.methods + .enqueue_static_call_to_pub_function( childContract.address, await childContract.methods.pub_set_value.selector(), [42n], - ), - ], - mode: 'simulate', - error: STATIC_CALL_STATE_MODIFICATION_ERROR, - }, - { - description: 'fails when performing illegal (nested) enqueued public static calls', - getInteractions: async () => [ - parentContract.methods.enqueue_static_nested_call_to_pub_function( + ) + .simulate({ from: owner }), + ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); + }); + + // Nested enqueued static call to a state-mutating function; asserts STATIC_CALL_STATE_MODIFICATION_ERROR. + it('fails when performing illegal (nested) enqueued public static calls', async () => { + await expect( + parentContract.methods + .enqueue_static_nested_call_to_pub_function( childContract.address, await childContract.methods.pub_set_value.selector(), [42n], - ), - ], - mode: 'simulate', - error: STATIC_CALL_STATE_MODIFICATION_ERROR, - }, - { - description: 'fails when performing non-static enqueue calls to poorly written public static functions', - getInteractions: async () => [ - parentContract.methods.enqueue_call( - childContract.address, - await childContract.methods.pub_illegal_inc_value.selector(), - [42n], - ), - ], - mode: 'simulate', - error: STATIC_CONTEXT_ASSERTION_ERROR, - }, - ]; + ) + .simulate({ from: owner }), + ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); + }); - it.each(cases)('$description', testCase => runStaticCallCase(testCase)); + // Parent enqueues a non-static call to a function that asserts it is called statically; asserts + // STATIC_CONTEXT_ASSERTION_ERROR. + it('fails when performing non-static enqueue calls to poorly written public static functions', async () => { + await expect( + parentContract.methods + .enqueue_call(childContract.address, await childContract.methods.pub_illegal_inc_value.selector(), [42n]) + .simulate({ from: owner }), + ).rejects.toThrow(STATIC_CONTEXT_ASSERTION_ERROR); + }); }); });