diff --git a/.github/workflows/aztec-cli-acceptance-test.yml b/.github/workflows/aztec-cli-acceptance-test.yml index ef9da15b4b45..1a1de972c320 100644 --- a/.github/workflows/aztec-cli-acceptance-test.yml +++ b/.github/workflows/aztec-cli-acceptance-test.yml @@ -40,6 +40,15 @@ jobs: timeout-minutes: 30 run: ./aztec-up/test/aztec-cli-acceptance-test/run-test.sh + - name: Upload diagnostics on failure + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + with: + name: acceptance-test-diagnostics-${{ matrix.os }} + path: ${{ runner.temp }}/aztec-cli-acceptance-test-*/** + if-no-files-found: warn + retention-days: 7 + notify: needs: release-acceptance if: always() && github.event_name != 'workflow_dispatch' diff --git a/aztec-up/test/aztec-cli-acceptance-test/aztec-cli-acceptance-test.ts b/aztec-up/test/aztec-cli-acceptance-test/aztec-cli-acceptance-test.ts index d207f90f9679..4f58deb6615a 100644 --- a/aztec-up/test/aztec-cli-acceptance-test/aztec-cli-acceptance-test.ts +++ b/aztec-up/test/aztec-cli-acceptance-test/aztec-cli-acceptance-test.ts @@ -49,7 +49,10 @@ if (!existsSync(join(AZTEC_INSTALL_DIR, "package.json"))) { process.exit(2); } -const TMP_DIR = mkdtempSync(join(tmpdir(), "aztec-cli-acceptance-test-")); +// Prefer RUNNER_TEMP so the GitHub Actions upload-artifact step can find the diagnostic +// tree on failure under a predictable parent path. Falls back to the system tmpdir locally. +const TMP_DIR_PARENT = process.env.RUNNER_TEMP ?? tmpdir(); +const TMP_DIR = mkdtempSync(join(TMP_DIR_PARENT, "aztec-cli-acceptance-test-")); const WORKSPACE_DIR = join(TMP_DIR, "my_workspace"); // Exit codes follow the Unix 128+signal convention for signal terminations. @@ -188,13 +191,32 @@ function locateArtifact(): string { async function startLocalNetwork(): Promise { const logPath = join(TMP_DIR, "local_network.log"); const logFd = openSync(logPath, "a"); + // LOG_LEVEL defaults to "debug" so failed CI runs leave useful traces in local_network.log; + // override with LOCAL_NETWORK_LOG_LEVEL=silent when running locally and the volume is noisy. + const logLevel = process.env.LOCAL_NETWORK_LOG_LEVEL ?? "debug"; + const reportDir = join(TMP_DIR, "node-reports"); + mkdirSync(reportDir, { recursive: true }); + const nodeOptions = [ + process.env.NODE_OPTIONS, + `--report-on-signal`, + `--report-directory=${reportDir}`, + ] + .filter(Boolean) + .join(" "); const proc = spawn("aztec", ["start", "--local-network"], { cwd: TMP_DIR, stdio: ["ignore", logFd, logFd], - env: { ...process.env, LOG_LEVEL: "silent", PXE_PROVER: "none" }, + env: { + ...process.env, + LOG_LEVEL: logLevel, + PXE_PROVER: "none", + NODE_OPTIONS: nodeOptions, + }, }); closeSync(logFd); - log(` local-network pid=${proc.pid}, log=${logPath}`); + log( + ` local-network pid=${proc.pid}, log=${logPath}, LOG_LEVEL=${logLevel}`, + ); // Kill the network on process exit (including SIGINT/SIGTERM via the signal handlers). process.on("exit", () => { @@ -214,6 +236,10 @@ async function startLocalNetwork(): Promise { ); } if (Date.now() > deadline) { + try { + process.kill(proc.pid!, "SIGUSR2"); + await delay(2000); + } catch {} dumpTail(logPath); fail( `timed out after ${msToSecs(LOCAL_NETWORK_READY_TIMEOUT_MS)}s waiting for local-network /status (see ${logPath})`, @@ -306,7 +332,7 @@ function leaveTmpDirForInspection() { console.error(`>>> Left tmp dir at ${TMP_DIR} for inspection`); } -function dumpTail(path: string, lines = 100) { +function dumpTail(path: string, lines = 400) { if (!existsSync(path)) { return; } diff --git a/docs/docs-developers/docs/aztec-nr/standards/aip-721.md b/docs/docs-developers/docs/aztec-nr/standards/aip-721.md index 7d307265a034..d661cde80af9 100644 --- a/docs/docs-developers/docs/aztec-nr/standards/aip-721.md +++ b/docs/docs-developers/docs/aztec-nr/standards/aip-721.md @@ -49,7 +49,7 @@ impl NFTNote { // ... creates encrypted log and validity commitment let partial_note = PartialNFTNote { commitment }; let validity_commitment = partial_note.compute_validity_commitment(completer); - context.push_nullifier(validity_commitment); + context.push_nullifier_unsafe(validity_commitment); partial_note } } diff --git a/docs/docs-developers/docs/foundational-topics/advanced/authwit.md b/docs/docs-developers/docs/foundational-topics/advanced/authwit.md index 664d2b5fe99f..d9cf8880def3 100644 --- a/docs/docs-developers/docs/foundational-topics/advanced/authwit.md +++ b/docs/docs-developers/docs/foundational-topics/advanced/authwit.md @@ -144,7 +144,7 @@ You can cancel an authwit before it's used by emitting its nullifier directly. T fn cancel_authwit(inner_hash: Field) { let on_behalf_of = self.msg_sender(); let nullifier = compute_authwit_nullifier(on_behalf_of, inner_hash); - self.context.push_nullifier(nullifier); + self.context.push_nullifier_unsafe(nullifier); } ``` diff --git a/docs/docs-developers/docs/resources/migration_notes.md b/docs/docs-developers/docs/resources/migration_notes.md index 077d386681f6..f519a12ef398 100644 --- a/docs/docs-developers/docs/resources/migration_notes.md +++ b/docs/docs-developers/docs/resources/migration_notes.md @@ -9,6 +9,87 @@ Aztec is in active development. Each version may introduce breaking changes that ## TBD +### [Aztec.js] `AccountManager.create` takes an options bag + +`AccountManager.create` no longer takes `salt` as a positional argument. The trailing `salt?: Salt` parameter has been folded into a new `AccountManagerCreateOptions` bag alongside `immutablesHash` and `deployer`: + +```diff +- AccountManager.create(wallet, secret, accountContract, salt) ++ AccountManager.create(wallet, secret, accountContract, { salt }) +``` + +`immutablesHash` lets callers commit a non-zero immutables hash on the resulting `ContractInstance` (folded into the salted initialization hash, so it affects the derived address). `deployer` overrides the deployer address recorded on the instance (defaults to `AztecAddress.ZERO`). The same `immutablesHash` field is now also threaded through `DeployMethod` / `DeployAccountMethod` so the address derived at deploy time matches the one on `accountManager.getInstance()`. + +### [Aztec.nr] Defining a custom `sync_state` function now requires `AztecConfig` + +Contracts that previously overrode the default `sync_state` by defining their own function with that name will now get a compile error. Use `AztecConfig::custom_sync_state()` instead. + +The custom hook receives the same parameters as `do_sync_state` and is responsible for calling it if default behavior is also desired. You can perform work before and/or after the default `do_sync_state` call, or skip it entirely. + +```diff ++ unconstrained fn my_custom_sync( ++ contract_address: AztecAddress, ++ compute_note_hash: ComputeNoteHash, ++ compute_note_nullifier: ComputeNoteNullifier, ++ process_custom_message: Option, ++ offchain_inbox_sync: Option, ++ scope: AztecAddress, ++ ) { ++ // optional: work before default sync ++ do_sync_state(contract_address, compute_note_hash, compute_note_nullifier, process_custom_message, offchain_inbox_sync, scope); ++ // optional: work after default sync ++ } + +- #[aztec] ++ #[aztec(::aztec::macros::AztecConfig::new().custom_sync_state(crate::my_custom_sync))] + contract MyContract { +- use aztec::macros::functions::external; +- +- #[external("utility")] +- unconstrained fn sync_state(scope: AztecAddress) { +- // custom sync logic +- } + } +``` + +**Impact**: Only contracts that manually defined a `sync_state` function are affected. Contracts using the default macro-generated `sync_state` require no changes. + +### [Aztec.nr] `push_nullifier` renamed to `push_nullifier_unsafe` + +`PrivateContext::push_nullifier` and `PublicContext::push_nullifier` have been renamed to `push_nullifier_unsafe` to +make it clear that they are low-level functions that require careful domain separation. This is consistent with the +`_unsafe` suffix already used by `emit_private_log_unsafe`, `emit_raw_note_log_unsafe`, and `emit_public_log_unsafe`. + +```diff +- context.push_nullifier(nullifier); ++ context.push_nullifier_unsafe(nullifier); +``` + +Prefer higher-level abstractions like `SingleUseClaim` or `destroy_note` which handle domain separation automatically. + +### [Aztec.nr] `LogRetrievalRequest` now includes `source`, `from_block`, and `to_block` fields + +`LogRetrievalRequest` has been extended with three new fields to support filtering logs by source and block range. The `get_logs_by_tag` oracle now also returns all matching logs per tag instead of only the first match. + +A `LogRetrievalRequest::new(contract_address, tag)` constructor is provided that defaults to querying both public and private logs with no block range filter: + +```rust +LogRetrievalRequest::new(contract_address, my_tag) +``` + +If you need to customize source or block range, construct the struct manually with the new fields: + +```diff + LogRetrievalRequest { + tag: my_tag, ++ source: LogSource.PUBLIC_AND_PRIVATE, ++ from_block: Option::none(), ++ to_block: Option::none(), + } +``` + +`source` controls which RPCs are queried: `LogSource.PRIVATE`, `LogSource.PUBLIC`, or `LogSource.PUBLIC_AND_PRIVATE`. `from_block` and `to_block` define a half-open `[from, to)` block range filter. Both are `Option` and default to `Option::none()` (no filtering). + ### [Aztec.nr] `emit_private_log_unsafe` / `emit_raw_note_log_unsafe` now take `BoundedVec` The old array-based `emit_private_log_unsafe(tag, log: [Field; N], length)` and `emit_raw_note_log_unsafe(tag, log: [Field; N], length, note_hash_counter)` have been removed. The temporary `_vec_unsafe` variants introduced in a prior release have been renamed to take their place. @@ -102,7 +183,7 @@ The `Schnorr` TypeScript API in `@aztec/foundation/crypto/schnorr` keeps the sam + env.call_public_incognito(SampleContract::at(addr).some_function()); ``` -If you need to call a public function *with* a sender, use `call_public` instead. +If you need to call a public function _with_ a sender, use `call_public` instead. ### [Aztec.nr] TXE `view_public_incognito` is deprecated @@ -255,7 +336,13 @@ If you set `Noir: Nargo Path` in the VS Code Noir extension to `$HOME/.aztec/cur ```typescript const result = await contract.methods.read_balance(account).simulate({ overrides: { - publicStorage: [{ contract: contract.address, slot: BALANCE_SLOT, value: new Fr(1_000_000n) }], + publicStorage: [ + { + contract: contract.address, + slot: BALANCE_SLOT, + value: new Fr(1_000_000n), + }, + ], }, }); ``` @@ -272,7 +359,7 @@ Direct callers of the `SimulationOverrides` constructor must switch from a posit `overrides.contracts` swaps contract instances in the simulator's contract DB — useful for simulating a contract being on a different class than the one it was deployed with. To simulate a complete onchain upgrade flow, use the `fastForwardContractUpdate` helper which returns a `SimulationOverrides` covering both registry storage rewrites and the upgraded instance entry: ```typescript -import { fastForwardContractUpdate } from '@aztec/aztec.js'; +import { fastForwardContractUpdate } from "@aztec/aztec.js"; const overrides = await fastForwardContractUpdate({ instanceAddress: contract.address, @@ -399,8 +486,8 @@ If you want the latest L1-confirmed checkpoint regardless of proposed state, swi ```ts // Throws BadRequestError when a proposed entry exists at the resolved number: -await node.getCheckpoint('proposed', { includeAttestations: true }); -await node.getCheckpoint('proposed', { includeL1PublishInfo: true }); +await node.getCheckpoint("proposed", { includeAttestations: true }); +await node.getCheckpoint("proposed", { includeL1PublishInfo: true }); // And when a by-number / by-slot lookup falls back to a proposed entry: await node.getCheckpoint({ number: N }, { includeAttestations: true }); diff --git a/noir-projects/aztec-nr/aztec/src/authwit/account.nr b/noir-projects/aztec-nr/aztec/src/authwit/account.nr index e35a5378ac05..72f5607f1392 100644 --- a/noir-projects/aztec-nr/aztec/src/authwit/account.nr +++ b/noir-projects/aztec-nr/aztec/src/authwit/account.nr @@ -74,7 +74,7 @@ impl AccountActions<&mut PrivateContext> { if cancellable { let tx_nullifier = poseidon2_hash_with_separator([app_payload.tx_nonce], DOM_SEP__TX_NULLIFIER); - self.context.push_nullifier(tx_nullifier); + self.context.push_nullifier_unsafe(tx_nullifier); } } diff --git a/noir-projects/aztec-nr/aztec/src/authwit/auth.nr b/noir-projects/aztec-nr/aztec/src/authwit/auth.nr index c62d4c5172e6..9fe5abad3d66 100644 --- a/noir-projects/aztec-nr/aztec/src/authwit/auth.nr +++ b/noir-projects/aztec-nr/aztec/src/authwit/auth.nr @@ -282,7 +282,7 @@ pub fn assert_inner_hash_valid_authwit(context: &mut PrivateContext, on_behalf_o // already be handled in the verification, so we just need something to nullify, that allows the same inner_hash // for multiple actors. let nullifier = compute_authwit_nullifier(on_behalf_of, inner_hash); - context.push_nullifier(nullifier); + context.push_nullifier_unsafe(nullifier); } /// Assert that `on_behalf_of` has authorized the current call in the authentication registry diff --git a/noir-projects/aztec-nr/aztec/src/context/private_context.nr b/noir-projects/aztec-nr/aztec/src/context/private_context.nr index 32f57b6aac67..7631593cefb5 100644 --- a/noir-projects/aztec-nr/aztec/src/context/private_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/private_context.nr @@ -366,16 +366,17 @@ impl PrivateContext { /// The raw `nullifier` is not what is inserted into the Aztec state tree: it will be first siloed by contract /// address via [`crate::protocol::hash::compute_siloed_nullifier`] in order to prevent accidental or malicious /// interference of nullifiers from different contracts. - pub fn push_nullifier(&mut self, nullifier: Field) { + pub fn push_nullifier_unsafe(&mut self, nullifier: Field) { notify_created_nullifier(nullifier); self.nullifiers.push(Nullifier { value: nullifier, note_hash: 0 }.count(self.next_counter())); } /// Creates a new [nullifier](crate::nullifier) associated with a note. /// - /// This is a variant of [`PrivateContext::push_nullifier`] that is used for note nullifiers, i.e. nullifiers that - /// correspond to a note. If a note and its nullifier are created in the same transaction, then the private kernels - /// will 'squash' these values, deleting them both as if they never existed and reducing transaction fees. + /// This is a variant of [`PrivateContext::push_nullifier_unsafe`] that is used for note nullifiers, i.e. + /// nullifiers that correspond to a note. If a note and its nullifier are created in the same transaction, then + /// the private kernels will 'squash' these values, deleting them both as if they never existed and reducing + /// transaction fees. /// /// The `nullification_note_hash` must be the result of calling /// [`crate::note::utils::compute_confirmed_note_hash_for_nullification`] for pending notes, and `0` for settled @@ -386,10 +387,10 @@ impl PrivateContext { /// This is a low-level function that must be used with great care to avoid subtle corruption of contract state. /// Instead of calling this function, consider using the higher-level [`crate::note::lifecycle::destroy_note`]. /// - /// The precautions listed for [`PrivateContext::push_nullifier`] apply here as well, and callers should + /// The precautions listed for [`PrivateContext::push_nullifier_unsafe`] apply here as well, and callers should /// additionally ensure `nullification_note_hash` corresponds to a note emitted by this contract, with its hash /// computed in the same transaction execution phase as the call to this function. Finally, only this function - /// should be used for note nullifiers, never [`PrivateContext::push_nullifier`]. + /// should be used for note nullifiers, never [`PrivateContext::push_nullifier_unsafe`]. /// /// Failure to do these things can result in unprovable contexts, accidental deletion of notes, or double-spend /// attacks. @@ -858,7 +859,7 @@ impl PrivateContext { ); // Push nullifier (and the "commitment" corresponding to this can be "empty") - self.push_nullifier(nullifier) + self.push_nullifier_unsafe(nullifier) } /// Emits a private log (an array of Fields) that will be published to an Ethereum blob. diff --git a/noir-projects/aztec-nr/aztec/src/context/public_context.nr b/noir-projects/aztec-nr/aztec/src/context/public_context.nr index 33b87501cb7c..49189174e051 100644 --- a/noir-projects/aztec-nr/aztec/src/context/public_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/public_context.nr @@ -253,7 +253,7 @@ impl PublicContext { assert(!self.nullifier_exists_unsafe(nullifier, self.this_address()), "L1-to-L2 message is already nullified"); assert(self.l1_to_l2_msg_exists(message_hash, leaf_index), "Tried to consume nonexistent L1-to-L2 message"); - self.push_nullifier(nullifier); + self.push_nullifier_unsafe(nullifier); } /// Sends an "L2 -> L1 message" from this function (Aztec, L2) to a smart contract on Ethereum (L1). L1 contracts @@ -406,7 +406,7 @@ impl PublicContext { /// The raw `nullifier` is not what is inserted into the Aztec state tree: it will be first siloed by contract /// address via [`crate::protocol::hash::compute_siloed_nullifier`] in order to prevent accidental or malicious /// interference of nullifiers from different contracts. - pub fn push_nullifier(_self: Self, nullifier: Field) { + pub fn push_nullifier_unsafe(_self: Self, nullifier: Field) { // Safety: AVM opcodes are constrained by the AVM itself unsafe { avm::emit_nullifier(nullifier) }; } diff --git a/noir-projects/aztec-nr/aztec/src/ephemeral/mod.nr b/noir-projects/aztec-nr/aztec/src/ephemeral/mod.nr index 9e4efde8cd5a..35be7114e025 100644 --- a/noir-projects/aztec-nr/aztec/src/ephemeral/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/ephemeral/mod.nr @@ -1,5 +1,6 @@ use crate::oracle::ephemeral; use crate::protocol::traits::{Deserialize, Serialize}; +use crate::protocol::utils::{reader::Reader, writer::Writer}; /// A dynamically sized array that exists only during a single contract call frame. /// @@ -101,6 +102,34 @@ impl EphemeralArray { } } +/// Serializes an `EphemeralArray` as its slot identifier, allowing oracle function signatures to use +/// `EphemeralArray` instead of opaque `Field` slots. +impl Serialize for EphemeralArray { + let N: u32 = 1; + + fn serialize(self) -> [Field; Self::N] { + [self.slot] + } + + fn stream_serialize(self, writer: &mut Writer) { + writer.write(self.slot); + } +} + +/// Deserializes a single Field into an `EphemeralArray` handle, treating the field value as the slot identifier. +/// This is the inverse of [`Serialize`]. +impl Deserialize for EphemeralArray { + let N: u32 = 1; + + fn deserialize(fields: [Field; Self::N]) -> Self { + Self { slot: fields[0] } + } + + fn stream_deserialize(reader: &mut Reader) -> Self { + Self { slot: reader.read() } + } +} + mod test { use crate::test::helpers::test_environment::TestEnvironment; use crate::test::mocks::MockStruct; diff --git a/noir-projects/aztec-nr/aztec/src/event/event_emission.nr b/noir-projects/aztec-nr/aztec/src/event/event_emission.nr index b24c06992d5d..192fcfb99cf8 100644 --- a/noir-projects/aztec-nr/aztec/src/event/event_emission.nr +++ b/noir-projects/aztec-nr/aztec/src/event/event_emission.nr @@ -29,7 +29,7 @@ where // The event commitment is emitted as a nullifier instead of as a note because these are simpler: nullifiers cannot // be squashed, making kernel processing simpler, and they have no nonce that recipients need to discover. let commitment = compute_private_event_commitment(event, randomness); - context.push_nullifier(commitment); + context.push_nullifier_unsafe(commitment); EventMessage::new(NewEvent { event, randomness }, context) } diff --git a/noir-projects/aztec-nr/aztec/src/history/nullifier.nr b/noir-projects/aztec-nr/aztec/src/history/nullifier.nr index 47ae008258b9..1ec2b4dd9e09 100644 --- a/noir-projects/aztec-nr/aztec/src/history/nullifier.nr +++ b/noir-projects/aztec-nr/aztec/src/history/nullifier.nr @@ -15,8 +15,9 @@ mod test; /// Asserts that a nullifier existed by the time a block was mined. /// /// This function takes a _siloed_ nullifier, i.e. the value that is actually stored in the tree. Use -/// [`crate::protocol::hash::compute_siloed_nullifier`] to convert an inner nullifier (what -/// [`PrivateContext::push_nullifier`](crate::context::PrivateContext::push_nullifier) takes) into a siloed one. +/// [`crate::protocol::hash::compute_siloed_nullifier`] to convert an inner nullifier +/// (what [`PrivateContext::push_nullifier_unsafe`](crate::context::PrivateContext::push_nullifier_unsafe) +/// takes) into a siloed one. /// /// Note that this does not mean that the nullifier was created **at** `block_header`, only that it was present in the /// tree once all transactions from `block_header` were executed. @@ -56,8 +57,9 @@ pub fn assert_nullifier_existed_by(block_header: BlockHeader, siloed_nullifier: /// Asserts that a nullifier did not exist by the time a block was mined. /// /// This function takes a _siloed_ nullifier, i.e. the value that is actually stored in the tree. Use -/// [`crate::protocol::hash::compute_siloed_nullifier`] to convert an inner nullifier (what -/// [`PrivateContext::push_nullifier`](crate::context::PrivateContext::push_nullifier) takes) into a siloed one. +/// [`crate::protocol::hash::compute_siloed_nullifier`] to convert an inner nullifier +/// (what [`PrivateContext::push_nullifier_unsafe`](crate::context::PrivateContext::push_nullifier_unsafe) +/// takes) into a siloed one. /// /// In order to prove that a nullifier **did** exist by `block_header`, use [`assert_nullifier_existed_by`]. /// @@ -69,8 +71,8 @@ pub fn assert_nullifier_existed_by(block_header: BlockHeader, siloed_nullifier: /// /// If you **must** prove that a nullifier does not exist by the time a transaction is executed, there only two ways to /// do this: by actually emitting the nullifier via -/// [`PrivateContext::push_nullifier`](crate::context::PrivateContext::push_nullifier) (which can of course can be done -/// once), or by calling a public contract function that calls +/// [`PrivateContext::push_nullifier_unsafe`](crate::context::PrivateContext::push_nullifier_unsafe) +/// (which can of course only be done once), or by calling a public contract function that calls /// [`PublicContext::nullifier_exists_unsafe`](crate::context::PublicContext::nullifier_exists_unsafe) (which leaks /// that this nullifier is being checked): /// diff --git a/noir-projects/aztec-nr/aztec/src/macros/aztec.nr b/noir-projects/aztec-nr/aztec/src/macros/aztec.nr index 84ee1bf2812b..dd6c11a96241 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/aztec.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/aztec.nr @@ -16,7 +16,7 @@ use crate::{ storage::STORAGE_LAYOUT_NAME, utils::{is_fn_contract_library_method, is_fn_external, is_fn_internal, is_fn_test, module_has_storage}, }, - messages::discovery::CustomMessageHandler, + messages::discovery::{CustomMessageHandler, CustomSyncHandler}, }; use compute_note_hash_and_nullifier::generate_contract_library_methods_compute_note_hash_and_nullifier; @@ -24,8 +24,8 @@ use compute_note_hash_and_nullifier::generate_contract_library_methods_compute_n /// Configuration for the [`aztec`] macro. /// /// This type lets users override different parts of the default aztec-nr contract behavior, such -/// as message handling. These are advanced features that require careful understanding of -/// the behavior of these systems. +/// as message handling and state synchronization. These are advanced features that require careful +/// understanding of the behavior of these systems. /// /// ## Examples /// @@ -34,7 +34,8 @@ use compute_note_hash_and_nullifier::generate_contract_library_methods_compute_n /// contract MyContract { ... } /// ``` pub struct AztecConfig { - custom_message_handler: Option>, + custom_message_handler: Option, + custom_sync_state: Option, } impl AztecConfig { @@ -43,7 +44,7 @@ impl AztecConfig { /// Calling `new` is equivalent to invoking the [`aztec`] macro with no parameters. The different methods /// (e.g. [`AztecConfig::custom_message_handler`]) can then be used to change the default behavior. pub comptime fn new() -> Self { - Self { custom_message_handler: Option::none() } + Self { custom_message_handler: Option::none(), custom_sync_state: Option::none() } } /// Sets a handler for custom messages. @@ -53,8 +54,23 @@ impl AztecConfig { /// /// `handler` must be a function that conforms to the /// [`crate::messages::discovery::CustomMessageHandler`] type signature. - pub comptime fn custom_message_handler(_self: Self, handler: CustomMessageHandler<()>) -> Self { - Self { custom_message_handler: Option::some(handler) } + pub comptime fn custom_message_handler(&mut self, handler: CustomMessageHandler) -> Self { + self.custom_message_handler = Option::some(handler); + *self + } + + /// Overrides the default state synchronization logic. + /// + /// The generated `sync_state` function will call `handler` instead of + /// [`crate::messages::discovery::do_sync_state`]. The handler receives all of the same parameters, so it can + /// run custom logic (e.g. fetching and decrypting custom logs) before, after, or instead of calling + /// `do_sync_state`. + /// + /// `handler` must be a function that conforms to the + /// [`crate::messages::discovery::CustomSyncHandler`] type signature. + pub comptime fn custom_sync_state(&mut self, handler: CustomSyncHandler) -> Self { + self.custom_sync_state = Option::some(handler); + *self } } @@ -105,7 +121,7 @@ pub comptime fn aztec(m: Module, args: [AztecConfig]) -> Quoted { let fn_abi_exports = create_fn_abi_exports(m); // We generate `_compute_note_hash`, `_compute_note_nullifier` (and the deprecated - // `_compute_note_hash_and_nullifier` wrapper) and `sync_state` functions only if they are not already implemented. + // `_compute_note_hash_and_nullifier` wrapper) only if they are not already implemented. // If they are implemented we just insert empty quotes. let contract_library_method_compute_note_hash_and_nullifier = if !m.functions().any(|f| { // Note that we don't test for `_compute_note_hash` or `_compute_note_nullifier` in order to make this simpler @@ -121,19 +137,29 @@ pub comptime fn aztec(m: Module, args: [AztecConfig]) -> Quoted { let handler = config.custom_message_handler.unwrap(); quote { Option::some($handler) } } else { - quote { Option::>::none() } + quote { Option::::none() } }; let offchain_inbox_sync_option = quote { Option::some(aztec::messages::processing::offchain::sync_inbox) }; - let sync_state_fn_and_abi_export = if !m.functions().any(|f| f.name() == quote { sync_state }) { - generate_sync_state(process_custom_message_option, offchain_inbox_sync_option) + if m.functions().any(|f| f.name() == quote { sync_state }) { + panic( + "User-defined 'sync_state' is not allowed. Use AztecConfig::custom_sync_state() to customize sync behavior.", + ); + } + + let custom_sync_handler = if config.custom_sync_state.is_some() { + let handler = config.custom_sync_state.unwrap(); + Option::some(quote { $handler }) } else { - quote {} + Option::none() }; + let sync_state_fn_and_abi_export = + generate_sync_state(process_custom_message_option, offchain_inbox_sync_option, custom_sync_handler); + if m.functions().any(|f| f.name() == quote { offchain_receive }) { panic( "User-defined 'offchain_receive' is not allowed. The function is auto-injected by the #[aztec] macro. See https://docs.aztec.network/errors/7", @@ -223,7 +249,36 @@ comptime fn generate_contract_interface(m: Module) -> Quoted { } /// Generates the `sync_state` utility function that performs message discovery. -comptime fn generate_sync_state(process_custom_message_option: Quoted, offchain_inbox_sync_option: Quoted) -> Quoted { +comptime fn generate_sync_state( + process_custom_message_option: Quoted, + offchain_inbox_sync_option: Quoted, + custom_sync_handler: Option, +) -> Quoted { + let body = if custom_sync_handler.is_some() { + let handler = custom_sync_handler.unwrap(); + quote { + $handler( + address, + _compute_note_hash, + _compute_note_nullifier, + $process_custom_message_option, + $offchain_inbox_sync_option, + scope, + ); + } + } else { + quote { + aztec::messages::discovery::do_sync_state( + address, + _compute_note_hash, + _compute_note_nullifier, + $process_custom_message_option, + $offchain_inbox_sync_option, + scope, + ); + } + }; + quote { pub struct sync_state_parameters { pub scope: aztec::protocol::address::AztecAddress, @@ -237,14 +292,7 @@ comptime fn generate_sync_state(process_custom_message_option: Quoted, offchain_ #[aztec::macros::internals_functions_generation::abi_attributes::abi_utility] unconstrained fn sync_state(scope: aztec::protocol::address::AztecAddress) { let address = aztec::context::UtilityContext::new().this_address(); - aztec::messages::discovery::do_sync_state( - address, - _compute_note_hash, - _compute_note_nullifier, - $process_custom_message_option, - $offchain_inbox_sync_option, - scope, - ); + $body } } } diff --git a/noir-projects/aztec-nr/aztec/src/macros/functions/initialization_utils.nr b/noir-projects/aztec-nr/aztec/src/macros/functions/initialization_utils.nr index a406af070329..d4634b4cf0a2 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/functions/initialization_utils.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/functions/initialization_utils.nr @@ -54,14 +54,14 @@ global EMIT_PUBLIC_INIT_NULLIFIER_SELECTOR: FunctionSelector = comptime { /// emitting the public nullifier without the private one). The macro-generated code handles this automatically. pub fn mark_as_initialized_public(context: PublicContext) { let init_nullifier = compute_public_initialization_nullifier(context.this_address()); - context.push_nullifier(init_nullifier); + context.push_nullifier_unsafe(init_nullifier); } fn mark_as_initialized_private(context: &mut PrivateContext) { let address = (*context).this_address(); let instance = get_contract_instance(address); let init_nullifier = compute_private_initialization_nullifier(address, instance.initialization_hash); - context.push_nullifier(init_nullifier); + context.push_nullifier_unsafe(init_nullifier); } /// Emits the private initialization nullifier and, if relevant, enqueues the emission of the public one. @@ -88,7 +88,7 @@ pub fn mark_as_initialized_from_public_initializer(context: PublicContext) { // currently executing, which by definition must have been deployed. let init_hash = get_contract_instance_initialization_hash_avm(address).unwrap(); let private_nullifier = compute_private_initialization_nullifier(address, init_hash); - context.push_nullifier(private_nullifier); + context.push_nullifier_unsafe(private_nullifier); mark_as_initialized_public(context); } diff --git a/noir-projects/aztec-nr/aztec/src/messages/discovery/mod.nr b/noir-projects/aztec-nr/aztec/src/messages/discovery/mod.nr index bb51eb7755cc..a5651e627c49 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/discovery/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/discovery/mod.nr @@ -98,7 +98,7 @@ pub type ComputeNoteHashAndNullifier = unconstrained fn[Env](/* packed_note /// Contracts that emit custom messages (i.e. any with a message type that is not in [`crate::messages::msg_type`]) /// need to use [`crate::macros::AztecConfig::custom_message_handler`] with a function of this type in order to /// process them. They will otherwise be **silently ignored**. -pub type CustomMessageHandler = unconstrained fn[Env]( +pub type CustomMessageHandler = unconstrained fn( /* contract_address */AztecAddress, /* msg_type_id */ u64, /* msg_metadata */ u64, @@ -106,6 +106,20 @@ pub type CustomMessageHandler = unconstrained fn[Env]( /* message_context */ MessageContext, /* scope */ AztecAddress); +/// Custom state synchronization handler. +/// +/// When set via [`crate::macros::AztecConfig::custom_sync_state`], the generated `sync_state` function will call +/// this handler instead of [`do_sync_state`]. It receives all of the same parameters, so it can run custom logic +/// (e.g. fetching and decrypting custom logs) before, after, or instead of calling [`do_sync_state`]. +pub type CustomSyncHandler = unconstrained fn( + /* contract_address */ AztecAddress, + /* compute_note_hash */ ComputeNoteHash, + /* compute_note_nullifier */ ComputeNoteNullifier, + /* process_custom_message */ Option, + /* offchain_inbox_sync */ Option, + /* scope */ AztecAddress, +); + /// Synchronizes the contract's private state with the network. /// /// As blocks are mined, it is possible for a contract's private state to change (e.g. with new notes being created), @@ -114,12 +128,12 @@ pub type CustomMessageHandler = unconstrained fn[Env]( /// /// The private state will be synchronized up to the block that will be used for private transactions (i.e. the anchor /// block. This will typically be close to the tip of the chain. -pub unconstrained fn do_sync_state( +pub unconstrained fn do_sync_state( contract_address: AztecAddress, compute_note_hash: ComputeNoteHash, compute_note_nullifier: ComputeNoteNullifier, - process_custom_message: Option>, - offchain_inbox_sync: Option>, + process_custom_message: Option, + offchain_inbox_sync: Option, scope: AztecAddress, ) { aztecnr_debug_log!("Performing state synchronization"); @@ -205,8 +219,8 @@ mod test { logs.push(PendingTaggedLog { log: BoundedVec::new(), context: std::mem::zeroed() }); assert_eq(logs.len(), 1); - let no_handler: Option> = Option::none(); - let no_inbox_sync: Option> = Option::none(); + let no_handler: Option = Option::none(); + let no_inbox_sync: Option = Option::none(); do_sync_state( contract_address, dummy_compute_note_hash, diff --git a/noir-projects/aztec-nr/aztec/src/messages/discovery/partial_notes.nr b/noir-projects/aztec-nr/aztec/src/messages/discovery/partial_notes.nr index ff61bfc6b69b..63548b99f117 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/discovery/partial_notes.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/discovery/partial_notes.nr @@ -1,5 +1,6 @@ use crate::{ capsules::CapsuleArray, + ephemeral::EphemeralArray, messages::{ discovery::{ComputeNoteHash, ComputeNoteNullifier, nonce_discovery::attempt_note_nonce_discovery}, encoding::MAX_MESSAGE_CONTENT_LEN, @@ -88,17 +89,17 @@ pub(crate) unconstrained fn fetch_and_process_partial_note_completion_logs( // Each of the pending partial notes might get completed by a log containing its public values. For performance // reasons, we fetch all of these logs concurrently and then process them one by one, minimizing the amount of time // waiting for the node roundtrip. - let maybe_completion_logs = get_pending_partial_notes_completion_logs(contract_address, pending_partial_notes); + let completion_logs = get_pending_partial_notes_completion_logs(contract_address, pending_partial_notes); - // Each entry in the maybe completion logs array corresponds to the entry in the pending partial notes array at the - // same index. This means we can use the same index as we iterate through the responses to get both the partial - // note and the log that might complete it. - assert_eq(maybe_completion_logs.len(), pending_partial_notes.len()); + // Each entry in the completion logs array corresponds to the entry in the pending partial notes array at the same + // index. Each inner array contains all matching LogRetrievalResponses. + assert_eq(completion_logs.len(), pending_partial_notes.len()); - maybe_completion_logs.for_each(|i, maybe_log: Option| { + completion_logs.for_each(|i, logs_for_tag: EphemeralArray| { let pending_partial_note = pending_partial_notes.get(i); + let num_logs = logs_for_tag.len(); - if maybe_log.is_none() { + if num_logs == 0 { aztecnr_debug_log_format!("Found no completion logs for partial note with tag {}")( [pending_partial_note.note_completion_log_tag], ); @@ -107,10 +108,15 @@ pub(crate) unconstrained fn fetch_and_process_partial_note_completion_logs( // searching for this tagged log when performing message discovery in the future until we either find it or // the entry is somehow removed from the array. } else { + assert( + num_logs == 1, + f"Expected at most 1 completion log per partial note, got {num_logs}", + ); + aztecnr_debug_log_format!("Completion log found for partial note with tag {}")([ pending_partial_note.note_completion_log_tag, ]); - let log = maybe_log.unwrap(); + let log = logs_for_tag.get(0); // The first field in the completion log payload is the storage slot, followed by the public note // content fields. diff --git a/noir-projects/aztec-nr/aztec/src/messages/discovery/process_message.nr b/noir-projects/aztec-nr/aztec/src/messages/discovery/process_message.nr index 55937a9ec6ea..96726873e4dc 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/discovery/process_message.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/discovery/process_message.nr @@ -26,11 +26,11 @@ use crate::protocol::address::AztecAddress; /// /// Events are processed by computing an event commitment from the serialized event data and its randomness field, then /// enqueueing the event data and commitment for validation. -pub unconstrained fn process_message_ciphertext( +pub unconstrained fn process_message_ciphertext( contract_address: AztecAddress, compute_note_hash: ComputeNoteHash, compute_note_nullifier: ComputeNoteNullifier, - process_custom_message: Option>, + process_custom_message: Option, message_ciphertext: BoundedVec, message_context: MessageContext, recipient: AztecAddress, @@ -52,11 +52,11 @@ pub unconstrained fn process_message_ciphertext( } } -pub(crate) unconstrained fn process_message_plaintext( +pub(crate) unconstrained fn process_message_plaintext( contract_address: AztecAddress, compute_note_hash: ComputeNoteHash, compute_note_nullifier: ComputeNoteNullifier, - process_custom_message: Option>, + process_custom_message: Option, message_plaintext: BoundedVec, message_context: MessageContext, recipient: AztecAddress, diff --git a/noir-projects/aztec-nr/aztec/src/messages/processing/log_retrieval_request.nr b/noir-projects/aztec-nr/aztec/src/messages/processing/log_retrieval_request.nr index c303df34e850..e853beda9ab3 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/processing/log_retrieval_request.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/processing/log_retrieval_request.nr @@ -1,30 +1,89 @@ use crate::protocol::{address::AztecAddress, traits::Serialize}; -/// A request for the `bulk_retrieve_logs` oracle to fetch either: -/// - a public log emitted by `contract_address` with `unsiloed_tag` -/// - a private log with tag equal to `compute_siloed_private_log_first_field(contract_address, unsiloed_tag)`. +pub(crate) struct LogSourceEnum { + pub PRIVATE: Field, + pub PUBLIC: Field, + pub PUBLIC_AND_PRIVATE: Field, +} + +pub(crate) global LogSource: LogSourceEnum = + LogSourceEnum { PRIVATE: 0, PUBLIC: 1, PUBLIC_AND_PRIVATE: 2 }; + +/// A request for the `bulk_retrieve_logs` oracle to fetch all logs matching a tag. #[derive(Serialize)] pub(crate) struct LogRetrievalRequest { pub contract_address: AztecAddress, pub unsiloed_tag: Field, - // TODO(#15052): choose source: public, private or either (current behavior) + /// Which log source to query: public, private, or both (the default). See [`LogSource`]. + pub source: Field, + /// Inclusive lower bound on block number. When unset, logs from the first block are included. + pub from_block: Option, + /// Exclusive upper bound on block number. When unset, logs up to the anchor block are included. + pub to_block: Option, +} + +impl LogRetrievalRequest { + /// Creates a request that queries both public and private logs with no block range filter. + pub(crate) fn new(contract_address: AztecAddress, unsiloed_tag: Field) -> Self { + LogRetrievalRequest { + contract_address, + unsiloed_tag, + source: LogSource.PUBLIC_AND_PRIVATE, + from_block: Option::none(), + to_block: Option::none(), + } + } } mod test { use crate::protocol::{address::AztecAddress, traits::{FromField, Serialize}}; - use super::LogRetrievalRequest; + use super::{LogRetrievalRequest, LogSource}; + + #[test] + fn serialization_of_defaults_matches_typescript() { + let request = LogRetrievalRequest { + contract_address: AztecAddress::from_field(1), + unsiloed_tag: 2, + source: LogSource.PUBLIC_AND_PRIVATE, + from_block: Option::none(), + to_block: Option::none(), + }; + + // We define the serialization in Noir and the deserialization in TS. If the deserialization changes from + // the snapshot value below, then log_retrieval_request.test.ts must be updated with the same value. + // Ideally we'd autogenerate this, but for now we only have single-sided snapshot generation, from TS to + // Noir, which is not what we need here. + let expected_serialization = [ + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000002, + 0x0000000000000000000000000000000000000000000000000000000000000002, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + ]; + + assert_eq(request.serialize(), expected_serialization); + } #[test] - fn serialization_matches_typescript() { - let request = LogRetrievalRequest { contract_address: AztecAddress::from_field(1), unsiloed_tag: 2 }; + fn serialization_with_values_matches_typescript() { + let request = LogRetrievalRequest { + contract_address: AztecAddress::from_field(1), + unsiloed_tag: 2, + source: LogSource.PUBLIC, + from_block: Option::some(10), + to_block: Option::some(20), + }; - // We define the serialization in Noir and the deserialization in TS. If the deserialization changes from the - // snapshot value below, then log_retrieval_request.test.ts must be updated with the same value. Ideally we'd - // autogenerate this, but for now we only have single-sided snapshot generation, from TS to Noir, which is not - // what we need here. let expected_serialization = [ 0x0000000000000000000000000000000000000000000000000000000000000001, 0x0000000000000000000000000000000000000000000000000000000000000002, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x000000000000000000000000000000000000000000000000000000000000000a, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000014, ]; assert_eq(request.serialize(), expected_serialization); diff --git a/noir-projects/aztec-nr/aztec/src/messages/processing/mod.nr b/noir-projects/aztec-nr/aztec/src/messages/processing/mod.nr index a793fe5df64f..f94d2cb539c5 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/processing/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/processing/mod.nr @@ -19,7 +19,10 @@ use crate::{ discovery::partial_notes::DeliveredPendingPartialNote, encoding::MESSAGE_CIPHERTEXT_LEN, logs::{event::MAX_EVENT_SERIALIZED_LEN, note::MAX_NOTE_PACKED_LEN}, - processing::{log_retrieval_request::LogRetrievalRequest, log_retrieval_response::LogRetrievalResponse}, + processing::{ + log_retrieval_request::LogRetrievalRequest, + log_retrieval_response::LogRetrievalResponse, + }, }, oracle::message_processing, }; @@ -137,8 +140,8 @@ pub unconstrained fn enqueue_event_for_validation( /// API (PXE::getPrivateEvents). pub unconstrained fn validate_and_store_enqueued_notes_and_events(scope: AztecAddress) { message_processing::validate_and_store_enqueued_notes_and_events( - NOTE_VALIDATION_REQUESTS_ARRAY_BASE_SLOT, - EVENT_VALIDATION_REQUESTS_ARRAY_BASE_SLOT, + EphemeralArray::at(NOTE_VALIDATION_REQUESTS_ARRAY_BASE_SLOT), + EphemeralArray::at(EVENT_VALIDATION_REQUESTS_ARRAY_BASE_SLOT), MAX_NOTE_PACKED_LEN as Field, MAX_EVENT_SERIALIZED_LEN as Field, scope, @@ -151,22 +154,19 @@ pub unconstrained fn validate_and_store_enqueued_notes_and_events(scope: AztecAd } /// Efficiently queries the node for logs that result in the completion of all `DeliveredPendingPartialNote`s stored in -/// a `CapsuleArray` by performing all node communication concurrently. Returns an `EphemeralArray` with Options -/// for the responses that correspond to the pending partial notes at the same index. -/// -/// For example, given an array with pending partial notes `[ p1, p2, p3 ]`, where `p1` and `p3` have corresponding -/// completion logs but `p2` does not, the returned `EphemeralArray` will have contents `[some(p1_log), none(), -/// some(p3_log)]`. +/// a `CapsuleArray` by performing all node communication concurrently. Returns a nested `EphemeralArray`, one inner +/// array per pending partial note, each containing all matching `LogRetrievalResponse`s (which may be empty if no +/// logs were found). pub(crate) unconstrained fn get_pending_partial_notes_completion_logs( contract_address: AztecAddress, pending_partial_notes: CapsuleArray, -) -> EphemeralArray> { +) -> EphemeralArray> { let log_retrieval_requests = EphemeralArray::at(LOG_RETRIEVAL_REQUESTS_ARRAY_BASE_SLOT); - // We create a LogRetrievalRequest for each PendingPartialNote in the EphemeralArray. Because we need the indices in - // the request array to match the indices in the partial note array, we can't use EphemeralArray::for_each, as that - // function has arbitrary iteration order. Instead, we manually iterate the array from the beginning and push into - // the requests array, which we expect to be empty. + // We create a LogRetrievalRequest for each PendingPartialNote in the EphemeralArray. Because we need the indices + // in the request array to match the indices in the partial note array, we can't use EphemeralArray::for_each, as + // that function has arbitrary iteration order. Instead, we manually iterate the array from the beginning and push + // into the requests array, which we expect to be empty. let mut i = 0; let pending_partial_notes_count = pending_partial_notes.len(); while i < pending_partial_notes_count { @@ -177,7 +177,7 @@ pub(crate) unconstrained fn get_pending_partial_notes_completion_logs( pending_partial_note.note_completion_log_tag, DOM_SEP__NOTE_COMPLETION_LOG_TAG, ); - log_retrieval_requests.push(LogRetrievalRequest { contract_address, unsiloed_tag: log_tag }); + log_retrieval_requests.push(LogRetrievalRequest::new(contract_address, log_tag)); i += 1; } diff --git a/noir-projects/aztec-nr/aztec/src/messages/processing/offchain.nr b/noir-projects/aztec-nr/aztec/src/messages/processing/offchain.nr index 066586b5955c..4775b95a6c74 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/processing/offchain.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/processing/offchain.nr @@ -48,7 +48,7 @@ global MAX_MSG_TTL: u64 = MAX_TX_LIFETIME + TX_EXPIRATION_TOLERANCE; /// /// The only current implementation of an `OffchainInboxSync` is [`sync_inbox`], which manages an inbox with expiration /// based eviction and automatic transaction context resolution. -pub(crate) type OffchainInboxSync = unconstrained fn[Env]( +pub type OffchainInboxSync = unconstrained fn( /* contract_address */AztecAddress, /* scope */ AztecAddress) -> EphemeralArray; /// A message delivered via the `offchain_receive` utility function. diff --git a/noir-projects/aztec-nr/aztec/src/nullifier/mod.nr b/noir-projects/aztec-nr/aztec/src/nullifier/mod.nr index 68b1453f5260..e0c6d0cdba45 100644 --- a/noir-projects/aztec-nr/aztec/src/nullifier/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/nullifier/mod.nr @@ -31,8 +31,8 @@ //! //! ## Nullifier Creation //! -//! The low-level mechanisms to create new nullifiers are [`crate::context::PrivateContext::push_nullifier`] and -//! [`crate::context::PublicContext::push_nullifier`], but these require care and can be hard to use correctly. +//! The low-level mechanisms to create new nullifiers are [`crate::context::PrivateContext::push_nullifier_unsafe`] and +//! [`crate::context::PublicContext::push_nullifier_unsafe`], but these require care and can be hard to use correctly. //! Higher-level abstractions exist which safely create nullifiers, such as [`crate::note::lifecycle::destroy_note`] //! and //! [`crate::state_vars::SingleUseClaim`]. diff --git a/noir-projects/aztec-nr/aztec/src/oracle/message_processing.nr b/noir-projects/aztec-nr/aztec/src/oracle/message_processing.nr index 7a2f2f5a666d..156c3b89dbc2 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/message_processing.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/message_processing.nr @@ -1,7 +1,8 @@ use crate::ephemeral::EphemeralArray; use crate::messages::processing::{ + event_validation_request::EventValidationRequest, log_retrieval_request::LogRetrievalRequest, log_retrieval_response::LogRetrievalResponse, MessageContext, - pending_tagged_log::PendingTaggedLog, + NoteValidationRequest, pending_tagged_log::PendingTaggedLog, }; use crate::protocol::address::AztecAddress; use crate::protocol::blob_data::TxEffect; @@ -9,24 +10,23 @@ use crate::protocol::blob_data::TxEffect; /// Finds new private logs that may have been sent to all registered accounts in PXE in the current contract and /// returns them in an ephemeral array with an oracle-allocated base slot. pub(crate) unconstrained fn get_pending_tagged_logs(scope: AztecAddress) -> EphemeralArray { - let result_slot = get_pending_tagged_logs_oracle(scope); - EphemeralArray::at(result_slot) + get_pending_tagged_logs_oracle(scope) } #[oracle(aztec_utl_getPendingTaggedLogs)] -unconstrained fn get_pending_tagged_logs_oracle(scope: AztecAddress) -> Field {} +unconstrained fn get_pending_tagged_logs_oracle(scope: AztecAddress) -> EphemeralArray {} /// Validates note/event requests stored in ephemeral arrays. pub(crate) unconstrained fn validate_and_store_enqueued_notes_and_events( - note_validation_requests_array_slot: Field, - event_validation_requests_array_slot: Field, + note_validation_requests: EphemeralArray, + event_validation_requests: EphemeralArray, max_note_packed_len: Field, max_event_serialized_len: Field, scope: AztecAddress, ) { validate_and_store_enqueued_notes_and_events_oracle( - note_validation_requests_array_slot, - event_validation_requests_array_slot, + note_validation_requests, + event_validation_requests, max_note_packed_len, max_event_serialized_len, scope, @@ -35,34 +35,39 @@ pub(crate) unconstrained fn validate_and_store_enqueued_notes_and_events( #[oracle(aztec_utl_validateAndStoreEnqueuedNotesAndEvents)] unconstrained fn validate_and_store_enqueued_notes_and_events_oracle( - note_validation_requests_array_slot: Field, - event_validation_requests_array_slot: Field, + note_validation_requests: EphemeralArray, + event_validation_requests: EphemeralArray, max_note_packed_len: Field, max_event_serialized_len: Field, scope: AztecAddress, ) {} -/// Fetches logs by tag from an ephemeral request array and returns a response ephemeral array. +/// Fetches all logs matching each request's tag and returns a nested ephemeral array. +/// +/// Each element in the outer array is an inner `EphemeralArray` containing all matching logs for +/// the request at the same index (which may be empty if no logs were found). pub(crate) unconstrained fn get_logs_by_tag( requests: EphemeralArray, -) -> EphemeralArray> { - let response_slot = get_logs_by_tag_oracle(requests.slot); - EphemeralArray::at(response_slot) +) -> EphemeralArray> { + get_logs_by_tag_oracle(requests) } #[oracle(aztec_utl_getLogsByTag)] -unconstrained fn get_logs_by_tag_oracle(request_array_slot: Field) -> Field {} +unconstrained fn get_logs_by_tag_oracle( + requests: EphemeralArray, +) -> EphemeralArray> {} /// Resolves message contexts for tx hashes in an ephemeral request array and returns a response ephemeral array. pub(crate) unconstrained fn get_message_contexts_by_tx_hash( requests: EphemeralArray, ) -> EphemeralArray> { - let response_slot = get_message_contexts_by_tx_hash_oracle(requests.slot); - EphemeralArray::at(response_slot) + get_message_contexts_by_tx_hash_oracle(requests) } #[oracle(aztec_utl_getMessageContextsByTxHash)] -unconstrained fn get_message_contexts_by_tx_hash_oracle(request_array_slot: Field) -> Field {} +unconstrained fn get_message_contexts_by_tx_hash_oracle( + requests: EphemeralArray, +) -> EphemeralArray> {} /// Fetches all effects of a settled transaction by its hash. pub unconstrained fn get_tx_effect(tx_hash: Field) -> Option { diff --git a/noir-projects/aztec-nr/aztec/src/oracle/shared_secret.nr b/noir-projects/aztec-nr/aztec/src/oracle/shared_secret.nr index 4785328f9d71..cbcd9390d71a 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/shared_secret.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/shared_secret.nr @@ -7,9 +7,9 @@ global GET_SHARED_SECRETS_REQUEST_SLOT: Field = #[oracle(aztec_utl_getSharedSecrets)] unconstrained fn get_shared_secrets_oracle( address: AztecAddress, - eph_pks_slot: Field, + eph_pks: EphemeralArray, contract_address: AztecAddress, -) -> Field {} +) -> EphemeralArray {} /// Convenience wrapper around [`get_shared_secrets`] for a single ephemeral public key. pub unconstrained fn get_shared_secret( @@ -48,8 +48,7 @@ pub unconstrained fn get_shared_secrets( EphemeralArray::at(GET_SHARED_SECRETS_REQUEST_SLOT).clear(); eph_pks.for_each(|pk| request_array.push(pk)); - let response_slot = get_shared_secrets_oracle(address, request_array.slot, contract_address); - let response_array: EphemeralArray = EphemeralArray::at(response_slot); + let response_array = get_shared_secrets_oracle(address, request_array, contract_address); assert( response_array.len() == eph_pks.len(), "get_shared_secrets: response length does not match request length", diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr index ef84849f3ebb..019b8219cabc 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr @@ -104,7 +104,7 @@ impl PrivateImmutable { // We emit an initialization nullifier to indicate that the struct is initialized. This also prevents the value // from being initialized again as a nullifier can be included only once. let nullifier = self.get_initialization_nullifier(); - self.context.push_nullifier(nullifier); + self.context.push_nullifier_unsafe(nullifier); create_note(self.context, self.owner, self.storage_slot, note) } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr index cc1bbade6d6e..81a61aade5d9 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr @@ -145,7 +145,7 @@ where { // Nullify the storage slot. let nullifier = self.get_initialization_nullifier(); - self.context.push_nullifier(nullifier); + self.context.push_nullifier_unsafe(nullifier); create_note(self.context, self.owner, self.storage_slot, note) } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr index da21ab7d3a1c..d7f7e99b4754 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr @@ -154,7 +154,7 @@ impl PublicImmutable { // We emit an initialization nullifier to indicate that the struct is initialized. This also prevents the value // from being initialized again as each nullifier can be emitted only once. let nullifier = self.compute_initialization_inner_nullifier(); - self.context.push_nullifier(nullifier); + self.context.push_nullifier_unsafe(nullifier); self.context.storage_write(self.storage_slot, WithHash::new(value)); } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/single_private_immutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/single_private_immutable.nr index 326a4843110d..6ae7103ee469 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/single_private_immutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/single_private_immutable.nr @@ -103,7 +103,7 @@ impl SinglePrivateImmutable { Note: NoteType + NoteHash + Packable, { let nullifier = self.get_initialization_nullifier(); - self.context.push_nullifier(nullifier); + self.context.push_nullifier_unsafe(nullifier); // The note owner is set to the contract's address. Strictly speaking, specifying a note owner is not required // here, as this note is never intended to be nullified. However, we must provide an owner because Aztec.nr diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/single_private_mutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/single_private_mutable.nr index d286aa346b78..14feab03af3c 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/single_private_mutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/single_private_mutable.nr @@ -139,7 +139,7 @@ where { // Nullify the storage slot. let nullifier = self.get_initialization_nullifier(); - self.context.push_nullifier(nullifier); + self.context.push_nullifier_unsafe(nullifier); create_note(self.context, owner, self.storage_slot, note) } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/single_use_claim.nr b/noir-projects/aztec-nr/aztec/src/state_vars/single_use_claim.nr index 28f0ac6f860d..a97301415b21 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/single_use_claim.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/single_use_claim.nr @@ -105,7 +105,7 @@ impl SingleUseClaim<&mut PrivateContext> { /// storage slot of the underlying state variable. pub fn claim(self) { let owner_nhk_app = self.context.request_nhk_app(get_public_keys(self.owner).npk_m.hash()); - self.context.push_nullifier(self.compute_nullifier(owner_nhk_app)); + self.context.push_nullifier_unsafe(self.compute_nullifier(owner_nhk_app)); } /// Asserts that the owner has already claimed this single use claim (i.e. that [`SingleUseClaim::claim`] has been diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index bb853bbfc1e7..561956ac8cfd 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -1318,7 +1318,7 @@ impl TestEnvironment { note.compute_nullifier_unconstrained(owner, unique_note_hash) }; - let process_custom_message: Option> = Option::none(); + let process_custom_message: Option = Option::none(); self.discover_data_in_message_plaintext( message_plaintext, opts.contract_address, @@ -1433,7 +1433,7 @@ impl TestEnvironment { ) }; - let process_custom_message: Option> = Option::none(); + let process_custom_message: Option = Option::none(); self.discover_data_in_message_plaintext( message_plaintext, opts.contract_address, @@ -1444,14 +1444,14 @@ impl TestEnvironment { ); } - unconstrained fn discover_data_in_message_plaintext( + unconstrained fn discover_data_in_message_plaintext( self, message_plaintext: BoundedVec, contract_address: Option, recipient: Option, compute_note_hash: ComputeNoteHash, compute_note_nullifier: ComputeNoteNullifier, - process_custom_message: Option>, + process_custom_message: Option, ) { // This function will emulate the message discovery and processing that would happen in a real contract, based // on a message plaintext. diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment/test/private_context.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment/test/private_context.nr index 0feac8ded02d..812334f4a8e2 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment/test/private_context.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment/test/private_context.nr @@ -145,8 +145,8 @@ unconstrained fn rejects_duplicate_transient_nullifiers() { let env = TestEnvironment::new(); env.private_context(|context| { - context.push_nullifier(1); - context.push_nullifier(1); + context.push_nullifier_unsafe(1); + context.push_nullifier_unsafe(1); }); } @@ -154,9 +154,9 @@ unconstrained fn rejects_duplicate_transient_nullifiers() { unconstrained fn rejects_duplicate_settled_nullifiers() { let env = TestEnvironment::new(); - env.private_context(|context| { context.push_nullifier(1); }); + env.private_context(|context| { context.push_nullifier_unsafe(1); }); - env.private_context(|context| { context.push_nullifier(1); }); + env.private_context(|context| { context.push_nullifier_unsafe(1); }); } #[test] @@ -207,7 +207,7 @@ unconstrained fn check_nullifier_exists_nonexistent() { unconstrained fn check_nullifier_exists_settled() { let env = TestEnvironment::new(); - env.private_context(|context| { context.push_nullifier(1); }); + env.private_context(|context| { context.push_nullifier_unsafe(1); }); env.private_context(|_| { assert(check_nullifier_exists(1)); }); } @@ -217,7 +217,7 @@ unconstrained fn check_nullifier_exists_pending() { let env = TestEnvironment::new(); env.private_context(|context| { - context.push_nullifier(1); + context.push_nullifier_unsafe(1); assert(check_nullifier_exists(1)); }); } diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment/test/public_context.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment/test/public_context.nr index 5fd8f6dc4d4f..e7d926c16abd 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment/test/public_context.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment/test/public_context.nr @@ -119,7 +119,7 @@ unconstrained fn read_public_nullifier_same_context() { let env = TestEnvironment::new(); env.public_context(|context| { - context.push_nullifier(nullifier); + context.push_nullifier_unsafe(nullifier); assert(context.nullifier_exists_unsafe(nullifier, context.this_address())); }); } @@ -128,7 +128,7 @@ unconstrained fn read_public_nullifier_same_context() { unconstrained fn read_public_nullifier_other_context() { let env = TestEnvironment::new(); - env.public_context(|context| { context.push_nullifier(nullifier); }); + env.public_context(|context| { context.push_nullifier_unsafe(nullifier); }); env.public_context(|context| { assert(context.nullifier_exists_unsafe(nullifier, context.this_address())); }); } @@ -137,7 +137,7 @@ unconstrained fn read_public_nullifier_other_context() { unconstrained fn read_private_nullifier() { let env = TestEnvironment::new(); - env.private_context(|context| { context.push_nullifier(nullifier); }); + env.private_context(|context| { context.push_nullifier_unsafe(nullifier); }); env.public_context(|context| { assert(context.nullifier_exists_unsafe(nullifier, context.this_address())); }); } @@ -147,8 +147,8 @@ unconstrained fn rejects_duplicate_transient_nullifiers() { let env = TestEnvironment::new(); env.public_context(|context| { - context.push_nullifier(1); - context.push_nullifier(1); + context.push_nullifier_unsafe(1); + context.push_nullifier_unsafe(1); }); } @@ -156,9 +156,9 @@ unconstrained fn rejects_duplicate_transient_nullifiers() { unconstrained fn rejects_duplicate_settled_nullifiers() { let env = TestEnvironment::new(); - env.public_context(|context| { context.push_nullifier(1); }); + env.public_context(|context| { context.push_nullifier_unsafe(1); }); - env.public_context(|context| { context.push_nullifier(1); }); + env.public_context(|context| { context.push_nullifier_unsafe(1); }); } #[test(should_fail_with = "Contract calls are forbidden")] diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment/test/utility_context.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment/test/utility_context.nr index 8c48bfc9defc..487665e4dc9c 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment/test/utility_context.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment/test/utility_context.nr @@ -75,7 +75,7 @@ unconstrained fn check_nullifier_exists_nonexistent() { unconstrained fn check_nullifier_exists_settled() { let env = TestEnvironment::new(); - env.private_context(|context| { context.push_nullifier(1); }); + env.private_context(|context| { context.push_nullifier_unsafe(1); }); env.utility_context(|_| { assert(check_nullifier_exists(1)); }); } diff --git a/noir-projects/aztec-nr/uint-note/src/uint_note.nr b/noir-projects/aztec-nr/uint-note/src/uint_note.nr index 979f3934775c..00583d1e7d73 100644 --- a/noir-projects/aztec-nr/uint-note/src/uint_note.nr +++ b/noir-projects/aztec-nr/uint-note/src/uint_note.nr @@ -135,7 +135,7 @@ impl UintNote { // nullifier tree since it uses its own separator, making collisions with actual note nullifiers practically // impossible. let validity_commitment = partial_note.compute_validity_commitment(completer); - context.push_nullifier(validity_commitment); + context.push_nullifier_unsafe(validity_commitment); partial_note } diff --git a/noir-projects/contract-snapshots/test_programs/compile_failure/user_defined_sync_state/Nargo.toml b/noir-projects/contract-snapshots/test_programs/compile_failure/user_defined_sync_state/Nargo.toml new file mode 100644 index 000000000000..43c0fecda407 --- /dev/null +++ b/noir-projects/contract-snapshots/test_programs/compile_failure/user_defined_sync_state/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "user_defined_sync_state" +type = "contract" +authors = [""] + +[dependencies] +aztec = { path = "../../../../aztec-nr/aztec" } diff --git a/noir-projects/contract-snapshots/test_programs/compile_failure/user_defined_sync_state/src/main.nr b/noir-projects/contract-snapshots/test_programs/compile_failure/user_defined_sync_state/src/main.nr new file mode 100644 index 000000000000..f57905c0fddf --- /dev/null +++ b/noir-projects/contract-snapshots/test_programs/compile_failure/user_defined_sync_state/src/main.nr @@ -0,0 +1,12 @@ +/// The name `sync_state` is reserved for the function auto-injected by the #[aztec] macro. +/// Use AztecConfig::custom_sync_state() to customize sync behavior. +use aztec::macros::aztec; + +#[aztec] +contract UserDefinedSyncState { + use aztec::macros::functions::external; + use aztec::protocol::address::AztecAddress; + + #[external("private")] + fn sync_state(_scope: AztecAddress) {} +} diff --git a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_from_wrong_type/snapshots__stderr.snap b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_from_wrong_type/snapshots__stderr.snap index a65ed2dba256..b2230482ea2f 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_from_wrong_type/snapshots__stderr.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_from_wrong_type/snapshots__stderr.snap @@ -12,7 +12,7 @@ error: Argument from in function foo must be of type AztecAddress, but is of typ 1: #[aztec] at src/main.nr:4:1 2: aztec - at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:97:21 + at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:111:21 3: process_functions at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/mod.nr:48:9 4: [T]::map @@ -20,7 +20,7 @@ error: Argument from in function foo must be of type AztecAddress, but is of typ 5: process_functions at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/mod.nr:48:41 6: generate_public_external - at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr:134:9 + at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr:131:9 7: create_authorize_once_check at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/helpers.nr:72:9 diff --git a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_missing_from_param/snapshots__stderr.snap b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_missing_from_param/snapshots__stderr.snap index 57f6c20570f6..1d0d83a4885f 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_missing_from_param/snapshots__stderr.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_missing_from_param/snapshots__stderr.snap @@ -12,7 +12,7 @@ error: Function foo does not have a from parameter. Please specify which one to 1: #[aztec] at src/main.nr:4:1 2: aztec - at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:97:21 + at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:111:21 3: process_functions at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/mod.nr:48:9 4: [T]::map @@ -20,7 +20,7 @@ error: Function foo does not have a from parameter. Please specify which one to 5: process_functions at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/mod.nr:48:41 6: generate_public_external - at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr:134:9 + at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr:131:9 7: create_authorize_once_check at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/helpers.nr:67:9 diff --git a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_missing_nonce_param/snapshots__stderr.snap b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_missing_nonce_param/snapshots__stderr.snap index a70f07d8bcc7..a2373050d810 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_missing_nonce_param/snapshots__stderr.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_missing_nonce_param/snapshots__stderr.snap @@ -12,7 +12,7 @@ error: Function foo does not have a authwit_nonce. Please specify which one to u 1: #[aztec] at src/main.nr:4:1 2: aztec - at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:97:21 + at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:111:21 3: process_functions at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/mod.nr:48:9 4: [T]::map @@ -20,7 +20,7 @@ error: Function foo does not have a authwit_nonce. Please specify which one to u 5: process_functions at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/mod.nr:48:41 6: generate_public_external - at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr:134:9 + at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr:131:9 7: create_authorize_once_check at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/helpers.nr:81:9 diff --git a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_nonce_wrong_type/snapshots__stderr.snap b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_nonce_wrong_type/snapshots__stderr.snap index 6a4a3efa9cd0..0a20fe9e7d8d 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_nonce_wrong_type/snapshots__stderr.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_nonce_wrong_type/snapshots__stderr.snap @@ -12,7 +12,7 @@ error: Argument authwit_nonce in function foo must be of type Field, but is of t 1: #[aztec] at src/main.nr:4:1 2: aztec - at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:97:21 + at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:111:21 3: process_functions at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/mod.nr:48:9 4: [T]::map @@ -20,7 +20,7 @@ error: Argument authwit_nonce in function foo must be of type Field, but is of t 5: process_functions at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/mod.nr:48:41 6: generate_public_external - at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr:134:9 + at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr:131:9 7: create_authorize_once_check at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/helpers.nr:86:9 diff --git a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/aztec_macro_too_many_args/snapshots__stderr.snap b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/aztec_macro_too_many_args/snapshots__stderr.snap index e7043b21b324..94af4f9fb2a5 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/aztec_macro_too_many_args/snapshots__stderr.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/aztec_macro_too_many_args/snapshots__stderr.snap @@ -12,6 +12,6 @@ error: #[aztec] expects 0 or 1 arguments, got 2 1: #[aztec] at src/main.nr:4:1 2: aztec - at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:89:9 + at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:103:9 Aborting due to 1 previous error diff --git a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/bob_token/snapshots__stderr.snap b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/bob_token/snapshots__stderr.snap index 48141769333e..423a820781ee 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/bob_token/snapshots__stderr.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/bob_token/snapshots__stderr.snap @@ -64,9 +64,9 @@ error: Function _assert_is_owner must be marked as either #[external(...)], #[in 1: #[aztec] at src/main.nr:18:1 2: aztec - at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:93:5 + at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:107:5 3: check_each_fn_macroified - at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:295:13 + at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:341:13 error: cannot find `self` in this scope ┌─ src/main.nr:41:9 diff --git a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/invalid_note/snapshots__stderr.snap b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/invalid_note/snapshots__stderr.snap index c8727783a33e..63957a4a7ed0 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/invalid_note/snapshots__stderr.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/invalid_note/snapshots__stderr.snap @@ -10,13 +10,13 @@ error: InvalidNote has a packed length of 9 fields, which exceeds the maximum al │ = Call stack: 1: generate_sync_state - at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:240:13 + at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:269:13 2: do_sync_state - at /noir-projects/aztec-nr/aztec/src/messages/discovery/mod.nr:130:5 + at /noir-projects/aztec-nr/aztec/src/messages/discovery/mod.nr:144:5 3: EphemeralArray::for_each at /noir-projects/aztec-nr/aztec/src/ephemeral/mod.nr:99:13 4: do_sync_state - at /noir-projects/aztec-nr/aztec/src/messages/discovery/mod.nr:139:13 + at /noir-projects/aztec-nr/aztec/src/messages/discovery/mod.nr:153:13 5: process_message_ciphertext at /noir-projects/aztec-nr/aztec/src/messages/discovery/process_message.nr:41:9 6: process_message_plaintext diff --git a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/public_function_selector_collision/snapshots__stderr.snap b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/public_function_selector_collision/snapshots__stderr.snap index 5e1f5985c358..ffa6b777f47a 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/public_function_selector_collision/snapshots__stderr.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/public_function_selector_collision/snapshots__stderr.snap @@ -12,7 +12,7 @@ error: Public function selector collision detected between functions 'fn_selecto 1: #[aztec] at src/main.nr:4:1 2: aztec - at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:145:27 + at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:169:27 3: generate_public_dispatch at /noir-projects/aztec-nr/aztec/src/macros/dispatch.nr:20:19 4: [T]::map diff --git a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/unmacroified_function_in_contract/snapshots__stderr.snap b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/unmacroified_function_in_contract/snapshots__stderr.snap index d96287947c14..d86875453f24 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/unmacroified_function_in_contract/snapshots__stderr.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/unmacroified_function_in_contract/snapshots__stderr.snap @@ -12,8 +12,8 @@ error: Function foo must be marked as either #[external(...)], #[internal(...)], 1: #[aztec] at src/main.nr:4:1 2: aztec - at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:93:5 + at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:107:5 3: check_each_fn_macroified - at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:295:13 + at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:341:13 Aborting due to 1 previous error diff --git a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/user_defined_offchain_receive/snapshots__stderr.snap b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/user_defined_offchain_receive/snapshots__stderr.snap index 75565319fd2a..f884b397a38d 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/user_defined_offchain_receive/snapshots__stderr.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/user_defined_offchain_receive/snapshots__stderr.snap @@ -12,6 +12,6 @@ error: User-defined 'offchain_receive' is not allowed. The function is auto-inje 1: #[aztec] at src/main.nr:4:1 2: aztec - at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:138:9 + at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:162:9 Aborting due to 1 previous error diff --git a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/user_defined_sync_state/snapshots__stderr.snap b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/user_defined_sync_state/snapshots__stderr.snap new file mode 100644 index 000000000000..3a3e1bf8ea88 --- /dev/null +++ b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/user_defined_sync_state/snapshots__stderr.snap @@ -0,0 +1,17 @@ +--- +source: tests/snapshots.rs +expression: stderr +--- +error: User-defined 'sync_state' is not allowed. Use AztecConfig::custom_sync_state() to customize sync behavior. + ┌─ std/panic.nr:8:12 + │ +8 │ assert(false, message); + │ ----- Assertion failed + │ + = Call stack: + 1: #[aztec] + at src/main.nr:5:1 + 2: aztec + at /noir-projects/aztec-nr/aztec/src/macros/aztec.nr:146:9 + +Aborting due to 1 previous error diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/amm_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/amm_contract/snapshots__expanded.snap index e671b64900c1..8d76a72665ad 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/amm_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/amm_contract/snapshots__expanded.snap @@ -1001,7 +1001,7 @@ pub contract AMM { unconstrained fn sync_state(scope: AztecAddress) { let address: AztecAddress = aztec::context::UtilityContext::new().this_address(); - aztec::messages::discovery::do_sync_state(address, _compute_note_hash, _compute_note_nullifier, Option::>::none(), Option:: aztec::ephemeral::EphemeralArray>::some(aztec::messages::processing::offchain::sync_inbox), scope); + aztec::messages::discovery::do_sync_state(address, _compute_note_hash, _compute_note_nullifier, Option::, aztec::messages::processing::MessageContext, AztecAddress)>::none(), Option:: aztec::ephemeral::EphemeralArray>::some(aztec::messages::processing::offchain::sync_inbox), scope); } pub struct offchain_receive_parameters { @@ -1424,7 +1424,7 @@ pub contract AMM { returns_hash.get_preimage() } - pub fn swap_tokens_for_exact_tokens(self, token_in: AztecAddress, token_out: AztecAddress, amount_out: u128, amount_in_max: u128, authwit_nonce: Field) { + pub fn swap_exact_tokens_for_tokens(self, token_in: AztecAddress, token_out: AztecAddress, amount_in: u128, amount_out_min: u128, authwit_nonce: Field) { let mut serialized_params: [Field; 5] = [0_Field; 5]; let mut offset: u32 = 0_u32; let serialized_member: [Field; 1] = ::serialize(token_in); @@ -1445,7 +1445,7 @@ pub contract AMM { } }; offset = offset + serialized_member_len; - let serialized_member: [Field; 1] = ::serialize(amount_out); + let serialized_member: [Field; 1] = ::serialize(amount_in); let serialized_member_len: u32 = ::N; for i in 0_u32..serialized_member_len { { @@ -1454,7 +1454,7 @@ pub contract AMM { } }; offset = offset + serialized_member_len; - let serialized_member: [Field; 1] = ::serialize(amount_in_max); + let serialized_member: [Field; 1] = ::serialize(amount_out_min); let serialized_member_len: u32 = ::N; for i in 0_u32..serialized_member_len { { @@ -1472,14 +1472,14 @@ pub contract AMM { } }; offset = offset + serialized_member_len; - let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(2620890703_Field); + let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(2960373586_Field); let args_hash: Field = aztec::hash::hash_args(serialized_params); aztec::oracle::execution_cache::store(serialized_params, args_hash); let returns_hash: aztec::context::ReturnsHash = self.context.call_private_function_with_args_hash(self.address, selector, args_hash, false); returns_hash.get_preimage() } - pub fn swap_exact_tokens_for_tokens(self, token_in: AztecAddress, token_out: AztecAddress, amount_in: u128, amount_out_min: u128, authwit_nonce: Field) { + pub fn swap_tokens_for_exact_tokens(self, token_in: AztecAddress, token_out: AztecAddress, amount_out: u128, amount_in_max: u128, authwit_nonce: Field) { let mut serialized_params: [Field; 5] = [0_Field; 5]; let mut offset: u32 = 0_u32; let serialized_member: [Field; 1] = ::serialize(token_in); @@ -1500,7 +1500,7 @@ pub contract AMM { } }; offset = offset + serialized_member_len; - let serialized_member: [Field; 1] = ::serialize(amount_in); + let serialized_member: [Field; 1] = ::serialize(amount_out); let serialized_member_len: u32 = ::N; for i in 0_u32..serialized_member_len { { @@ -1509,7 +1509,7 @@ pub contract AMM { } }; offset = offset + serialized_member_len; - let serialized_member: [Field; 1] = ::serialize(amount_out_min); + let serialized_member: [Field; 1] = ::serialize(amount_in_max); let serialized_member_len: u32 = ::N; for i in 0_u32..serialized_member_len { { @@ -1527,7 +1527,7 @@ pub contract AMM { } }; offset = offset + serialized_member_len; - let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(2960373586_Field); + let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(2620890703_Field); let args_hash: Field = aztec::hash::hash_args(serialized_params); aztec::oracle::execution_cache::store(serialized_params, args_hash); let returns_hash: aztec::context::ReturnsHash = self.context.call_private_function_with_args_hash(self.address, selector, args_hash, false); diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/avm_gadgets_test_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/avm_gadgets_test_contract/snapshots__expanded.snap index e1341d2cba98..c7f2843131a6 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/avm_gadgets_test_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/avm_gadgets_test_contract/snapshots__expanded.snap @@ -332,7 +332,7 @@ contract AvmGadgetsTest { unconstrained fn sync_state(scope: aztec::protocol::address::AztecAddress) { let address: aztec::protocol::address::AztecAddress = aztec::context::UtilityContext::new().this_address(); - aztec::messages::discovery::do_sync_state(address, _compute_note_hash, _compute_note_nullifier, Option::>::none(), Option:: aztec::ephemeral::EphemeralArray>::some(aztec::messages::processing::offchain::sync_inbox), scope); + aztec::messages::discovery::do_sync_state(address, _compute_note_hash, _compute_note_nullifier, Option::, aztec::messages::processing::MessageContext, aztec::protocol::address::AztecAddress)>::none(), Option:: aztec::ephemeral::EphemeralArray>::some(aztec::messages::processing::offchain::sync_inbox), scope); } pub struct offchain_receive_parameters { diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/avm_test_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/avm_test_contract/snapshots__expanded.snap index 2ec9f691f21c..7fc55e104f8f 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/avm_test_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/avm_test_contract/snapshots__expanded.snap @@ -1492,10 +1492,10 @@ pub contract AvmTest { aztec::context::calls::PublicCall::<41, 2, ()>::new(self.target_contract, selector, "create_different_nullifier_in_nested_call", serialized_params) } - pub fn call_fee_juice(self) -> aztec::context::calls::PublicCall<14, 0, ()> { - let serialized_params: [Field; 0] = []; - let selector: FunctionSelector = FunctionSelector::from_field(2734888597_Field); - aztec::context::calls::PublicCall::<14, 0, ()>::new(self.target_contract, selector, "call_fee_juice", serialized_params) + pub fn nested_call_large_calldata(self, arr: [Field; 300]) -> aztec::context::calls::PublicCall<26, 300, Field> { + let serialized_params: [Field; 300] = arr.serialize(); + let selector: FunctionSelector = FunctionSelector::from_field(14812432_Field); + aztec::context::calls::PublicCall::<26, 300, Field>::new(self.target_contract, selector, "nested_call_large_calldata", serialized_params) } pub fn get_transaction_fee(self) -> aztec::context::calls::PublicCall<19, 0, Field> { @@ -1522,6 +1522,12 @@ pub contract AvmTest { aztec::context::calls::PublicStaticCall::<18, 0, u8>::new(self.target_contract, selector, "set_opcode_u8_view", serialized_params) } + pub fn call_fee_juice(self) -> aztec::context::calls::PublicCall<14, 0, ()> { + let serialized_params: [Field; 0] = []; + let selector: FunctionSelector = FunctionSelector::from_field(2734888597_Field); + aztec::context::calls::PublicCall::<14, 0, ()>::new(self.target_contract, selector, "call_fee_juice", serialized_params) + } + pub fn assert_nullifier_exists(self, nullifier: Field) -> aztec::context::calls::PublicCall<23, 1, ()> { let serialized_params: [Field; 1] = nullifier.serialize(); let selector: FunctionSelector = FunctionSelector::from_field(2810255355_Field); @@ -1540,12 +1546,6 @@ pub contract AvmTest { aztec::context::calls::PublicCall::<13, 0, ()>::new(self.target_contract, selector, "debug_logging", serialized_params) } - pub fn nested_call_large_calldata(self, arr: [Field; 300]) -> aztec::context::calls::PublicCall<26, 300, Field> { - let serialized_params: [Field; 300] = arr.serialize(); - let selector: FunctionSelector = FunctionSelector::from_field(14812432_Field); - aztec::context::calls::PublicCall::<26, 300, Field>::new(self.target_contract, selector, "nested_call_large_calldata", serialized_params) - } - pub fn external_call_to_divide_by_zero_recovers(self) -> aztec::context::calls::PublicCall<40, 0, ()> { let serialized_params: [Field; 0] = []; let selector: FunctionSelector = FunctionSelector::from_field(2245930342_Field); @@ -1874,7 +1874,7 @@ pub contract AvmTest { unconstrained fn sync_state(scope: AztecAddress) { let address: AztecAddress = aztec::context::UtilityContext::new().this_address(); - aztec::messages::discovery::do_sync_state(address, _compute_note_hash, _compute_note_nullifier, Option::>::none(), Option:: aztec::ephemeral::EphemeralArray>::some(aztec::messages::processing::offchain::sync_inbox), scope); + aztec::messages::discovery::do_sync_state(address, _compute_note_hash, _compute_note_nullifier, Option::, aztec::messages::processing::MessageContext, AztecAddress)>::none(), Option:: aztec::ephemeral::EphemeralArray>::some(aztec::messages::processing::offchain::sync_inbox), scope); } pub struct offchain_receive_parameters { @@ -2131,34 +2131,6 @@ pub contract AvmTest { } } - pub fn add_args_return(self, arg_a: Field, arg_b: Field) -> Field { - let mut serialized_params: [Field; 2] = [0_Field; 2]; - let mut offset: u32 = 0_u32; - let serialized_member: [Field; 1] = arg_a.serialize(); - let serialized_member_len: u32 = ::N; - for i in 0_u32..serialized_member_len { - { - let i_0: u32 = i + offset; - serialized_params[i_0] = serialized_member[i]; - } - }; - offset = offset + serialized_member_len; - let serialized_member: [Field; 1] = arg_b.serialize(); - let serialized_member_len: u32 = ::N; - for i in 0_u32..serialized_member_len { - { - let i_1: u32 = i + offset; - serialized_params[i_1] = serialized_member[i]; - } - }; - offset = offset + serialized_member_len; - let selector: FunctionSelector = FunctionSelector::from_field(2858633377_Field); - // Safety: comment added by `nargo expand` - unsafe { - aztec::context::calls::PublicCall::<15, 2, Field>::new(self.address, selector, "add_args_return", serialized_params).call(self.context) - } - } - pub fn variable_base_msm(self, scalar_lo: Field, scalar_hi: Field, scalar2_lo: Field, scalar2_hi: Field) -> Point { let mut serialized_params: [Field; 4] = [0_Field; 4]; let mut offset: u32 = 0_u32; @@ -2205,6 +2177,34 @@ pub contract AvmTest { } } + pub fn add_args_return(self, arg_a: Field, arg_b: Field) -> Field { + let mut serialized_params: [Field; 2] = [0_Field; 2]; + let mut offset: u32 = 0_u32; + let serialized_member: [Field; 1] = arg_a.serialize(); + let serialized_member_len: u32 = ::N; + for i in 0_u32..serialized_member_len { + { + let i_0: u32 = i + offset; + serialized_params[i_0] = serialized_member[i]; + } + }; + offset = offset + serialized_member_len; + let serialized_member: [Field; 1] = arg_b.serialize(); + let serialized_member_len: u32 = ::N; + for i in 0_u32..serialized_member_len { + { + let i_1: u32 = i + offset; + serialized_params[i_1] = serialized_member[i]; + } + }; + offset = offset + serialized_member_len; + let selector: FunctionSelector = FunctionSelector::from_field(2858633377_Field); + // Safety: comment added by `nargo expand` + unsafe { + aztec::context::calls::PublicCall::<15, 2, Field>::new(self.address, selector, "add_args_return", serialized_params).call(self.context) + } + } + pub fn test_get_contract_instance_matches(self, address: AztecAddress, expected_deployer: AztecAddress, expected_class_id: ContractClassId, expected_initialization_hash: Field) { let mut serialized_params: [Field; 4] = [0_Field; 4]; let mut offset: u32 = 0_u32; @@ -2498,15 +2498,6 @@ pub contract AvmTest { } } - pub fn new_nullifier(self, nullifier: Field) { - let serialized_params: [Field; 1] = nullifier.serialize(); - let selector: FunctionSelector = FunctionSelector::from_field(3648851082_Field); - // Safety: comment added by `nargo expand` - unsafe { - aztec::context::calls::PublicCall::<13, 1, ()>::new(self.address, selector, "new_nullifier", serialized_params).call(self.context) - } - } - pub fn divide_by_zero(self, denominator: u8) -> u8 { let serialized_params: [Field; 1] = denominator.serialize(); let selector: FunctionSelector = FunctionSelector::from_field(815932481_Field); @@ -2525,6 +2516,15 @@ pub contract AvmTest { } } + pub fn new_nullifier(self, nullifier: Field) { + let serialized_params: [Field; 1] = nullifier.serialize(); + let selector: FunctionSelector = FunctionSelector::from_field(3648851082_Field); + // Safety: comment added by `nargo expand` + unsafe { + aztec::context::calls::PublicCall::<13, 1, ()>::new(self.address, selector, "new_nullifier", serialized_params).call(self.context) + } + } + pub fn raw_l2_to_l1_msg(self, recipient: EthAddress, content: Field) { let mut serialized_params: [Field; 2] = [0_Field; 2]; let mut offset: u32 = 0_u32; @@ -2627,15 +2627,6 @@ pub contract AvmTest { } } - pub fn returndata_copy_oracle(self) { - let serialized_params: [Field; 0] = []; - let selector: FunctionSelector = FunctionSelector::from_field(4172530660_Field); - // Safety: comment added by `nargo expand` - unsafe { - aztec::context::calls::PublicCall::<22, 0, ()>::new(self.address, selector, "returndata_copy_oracle", serialized_params).call(self.context) - } - } - pub fn set_read_storage_single(self, a: Field) -> Field { let serialized_params: [Field; 1] = a.serialize(); let selector: FunctionSelector = FunctionSelector::from_field(1932358992_Field); @@ -2645,12 +2636,12 @@ pub contract AvmTest { } } - pub fn n_new_public_logs(self, num: u32) { - let serialized_params: [Field; 1] = num.serialize(); - let selector: FunctionSelector = FunctionSelector::from_field(2907114368_Field); + pub fn returndata_copy_oracle(self) { + let serialized_params: [Field; 0] = []; + let selector: FunctionSelector = FunctionSelector::from_field(4172530660_Field); // Safety: comment added by `nargo expand` unsafe { - aztec::context::calls::PublicCall::<17, 1, ()>::new(self.address, selector, "n_new_public_logs", serialized_params).call(self.context) + aztec::context::calls::PublicCall::<22, 0, ()>::new(self.address, selector, "returndata_copy_oracle", serialized_params).call(self.context) } } @@ -2700,6 +2691,15 @@ pub contract AvmTest { } } + pub fn n_new_public_logs(self, num: u32) { + let serialized_params: [Field; 1] = num.serialize(); + let selector: FunctionSelector = FunctionSelector::from_field(2907114368_Field); + // Safety: comment added by `nargo expand` + unsafe { + aztec::context::calls::PublicCall::<17, 1, ()>::new(self.address, selector, "n_new_public_logs", serialized_params).call(self.context) + } + } + pub fn nested_call_to_nothing(self) { let serialized_params: [Field; 0] = []; let selector: FunctionSelector = FunctionSelector::from_field(1183868090_Field); @@ -2783,12 +2783,12 @@ pub contract AvmTest { } } - pub fn call_fee_juice(self) { - let serialized_params: [Field; 0] = []; - let selector: FunctionSelector = FunctionSelector::from_field(2734888597_Field); + pub fn nested_call_large_calldata(self, arr: [Field; 300]) -> Field { + let serialized_params: [Field; 300 * 1] = arr.serialize(); + let selector: FunctionSelector = FunctionSelector::from_field(14812432_Field); // Safety: comment added by `nargo expand` unsafe { - aztec::context::calls::PublicCall::<14, 0, ()>::new(self.address, selector, "call_fee_juice", serialized_params).call(self.context) + aztec::context::calls::PublicCall::<26, 300 * 1, Field>::new(self.address, selector, "nested_call_large_calldata", serialized_params).call(self.context) } } @@ -2819,6 +2819,15 @@ pub contract AvmTest { } } + pub fn call_fee_juice(self) { + let serialized_params: [Field; 0] = []; + let selector: FunctionSelector = FunctionSelector::from_field(2734888597_Field); + // Safety: comment added by `nargo expand` + unsafe { + aztec::context::calls::PublicCall::<14, 0, ()>::new(self.address, selector, "call_fee_juice", serialized_params).call(self.context) + } + } + pub fn assert_nullifier_exists(self, nullifier: Field) { let serialized_params: [Field; 1] = nullifier.serialize(); let selector: FunctionSelector = FunctionSelector::from_field(2810255355_Field); @@ -2846,15 +2855,6 @@ pub contract AvmTest { } } - pub fn nested_call_large_calldata(self, arr: [Field; 300]) -> Field { - let serialized_params: [Field; 300 * 1] = arr.serialize(); - let selector: FunctionSelector = FunctionSelector::from_field(14812432_Field); - // Safety: comment added by `nargo expand` - unsafe { - aztec::context::calls::PublicCall::<26, 300 * 1, Field>::new(self.address, selector, "nested_call_large_calldata", serialized_params).call(self.context) - } - } - pub fn external_call_to_divide_by_zero_recovers(self) { let serialized_params: [Field; 0] = []; let selector: FunctionSelector = FunctionSelector::from_field(2245930342_Field); @@ -3871,15 +3871,6 @@ pub contract AvmTest { self.context.call_public_function_with_calldata_hash(self.address, calldata_hash, false, false); } - pub fn new_nullifier(self, nullifier: Field) { - let serialized_params: [Field; 1] = nullifier.serialize(); - let selector: FunctionSelector = FunctionSelector::from_field(3648851082_Field); - let calldata: [Field; 1 + 1] = [selector.to_field()].concat(serialized_params); - let calldata_hash: Field = aztec::hash::hash_calldata_array(calldata); - aztec::oracle::execution_cache::store(calldata, calldata_hash); - self.context.call_public_function_with_calldata_hash(self.address, calldata_hash, false, false); - } - pub fn divide_by_zero(self, denominator: u8) { let serialized_params: [Field; 1] = denominator.serialize(); let selector: FunctionSelector = FunctionSelector::from_field(815932481_Field); @@ -3898,6 +3889,15 @@ pub contract AvmTest { self.context.call_public_function_with_calldata_hash(self.address, calldata_hash, false, false); } + pub fn new_nullifier(self, nullifier: Field) { + let serialized_params: [Field; 1] = nullifier.serialize(); + let selector: FunctionSelector = FunctionSelector::from_field(3648851082_Field); + let calldata: [Field; 1 + 1] = [selector.to_field()].concat(serialized_params); + let calldata_hash: Field = aztec::hash::hash_calldata_array(calldata); + aztec::oracle::execution_cache::store(calldata, calldata_hash); + self.context.call_public_function_with_calldata_hash(self.address, calldata_hash, false, false); + } + pub fn raw_l2_to_l1_msg(self, recipient: EthAddress, content: Field) { let mut serialized_params: [Field; 2] = [0_Field; 2]; let mut offset: u32 = 0_u32; @@ -4156,6 +4156,15 @@ pub contract AvmTest { self.context.call_public_function_with_calldata_hash(self.address, calldata_hash, false, false); } + pub fn nested_call_large_calldata(self, arr: [Field; 300]) { + let serialized_params: [Field; 300 * 1] = arr.serialize(); + let selector: FunctionSelector = FunctionSelector::from_field(14812432_Field); + let calldata: [Field; 1 + (300 * 1)] = [selector.to_field()].concat(serialized_params); + let calldata_hash: Field = aztec::hash::hash_calldata_array(calldata); + aztec::oracle::execution_cache::store(calldata, calldata_hash); + self.context.call_public_function_with_calldata_hash(self.address, calldata_hash, false, false); + } + pub fn call_fee_juice(self) { let serialized_params: [Field; 0] = []; let selector: FunctionSelector = FunctionSelector::from_field(2734888597_Field); @@ -4219,15 +4228,6 @@ pub contract AvmTest { self.context.call_public_function_with_calldata_hash(self.address, calldata_hash, false, false); } - pub fn nested_call_large_calldata(self, arr: [Field; 300]) { - let serialized_params: [Field; 300 * 1] = arr.serialize(); - let selector: FunctionSelector = FunctionSelector::from_field(14812432_Field); - let calldata: [Field; 1 + (300 * 1)] = [selector.to_field()].concat(serialized_params); - let calldata_hash: Field = aztec::hash::hash_calldata_array(calldata); - aztec::oracle::execution_cache::store(calldata, calldata_hash); - self.context.call_public_function_with_calldata_hash(self.address, calldata_hash, false, false); - } - pub fn external_call_to_divide_by_zero_recovers(self) { let serialized_params: [Field; 0] = []; let selector: FunctionSelector = FunctionSelector::from_field(2245930342_Field); diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/public_fns_with_emit_repro_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/public_fns_with_emit_repro_contract/snapshots__expanded.snap index 7bd15a82795d..c863124e5be8 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/public_fns_with_emit_repro_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/public_fns_with_emit_repro_contract/snapshots__expanded.snap @@ -308,7 +308,7 @@ pub contract PublicFnsWithEmitRepro { unconstrained fn sync_state(scope: aztec::protocol::address::AztecAddress) { let address: aztec::protocol::address::AztecAddress = aztec::context::UtilityContext::new().this_address(); - aztec::messages::discovery::do_sync_state(address, _compute_note_hash, _compute_note_nullifier, Option::>::none(), Option:: aztec::ephemeral::EphemeralArray>::some(aztec::messages::processing::offchain::sync_inbox), scope); + aztec::messages::discovery::do_sync_state(address, _compute_note_hash, _compute_note_nullifier, Option::, aztec::messages::processing::MessageContext, aztec::protocol::address::AztecAddress)>::none(), Option:: aztec::ephemeral::EphemeralArray>::some(aztec::messages::processing::offchain::sync_inbox), scope); } pub struct offchain_receive_parameters { diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/storage_proof_test_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/storage_proof_test_contract/snapshots__expanded.snap index e08b63d47b43..c864347061c9 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/storage_proof_test_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/storage_proof_test_contract/snapshots__expanded.snap @@ -309,7 +309,7 @@ contract StorageProofTest { unconstrained fn sync_state(scope: AztecAddress) { let address: AztecAddress = aztec::context::UtilityContext::new().this_address(); - aztec::messages::discovery::do_sync_state(address, _compute_note_hash, _compute_note_nullifier, Option::>::none(), Option:: aztec::ephemeral::EphemeralArray>::some(aztec::messages::processing::offchain::sync_inbox), scope); + aztec::messages::discovery::do_sync_state(address, _compute_note_hash, _compute_note_nullifier, Option::, aztec::messages::processing::MessageContext, AztecAddress)>::none(), Option:: aztec::ephemeral::EphemeralArray>::some(aztec::messages::processing::offchain::sync_inbox), scope); } pub struct offchain_receive_parameters { diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/token_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/token_contract/snapshots__expanded.snap index e7bc147862b9..075c453dacba 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/token_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/token_contract/snapshots__expanded.snap @@ -1217,7 +1217,7 @@ pub contract Token { unconstrained fn sync_state(scope: AztecAddress) { let address: AztecAddress = aztec::context::UtilityContext::new().this_address(); - aztec::messages::discovery::do_sync_state(address, _compute_note_hash, _compute_note_nullifier, Option::>::none(), Option:: aztec::ephemeral::EphemeralArray>::some(aztec::messages::processing::offchain::sync_inbox), scope); + aztec::messages::discovery::do_sync_state(address, _compute_note_hash, _compute_note_nullifier, Option::, aztec::messages::processing::MessageContext, AztecAddress)>::none(), Option:: aztec::ephemeral::EphemeralArray>::some(aztec::messages::processing::offchain::sync_inbox), scope); } pub struct offchain_receive_parameters { @@ -1309,10 +1309,10 @@ pub contract Token { } } - pub fn constructor(self, admin: AztecAddress, name: str<31>, symbol: str<31>, decimals: u8) { - let mut serialized_params: [Field; 64] = [0_Field; 64]; + pub fn mint_to_public(self, to: AztecAddress, amount: u128) { + let mut serialized_params: [Field; 2] = [0_Field; 2]; let mut offset: u32 = 0_u32; - let serialized_member: [Field; 1] = ::serialize(admin); + let serialized_member: [Field; 1] = ::serialize(to); let serialized_member_len: u32 = ::N; for i in 0_u32..serialized_member_len { { @@ -1321,8 +1321,8 @@ pub contract Token { } }; offset = offset + serialized_member_len; - let serialized_member: [Field; 31] = as aztec::protocol::traits::Serialize>::serialize(name); - let serialized_member_len: u32 = as aztec::protocol::traits::Serialize>::N; + let serialized_member: [Field; 1] = ::serialize(amount); + let serialized_member_len: u32 = ::N; for i in 0_u32..serialized_member_len { { let i_1: u32 = i + offset; @@ -1330,36 +1330,18 @@ pub contract Token { } }; offset = offset + serialized_member_len; - let serialized_member: [Field; 31] = as aztec::protocol::traits::Serialize>::serialize(symbol); - let serialized_member_len: u32 = as aztec::protocol::traits::Serialize>::N; - for i in 0_u32..serialized_member_len { - { - let i_2: u32 = i + offset; - serialized_params[i_2] = serialized_member[i]; - } - }; - offset = offset + serialized_member_len; - let serialized_member: [Field; 1] = ::serialize(decimals); - let serialized_member_len: u32 = ::N; - for i in 0_u32..serialized_member_len { - { - let i_3: u32 = i + offset; - serialized_params[i_3] = serialized_member[i]; - } - }; - offset = offset + serialized_member_len; - let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(1578322623_Field); + let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(1159421870_Field); // Safety: comment added by `nargo expand` unsafe { - aztec::context::calls::PublicCall::<11, 64, ()>::new(self.address, selector, "constructor", serialized_params).call(self.context) + aztec::context::calls::PublicCall::<14, 2, ()>::new(self.address, selector, "mint_to_public", serialized_params).call(self.context) } } - pub fn mint_to_public(self, to: AztecAddress, amount: u128) { + pub fn finalize_transfer_to_private(self, amount: u128, partial_note: PartialUintNote) { let mut serialized_params: [Field; 2] = [0_Field; 2]; let mut offset: u32 = 0_u32; - let serialized_member: [Field; 1] = ::serialize(to); - let serialized_member_len: u32 = ::N; + let serialized_member: [Field; 1] = ::serialize(amount); + let serialized_member_len: u32 = ::N; for i in 0_u32..serialized_member_len { { let i_0: u32 = i + offset; @@ -1367,8 +1349,8 @@ pub contract Token { } }; offset = offset + serialized_member_len; - let serialized_member: [Field; 1] = ::serialize(amount); - let serialized_member_len: u32 = ::N; + let serialized_member: [Field; 1] = ::serialize(partial_note); + let serialized_member_len: u32 = ::N; for i in 0_u32..serialized_member_len { { let i_1: u32 = i + offset; @@ -1376,18 +1358,18 @@ pub contract Token { } }; offset = offset + serialized_member_len; - let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(1159421870_Field); + let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(2599745508_Field); // Safety: comment added by `nargo expand` unsafe { - aztec::context::calls::PublicCall::<14, 2, ()>::new(self.address, selector, "mint_to_public", serialized_params).call(self.context) + aztec::context::calls::PublicCall::<28, 2, ()>::new(self.address, selector, "finalize_transfer_to_private", serialized_params).call(self.context) } } - pub fn finalize_transfer_to_private(self, amount: u128, partial_note: PartialUintNote) { - let mut serialized_params: [Field; 2] = [0_Field; 2]; + pub fn constructor(self, admin: AztecAddress, name: str<31>, symbol: str<31>, decimals: u8) { + let mut serialized_params: [Field; 64] = [0_Field; 64]; let mut offset: u32 = 0_u32; - let serialized_member: [Field; 1] = ::serialize(amount); - let serialized_member_len: u32 = ::N; + let serialized_member: [Field; 1] = ::serialize(admin); + let serialized_member_len: u32 = ::N; for i in 0_u32..serialized_member_len { { let i_0: u32 = i + offset; @@ -1395,8 +1377,8 @@ pub contract Token { } }; offset = offset + serialized_member_len; - let serialized_member: [Field; 1] = ::serialize(partial_note); - let serialized_member_len: u32 = ::N; + let serialized_member: [Field; 31] = as aztec::protocol::traits::Serialize>::serialize(name); + let serialized_member_len: u32 = as aztec::protocol::traits::Serialize>::N; for i in 0_u32..serialized_member_len { { let i_1: u32 = i + offset; @@ -1404,10 +1386,28 @@ pub contract Token { } }; offset = offset + serialized_member_len; - let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(2599745508_Field); + let serialized_member: [Field; 31] = as aztec::protocol::traits::Serialize>::serialize(symbol); + let serialized_member_len: u32 = as aztec::protocol::traits::Serialize>::N; + for i in 0_u32..serialized_member_len { + { + let i_2: u32 = i + offset; + serialized_params[i_2] = serialized_member[i]; + } + }; + offset = offset + serialized_member_len; + let serialized_member: [Field; 1] = ::serialize(decimals); + let serialized_member_len: u32 = ::N; + for i in 0_u32..serialized_member_len { + { + let i_3: u32 = i + offset; + serialized_params[i_3] = serialized_member[i]; + } + }; + offset = offset + serialized_member_len; + let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(1578322623_Field); // Safety: comment added by `nargo expand` unsafe { - aztec::context::calls::PublicCall::<28, 2, ()>::new(self.address, selector, "finalize_transfer_to_private", serialized_params).call(self.context) + aztec::context::calls::PublicCall::<11, 64, ()>::new(self.address, selector, "constructor", serialized_params).call(self.context) } } @@ -1439,6 +1439,15 @@ pub contract Token { } } + pub fn _reduce_total_supply(self, amount: u128) { + let serialized_params: [Field; 1] = ::serialize(amount); + let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(2253390209_Field); + // Safety: comment added by `nargo expand` + unsafe { + aztec::context::calls::PublicCall::<20, 1, ()>::new(self.address, selector, "_reduce_total_supply", serialized_params).call(self.context) + } + } + pub fn transfer_in_public(self, from: AztecAddress, to: AztecAddress, amount: u128, authwit_nonce: Field) { let mut serialized_params: [Field; 4] = [0_Field; 4]; let mut offset: u32 = 0_u32; @@ -1485,15 +1494,6 @@ pub contract Token { } } - pub fn _reduce_total_supply(self, amount: u128) { - let serialized_params: [Field; 1] = ::serialize(amount); - let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(2253390209_Field); - // Safety: comment added by `nargo expand` - unsafe { - aztec::context::calls::PublicCall::<20, 1, ()>::new(self.address, selector, "_reduce_total_supply", serialized_params).call(self.context) - } - } - pub fn set_admin(self, new_admin: AztecAddress) { let serialized_params: [Field; 1] = ::serialize(new_admin); let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(2519591682_Field); @@ -1598,10 +1598,10 @@ pub contract Token { } impl CallSelf<&mut PrivateContext> { - pub fn _recurse_subtract_balance(self, account: AztecAddress, amount: u128) -> u128 { + pub fn transfer(self, to: AztecAddress, amount: u128) { let mut serialized_params: [Field; 2] = [0_Field; 2]; let mut offset: u32 = 0_u32; - let serialized_member: [Field; 1] = ::serialize(account); + let serialized_member: [Field; 1] = ::serialize(to); let serialized_member_len: u32 = ::N; for i in 0_u32..serialized_member_len { { @@ -1619,17 +1619,17 @@ pub contract Token { } }; offset = offset + serialized_member_len; - let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(1536394406_Field); + let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(1968158567_Field); let args_hash: Field = aztec::hash::hash_args(serialized_params); aztec::oracle::execution_cache::store(serialized_params, args_hash); let returns_hash: aztec::context::ReturnsHash = self.context.call_private_function_with_args_hash(self.address, selector, args_hash, false); returns_hash.get_preimage() } - pub fn transfer(self, to: AztecAddress, amount: u128) { + pub fn _recurse_subtract_balance(self, account: AztecAddress, amount: u128) -> u128 { let mut serialized_params: [Field; 2] = [0_Field; 2]; let mut offset: u32 = 0_u32; - let serialized_member: [Field; 1] = ::serialize(to); + let serialized_member: [Field; 1] = ::serialize(account); let serialized_member_len: u32 = ::N; for i in 0_u32..serialized_member_len { { @@ -1647,7 +1647,7 @@ pub contract Token { } }; offset = offset + serialized_member_len; - let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(1968158567_Field); + let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(1536394406_Field); let args_hash: Field = aztec::hash::hash_args(serialized_params); aztec::oracle::execution_cache::store(serialized_params, args_hash); let returns_hash: aztec::context::ReturnsHash = self.context.call_private_function_with_args_hash(self.address, selector, args_hash, false); @@ -2067,18 +2067,18 @@ pub contract Token { } impl CallSelfStatic<&mut PrivateContext> { - pub fn private_get_name(self) -> FieldCompressedString { + pub fn private_get_symbol(self) -> FieldCompressedString { let serialized_params: [Field; 0] = []; - let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(3516522363_Field); + let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(1295669651_Field); let args_hash: Field = aztec::hash::hash_args(serialized_params); aztec::oracle::execution_cache::store(serialized_params, args_hash); let returns_hash: aztec::context::ReturnsHash = self.context.call_private_function_with_args_hash(self.address, selector, args_hash, true); returns_hash.get_preimage() } - pub fn private_get_symbol(self) -> FieldCompressedString { + pub fn private_get_name(self) -> FieldCompressedString { let serialized_params: [Field; 0] = []; - let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(1295669651_Field); + let selector: aztec::protocol::abis::function_selector::FunctionSelector = ::from_field(3516522363_Field); let args_hash: Field = aztec::hash::hash_args(serialized_params); aztec::oracle::execution_cache::store(serialized_params, args_hash); let returns_hash: aztec::context::ReturnsHash = self.context.call_private_function_with_args_hash(self.address, selector, args_hash, true); diff --git a/noir-projects/noir-contracts/Nargo.toml b/noir-projects/noir-contracts/Nargo.toml index d0a2168911fe..25247903ccb1 100644 --- a/noir-projects/noir-contracts/Nargo.toml +++ b/noir-projects/noir-contracts/Nargo.toml @@ -47,6 +47,7 @@ members = [ "contracts/test/child_contract", "contracts/test/counter/counter_contract", "contracts/test/custom_message_contract", + "contracts/test/custom_sync_state_contract", "contracts/test/ephemeral_child_contract", "contracts/test/ephemeral_parent_contract", "contracts/test/event_only_contract", diff --git a/noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/src/main.nr index af4511c9ec29..0544820909ac 100644 --- a/noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/src/main.nr @@ -38,10 +38,10 @@ pub contract SimulatedEcdsaAccount { let seed = unsafe { random() }; // Emit the initialization nullifier for the contract itself. - self.context.push_nullifier(seed); + self.context.push_nullifier_unsafe(seed); // Emit the initialization nullifier for the signing_public_key SinglePrivateImmutable. - self.context.push_nullifier(seed + 1); + self.context.push_nullifier_unsafe(seed + 1); // Emit the note hash for the signing key note. self.context.push_note_hash(seed + 2); diff --git a/noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/src/main.nr index cfbdea31d4dc..9d3959ed947f 100644 --- a/noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/src/main.nr @@ -40,10 +40,10 @@ pub contract SimulatedSchnorrAccount { let seed = unsafe { random() }; // Emit the initialization nullifier for the contract itself. - self.context.push_nullifier(seed); + self.context.push_nullifier_unsafe(seed); // Emit the initialization nullifier for the signing_public_key SinglePrivateImmutable. - self.context.push_nullifier(seed + 1); + self.context.push_nullifier_unsafe(seed + 1); // Emit the note hash for the signing key note. self.context.push_note_hash(seed + 2); diff --git a/noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr index f8c5a2f4b816..90189549f536 100644 --- a/noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr @@ -204,7 +204,7 @@ pub contract NFT { fn cancel_authwit(inner_hash: Field) { let on_behalf_of = self.msg_sender(); let nullifier = compute_authwit_nullifier(on_behalf_of, inner_hash); - self.context.push_nullifier(nullifier); + self.context.push_nullifier_unsafe(nullifier); } #[authorize_once("from", "authwit_nonce")] diff --git a/noir-projects/noir-contracts/contracts/app/nft_contract/src/types/nft_note.nr b/noir-projects/noir-contracts/contracts/app/nft_contract/src/types/nft_note.nr index 4b52fc52d803..6f0e47db7085 100644 --- a/noir-projects/noir-contracts/contracts/app/nft_contract/src/types/nft_note.nr +++ b/noir-projects/noir-contracts/contracts/app/nft_contract/src/types/nft_note.nr @@ -131,7 +131,7 @@ impl NFTNote { // the nullifier tree since it uses its own separator, making collisions with actual note nullifiers // practically impossible. let validity_commitment = partial_note.compute_validity_commitment(completer); - context.push_nullifier(validity_commitment); + context.push_nullifier_unsafe(validity_commitment); partial_note } diff --git a/noir-projects/noir-contracts/contracts/app/orderbook_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/orderbook_contract/src/main.nr index 9da0106f0064..1032473e2dba 100644 --- a/noir-projects/noir-contracts/contracts/app/orderbook_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/orderbook_contract/src/main.nr @@ -141,7 +141,7 @@ pub contract Orderbook { // a nullifier is cheaper (1 Field in DA instead of multiple Fields for the order). // TODO(F-399): pushing a raw nullifier with no domain separator like we do here is unsafe: we should instead // use something like a singleton `SingleUseClaim`. - self.context.push_nullifier(order_id); + self.context.push_nullifier_unsafe(order_id); // Enqueue the fulfillment to finalize both partial notes self.enqueue_self._fulfill_order(order_id, taker_partial_note, bid_token, order.bid_amount); diff --git a/noir-projects/noir-contracts/contracts/app/simple_token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/simple_token_contract/src/main.nr index bb2da32153ad..06c66a0df8fc 100644 --- a/noir-projects/noir-contracts/contracts/app/simple_token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/simple_token_contract/src/main.nr @@ -247,7 +247,7 @@ pub contract SimpleToken { fn cancel_authwit(inner_hash: Field) { let on_behalf_of = self.msg_sender(); let nullifier = compute_authwit_nullifier(on_behalf_of, inner_hash); - self.context.push_nullifier(nullifier); + self.context.push_nullifier_unsafe(nullifier); } #[internal("private")] diff --git a/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr index 9805be627732..8afb7bdd16c9 100644 --- a/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr @@ -276,7 +276,7 @@ pub contract Token { fn cancel_authwit(inner_hash: Field) { let on_behalf_of = self.msg_sender(); let nullifier = compute_authwit_nullifier(on_behalf_of, inner_hash); - self.context.push_nullifier(nullifier); + self.context.push_nullifier_unsafe(nullifier); } // docs:end:cancel_authwit diff --git a/noir-projects/noir-contracts/contracts/test/avm_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/avm_test_contract/src/main.nr index 6ae17f9c7354..91e52f588160 100644 --- a/noir-projects/noir-contracts/contracts/test/avm_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test/avm_test_contract/src/main.nr @@ -610,7 +610,7 @@ pub contract AvmTest { #[contract_library_method] fn _new_nullifier(context: PublicContext, nullifier: Field) { - context.push_nullifier(nullifier); + context.push_nullifier_unsafe(nullifier); } #[external("public")] @@ -630,7 +630,7 @@ pub contract AvmTest { #[external("public")] fn n_new_nullifiers(num: u32) { for i in 0..num { - self.context.push_nullifier(i as Field); + self.context.push_nullifier_unsafe(i as Field); } } @@ -672,7 +672,7 @@ pub contract AvmTest { // Use the standard context interface to emit a new nullifier #[external("public")] fn emit_nullifier_and_check(nullifier: Field) { - self.context.push_nullifier(nullifier); + self.context.push_nullifier_unsafe(nullifier); let exists = self.context.nullifier_exists_unsafe(nullifier, self.address); assert(exists, "Nullifier was just created, but its existence wasn't detected!"); } @@ -680,9 +680,9 @@ pub contract AvmTest { // Create the same nullifier twice (shouldn't work!) #[external("public")] fn nullifier_collision(nullifier: Field) { - self.context.push_nullifier(nullifier); + self.context.push_nullifier_unsafe(nullifier); // Can't do this twice! - self.context.push_nullifier(nullifier); + self.context.push_nullifier_unsafe(nullifier); } #[external("public")] @@ -780,13 +780,13 @@ pub contract AvmTest { #[external("public")] fn create_same_nullifier_in_nested_call(nestedAddress: AztecAddress, nullifier: Field) { - self.context.push_nullifier(nullifier); + self.context.push_nullifier_unsafe(nullifier); self.call(AvmTest::at(nestedAddress).new_nullifier(nullifier)); } #[external("public")] fn create_different_nullifier_in_nested_call(nestedAddress: AztecAddress, nullifier: Field) { - self.context.push_nullifier(nullifier); + self.context.push_nullifier_unsafe(nullifier); self.call(AvmTest::at(nestedAddress).new_nullifier(nullifier + 1)); } diff --git a/noir-projects/noir-contracts/contracts/test/benchmarking_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/benchmarking_contract/src/main.nr index 0596a0b081c8..7f77d9c3e57f 100644 --- a/noir-projects/noir-contracts/contracts/test/benchmarking_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test/benchmarking_contract/src/main.nr @@ -78,7 +78,7 @@ pub contract Benchmarking { // Safety: Benchmarking code let random_seed = unsafe { random() }; for i in 0..MAX_NULLIFIERS_PER_CALL { - self.context.push_nullifier(random_seed + (i as Field)); + self.context.push_nullifier_unsafe(random_seed + (i as Field)); } } diff --git a/noir-projects/noir-contracts/contracts/test/custom_message_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/custom_message_contract/src/main.nr index 31305cf716f8..b31163f2336c 100644 --- a/noir-projects/noir-contracts/contracts/test/custom_message_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test/custom_message_contract/src/main.nr @@ -147,7 +147,7 @@ contract CustomMessage { // Emit commitment as nullifier computed from FULL event (same as standard events) let commitment = compute_private_event_commitment(event, randomness); - self.context.push_nullifier(commitment); + self.context.push_nullifier_unsafe(commitment); let event_type_id = MultiLogEvent::get_event_type_id().to_field(); diff --git a/noir-projects/noir-contracts/contracts/test/custom_sync_state_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/test/custom_sync_state_contract/Nargo.toml new file mode 100644 index 000000000000..e48264fbe26b --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test/custom_sync_state_contract/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "custom_sync_state_contract" +authors = [""] +compiler_version = ">=0.25.0" +type = "contract" + +[dependencies] +aztec = { path = "../../../../aztec-nr/aztec" } +field_note = { path = "../../../../aztec-nr/field-note" } diff --git a/noir-projects/noir-contracts/contracts/test/custom_sync_state_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/custom_sync_state_contract/src/main.nr new file mode 100644 index 000000000000..87b1215db425 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test/custom_sync_state_contract/src/main.nr @@ -0,0 +1,70 @@ +// Tests the custom_sync_state AztecConfig option by registering a hook that counts how many times +// sync_state is called and then forwards to do_sync_state. +mod test; + +use ::aztec::{ + messages::discovery::{ + ComputeNoteHash, ComputeNoteNullifier, CustomMessageHandler, do_sync_state, + }, + messages::processing::offchain::OffchainInboxSync, + oracle::capsules, + protocol::{address::AztecAddress, hash::sha256_to_field}, +}; +use ::aztec::macros::aztec; + +global SYNC_COUNTER_SLOT: Field = sha256_to_field("SYNC_COUNTER".as_bytes()); + +#[aztec(::aztec::macros::AztecConfig::new().custom_sync_state(crate::my_custom_sync))] +contract CustomSyncState { + use aztec::macros::{ + functions::{external, initializer}, + storage::storage, + }; + use aztec::messages::message_delivery::MessageDelivery; + use aztec::protocol::address::AztecAddress; + use aztec::state_vars::{Owned, PrivateImmutable}; + use field_note::FieldNote; + + #[storage] + struct Storage { + value: Owned, Context>, + } + + #[external("private")] + #[initializer] + fn constructor(owner: AztecAddress, value: Field) { + let note = FieldNote { value }; + self.storage.value.at(owner).initialize(note).deliver( + MessageDelivery.ONCHAIN_UNCONSTRAINED, + ); + } + + #[external("utility")] + unconstrained fn read_value(owner: AztecAddress) -> pub Field { + self.storage.value.at(owner).view_note().value + } +} + +/// Counts how many times `sync_state` is invoked by incrementing a capsule counter, then forwards +/// to [`do_sync_state`] so that normal note discovery still happens. +unconstrained fn my_custom_sync( + contract_address: AztecAddress, + compute_note_hash: ComputeNoteHash, + compute_note_nullifier: ComputeNoteNullifier, + process_custom_message: Option, + offchain_inbox_sync: Option, + scope: AztecAddress, +) { + let current: Option = + capsules::load(contract_address, crate::SYNC_COUNTER_SLOT, scope); + let new_count = current.map(|c| c + 1).unwrap_or(1); + capsules::store(contract_address, crate::SYNC_COUNTER_SLOT, new_count, scope); + do_sync_state( + contract_address, + compute_note_hash, + compute_note_nullifier, + process_custom_message, + offchain_inbox_sync, + scope, + ); +} diff --git a/noir-projects/noir-contracts/contracts/test/custom_sync_state_contract/src/test.nr b/noir-projects/noir-contracts/contracts/test/custom_sync_state_contract/src/test.nr new file mode 100644 index 000000000000..949bf53535a9 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test/custom_sync_state_contract/src/test.nr @@ -0,0 +1,42 @@ +use crate::CustomSyncState; +use aztec::oracle::capsules; +use aztec::protocol::address::AztecAddress; +use aztec::test::helpers::test_environment::TestEnvironment; + +#[test] +unconstrained fn test_custom_sync_hook_is_called() { + let mut env = TestEnvironment::new(); + let owner = env.create_light_account(); + let value: Field = 42; + + let contract_address = env.deploy("CustomSyncState").with_private_initializer( + owner, + CustomSyncState::interface().constructor(owner, value), + ); + let target = CustomSyncState::at(contract_address); + + // After deploying and calling the private initializer, the hook should have been called once + let count = get_sync_count(env, contract_address, owner); + assert_eq(count, 1); + + // Reading the note via a utility function triggers another sync + let result = env.execute_utility(target.read_value(owner)); + + let count = get_sync_count(env, contract_address, owner); + assert_eq(count, 2); + + // The note was correctly discovered by do_sync_state (called by the hook) + assert_eq(result, value); +} + +unconstrained fn get_sync_count( + env: TestEnvironment, + contract_address: AztecAddress, + scope: AztecAddress, +) -> Field { + env.utility_context_at(contract_address, |_| { + let count: Option = + capsules::load(contract_address, crate::SYNC_COUNTER_SLOT, scope); + count.unwrap_or(0) + }) +} diff --git a/noir-projects/noir-contracts/contracts/test/private_init_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/private_init_test_contract/src/main.nr index f4eadfc6292f..914301a00aba 100644 --- a/noir-projects/noir-contracts/contracts/test/private_init_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test/private_init_test_contract/src/main.nr @@ -37,7 +37,7 @@ pub contract PrivateInitTest { #[external("private")] #[noinitcheck] fn private_no_init_check_emit_nullifier(nullifier: Field) { - self.context.push_nullifier(nullifier); + self.context.push_nullifier_unsafe(nullifier); } #[external("utility")] diff --git a/noir-projects/noir-contracts/contracts/test/spam_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/spam_contract/src/main.nr index 7610fa0180b6..7de11f2d7754 100644 --- a/noir-projects/noir-contracts/contracts/test/spam_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test/spam_contract/src/main.nr @@ -31,7 +31,7 @@ pub contract Spam { for i in 0..MAX_NULLIFIERS_PER_CALL { if (i < nullifier_count) { - self.context.push_nullifier(compute_note_nullifier(nullifier_seed, [i as Field])); + self.context.push_nullifier_unsafe(compute_note_nullifier(nullifier_seed, [i as Field])); } } diff --git a/noir-projects/noir-contracts/contracts/test/test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/test_contract/src/main.nr index 674bcb767c3d..10084a241a8b 100644 --- a/noir-projects/noir-contracts/contracts/test/test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test/test_contract/src/main.nr @@ -287,13 +287,13 @@ pub contract Test { #[external("public")] fn emit_nullifier_public(nullifier: Field) { - self.context.push_nullifier(nullifier); + self.context.push_nullifier_unsafe(nullifier); } #[external("private")] #[noinitcheck] fn emit_nullifier(nullifier: Field) { - self.context.push_nullifier(nullifier); + self.context.push_nullifier_unsafe(nullifier); } // For testing non-note encrypted logs diff --git a/noir-projects/protocol-fuzzer/contracts/side_effect_contract/src/main.nr b/noir-projects/protocol-fuzzer/contracts/side_effect_contract/src/main.nr index 2b9b29f171b5..db015882abb0 100644 --- a/noir-projects/protocol-fuzzer/contracts/side_effect_contract/src/main.nr +++ b/noir-projects/protocol-fuzzer/contracts/side_effect_contract/src/main.nr @@ -165,7 +165,7 @@ pub contract SideEffect { #[external("private")] #[noinitcheck] fn emit_nullifier(nullifier: Field) { - self.context.push_nullifier(nullifier); + self.context.push_nullifier_unsafe(nullifier); } #[external("private")] diff --git a/yarn-project/aztec.js/src/account/account_with_secret_key.ts b/yarn-project/aztec.js/src/account/account_with_secret_key.ts index 7bc50132bdec..45ec1bd1adf4 100644 --- a/yarn-project/aztec.js/src/account/account_with_secret_key.ts +++ b/yarn-project/aztec.js/src/account/account_with_secret_key.ts @@ -8,7 +8,7 @@ import { computeAddressSecret, deriveMasterIncomingViewingSecretKey } from '@azt import type { ExecutionPayload, TxExecutionRequest } from '@aztec/stdlib/tx'; import type { CallIntent, IntentInnerHash } from '../utils/authwit.js'; -import type { Account, Salt } from './index.js'; +import type { Account } from './index.js'; /** * Extends {@link BaseAccount} with the encryption private key. Not required for @@ -19,8 +19,6 @@ export class AccountWithSecretKey implements Account { constructor( private account: Account, private secretKey: Fr, - /** Deployment salt for this account contract. */ - public readonly salt: Salt, ) {} createTxExecutionRequest( diff --git a/yarn-project/aztec.js/src/contract/deploy_method.ts b/yarn-project/aztec.js/src/contract/deploy_method.ts index 2e46154323fe..20fb1b43d310 100644 --- a/yarn-project/aztec.js/src/contract/deploy_method.ts +++ b/yarn-project/aztec.js/src/contract/deploy_method.ts @@ -67,6 +67,11 @@ export type DeployInstantiationOptions = { universalDeploy?: boolean; /** Public keys mixed into the address. Defaults to PublicKeys.default(). */ publicKeys?: PublicKeys; + /** + * Commitment to the contract's immutable storage values. Folded into the salted initialization + * hash, so a non-zero value affects the derived address. Defaults to `Fr.ZERO`. + */ + immutablesHash?: Fr; }; /** @@ -79,6 +84,11 @@ type SharedInstantiationOptions = { salt?: Fr; /** Public keys mixed into the address. Defaults to `PublicKeys.default()`. */ publicKeys?: PublicKeys; + /** + * Commitment to the contract's immutable storage values. Folded into the salted initialization + * hash, so a non-zero value affects the derived address. Defaults to `Fr.ZERO`. + */ + immutablesHash?: Fr; }; /** @@ -242,6 +252,8 @@ export abstract class DeployMethod; @@ -264,6 +276,7 @@ export abstract class DeployMethod, salt: Fr | undefined, publicKeys: PublicKeys | undefined, + immutablesHash: Fr | undefined, payload: DeployMethodPayload = {}, ) { super(wallet, payload.authWitnesses ?? [], payload.capsules ?? []); @@ -273,6 +286,7 @@ export abstract class DeployMethod( wallet, contract, - { salt, publicKeys, universalDeploy: true }, + { salt, publicKeys, immutablesHash, universalDeploy: true }, payload, ); } if (deployer !== undefined) { - return new BoundDeployMethod(wallet, contract, { salt, publicKeys, deployer }, payload); + return new BoundDeployMethod( + wallet, + contract, + { salt, publicKeys, immutablesHash, deployer }, + payload, + ); } - return new PendingDeployMethod(wallet, contract, { salt, publicKeys }, payload); + return new PendingDeployMethod(wallet, contract, { salt, publicKeys, immutablesHash }, payload); } /** @@ -540,6 +559,7 @@ export abstract class DeployMethod { @@ -676,7 +696,7 @@ export class BoundDeployMethod ex 'BoundDeployMethod requires a non-zero `deployer`; use `UniversalDeployMethod` (`{ universalDeploy: true }`) for universal deploys.', ); } - super(wallet, contract, instantiation.salt, instantiation.publicKeys, payload); + super(wallet, contract, instantiation.salt, instantiation.publicKeys, instantiation.immutablesHash, payload); this.deployer = instantiation.deployer; } @@ -703,7 +723,12 @@ export class BoundDeployMethod ex /** Re-emits this method's `DeployInstantiationOptions` for `with(...)` to consume. */ public cloneInstantiation(): DeployInstantiationOptions { - return { salt: this.salt, publicKeys: this.publicKeys, deployer: this.deployer }; + return { + salt: this.salt, + publicKeys: this.publicKeys, + immutablesHash: this.immutablesHash, + deployer: this.deployer, + }; } } @@ -720,7 +745,7 @@ export class UniversalDeployMethod instantiation: PendingInstantiationOptions = {}, payload: DeployMethodPayload = {}, ) { - super(wallet, contract, instantiation.salt, instantiation.publicKeys, payload); + super(wallet, contract, instantiation.salt, instantiation.publicKeys, instantiation.immutablesHash, payload); } /** @@ -802,7 +832,7 @@ export class PendingDeployMethod /** Re-emits this method's `DeployInstantiationOptions` for `with(...)` to consume. */ public cloneInstantiation(): DeployInstantiationOptions { if (!this.#locked) { - return { salt: this.salt, publicKeys: this.publicKeys }; + return { salt: this.salt, publicKeys: this.publicKeys, immutablesHash: this.immutablesHash }; } return this.#locked.cloneInstantiation(); } @@ -815,7 +845,18 @@ export class PendingDeployMethod #promoteFrom( from: SendInteractionOptionsWithoutWait['from'] | undefined, ): BoundDeployMethod | UniversalDeployMethod { - const { wallet, artifact, postDeployCtor, args, salt, publicKeys, authWitnesses, capsules, extraHashedArgs } = this; + const { + wallet, + artifact, + postDeployCtor, + args, + salt, + publicKeys, + immutablesHash, + authWitnesses, + capsules, + extraHashedArgs, + } = this; const contract: DeployMethodContract = { artifact, postDeployCtor, @@ -827,10 +868,15 @@ export class PendingDeployMethod return new UniversalDeployMethod( wallet, contract, - { salt, publicKeys, universalDeploy: true }, + { salt, publicKeys, immutablesHash, universalDeploy: true }, payload, ); } - return new BoundDeployMethod(wallet, contract, { salt, publicKeys, deployer: from }, payload); + return new BoundDeployMethod( + wallet, + contract, + { salt, publicKeys, immutablesHash, deployer: from }, + payload, + ); } } diff --git a/yarn-project/aztec.js/src/wallet/account_manager.ts b/yarn-project/aztec.js/src/wallet/account_manager.ts index a4d653192940..0cfa9a50eb1d 100644 --- a/yarn-project/aztec.js/src/wallet/account_manager.ts +++ b/yarn-project/aztec.js/src/wallet/account_manager.ts @@ -1,4 +1,5 @@ import { Fr } from '@aztec/foundation/curves/bn254'; +import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import { CompleteAddress, type ContractInstanceWithAddress, @@ -13,6 +14,21 @@ import { Contract } from '../contract/contract.js'; import { DeployAccountMethod } from './deploy_account_method.js'; import type { Wallet } from './wallet.js'; +/** + * Optional overrides passed to {@link AccountManager.create}. + */ +export interface AccountManagerCreateOptions { + /** Contract instantiation salt. Defaults to a random `Fr`. */ + salt?: Salt; + /** + * Commitment to the contract's immutable storage values. Folded into the salted initialization + * hash, so a non-zero value affects the derived address. Defaults to `Fr.ZERO`. + */ + immutablesHash?: Fr; + /** Address recorded as the instance deployer. Defaults to `AztecAddress.ZERO`. */ + deployer?: AztecAddress; +} + /** * Manages a user account. Provides methods for calculating the account's address and other related data, * plus a helper to return a preconfigured deploy method. @@ -23,15 +39,16 @@ export class AccountManager { private secretKey: Fr, private accountContract: AccountContract, private instance: ContractInstanceWithAddress, - /** - * Contract instantiation salt for the account contract - */ - public readonly salt: Salt, ) {} - static async create(wallet: Wallet, secretKey: Fr, accountContract: AccountContract, salt?: Salt) { + static async create( + wallet: Wallet, + secretKey: Fr, + accountContract: AccountContract, + opts?: AccountManagerCreateOptions, + ) { const { publicKeys } = await deriveKeys(secretKey); - salt = salt !== undefined ? new Fr(salt) : Fr.random(); + const salt = opts?.salt !== undefined ? new Fr(opts.salt) : Fr.random(); const { constructorName, constructorArgs } = (await accountContract.getInitializationFunctionAndArgs()) ?? { constructorName: undefined, @@ -42,11 +59,13 @@ export class AccountManager { const instance = await getContractInstanceFromInstantiationParams(artifact, { constructorArtifact: constructorName, constructorArgs, - salt: salt, + salt, publicKeys, + deployer: opts?.deployer, + immutablesHash: opts?.immutablesHash, }); - return new AccountManager(wallet, secretKey, accountContract, instance, salt); + return new AccountManager(wallet, secretKey, accountContract, instance); } protected getPublicKeys() { @@ -94,7 +113,7 @@ export class AccountManager { public async getAccount(): Promise { const completeAddress = await this.getCompleteAddress(); const account = this.accountContract.getAccount(completeAddress); - return new AccountWithSecretKey(account, this.secretKey, this.salt); + return new AccountWithSecretKey(account, this.secretKey); } /** @@ -130,7 +149,8 @@ export class AccountManager { this.wallet, artifact, instance => Contract.at(instance.address, artifact, this.wallet), - new Fr(this.salt), + this.instance.salt, + this.instance.immutablesHash, account, constructorArgs, constructorName, diff --git a/yarn-project/aztec.js/src/wallet/deploy_account_method.ts b/yarn-project/aztec.js/src/wallet/deploy_account_method.ts index c711d43cdf61..96e91a7c8a94 100644 --- a/yarn-project/aztec.js/src/wallet/deploy_account_method.ts +++ b/yarn-project/aztec.js/src/wallet/deploy_account_method.ts @@ -85,6 +85,7 @@ export class DeployAccountMethod exte artifact: ContractArtifact, postDeployCtor: (instance: ContractInstanceWithAddress, wallet: Wallet) => TContract, salt: Fr, + immutablesHash: Fr, private account: Account, args: any[] = [], constructorNameOrArtifact?: string | FunctionArtifact, @@ -96,7 +97,7 @@ export class DeployAccountMethod exte wallet, { artifact, postDeployCtor, args, constructorNameOrArtifact }, // Account contracts are always deployed universally. - { salt, universalDeploy: true, publicKeys }, + { salt, universalDeploy: true, publicKeys, immutablesHash }, { authWitnesses, capsules, extraHashedArgs }, ); } @@ -221,6 +222,7 @@ export class DeployAccountMethod exte this.artifact, this.postDeployCtor, this.salt, + this.immutablesHash, this.account, this.args, this.constructorArtifact?.name, diff --git a/yarn-project/bootstrap.sh b/yarn-project/bootstrap.sh index d263e662de9c..140dd2c6ca78 100755 --- a/yarn-project/bootstrap.sh +++ b/yarn-project/bootstrap.sh @@ -143,9 +143,11 @@ function compile_all { get_projects | compile_project - # Run oracle version check for pxe after compilation + # Run oracle version checks after compilation cd pxe && yarn check_oracle_version cd .. + cd txe && yarn check_txe_oracle_version + cd .. cmds=('format --check' 'yarn tsgo -b --emitDeclarationOnly') if [ "${CI:-0}" -eq 1 ]; then diff --git a/yarn-project/cli-wallet/src/utils/wallet.ts b/yarn-project/cli-wallet/src/utils/wallet.ts index 2ace44debe9c..e81e78d1fac3 100644 --- a/yarn-project/cli-wallet/src/utils/wallet.ts +++ b/yarn-project/cli-wallet/src/utils/wallet.ts @@ -138,7 +138,7 @@ export class CLIWallet extends BaseWallet { } private async createAccount(secret: Fr, salt: Fr, contract: AccountContract): Promise { - const accountManager = await AccountManager.create(this, secret, contract, salt); + const accountManager = await AccountManager.create(this, secret, contract, { salt }); const instance = accountManager.getInstance(); const artifact = await contract.getContractArtifact(); diff --git a/yarn-project/end-to-end/src/spartan/block_capacity.test.ts b/yarn-project/end-to-end/src/spartan/block_capacity.test.ts index 41f99888907a..b62b3e68d83d 100644 --- a/yarn-project/end-to-end/src/spartan/block_capacity.test.ts +++ b/yarn-project/end-to-end/src/spartan/block_capacity.test.ts @@ -112,7 +112,7 @@ describe('block capacity benchmark', () => { wallet, secret, new SchnorrAccountContract(deriveSigningKey(secret)), - salt, + { salt }, ); const deployMethod = await manager.getDeployMethod(); await deployMethod.send({ diff --git a/yarn-project/end-to-end/src/spartan/n_tps.test.ts b/yarn-project/end-to-end/src/spartan/n_tps.test.ts index 16bda90b2799..c8bb4b95c8bd 100644 --- a/yarn-project/end-to-end/src/spartan/n_tps.test.ts +++ b/yarn-project/end-to-end/src/spartan/n_tps.test.ts @@ -318,7 +318,7 @@ describe('sustained N TPS test', () => { wallet, secret, new SchnorrAccountContract(deriveSigningKey(secret)), - salt, + { salt }, ); const deployMethod = await manager.getDeployMethod(); // Explicit gas estimation: BaseWallet's fallback bakes diff --git a/yarn-project/end-to-end/src/spartan/n_tps_prove.test.ts b/yarn-project/end-to-end/src/spartan/n_tps_prove.test.ts index 7dd82411b8aa..76190e7a0f2d 100644 --- a/yarn-project/end-to-end/src/spartan/n_tps_prove.test.ts +++ b/yarn-project/end-to-end/src/spartan/n_tps_prove.test.ts @@ -315,7 +315,7 @@ describe(`prove ${TARGET_TPS}TPS test`, () => { wallet, secret, new SchnorrAccountContract(deriveSigningKey(secret)), - salt, + { salt }, ); const deployMethod = await manager.getDeployMethod(); await deployMethod.send({ diff --git a/yarn-project/end-to-end/src/test-wallet/test_wallet.ts b/yarn-project/end-to-end/src/test-wallet/test_wallet.ts index 55115681d120..fba928c618d9 100644 --- a/yarn-project/end-to-end/src/test-wallet/test_wallet.ts +++ b/yarn-project/end-to-end/src/test-wallet/test_wallet.ts @@ -221,7 +221,7 @@ export class TestWallet extends BaseWallet { const type = accountData?.type ?? 'schnorr'; const contract = accountData?.contract ?? new SchnorrAccountContract(GrumpkinScalar.random()); - const accountManager = await AccountManager.create(this, secret, contract, salt); + const accountManager = await AccountManager.create(this, secret, contract, { salt }); const instance = accountManager.getInstance(); const artifact = await contract.getContractArtifact(); diff --git a/yarn-project/pxe/package.json b/yarn-project/pxe/package.json index 0cfaf010e6e8..35908adc42f2 100644 --- a/yarn-project/pxe/package.json +++ b/yarn-project/pxe/package.json @@ -16,7 +16,8 @@ "./client/lazy": "./dest/entrypoints/client/lazy/index.js", "./client/bundle": "./dest/entrypoints/client/bundle/index.js", "./simulator": "./dest/contract_function_simulator/index.js", - "./config": "./dest/config/index.js" + "./config": "./dest/config/index.js", + "./bin": "./dest/bin/index.js" }, "bin": "./dest/bin/index.js", "scripts": { diff --git a/yarn-project/pxe/src/bin/check_oracle_version.ts b/yarn-project/pxe/src/bin/check_oracle_version.ts index 1393d7df640e..cf45050592cf 100644 --- a/yarn-project/pxe/src/bin/check_oracle_version.ts +++ b/yarn-project/pxe/src/bin/check_oracle_version.ts @@ -16,27 +16,8 @@ import { ORACLE_INTERFACE_HASH } from '../oracle_version.js'; * changed and the oracle version needs to be bumped: * - If the change is backward-breaking (e.g. removing/renaming an oracle), bump ORACLE_VERSION_MAJOR. * - If the change is an oracle addition (non-breaking), bump ORACLE_VERSION_MINOR. - * - * TODO(F-667): The following only takes into consideration changes to the oracles defined in Oracle.ts and omits TXE - * oracles. Ensure this checks TXE oracles as well. This hasn't been implemented yet since we don't have a clean TXE - * oracle interface like we do in PXE (i.e., there is no single Oracle class that contains only the oracles). */ function assertOracleInterfaceMatches(): void { - const oracleInterfaceSignature = getOracleInterfaceSignature(); - - // We use keccak256 here just because we already have it in the dependencies. - const oracleInterfaceHash = keccak256String(oracleInterfaceSignature); - if (oracleInterfaceHash !== ORACLE_INTERFACE_HASH) { - throw new Error( - `The Oracle interface has changed. Update ORACLE_INTERFACE_HASH to ${oracleInterfaceHash} in pxe/src/oracle_version.ts and bump the oracle version (ORACLE_VERSION_MAJOR for breaking changes, ORACLE_VERSION_MINOR for oracle additions).`, - ); - } -} - -/** - * Constructs a signature of the Oracle interface while ignoring methods that are not foreign call handlers. - */ -function getOracleInterfaceSignature(): string { const excludedProps = [ 'handler', 'constructor', @@ -44,7 +25,7 @@ function getOracleInterfaceSignature(): string { 'handlerAsMisc', 'handlerAsUtility', 'handlerAsPrivate', - ] as const; + ]; // Get the path to Oracle.ts source file // The script runs from dest/bin/ after compilation, so we need to go up to the package root @@ -54,23 +35,52 @@ function getOracleInterfaceSignature(): string { const packageRoot = dirname(dirname(currentDir)); // Go up from bin/ to pxe/ const oracleSourcePath = join(packageRoot, 'src/contract_function_simulator/oracle/oracle.ts'); + const oracleInterfaceSignature = getOracleInterfaceSignature(oracleSourcePath, ['Oracle'], excludedProps); + + // We use keccak256 here just because we already have it in the dependencies. + const oracleInterfaceHash = keccak256String(oracleInterfaceSignature); + if (oracleInterfaceHash !== ORACLE_INTERFACE_HASH) { + throw new Error( + `The Oracle interface has changed. Update ORACLE_INTERFACE_HASH to ${oracleInterfaceHash} in pxe/src/oracle_version.ts and bump the oracle version (ORACLE_VERSION_MAJOR for breaking changes, ORACLE_VERSION_MINOR for oracle additions).`, + ); + } +} + +/** + * Extracts method signatures from TypeScript classes or interfaces and returns a deterministic string representation. + * + * This is used to detect when an oracle interface changes so that the oracle version can be bumped. It works with both + * class declarations (e.g. PXE's `Oracle` class) and interface declarations (e.g. TXE's `IAvmExecutionOracle`). + * + * @param sourcePath - Absolute path to the TypeScript source file to parse. + * @param targets - Names of classes or interfaces to extract methods from. + * @param excludedMembers - Method names to skip (e.g. non-oracle helpers like `constructor`). + */ +export function getOracleInterfaceSignature(sourcePath: string, targets: string[], excludedMembers: string[]): string { // Read and parse the TypeScript source file - const sourceCode = readFileSync(oracleSourcePath, 'utf-8'); - const sourceFile = ts.createSourceFile('oracle.ts', sourceCode, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); + const sourceCode = readFileSync(sourcePath, 'utf-8'); + const sourceFile = ts.createSourceFile(sourcePath, sourceCode, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); - // Extract method signatures from the Oracle class + // Extract method signatures from the target classes/interfaces const methodSignatures: string[] = []; function visit(node: ts.Node) { - // Look for class declaration named "Oracle" - if (ts.isClassDeclaration(node) && node.name?.text === 'Oracle') { - // Visit all members of the class + // Look for class or interface declarations matching the target names + const isTarget = + (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) && targets.includes(node.name?.text ?? ''); + + if (isTarget) { + // Visit all members of the class/interface node.members.forEach(member => { - if (ts.isMethodDeclaration(member) && member.name && ts.isIdentifier(member.name)) { + if ( + (ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) && + member.name && + ts.isIdentifier(member.name) + ) { const methodName = member.name.text; // Skip excluded methods - if (excludedProps.includes(methodName as (typeof excludedProps)[number])) { + if (excludedMembers.includes(methodName)) { return; } diff --git a/yarn-project/pxe/src/bin/index.ts b/yarn-project/pxe/src/bin/index.ts new file mode 100644 index 000000000000..bda0bdc9d99b --- /dev/null +++ b/yarn-project/pxe/src/bin/index.ts @@ -0,0 +1 @@ +export { getOracleInterfaceSignature } from './check_oracle_version.js'; diff --git a/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_request.test.ts b/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_request.test.ts index d33ba2470a10..175f7f906bcb 100644 --- a/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_request.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_request.test.ts @@ -1,19 +1,88 @@ +import { BlockNumber } from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { Tag } from '@aztec/stdlib/logs'; -import { LogRetrievalRequest } from './log_retrieval_request.js'; +import { LogRetrievalRequest, LogSource } from './log_retrieval_request.js'; describe('LogRetrievalRequest', () => { - it('output of Noir serialization deserializes as expected', () => { + it('output of Noir serialization with defaults deserializes as expected', () => { const serialized = [ '0x0000000000000000000000000000000000000000000000000000000000000001', '0x0000000000000000000000000000000000000000000000000000000000000002', + '0x0000000000000000000000000000000000000000000000000000000000000002', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', + ].map(Fr.fromHexString); + + const request = LogRetrievalRequest.fromFields(serialized); + + expect(request.contractAddress).toEqual(AztecAddress.fromBigInt(1n)); + expect(request.tag).toEqual(new Tag(new Fr(2))); + expect(request.source).toEqual(LogSource.PUBLIC_AND_PRIVATE); + expect(request.fromBlock).toBeUndefined(); + expect(request.toBlock).toBeUndefined(); + }); + + it('output of Noir serialization with values deserializes as expected', () => { + const serialized = [ + '0x0000000000000000000000000000000000000000000000000000000000000001', + '0x0000000000000000000000000000000000000000000000000000000000000002', + '0x0000000000000000000000000000000000000000000000000000000000000001', + '0x0000000000000000000000000000000000000000000000000000000000000001', + '0x000000000000000000000000000000000000000000000000000000000000000a', + '0x0000000000000000000000000000000000000000000000000000000000000001', + '0x0000000000000000000000000000000000000000000000000000000000000014', ].map(Fr.fromHexString); const request = LogRetrievalRequest.fromFields(serialized); expect(request.contractAddress).toEqual(AztecAddress.fromBigInt(1n)); expect(request.tag).toEqual(new Tag(new Fr(2))); + expect(request.source).toEqual(LogSource.PUBLIC); + expect(request.fromBlock).toEqual(BlockNumber(10)); + expect(request.toBlock).toEqual(BlockNumber(20)); + }); + + it('rejects an invalid LogSource value', () => { + const serialized = [ + '0x0000000000000000000000000000000000000000000000000000000000000001', + '0x0000000000000000000000000000000000000000000000000000000000000002', + '0x000000000000000000000000000000000000000000000000000000000000002a', // 42 — invalid + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', + ].map(Fr.fromHexString); + + expect(() => LogRetrievalRequest.fromFields(serialized)).toThrow(/Invalid LogSource value 42/); + }); + + it('accepts all valid LogSource values', () => { + for (const source of [LogSource.PRIVATE, LogSource.PUBLIC, LogSource.PUBLIC_AND_PRIVATE]) { + const fields = new LogRetrievalRequest(AztecAddress.fromBigInt(1n), new Tag(new Fr(2)), source).toFields(); + const restored = LogRetrievalRequest.fromFields(fields); + expect(restored.source).toEqual(source); + } + }); + + it('round-trips through toFields and fromFields', () => { + const original = new LogRetrievalRequest( + AztecAddress.fromBigInt(42n), + new Tag(new Fr(99)), + LogSource.PRIVATE, + BlockNumber(5), + BlockNumber(100), + ); + + const restored = LogRetrievalRequest.fromFields(original.toFields()); + + expect(restored.contractAddress).toEqual(original.contractAddress); + expect(restored.tag).toEqual(original.tag); + expect(restored.source).toEqual(original.source); + expect(restored.fromBlock).toEqual(original.fromBlock); + expect(restored.toBlock).toEqual(original.toBlock); }); }); diff --git a/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_request.ts b/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_request.ts index 377213a23106..549b452d2d39 100644 --- a/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_request.ts +++ b/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_request.ts @@ -1,8 +1,16 @@ +import { BlockNumber } from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; import { FieldReader } from '@aztec/foundation/serialize'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { Tag } from '@aztec/stdlib/logs'; +/** Discriminant for which log source to query. */ +export enum LogSource { + PRIVATE = 0, + PUBLIC = 1, + PUBLIC_AND_PRIVATE = 2, +} + /** * Intermediate struct used to perform batch log retrieval by PXE. The `utilityBulkRetrieveLogs` oracle expects values of this * type to be stored in a `EphemeralArray`. @@ -11,10 +19,21 @@ export class LogRetrievalRequest { constructor( public contractAddress: AztecAddress, public tag: Tag, + public source: LogSource = LogSource.PUBLIC_AND_PRIVATE, + public fromBlock?: BlockNumber, + public toBlock?: BlockNumber, ) {} toFields(): Fr[] { - return [this.contractAddress.toField(), this.tag.value]; + return [ + this.contractAddress.toField(), + this.tag.value, + new Fr(this.source), + new Fr(this.fromBlock !== undefined ? 1 : 0), + new Fr(this.fromBlock ?? 0), + new Fr(this.toBlock !== undefined ? 1 : 0), + new Fr(this.toBlock ?? 0), + ]; } static fromFields(fields: Fr[] | FieldReader): LogRetrievalRequest { @@ -22,7 +41,21 @@ export class LogRetrievalRequest { const contractAddress = AztecAddress.fromField(reader.readField()); const tag = new Tag(reader.readField()); + const sourceNum = reader.readField().toNumber(); + if (!(sourceNum in LogSource)) { + const validNames = Object.keys(LogSource).filter(k => isNaN(Number(k))); + throw new Error(`Invalid LogSource value ${sourceNum}, expected one of ${validNames.join(', ')}`); + } + const source = sourceNum as LogSource; + + const fromBlockIsSome = reader.readBoolean(); + const fromBlockValue = reader.readField(); + const fromBlock = fromBlockIsSome ? BlockNumber(fromBlockValue.toNumber()) : undefined; + + const toBlockIsSome = reader.readBoolean(); + const toBlockValue = reader.readField(); + const toBlock = toBlockIsSome ? BlockNumber(toBlockValue.toNumber()) : undefined; - return new LogRetrievalRequest(contractAddress, tag); + return new LogRetrievalRequest(contractAddress, tag, source, fromBlock, toBlock); } } diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/interfaces.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/interfaces.ts index 17a165e6babe..0d18d9ed747c 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/interfaces.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/interfaces.ts @@ -70,25 +70,19 @@ export interface IUtilityExecutionOracle { getNoteHashMembershipWitness( anchorBlockHash: BlockHash, noteHash: Fr, - ): Promise | undefined>; + ): Promise>; getBlockHashMembershipWitness( anchorBlockHash: BlockHash, blockHash: BlockHash, ): Promise | undefined>; - getNullifierMembershipWitness( - anchorBlockHash: BlockHash, - nullifier: Fr, - ): Promise; - getPublicDataWitness(anchorBlockHash: BlockHash, leafSlot: Fr): Promise; - getLowNullifierMembershipWitness( - anchorBlockHash: BlockHash, - nullifier: Fr, - ): Promise; - getBlockHeader(blockNumber: BlockNumber): Promise; + getNullifierMembershipWitness(anchorBlockHash: BlockHash, nullifier: Fr): Promise; + getPublicDataWitness(anchorBlockHash: BlockHash, leafSlot: Fr): Promise; + getLowNullifierMembershipWitness(anchorBlockHash: BlockHash, nullifier: Fr): Promise; + getBlockHeader(blockNumber: BlockNumber): Promise; getPublicKeysAndPartialAddress( account: AztecAddress, ): Promise<{ publicKeys: PublicKeys; partialAddress: PartialAddress } | undefined>; - getAuthWitness(messageHash: Fr): Promise; + getAuthWitness(messageHash: Fr): Promise; getNotes( owner: AztecAddress | undefined, storageSlot: Fr, @@ -139,7 +133,7 @@ export interface IUtilityExecutionOracle { numEntries: number, scope: AztecAddress, ): Promise; - decryptAes128(ciphertext: Buffer, iv: Buffer, symKey: Buffer): Promise; + decryptAes128(ciphertext: Buffer, iv: Buffer, symKey: Buffer): Promise; getSharedSecrets(address: AztecAddress, ephPksSlot: Fr, contractAddress: AztecAddress): Promise; setContractSyncCacheInvalid(contractAddress: AztecAddress, scopes: AztecAddress[]): void; emitOffchainEffect(data: Fr[]): Promise; diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle.ts index 670d2bdc86b1..0a3fffe870bf 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle.ts @@ -238,11 +238,6 @@ export class Oracle { const parsedNoteHash = Fr.fromString(noteHash); const witness = await this.handlerAsUtility().getNoteHashMembershipWitness(parsedAnchorBlockHash, parsedNoteHash); - if (!witness) { - throw new Error( - `Note hash ${noteHash} not found in the note hash tree at anchor block hash ${parsedAnchorBlockHash.toString()}.`, - ); - } return witness.toNoirRepresentation(); } @@ -268,11 +263,6 @@ export class Oracle { const parsedNullifier = Fr.fromString(nullifier); const witness = await this.handlerAsUtility().getNullifierMembershipWitness(parsedBlockHash, parsedNullifier); - if (!witness) { - throw new Error( - `Nullifier witness not found for nullifier ${parsedNullifier} at block hash ${parsedBlockHash.toString()}.`, - ); - } return witness.toNoirRepresentation(); } @@ -285,11 +275,6 @@ export class Oracle { const parsedNullifier = Fr.fromString(nullifier); const witness = await this.handlerAsUtility().getLowNullifierMembershipWitness(parsedBlockHash, parsedNullifier); - if (!witness) { - throw new Error( - `Low nullifier witness not found for nullifier ${parsedNullifier} at block hash ${parsedBlockHash.toString()}.`, - ); - } return witness.toNoirRepresentation(); } @@ -302,11 +287,6 @@ export class Oracle { const parsedLeafSlot = Fr.fromString(leafSlot); const witness = await this.handlerAsUtility().getPublicDataWitness(parsedBlockHash, parsedLeafSlot); - if (!witness) { - throw new Error( - `Public data witness not found for slot ${parsedLeafSlot} at block hash ${parsedBlockHash.toString()}.`, - ); - } return witness.toNoirRepresentation(); } @@ -315,9 +295,6 @@ export class Oracle { const parsedBlockNumber = Fr.fromString(blockNumber).toNumber(); const header = await this.handlerAsUtility().getBlockHeader(BlockNumber(parsedBlockNumber)); - if (!header) { - throw new Error(`Block header not found for block ${parsedBlockNumber}.`); - } return header.toFields().map(toACVMField); } @@ -325,9 +302,6 @@ export class Oracle { async aztec_utl_getAuthWitness([messageHash]: ACVMField[]): Promise { const messageHashField = Fr.fromString(messageHash); const witness = await this.handlerAsUtility().getAuthWitness(messageHashField); - if (!witness) { - throw new Error(`Unknown auth witness for message hash ${messageHashField}`); - } return [witness.map(toACVMField)]; } @@ -782,11 +756,11 @@ export class Oracle { const symKeyBuffer = fromUintArray(symKey, 8); // Noir Option is encoded as [is_some: Field, storage: Field[], length: Field]. - try { - const plaintext = await this.handlerAsUtility().decryptAes128(ciphertext, ivBuffer, symKeyBuffer); + const plaintext = await this.handlerAsUtility().decryptAes128(ciphertext, ivBuffer, symKeyBuffer); + if (plaintext) { const [storage, length] = bufferToBoundedVec(plaintext, ciphertextBVecStorage.length); return [toACVMField(1), storage, length]; - } catch { + } else { const zeroStorage = Array(ciphertextBVecStorage.length).fill(toACVMField(0)); return [toACVMField(0), zeroStorage, toACVMField(0)]; } diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts index 14b728ce67b4..e03a4ee93c19 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts @@ -48,7 +48,6 @@ import type { SenderAddressBookStore } from '../../storage/tagging_store/sender_ import { EphemeralArrayService } from '../ephemeral_array_service.js'; import { EventValidationRequest } from '../noir-structs/event_validation_request.js'; import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; -import { LogRetrievalResponse } from '../noir-structs/log_retrieval_response.js'; import { NoteValidationRequest } from '../noir-structs/note_validation_request.js'; import { UtilityContext } from '../noir-structs/utility_context.js'; import { pickNotes } from '../pick_notes.js'; @@ -201,13 +200,17 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @param noteHash - The note hash to find in the note hash tree. * @returns The membership witness containing the leaf index and sibling path */ - public getNoteHashMembershipWitness( + public async getNoteHashMembershipWitness( blockHash: BlockHash, noteHash: Fr, - ): Promise | undefined> { - return this.#queryWithBlockHashNotAfterAnchor(blockHash, () => + ): Promise> { + const witness = await this.#queryWithBlockHashNotAfterAnchor(blockHash, () => this.aztecNode.getNoteHashMembershipWitness(blockHash, noteHash), ); + if (!witness) { + throw new Error(`Note hash ${noteHash} not found in the note hash tree at block ${blockHash.toString()}.`); + } + return witness; } /** @@ -239,13 +242,14 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @param nullifier - Nullifier we try to find witness for. * @returns The nullifier membership witness (if found). */ - public getNullifierMembershipWitness( - blockHash: BlockHash, - nullifier: Fr, - ): Promise { - return this.#queryWithBlockHashNotAfterAnchor(blockHash, () => + public async getNullifierMembershipWitness(blockHash: BlockHash, nullifier: Fr): Promise { + const witness = await this.#queryWithBlockHashNotAfterAnchor(blockHash, () => this.aztecNode.getNullifierMembershipWitness(blockHash, nullifier), ); + if (!witness) { + throw new Error(`Nullifier membership witness not found at block ${blockHash.toString()}.`); + } + return witness; } /** @@ -257,13 +261,19 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * list structure" of leaves and proving that a lower nullifier is pointing to a bigger next value than the nullifier * we are trying to prove non-inclusion for. */ - public getLowNullifierMembershipWitness( + public async getLowNullifierMembershipWitness( blockHash: BlockHash, nullifier: Fr, - ): Promise { - return this.#queryWithBlockHashNotAfterAnchor(blockHash, () => + ): Promise { + const witness = await this.#queryWithBlockHashNotAfterAnchor(blockHash, () => this.aztecNode.getLowNullifierMembershipWitness(blockHash, nullifier), ); + if (!witness) { + throw new Error( + `Low nullifier witness not found for nullifier ${nullifier} at block hash ${blockHash.toString()}.`, + ); + } + return witness; } /** @@ -272,10 +282,14 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @param leafSlot - The slot of the public data tree to get the witness for. * @returns - The witness */ - public getPublicDataWitness(blockHash: BlockHash, leafSlot: Fr): Promise { - return this.#queryWithBlockHashNotAfterAnchor(blockHash, () => + public async getPublicDataWitness(blockHash: BlockHash, leafSlot: Fr): Promise { + const witness = await this.#queryWithBlockHashNotAfterAnchor(blockHash, () => this.aztecNode.getPublicDataWitness(blockHash, leafSlot), ); + if (!witness) { + throw new Error(`Public data witness not found for slot ${leafSlot} at block hash ${blockHash.toString()}.`); + } + return witness; } /** @@ -283,7 +297,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @param blockNumber - The number of a block of which to get the block header. * @returns Block extracted from a block with block number `blockNumber`. */ - public async getBlockHeader(blockNumber: BlockNumber): Promise { + public async getBlockHeader(blockNumber: BlockNumber): Promise { const anchorBlockNumber = this.anchorBlockHeader.getBlockNumber(); if (blockNumber > anchorBlockNumber) { throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); @@ -295,7 +309,10 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra } const block = await this.aztecNode.getBlock(blockNumber); - return block?.header; + if (!block?.header) { + throw new Error(`Block header not found for block ${blockNumber}.`); + } + return block.header; } /** @@ -342,8 +359,12 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @param messageHash - Hash of the message to authenticate. * @returns Authentication witness for the requested message hash, or undefined if not found. */ - public getAuthWitness(messageHash: Fr): Promise { - return Promise.resolve(this.authWitnesses.find(w => w.requestHash.equals(messageHash))?.witness); + public getAuthWitness(messageHash: Fr): Promise { + const witness = this.authWitnesses.find(w => w.requestHash.equals(messageHash))?.witness; + if (!witness) { + throw new Error(`Unknown auth witness for message hash ${messageHash}`); + } + return Promise.resolve(witness); } /** @@ -578,9 +599,14 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra .map(LogRetrievalRequest.fromFields); const logService = this.#createLogService(); - const maybeLogRetrievalResponses = await logService.fetchLogsByTag(this.contractAddress, logRetrievalRequests); + const logRetrievalResponses = await logService.fetchLogsByTag(this.contractAddress, logRetrievalRequests); - return this.ephemeralArrayService.newArray(maybeLogRetrievalResponses.map(LogRetrievalResponse.toSerializedOption)); + // Create an inner ephemeral array for each request's matching logs, then wrap all slots in an outer array. + const innerSlots = logRetrievalResponses.map(responses => + this.ephemeralArrayService.newArray(responses.map(r => r.toFields())), + ); + + return this.ephemeralArrayService.newArray(innerSlots.map(slot => [slot])); } /** Reads tx hash requests from an ephemeral array, resolves their contexts, and returns the response slot. */ @@ -671,9 +697,13 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra } // TODO(#11849): consider replacing this oracle with a pure Noir implementation of aes decryption. - public decryptAes128(ciphertext: Buffer, iv: Buffer, symKey: Buffer): Promise { - const aes128 = new Aes128(); - return aes128.decryptBufferCBC(ciphertext, iv, symKey); + public async decryptAes128(ciphertext: Buffer, iv: Buffer, symKey: Buffer): Promise { + try { + const aes128 = new Aes128(); + return await aes128.decryptBufferCBC(ciphertext, iv, symKey); + } catch { + return undefined; + } } /** diff --git a/yarn-project/pxe/src/logs/log_service.test.ts b/yarn-project/pxe/src/logs/log_service.test.ts index fbaef16f94d9..7eb33274d934 100644 --- a/yarn-project/pxe/src/logs/log_service.test.ts +++ b/yarn-project/pxe/src/logs/log_service.test.ts @@ -6,12 +6,12 @@ import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { L2TipsProvider } from '@aztec/stdlib/block'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; -import { Tag } from '@aztec/stdlib/logs'; +import { SiloedTag, Tag } from '@aztec/stdlib/logs'; import { makeBlockHeader, randomTxScopedPrivateL2Log } from '@aztec/stdlib/testing'; import { type MockProxy, mock } from 'jest-mock-extended'; -import { LogRetrievalRequest } from '../contract_function_simulator/noir-structs/log_retrieval_request.js'; +import { LogRetrievalRequest, LogSource } from '../contract_function_simulator/noir-structs/log_retrieval_request.js'; import { AddressStore } from '../storage/address_store/address_store.js'; import { RecipientTaggingStore } from '../storage/tagging_store/recipient_tagging_store.js'; import { SenderAddressBookStore } from '../storage/tagging_store/sender_address_book_store.js'; @@ -26,7 +26,7 @@ describe('LogService', () => { let senderAddressBookStore: SenderAddressBookStore; let logService: LogService; - describe('bulkRetrieveLogs', () => { + describe('fetchLogsByTag', () => { const tag = Tag.random(); beforeEach(async () => { @@ -58,30 +58,15 @@ describe('LogService', () => { ); }); - it('returns no logs if none are found', async () => { + it('returns empty arrays if no logs are found', async () => { aztecNode.getPrivateLogsByTags.mockResolvedValue([[]]); aztecNode.getPublicLogsByTagsFromContract.mockResolvedValue([[]]); const request = new LogRetrievalRequest(contractAddress, tag); const responses = await logService.fetchLogsByTag(contractAddress, [request]); - expect(responses.length).toEqual(1); - expect(responses[0]).toBeNull(); + expect(responses).toEqual([[]]); }); - it('returns a public log if one is found', async () => { - const scopedLog = randomTxScopedPrivateL2Log(); - - aztecNode.getPublicLogsByTagsFromContract.mockResolvedValue([[scopedLog]]); - aztecNode.getPrivateLogsByTags.mockResolvedValue([[]]); - - const request = new LogRetrievalRequest(contractAddress, new Tag(scopedLog.logData[0])); - - const responses = await logService.fetchLogsByTag(contractAddress, [request]); - - expect(responses.length).toEqual(1); - expect(responses[0]).not.toBeNull(); - }); - - it('returns first log when multiple public logs are found for a single tag', async () => { + it('returns all logs when multiple public logs exist for a single tag', async () => { const scopedLog1 = randomTxScopedPrivateL2Log(); const scopedLog2 = randomTxScopedPrivateL2Log(); @@ -91,11 +76,12 @@ describe('LogService', () => { const request = new LogRetrievalRequest(contractAddress, tag); const responses = await logService.fetchLogsByTag(contractAddress, [request]); - expect(responses.length).toEqual(1); - expect(responses[0]).not.toBeNull(); + expect(responses[0]).toHaveLength(2); + expect(responses[0][0].txHash).toEqual(scopedLog1.txHash); + expect(responses[0][1].txHash).toEqual(scopedLog2.txHash); }); - it('returns first log when multiple private logs are found for a single tag', async () => { + it('returns all logs when multiple private logs exist for a single tag', async () => { const scopedLog1 = randomTxScopedPrivateL2Log(); const scopedLog2 = randomTxScopedPrivateL2Log(); @@ -105,11 +91,12 @@ describe('LogService', () => { const request = new LogRetrievalRequest(contractAddress, tag); const responses = await logService.fetchLogsByTag(contractAddress, [request]); - expect(responses.length).toEqual(1); - expect(responses[0]).not.toBeNull(); + expect(responses[0]).toHaveLength(2); + expect(responses[0][0].txHash).toEqual(scopedLog1.txHash); + expect(responses[0][1].txHash).toEqual(scopedLog2.txHash); }); - it('returns first log when both a public and private log are found for a single tag', async () => { + it('returns combined public and private logs for a single tag', async () => { const publicLog = randomTxScopedPrivateL2Log(); const privateLog = randomTxScopedPrivateL2Log(); @@ -119,22 +106,9 @@ describe('LogService', () => { const request = new LogRetrievalRequest(contractAddress, tag); const responses = await logService.fetchLogsByTag(contractAddress, [request]); - expect(responses.length).toEqual(1); - expect(responses[0]).not.toBeNull(); - }); - - it('returns a private log if one is found', async () => { - const scopedLog = randomTxScopedPrivateL2Log(); - - aztecNode.getPrivateLogsByTags.mockResolvedValue([[scopedLog]]); - aztecNode.getPublicLogsByTagsFromContract.mockResolvedValue([[]]); - - const request = new LogRetrievalRequest(contractAddress, new Tag(scopedLog.logData[0])); - - const responses = await logService.fetchLogsByTag(contractAddress, [request]); - - expect(responses.length).toEqual(1); - expect(responses[0]).not.toBeNull(); + expect(responses[0]).toHaveLength(2); + expect(responses[0][0].txHash).toEqual(publicLog.txHash); + expect(responses[0][1].txHash).toEqual(privateLog.txHash); }); it('rejects a batch where at least one request targets a different contract', async () => { @@ -167,9 +141,11 @@ describe('LogService', () => { const responses = await logService.fetchLogsByTag(contractAddress, requests); expect(responses).toHaveLength(3); - expect(responses[0]).toEqual(expect.objectContaining({ txHash: publicLog1.txHash })); - expect(responses[1]).toEqual(expect.objectContaining({ txHash: privateLog2.txHash })); - expect(responses[2]).toBeNull(); + expect(responses[0]).toHaveLength(1); + expect(responses[0][0].txHash).toEqual(publicLog1.txHash); + expect(responses[1]).toHaveLength(1); + expect(responses[1][0].txHash).toEqual(privateLog2.txHash); + expect(responses[2]).toEqual([]); expect(aztecNode.getPublicLogsByTagsFromContract).toHaveBeenCalledTimes(1); expect(aztecNode.getPrivateLogsByTags).toHaveBeenCalledTimes(1); @@ -181,5 +157,135 @@ describe('LogService', () => { expect(aztecNode.getPublicLogsByTagsFromContract).not.toHaveBeenCalled(); expect(aztecNode.getPrivateLogsByTags).not.toHaveBeenCalled(); }); + + describe('block range filtering', () => { + it('fromBlock is inclusive', async () => { + const logBefore = randomTxScopedPrivateL2Log({ blockNumber: 9 }); + const logAtBoundary = randomTxScopedPrivateL2Log({ blockNumber: 10 }); + + aztecNode.getPublicLogsByTagsFromContract.mockResolvedValue([[logBefore, logAtBoundary]]); + aztecNode.getPrivateLogsByTags.mockResolvedValue([[]]); + + const request = new LogRetrievalRequest(contractAddress, tag, LogSource.PUBLIC_AND_PRIVATE, BlockNumber(10)); + const responses = await logService.fetchLogsByTag(contractAddress, [request]); + + expect(responses[0]).toHaveLength(1); + expect(responses[0][0].txHash).toEqual(logAtBoundary.txHash); + }); + + it('toBlock is exclusive', async () => { + const logBeforeBoundary = randomTxScopedPrivateL2Log({ blockNumber: 9 }); + const logAtBoundary = randomTxScopedPrivateL2Log({ blockNumber: 10 }); + + aztecNode.getPublicLogsByTagsFromContract.mockResolvedValue([[logBeforeBoundary, logAtBoundary]]); + aztecNode.getPrivateLogsByTags.mockResolvedValue([[]]); + + const request = new LogRetrievalRequest( + contractAddress, + tag, + LogSource.PUBLIC_AND_PRIVATE, + undefined, + BlockNumber(10), + ); + const responses = await logService.fetchLogsByTag(contractAddress, [request]); + + expect(responses[0]).toHaveLength(1); + expect(responses[0][0].txHash).toEqual(logBeforeBoundary.txHash); + }); + + it('filters with both fromBlock and toBlock', async () => { + const logBefore = randomTxScopedPrivateL2Log({ blockNumber: 3 }); + const logInRange = randomTxScopedPrivateL2Log({ blockNumber: 15 }); + const logAfter = randomTxScopedPrivateL2Log({ blockNumber: 25 }); + + aztecNode.getPublicLogsByTagsFromContract.mockResolvedValue([[logBefore, logInRange, logAfter]]); + aztecNode.getPrivateLogsByTags.mockResolvedValue([[]]); + + const request = new LogRetrievalRequest( + contractAddress, + tag, + LogSource.PUBLIC_AND_PRIVATE, + BlockNumber(10), + BlockNumber(20), + ); + const responses = await logService.fetchLogsByTag(contractAddress, [request]); + + expect(responses[0]).toHaveLength(1); + expect(responses[0][0].txHash).toEqual(logInRange.txHash); + }); + }); + + describe('source filtering', () => { + it('returns only public logs and skips private RPC when source is PUBLIC', async () => { + const publicLog = randomTxScopedPrivateL2Log(); + + aztecNode.getPublicLogsByTagsFromContract.mockResolvedValue([[publicLog]]); + + const request = new LogRetrievalRequest(contractAddress, tag, LogSource.PUBLIC); + const responses = await logService.fetchLogsByTag(contractAddress, [request]); + + expect(responses[0]).toHaveLength(1); + expect(responses[0][0].txHash).toEqual(publicLog.txHash); + expect(aztecNode.getPrivateLogsByTags).not.toHaveBeenCalled(); + }); + + it('returns only private logs and skips public RPC when source is PRIVATE', async () => { + const privateLog = randomTxScopedPrivateL2Log(); + + aztecNode.getPrivateLogsByTags.mockResolvedValue([[privateLog]]); + + const request = new LogRetrievalRequest(contractAddress, tag, LogSource.PRIVATE); + const responses = await logService.fetchLogsByTag(contractAddress, [request]); + + expect(responses[0]).toHaveLength(1); + expect(responses[0][0].txHash).toEqual(privateLog.txHash); + expect(aztecNode.getPublicLogsByTagsFromContract).not.toHaveBeenCalled(); + }); + + it('only sends relevant tags per source in a mixed batch', async () => { + const tag1 = Tag.random(); + const tag2 = Tag.random(); + const tag3 = Tag.random(); + + const publicLog1 = randomTxScopedPrivateL2Log(); + const privateLog2 = randomTxScopedPrivateL2Log(); + const publicLog3 = randomTxScopedPrivateL2Log(); + const privateLog3 = randomTxScopedPrivateL2Log(); + + aztecNode.getPublicLogsByTagsFromContract.mockResolvedValue([[publicLog1], [publicLog3]]); + aztecNode.getPrivateLogsByTags.mockResolvedValue([[privateLog2], [privateLog3]]); + + const requests = [ + new LogRetrievalRequest(contractAddress, tag1, LogSource.PUBLIC), + new LogRetrievalRequest(contractAddress, tag2, LogSource.PRIVATE), + new LogRetrievalRequest(contractAddress, tag3, LogSource.PUBLIC_AND_PRIVATE), + ]; + + const responses = await logService.fetchLogsByTag(contractAddress, requests); + + // Public RPC receives tag1 and tag3, private RPC receives tag2 and tag3 + expect(aztecNode.getPublicLogsByTagsFromContract).toHaveBeenCalledTimes(1); + const publicCallTags = aztecNode.getPublicLogsByTagsFromContract.mock.calls[0][1]; + expect(publicCallTags).toHaveLength(2); + expect(publicCallTags[0]).toEqual(tag1); + expect(publicCallTags[1]).toEqual(tag3); + + expect(aztecNode.getPrivateLogsByTags).toHaveBeenCalledTimes(1); + const privateCallTags = aztecNode.getPrivateLogsByTags.mock.calls[0][0]; + expect(privateCallTags).toHaveLength(2); + const expectedSiloedTag2 = await SiloedTag.computeFromTagAndApp(tag2, contractAddress); + const expectedSiloedTag3 = await SiloedTag.computeFromTagAndApp(tag3, contractAddress); + expect(privateCallTags[0]).toEqual(expectedSiloedTag2); + expect(privateCallTags[1]).toEqual(expectedSiloedTag3); + + expect(responses[0]).toHaveLength(1); + expect(responses[0][0].txHash).toEqual(publicLog1.txHash); + expect(responses[1]).toHaveLength(1); + expect(responses[1][0].txHash).toEqual(privateLog2.txHash); + expect(responses[2]).toHaveLength(2); + expect(responses[2][0].txHash).toEqual(publicLog3.txHash); + expect(responses[2][1].txHash).toEqual(privateLog3.txHash); + }); + }); }); }); diff --git a/yarn-project/pxe/src/logs/log_service.ts b/yarn-project/pxe/src/logs/log_service.ts index 6edd28fae40f..27bb8093deba 100644 --- a/yarn-project/pxe/src/logs/log_service.ts +++ b/yarn-project/pxe/src/logs/log_service.ts @@ -1,7 +1,8 @@ +import type { BlockNumber } from '@aztec/foundation/branded-types'; import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log'; import type { KeyStore } from '@aztec/key-store'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { L2TipsProvider } from '@aztec/stdlib/block'; +import type { BlockHash, L2TipsProvider } from '@aztec/stdlib/block'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import { ExtendedDirectionalAppTaggingSecret, @@ -11,7 +12,10 @@ import { } from '@aztec/stdlib/logs'; import type { BlockHeader } from '@aztec/stdlib/tx'; -import type { LogRetrievalRequest } from '../contract_function_simulator/noir-structs/log_retrieval_request.js'; +import { + type LogRetrievalRequest, + LogSource, +} from '../contract_function_simulator/noir-structs/log_retrieval_request.js'; import { LogRetrievalResponse } from '../contract_function_simulator/noir-structs/log_retrieval_response.js'; import { AddressStore } from '../storage/address_store/address_store.js'; import type { RecipientTaggingStore } from '../storage/tagging_store/recipient_tagging_store.js'; @@ -39,10 +43,11 @@ export class LogService { this.log = createLogger('pxe:log_service', bindings); } + /** Fetches all logs matching each request's tag, returning an array of log arrays (one per request). */ public async fetchLogsByTag( contractAddress: AztecAddress, logRetrievalRequests: LogRetrievalRequest[], - ): Promise<(LogRetrievalResponse | null)[]> { + ): Promise { for (const request of logRetrievalRequests) { if (!contractAddress.equals(request.contractAddress)) { throw new Error(`Got a log retrieval request from ${request.contractAddress}, expected ${contractAddress}`); @@ -54,51 +59,80 @@ export class LogService { } const anchorBlockHash = await this.anchorBlockHeader.hash(); - const tags = logRetrievalRequests.map(r => r.tag); - const siloedTags = await Promise.all( - logRetrievalRequests.map(r => SiloedTag.computeFromTagAndApp(r.tag, r.contractAddress)), - ); - const [allPublicLogsPerTag, allPrivateLogsPerTag] = await Promise.all([ - getAllPublicLogsByTagsFromContract(this.aztecNode, contractAddress, tags, anchorBlockHash), - getAllPrivateLogsByTags(this.aztecNode, siloedTags, anchorBlockHash), + const [publicLogsPerTag, privateLogsPerTag] = await Promise.all([ + this.#fetchPublicLogs(contractAddress, logRetrievalRequests, anchorBlockHash), + this.#fetchPrivateLogs(logRetrievalRequests, anchorBlockHash), ]); - return logRetrievalRequests.map((request, i) => { - const publicLog = this.#extractSingleLog( - allPublicLogsPerTag[i], - `public log for tag ${request.tag} and contract ${request.contractAddress.toString()}`, - ); - const privateLog = this.#extractSingleLog(allPrivateLogsPerTag[i], `private log for tag ${siloedTags[i]}`); + return logRetrievalRequests.map((request, i) => [ + ...this.#extractLogs(publicLogsPerTag[i], request.fromBlock, request.toBlock), + ...this.#extractLogs(privateLogsPerTag[i], request.fromBlock, request.toBlock), + ]); + } - if (publicLog !== null && privateLog !== null) { - this.log.warn( - `Found both a public and private log for tag ${request.tag} from contract ${request.contractAddress}. This may indicate a contract bug. Returning the public log.`, - ); - } + async #fetchPublicLogs( + contractAddress: AztecAddress, + requests: LogRetrievalRequest[], + anchorBlockHash: BlockHash, + ): Promise { + const indices = requests.flatMap((r, i) => (r.source !== LogSource.PRIVATE ? [i] : [])); + if (indices.length === 0) { + return requests.map(() => []); + } - return publicLog ?? privateLog; + const results = await getAllPublicLogsByTagsFromContract( + this.aztecNode, + contractAddress, + indices.map(i => requests[i].tag), + anchorBlockHash, + ); + + const logsPerTag: TxScopedL2Log[][] = requests.map(() => []); + indices.forEach((originalIdx, resultIdx) => { + logsPerTag[originalIdx] = results[resultIdx]; }); + return logsPerTag; } - #extractSingleLog(logsForTag: TxScopedL2Log[], description: string): LogRetrievalResponse | null { - if (logsForTag.length === 0) { - return null; + async #fetchPrivateLogs(requests: LogRetrievalRequest[], anchorBlockHash: BlockHash): Promise { + const indices = requests.flatMap((r, i) => (r.source !== LogSource.PUBLIC ? [i] : [])); + if (indices.length === 0) { + return requests.map(() => []); } - if (logsForTag.length > 1) { - this.log.warn( - `Expected at most 1 ${description}, got ${logsForTag.length}. This may indicate a contract bug. Returning the first log.`, - ); - } + const siloedTags = await Promise.all( + indices.map(i => SiloedTag.computeFromTagAndApp(requests[i].tag, requests[i].contractAddress)), + ); - const scopedLog = logsForTag[0]; + const results = await getAllPrivateLogsByTags(this.aztecNode, siloedTags, anchorBlockHash); - return new LogRetrievalResponse( - scopedLog.logData.slice(1), // Skip the tag - scopedLog.txHash, - scopedLog.noteHashes, - scopedLog.firstNullifier, + const logsPerTag: TxScopedL2Log[][] = requests.map(() => []); + indices.forEach((originalIdx, resultIdx) => { + logsPerTag[originalIdx] = results[resultIdx]; + }); + return logsPerTag; + } + + #extractLogs(logsForTag: TxScopedL2Log[], fromBlock?: BlockNumber, toBlock?: BlockNumber): LogRetrievalResponse[] { + // TODO(F-650): push the block range filter down to the node query instead of filtering in memory. + const filtered = + fromBlock !== undefined || toBlock !== undefined + ? logsForTag.filter( + log => + (fromBlock === undefined || log.blockNumber >= fromBlock) && + (toBlock === undefined || log.blockNumber < toBlock), + ) + : logsForTag; + + return filtered.map( + scopedLog => + new LogRetrievalResponse( + scopedLog.logData.slice(1), // Skip the tag + scopedLog.txHash, + scopedLog.noteHashes, + scopedLog.firstNullifier, + ), ); } diff --git a/yarn-project/txe/package.json b/yarn-project/txe/package.json index db43d45ef508..913283c72c0a 100644 --- a/yarn-project/txe/package.json +++ b/yarn-project/txe/package.json @@ -17,7 +17,8 @@ "clean": "rm -rf ./dest .tsbuildinfo", "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}", "dev": "LOG_LEVEL=\"debug; trace: simulator:state_manager; info: json-rpc:proxy\" node ./dest/bin/index.js", - "start": "node --no-warnings ./dest/bin/index.js" + "start": "node --no-warnings ./dest/bin/index.js", + "check_txe_oracle_version": "node ./dest/bin/check_txe_oracle_version.js" }, "inherits": [ "../package.common.json" diff --git a/yarn-project/txe/src/bin/check_txe_oracle_version.ts b/yarn-project/txe/src/bin/check_txe_oracle_version.ts new file mode 100644 index 000000000000..889b48f912ab --- /dev/null +++ b/yarn-project/txe/src/bin/check_txe_oracle_version.ts @@ -0,0 +1,37 @@ +import { keccak256String } from '@aztec/foundation/crypto/keccak'; +import { getOracleInterfaceSignature } from '@aztec/pxe/bin'; + +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; + +import { TXE_ORACLE_INTERFACE_HASH } from '../txe_oracle_version.js'; + +/** + * Verifies that the TXE oracle interfaces match the expected interface hash. + * + * The TXE oracle interfaces need to be versioned to ensure compatibility between Aztec.nr tests and TXE. This function + * computes a hash of the TXE oracle interfaces and compares it against a known hash. If they don't match, it means an + * interface has changed and the TXE oracle version needs to be bumped: + * - If the change is backward-breaking (e.g. removing/renaming an oracle), bump TXE_ORACLE_VERSION_MAJOR. + * - If the change is an oracle addition (non-breaking), bump TXE_ORACLE_VERSION_MINOR. + */ +function assertTxeOracleInterfaceMatches(): void { + const currentDir = dirname(fileURLToPath(import.meta.url)); + const packageRoot = dirname(dirname(currentDir)); + const interfacesSourcePath = join(packageRoot, 'src/oracle/interfaces.ts'); + + const targets = ['IAvmExecutionOracle', 'ITxeExecutionOracle']; + // Not an oracle foreign call handler (see TODO(F-335) in interfaces.ts). + const excludedMembers = ['syncContractNonOracleMethod']; + + const txeOracleInterfaceSignature = getOracleInterfaceSignature(interfacesSourcePath, targets, excludedMembers); + + const txeOracleInterfaceHash = keccak256String(txeOracleInterfaceSignature); + if (txeOracleInterfaceHash !== TXE_ORACLE_INTERFACE_HASH) { + throw new Error( + `The TXE oracle interface has changed. Update TXE_ORACLE_INTERFACE_HASH to ${txeOracleInterfaceHash} in txe/src/txe_oracle_version.ts and bump the TXE oracle version (TXE_ORACLE_VERSION_MAJOR for breaking changes, TXE_ORACLE_VERSION_MINOR for oracle additions).`, + ); + } +} + +assertTxeOracleInterfaceMatches(); diff --git a/yarn-project/txe/src/constants.ts b/yarn-project/txe/src/constants.ts index 24230b9217a4..49307f0e814a 100644 --- a/yarn-project/txe/src/constants.ts +++ b/yarn-project/txe/src/constants.ts @@ -1,3 +1,9 @@ +import { PRIVATE_LOG_CIPHERTEXT_LEN } from '@aztec/constants'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; export const DEFAULT_ADDRESS = AztecAddress.fromNumber(42); + +// Arbitrarily set at 64 because we need a bound. Nothing inherent about it. +export const MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY = 64; +// Must match MAX_OFFCHAIN_EFFECT_LEN in noir-projects/aztec-nr/aztec/src/test/helpers/txe_oracles.nr. +export const MAX_OFFCHAIN_EFFECT_LEN = 2 + PRIVATE_LOG_CIPHERTEXT_LEN; diff --git a/yarn-project/txe/src/oracle/interfaces.ts b/yarn-project/txe/src/oracle/interfaces.ts index 0d0ecdc845cc..4ee05b93157b 100644 --- a/yarn-project/txe/src/oracle/interfaces.ts +++ b/yarn-project/txe/src/oracle/interfaces.ts @@ -38,6 +38,11 @@ export interface IAvmExecutionOracle { nullifierExists(siloedNullifier: Fr): Promise; storageWrite(slot: Fr, value: Fr): Promise; storageRead(slot: Fr, contractAddress: AztecAddress): Promise; + returndataSize(): Promise; + returndataCopy(rdOffset: number, copySize: number): Promise; + call(l2Gas: number, daGas: number, address: AztecAddress, argsLength: number, args: Fr[]): Promise; + staticCall(l2Gas: number, daGas: number, address: AztecAddress, argsLength: number, args: Fr[]): Promise; + successCopy(): Promise; } /** diff --git a/yarn-project/txe/src/oracle/txe_oracle_public_context.ts b/yarn-project/txe/src/oracle/txe_oracle_public_context.ts index 3631cc067d69..84e24a696896 100644 --- a/yarn-project/txe/src/oracle/txe_oracle_public_context.ts +++ b/yarn-project/txe/src/oracle/txe_oracle_public_context.ts @@ -122,6 +122,36 @@ export class TXEOraclePublicContext implements IAvmExecutionOracle { return value; } + returndataSize(): Promise { + throw new Error( + 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', + ); + } + + returndataCopy(_rdOffset: number, _copySize: number): Promise { + throw new Error( + 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', + ); + } + + call(_l2Gas: number, _daGas: number, _address: AztecAddress, _argsLength: number, _args: Fr[]): Promise { + throw new Error( + 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', + ); + } + + staticCall(_l2Gas: number, _daGas: number, _address: AztecAddress, _argsLength: number, _args: Fr[]): Promise { + throw new Error( + 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', + ); + } + + successCopy(): Promise { + throw new Error( + 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', + ); + } + async close(): Promise { this.logger.debug('Exiting Public Context, building block with collected side effects', { blockNumber: this.globalVariables.blockNumber, diff --git a/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts b/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts index 24c1ce0d3239..0b0bf3316f2f 100644 --- a/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts +++ b/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts @@ -1,4 +1,8 @@ -import { CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/constants'; +import { + CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, + MAX_PRIVATE_LOGS_PER_TX, + NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, +} from '@aztec/constants'; import { BlockNumber } from '@aztec/foundation/branded-types'; import { Schnorr } from '@aztec/foundation/crypto/schnorr'; import { Fr } from '@aztec/foundation/curves/bn254'; @@ -175,11 +179,16 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl const txEffects = block!.body.txEffects[0]; + const privateLogs = txEffects.privateLogs; + if (privateLogs.length > MAX_PRIVATE_LOGS_PER_TX) { + throw new Error(`${privateLogs.length} private logs exceed max ${MAX_PRIVATE_LOGS_PER_TX}`); + } + return { txHash: txEffects.txHash, noteHashes: txEffects.noteHashes, nullifiers: txEffects.nullifiers, - privateLogs: txEffects.privateLogs, + privateLogs, }; } diff --git a/yarn-project/txe/src/oracle/txe_private_execution_oracle.ts b/yarn-project/txe/src/oracle/txe_private_execution_oracle.ts new file mode 100644 index 000000000000..e0dc84d1a163 --- /dev/null +++ b/yarn-project/txe/src/oracle/txe_private_execution_oracle.ts @@ -0,0 +1,30 @@ +import type { Fr } from '@aztec/foundation/curves/bn254'; +import { PrivateExecutionOracle } from '@aztec/pxe/simulator'; +import type { FunctionSelector } from '@aztec/stdlib/abi'; +import type { AztecAddress } from '@aztec/stdlib/aztec-address'; + +/** + * TXE-specific subclass of PrivateExecutionOracle that forbids operations not supported in + * TestEnvironment::private_context. TXE uses dedicated oracle flows (e.g. private_call) instead. + */ +export class TXEPrivateExecutionOracle extends PrivateExecutionOracle { + override callPrivateFunction( + _targetContractAddress: AztecAddress, + _functionSelector: FunctionSelector, + _argsHash: Fr, + _sideEffectCounter: number, + _isStaticCall: boolean, + ): Promise<{ endSideEffectCounter: Fr; returnsHash: Fr }> { + throw new Error( + 'Contract calls are forbidden inside a `TestEnvironment::private_context`, use `private_call` instead', + ); + } + + override assertValidPublicCalldata(_calldataHash: Fr): Promise { + throw new Error('Enqueueing public calls is not supported in TestEnvironment::private_context'); + } + + override notifyRevertiblePhaseStart(_minRevertibleSideEffectCounter: number): Promise { + throw new Error('Enqueueing public calls is not supported in TestEnvironment::private_context'); + } +} diff --git a/yarn-project/txe/src/rpc_translator.ts b/yarn-project/txe/src/rpc_translator.ts index bb5fa4b8d356..bef1d2f19ce2 100644 --- a/yarn-project/txe/src/rpc_translator.ts +++ b/yarn-project/txe/src/rpc_translator.ts @@ -5,7 +5,6 @@ import { MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, MAX_PRIVATE_LOGS_PER_TX, - PRIVATE_LOG_CIPHERTEXT_LEN, PRIVATE_LOG_SIZE_IN_FIELDS, } from '@aztec/constants'; import { BlockNumber } from '@aztec/foundation/branded-types'; @@ -21,8 +20,8 @@ import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { BlockHash } from '@aztec/stdlib/block'; import { GasSettings } from '@aztec/stdlib/gas'; +import { MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY, MAX_OFFCHAIN_EFFECT_LEN } from './constants.js'; import type { IAvmExecutionOracle, ITxeExecutionOracle } from './oracle/interfaces.js'; -import { TXE_ORACLE_VERSION_MAJOR } from './txe_oracle_version.js'; import type { TXESessionStateHandler } from './txe_session.js'; import { type ForeignCallArray, @@ -118,17 +117,7 @@ export class RPCTranslator { const major = fromSingle(foreignMajor).toNumber(); const minor = fromSingle(foreignMinor).toNumber(); - if (major !== TXE_ORACLE_VERSION_MAJOR) { - const hint = - major > TXE_ORACLE_VERSION_MAJOR - ? 'The test was compiled with a newer version of Aztec.nr than your test environment supports. Upgrade your test environment to a compatible version.' - : 'The test was compiled with an older version of Aztec.nr than your test environment supports. Recompile the test with a compatible version of Aztec.nr.'; - throw new Error( - `Incompatible test environment version: ${hint} See https://docs.aztec.network/errors/12 (expected test oracle major version ${TXE_ORACLE_VERSION_MAJOR}, got ${major})`, - ); - } - - this.stateHandler.setTxeOracleVersion({ major, minor }); + this.stateHandler.setTxeOracleVersion(major, minor); return toForeignCallResult([]); } @@ -329,10 +318,6 @@ export class RPCTranslator { async aztec_txe_getLastTxEffects() { const { txHash, noteHashes, nullifiers, privateLogs } = await this.handlerAsTxe().getLastTxEffects(); - if (privateLogs.length > MAX_PRIVATE_LOGS_PER_TX) { - throw new Error(`${privateLogs.length} private logs exceed max ${MAX_PRIVATE_LOGS_PER_TX}`); - } - // Same workaround as `aztec_txe_getPrivateEvents`: Noir cannot yet return nested structs with arrays, so we return // a flat multidimensional array plus per-log lengths and the total count, and reassemble into a // `BoundedVec>` on the Noir side. Each log contributes only its emitted fields. The rest @@ -364,21 +349,8 @@ export class RPCTranslator { // eslint-disable-next-line camelcase aztec_txe_getLastCallOffchainEffects() { - // This oracle returns all offchain effect payloads (messages, authwit requests, etc.) emitted by the last top-level call, - // MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY is arbitrarily set at 64 because we need a bound. Nothing inherent about it. - const MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY = 64; - // Must match MAX_OFFCHAIN_EFFECT_LEN in txe_oracles.nr. - const MAX_OFFCHAIN_EFFECT_LEN = 2 + PRIVATE_LOG_CIPHERTEXT_LEN; - const { effects } = this.stateHandler.getLastCallOffchainEffects(); - if (effects.length > MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY) { - throw new Error(`${effects.length} offchain effects exceed max ${MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY}`); - } - if (effects.some(e => e.length > MAX_OFFCHAIN_EFFECT_LEN)) { - throw new Error(`Some offchain effect has length larger than max ${MAX_OFFCHAIN_EFFECT_LEN}`); - } - const rawArrayStorage = effects .map(e => e.concat(Array(MAX_OFFCHAIN_EFFECT_LEN - e.length).fill(new Fr(0)))) .concat( @@ -510,10 +482,6 @@ export class RPCTranslator { const leafSlot = fromSingle(foreignLeafSlot); const witness = await this.handlerAsUtility().getPublicDataWitness(blockHash, leafSlot); - - if (!witness) { - throw new Error(`Public data witness not found for slot ${leafSlot} at block ${blockHash.toString()}.`); - } return toForeignCallResult(witness.toNoirRepresentation()); } @@ -589,12 +557,10 @@ export class RPCTranslator { }), ); - // Now we convert each sub-array to an array of ForeignCallSingles const returnDataAsArrayOfForeignCallSingleArrays = returnDataAsArrayOfArrays.map(subArray => subArray.map(toSingle), ); - // At last we convert the array of arrays to a bounded vec of arrays return toForeignCallResult( arrayOfArraysToBoundedVecOfArrays(returnDataAsArrayOfForeignCallSingleArrays, maxNotes, packedHintedNoteLength), ); @@ -716,16 +682,27 @@ export class RPCTranslator { } // eslint-disable-next-line camelcase - aztec_prv_callPrivateFunction( - _foreignTargetContractAddress: ForeignCallSingle, - _foreignFunctionSelector: ForeignCallSingle, - _foreignArgsHash: ForeignCallSingle, - _foreignSideEffectCounter: ForeignCallSingle, - _foreignIsStaticCall: ForeignCallSingle, + async aztec_prv_callPrivateFunction( + foreignTargetContractAddress: ForeignCallSingle, + foreignFunctionSelector: ForeignCallSingle, + foreignArgsHash: ForeignCallSingle, + foreignSideEffectCounter: ForeignCallSingle, + foreignIsStaticCall: ForeignCallSingle, ) { - throw new Error( - 'Contract calls are forbidden inside a `TestEnvironment::private_context`, use `private_call` instead', + const targetContractAddress = addressFromSingle(foreignTargetContractAddress); + const functionSelector = FunctionSelector.fromField(fromSingle(foreignFunctionSelector)); + const argsHash = fromSingle(foreignArgsHash); + const sideEffectCounter = fromSingle(foreignSideEffectCounter).toNumber(); + const isStaticCall = fromSingle(foreignIsStaticCall).toBool(); + + const { endSideEffectCounter, returnsHash } = await this.handlerAsPrivate().callPrivateFunction( + targetContractAddress, + functionSelector, + argsHash, + sideEffectCounter, + isStaticCall, ); + return toForeignCallResult([[endSideEffectCounter, returnsHash].map(toSingle)]); } // eslint-disable-next-line camelcase @@ -737,10 +714,6 @@ export class RPCTranslator { const nullifier = fromSingle(foreignNullifier); const witness = await this.handlerAsUtility().getNullifierMembershipWitness(blockHash, nullifier); - - if (!witness) { - throw new Error(`Nullifier membership witness not found at block ${blockHash}.`); - } return toForeignCallResult(witness.toNoirRepresentation()); } @@ -749,21 +722,21 @@ export class RPCTranslator { const messageHash = fromSingle(foreignMessageHash); const authWitness = await this.handlerAsUtility().getAuthWitness(messageHash); - - if (!authWitness) { - throw new Error(`Auth witness not found for message hash ${messageHash}.`); - } return toForeignCallResult([toArray(authWitness)]); } // eslint-disable-next-line camelcase - public aztec_prv_assertValidPublicCalldata(_foreignCalldataHash: ForeignCallSingle) { - throw new Error('Enqueueing public calls is not supported in TestEnvironment::private_context'); + public async aztec_prv_assertValidPublicCalldata(foreignCalldataHash: ForeignCallSingle) { + const calldataHash = fromSingle(foreignCalldataHash); + await this.handlerAsPrivate().assertValidPublicCalldata(calldataHash); + return toForeignCallResult([]); } // eslint-disable-next-line camelcase - public aztec_prv_notifyRevertiblePhaseStart(_foreignMinRevertibleSideEffectCounter: ForeignCallSingle) { - throw new Error('Enqueueing public calls is not supported in TestEnvironment::private_context'); + public async aztec_prv_notifyRevertiblePhaseStart(foreignMinRevertibleSideEffectCounter: ForeignCallSingle) { + const minRevertibleSideEffectCounter = fromSingle(foreignMinRevertibleSideEffectCounter).toNumber(); + await this.handlerAsPrivate().notifyRevertiblePhaseStart(minRevertibleSideEffectCounter); + return toForeignCallResult([]); } // eslint-disable-next-line camelcase @@ -785,10 +758,6 @@ export class RPCTranslator { const blockNumber = BlockNumber(fromSingle(foreignBlockNumber).toNumber()); const header = await this.handlerAsUtility().getBlockHeader(blockNumber); - - if (!header) { - throw new Error(`Block header not found for block ${blockNumber}.`); - } return toForeignCallResult(header.toFields().map(toSingle)); } @@ -801,10 +770,6 @@ export class RPCTranslator { const noteHash = fromSingle(foreignNoteHash); const witness = await this.handlerAsUtility().getNoteHashMembershipWitness(blockHash, noteHash); - - if (!witness) { - throw new Error(`Note hash ${noteHash} not found in the note hash tree at block ${blockHash.toString()}.`); - } return toForeignCallResult(witness.toNoirRepresentation()); } @@ -830,10 +795,6 @@ export class RPCTranslator { const nullifier = fromSingle(foreignNullifier); const witness = await this.handlerAsUtility().getLowNullifierMembershipWitness(blockHash, nullifier); - - if (!witness) { - throw new Error(`Low nullifier witness not found for nullifier ${nullifier} at block ${blockHash}.`); - } return toForeignCallResult(witness.toNoirRepresentation()); } @@ -1033,14 +994,14 @@ export class RPCTranslator { const symKey = fromUintArray(foreignSymKey, 8); // Noir Option is encoded as [is_some: Field, storage: Field[], length: Field]. - try { - const plaintextBuffer = await this.handlerAsUtility().decryptAes128(ciphertext, iv, symKey); + const plaintextBuffer = await this.handlerAsUtility().decryptAes128(ciphertext, iv, symKey); + if (plaintextBuffer) { const [storage, length] = arrayToBoundedVec( bufferToU8Array(plaintextBuffer), foreignCiphertextBVecStorage.length, ); return toForeignCallResult([toSingle(new Fr(1)), storage, length]); - } catch { + } else { const zeroStorage = toArray(Array(foreignCiphertextBVecStorage.length).fill(new Fr(0))); return toForeignCallResult([toSingle(new Fr(0)), zeroStorage, toSingle(new Fr(0))]); } @@ -1232,52 +1193,60 @@ export class RPCTranslator { } // eslint-disable-next-line camelcase - aztec_avm_returndataSize() { - throw new Error( - 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', - ); + async aztec_avm_returndataSize() { + const result = await this.handlerAsAvm().returndataSize(); + return toForeignCallResult([toSingle(result)]); } // eslint-disable-next-line camelcase - aztec_avm_returndataCopy(_foreignRdOffset: ForeignCallSingle, _foreignCopySize: ForeignCallSingle) { - throw new Error( - 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', - ); + async aztec_avm_returndataCopy(foreignRdOffset: ForeignCallSingle, foreignCopySize: ForeignCallSingle) { + const rdOffset = fromSingle(foreignRdOffset).toNumber(); + const copySize = fromSingle(foreignCopySize).toNumber(); + const result = await this.handlerAsAvm().returndataCopy(rdOffset, copySize); + return toForeignCallResult([toArray(result)]); } // eslint-disable-next-line camelcase - aztec_avm_call( - _foreignL2Gas: ForeignCallSingle, - _foreignDaGas: ForeignCallSingle, - _foreignAddress: ForeignCallSingle, - _foreignLength: ForeignCallSingle, - _foreignArgs: ForeignCallArray, + async aztec_avm_call( + foreignL2Gas: ForeignCallSingle, + foreignDaGas: ForeignCallSingle, + foreignAddress: ForeignCallSingle, + foreignLength: ForeignCallSingle, + foreignArgs: ForeignCallArray, ) { - throw new Error( - 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', - ); + const l2Gas = fromSingle(foreignL2Gas).toNumber(); + const daGas = fromSingle(foreignDaGas).toNumber(); + const address = addressFromSingle(foreignAddress); + const argsLength = fromSingle(foreignLength).toNumber(); + const args = fromArray(foreignArgs); + const result = await this.handlerAsAvm().call(l2Gas, daGas, address, argsLength, args); + return toForeignCallResult([toArray(result)]); } // eslint-disable-next-line camelcase - aztec_avm_staticCall( - _foreignL2Gas: ForeignCallSingle, - _foreignDaGas: ForeignCallSingle, - _foreignAddress: ForeignCallSingle, - _foreignLength: ForeignCallSingle, - _foreignArgs: ForeignCallArray, + async aztec_avm_staticCall( + foreignL2Gas: ForeignCallSingle, + foreignDaGas: ForeignCallSingle, + foreignAddress: ForeignCallSingle, + foreignLength: ForeignCallSingle, + foreignArgs: ForeignCallArray, ) { - throw new Error( - 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', - ); + const l2Gas = fromSingle(foreignL2Gas).toNumber(); + const daGas = fromSingle(foreignDaGas).toNumber(); + const address = addressFromSingle(foreignAddress); + const argsLength = fromSingle(foreignLength).toNumber(); + const args = fromArray(foreignArgs); + const result = await this.handlerAsAvm().staticCall(l2Gas, daGas, address, argsLength, args); + return toForeignCallResult([toArray(result)]); } // eslint-disable-next-line camelcase - aztec_avm_successCopy() { - throw new Error( - 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', - ); + async aztec_avm_successCopy() { + const result = await this.handlerAsAvm().successCopy(); + return toForeignCallResult([toSingle(result)]); } + // TODO(F-674): Move orchestration logic into the handler so the transport layer is pure serialize→delegate→deserialize. // eslint-disable-next-line camelcase async aztec_txe_privateCallNewFlow( foreignFromIsSome: ForeignCallSingle, @@ -1341,6 +1310,7 @@ export class RPCTranslator { return toForeignCallResult([toArray(returnValues)]); } + // TODO(F-674): Move orchestration logic into the handler so the transport layer is pure serialize→delegate→deserialize. // eslint-disable-next-line camelcase async aztec_txe_executeUtilityFunction( foreignTargetContractAddress: ForeignCallSingle, @@ -1373,6 +1343,7 @@ export class RPCTranslator { return toForeignCallResult([toArray(returnValues)]); } + // TODO(F-674): Move orchestration logic into the handler so the transport layer is pure serialize→delegate→deserialize. // eslint-disable-next-line camelcase async aztec_txe_publicCallNewFlow( foreignFromIsSome: ForeignCallSingle, diff --git a/yarn-project/txe/src/txe_oracle_version.ts b/yarn-project/txe/src/txe_oracle_version.ts index ae8712abb4c0..015357f412f8 100644 --- a/yarn-project/txe/src/txe_oracle_version.ts +++ b/yarn-project/txe/src/txe_oracle_version.ts @@ -6,4 +6,12 @@ * The Noir counterparts are in `noir-projects/aztec-nr/aztec/src/test/helpers/txe_oracles.nr`. */ export const TXE_ORACLE_VERSION_MAJOR = 1; -export const TXE_ORACLE_VERSION_MINOR = 0; +export const TXE_ORACLE_VERSION_MINOR = 1; + +/** + * This hash is computed from the TXE oracle interfaces (IAvmExecutionOracle and ITxeExecutionOracle) and is used to + * detect when those interfaces change. When it does, bump: + * - TXE_ORACLE_VERSION_MAJOR (and reset MINOR to 0) for breaking changes, or + * - TXE_ORACLE_VERSION_MINOR for additive changes (new oracle method added). + */ +export const TXE_ORACLE_INTERFACE_HASH = '833b77ccdba925b1ed129028432c7d4b116867fb426cee40f9d6104c5aa31e42'; diff --git a/yarn-project/txe/src/txe_session.ts b/yarn-project/txe/src/txe_session.ts index 2c7c7fd493fb..32f674eeab30 100644 --- a/yarn-project/txe/src/txe_session.ts +++ b/yarn-project/txe/src/txe_session.ts @@ -24,7 +24,6 @@ import { type IPrivateExecutionOracle, type IUtilityExecutionOracle, Oracle, - PrivateExecutionOracle, UtilityExecutionOracle, } from '@aztec/pxe/simulator'; import { @@ -46,10 +45,11 @@ import { CallContext, GlobalVariables, OFFCHAIN_MESSAGE_IDENTIFIER, TxContext } import { z } from 'zod'; -import { DEFAULT_ADDRESS } from './constants.js'; +import { DEFAULT_ADDRESS, MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY, MAX_OFFCHAIN_EFFECT_LEN } from './constants.js'; import type { IAvmExecutionOracle, ITxeExecutionOracle } from './oracle/interfaces.js'; import { TXEOraclePublicContext } from './oracle/txe_oracle_public_context.js'; import { TXEOracleTopLevelContext } from './oracle/txe_oracle_top_level_context.js'; +import { TXEPrivateExecutionOracle } from './oracle/txe_private_execution_oracle.js'; import { RPCTranslator } from './rpc_translator.js'; import { TXEArchiver } from './state_machine/archiver.js'; import { TXEStateMachine } from './state_machine/index.js'; @@ -111,7 +111,7 @@ export type TXEOracleFunctionName = Exclude< export interface TXESessionStateHandler { /** Records the TXE oracle version reported by the Noir test code for diagnostics. */ - setTxeOracleVersion(version: { major: number; minor: number }): void; + setTxeOracleVersion(major: number, minor: number): void; enterTopLevelState(): Promise; enterPublicState(contractAddress?: AztecAddress): Promise; @@ -397,7 +397,16 @@ export class TXESession implements TXESessionStateHandler { getLastCallOffchainEffects(): { effects: Fr[][] } { this.lastCallInfo.queried = true; - return { effects: this.lastCallInfo.offchainEffects }; + const effects = this.lastCallInfo.offchainEffects; + + if (effects.length > MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY) { + throw new Error(`${effects.length} offchain effects exceed max ${MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY}`); + } + if (effects.some(e => e.length > MAX_OFFCHAIN_EFFECT_LEN)) { + throw new Error(`Some offchain effect has length larger than max ${MAX_OFFCHAIN_EFFECT_LEN}`); + } + + return { effects }; } getLastCallContext(): { txHash: Fr; anchorBlockTimestamp: bigint } { @@ -405,9 +414,19 @@ export class TXESession implements TXESessionStateHandler { return { txHash, anchorBlockTimestamp }; } - setTxeOracleVersion(version: { major: number; minor: number }): void { - this.txeOracleVersion = version; - this.logger.debug(`Test compiled with test oracle version ${version.major}.${version.minor}`); + setTxeOracleVersion(major: number, minor: number): void { + if (major !== TXE_ORACLE_VERSION_MAJOR) { + const hint = + major > TXE_ORACLE_VERSION_MAJOR + ? 'The test was compiled with a newer version of Aztec.nr than your test environment supports. Upgrade your test environment to a compatible version.' + : 'The test was compiled with an older version of Aztec.nr than your test environment supports. Recompile the test with a compatible version of Aztec.nr.'; + throw new Error( + `Incompatible test environment version: ${hint} See https://docs.aztec.network/errors/12 (expected test oracle major version ${TXE_ORACLE_VERSION_MAJOR}, got ${major})`, + ); + } + + this.txeOracleVersion = { major, minor }; + this.logger.debug(`Test compiled with test oracle version ${major}.${minor}`); } async enterTopLevelState() { @@ -489,7 +508,7 @@ export class TXESession implements TXESessionStateHandler { const taggingIndexCache = new ExecutionTaggingIndexCache(); const utilityExecutor = this.utilityExecutorForContractSync(anchorBlock); - this.oracleHandler = new PrivateExecutionOracle({ + this.oracleHandler = new TXEPrivateExecutionOracle({ argsHash: Fr.ZERO, txContext: new TxContext(this.chainId, this.version, gasSettings), callContext: new CallContext(AztecAddress.ZERO, contractAddress, FunctionSelector.empty(), false), @@ -530,7 +549,7 @@ export class TXESession implements TXESessionStateHandler { // via `anchorBlockNumber`, "latest" would be the wrong anchor for offchain-message semantics. this.setLastCallContext(Fr.ZERO, anchorBlock!.globalVariables.timestamp); - return (this.oracleHandler as PrivateExecutionOracle).getPrivateContextInputs(); + return (this.oracleHandler as TXEPrivateExecutionOracle).getPrivateContextInputs(); } async enterPublicState(contractAddress?: AztecAddress) { diff --git a/yarn-project/wallets/src/embedded/embedded_wallet.ts b/yarn-project/wallets/src/embedded/embedded_wallet.ts index 6f427994504e..b89819ac9e7b 100644 --- a/yarn-project/wallets/src/embedded/embedded_wallet.ts +++ b/yarn-project/wallets/src/embedded/embedded_wallet.ts @@ -392,7 +392,7 @@ export class EmbeddedWallet extends BaseWallet { } } - const accountManager = await AccountManager.create(this, secret, contract, salt); + const accountManager = await AccountManager.create(this, secret, contract, { salt }); const instance = accountManager.getInstance(); const existingInstance = await this.pxe.getContractInstance(instance.address);