diff --git a/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/helpers.nr b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/helpers.nr index 0a0fdc74b097..25a7e2453ba4 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/helpers.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/helpers.nr @@ -25,21 +25,6 @@ pub(crate) comptime fn get_abi_relevant_attributes(f: FunctionDefinition) -> Quo attributes } -/// Injects a call to `aztec::messages::discovery::discover_new_messages`, causing for new notes to be added to PXE and made -/// available for the current execution. -pub(crate) comptime fn create_message_discovery_call() -> Quoted { - quote { - /// Safety: message discovery returns nothing and is performed solely for its side-effects. It is therefore - /// always safe to call. - unsafe { - dep::aztec::messages::discovery::discover_new_messages( - self.address, - _compute_note_hash_and_nullifier, - ); - }; - } -} - /// Injects an authwit verification check of the form: /// ``` /// if (!from.eq(context.msg_sender().unwrap())) { diff --git a/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/private.nr b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/private.nr index 19ae22416ed4..2a5c578fcfd8 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/private.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/private.nr @@ -1,8 +1,7 @@ use crate::macros::{ internals_functions_generation::external::helpers::{ - create_authorize_once_check, create_message_discovery_call, get_abi_relevant_attributes, + create_authorize_once_check, get_abi_relevant_attributes, }, - notes::NOTES, utils::{ fn_has_authorize_once, fn_has_noinitcheck, fn_has_nophasecheck, is_fn_initializer, is_fn_only_self, is_fn_view, module_has_initializer, module_has_storage, @@ -120,16 +119,6 @@ pub(crate) comptime fn generate_private_external(f: FunctionDefinition) -> Quote } }; - // All private functions perform message discovery, since they may need to access notes. This is slightly - // inefficient and could be improved by only doing it once we actually attempt to read any. Note that the message - // discovery call syncs private events as well. We do not sync those here if there are no notes because we don't - // have an API that would access events from private functions. - let message_discovery_call = if NOTES.len() > 0 { - create_message_discovery_call() - } else { - quote {} - }; - // Inject the authwit check if the function is marked with #[authorize_once]. let authorize_once_check = if fn_has_authorize_once(f) { create_authorize_once_check(f, true) @@ -192,7 +181,6 @@ pub(crate) comptime fn generate_private_external(f: FunctionDefinition) -> Quote $init_check $internal_check $view_check - $message_discovery_call $authorize_once_check }; diff --git a/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/utility.nr b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/utility.nr index fbb246c72cad..7b18c0e22c14 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/utility.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/utility.nr @@ -1,7 +1,4 @@ -use crate::macros::{ - internals_functions_generation::external::helpers::create_message_discovery_call, - utils::module_has_storage, -}; +use crate::macros::utils::module_has_storage; pub(crate) comptime fn generate_utility_external(f: FunctionDefinition) -> Quoted { // Initialize Storage if module has storage @@ -28,16 +25,10 @@ pub(crate) comptime fn generate_utility_external(f: FunctionDefinition) -> Quote }; }; - // All utility functions perform message discovery, since they may need to access private notes that would be - // found during this process or they may be used to sync private events from TypeScript - // (`sync_private_state` function gets invoked by PXE::getPrivateEvents function). - let message_discovery_call = create_message_discovery_call(); - // A quote to be injected at the beginning of the function body. let to_prepend = quote { dep::aztec::oracle::version::assert_compatible_oracle_version(); $contract_self_creation - $message_discovery_call }; let original_function_name = f.name(); diff --git a/yarn-project/end-to-end/src/e2e_multiple_blobs.test.ts b/yarn-project/end-to-end/src/e2e_multiple_blobs.test.ts index 12778e358ef8..9bd91976c1d4 100644 --- a/yarn-project/end-to-end/src/e2e_multiple_blobs.test.ts +++ b/yarn-project/end-to-end/src/e2e_multiple_blobs.test.ts @@ -60,9 +60,10 @@ describe('e2e_multiple_blobs', () => { const provenTxs = [ // 1 contract deployment tx. await publishContractClass(wallet, AvmTestContract.artifact), - // 2 private function broadcast txs. + // 2 private function broadcast txs. We pick [2] because it has large bytecode (~1,807 fields), + // which combined with the contract class publication exceeds FIELDS_PER_BLOB (4,096). await broadcastFunction(privateFunctions[0]), - await broadcastFunction(privateFunctions[1]), + await broadcastFunction(privateFunctions[2]), // 1 utility function broadcast tx. await broadcastFunction(utilityFunctions[0]), // 1 tx to emit note hash, nullifier, l2_to_l1_message, private log and public log. diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index c30cfbb69c5c..7520a21f6a88 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -136,6 +136,10 @@ export class ContractFunctionSimulator { ): Promise { const simulatorSetupTimer = new Timer(); + await this.contractStore.syncPrivateState(contractAddress, selector, privateSyncCall => + this.runUtility(privateSyncCall, [], anchorBlockHeader, scopes), + ); + await verifyCurrentClassId(contractAddress, this.aztecNode, this.contractStore, anchorBlockHeader); const entryPointArtifact = await this.contractStore.getFunctionArtifactWithDebugMetadata(contractAddress, selector); @@ -169,6 +173,9 @@ export class ContractFunctionSimulator { request.txContext, callContext, anchorBlockHeader, + async call => { + await this.runUtility(call, [], anchorBlockHeader, scopes); + }, request.authWitnesses, request.capsules, HashedValuesCache.create(request.argsOfCalls), diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index 7e188966164d..846afe6c7b5c 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -427,6 +427,26 @@ describe('Private Execution test suite', () => { } return { ...artifact, debug: undefined }; }); + contractStore.getFunctionCall.mockImplementation(async (functionName, args, to) => { + const contract = contracts[to.toString()]; + if (!contract) { + throw new Error(`Contract not found: ${to}`); + } + const functionArtifact = getFunctionArtifactByName(contract, functionName); + return { + name: functionArtifact.name, + args: encodeArguments(functionArtifact, args), + selector: await FunctionSelector.fromNameAndParameters(functionArtifact.name, functionArtifact.parameters), + type: functionArtifact.functionType, + to, + hideMsgSender: false, + isStatic: functionArtifact.isStatic, + returnTypes: functionArtifact.returnTypes, + }; + }); + contractStore.syncPrivateState.mockImplementation(async (contractAddress, _functionSelector, _executor) => { + await contractStore.getFunctionCall('sync_private_state', [], contractAddress); + }); capsuleStore.loadCapsule.mockImplementation((_, __) => Promise.resolve(null)); @@ -683,6 +703,7 @@ describe('Private Execution test suite', () => { artifact: ParentContractArtifact, anchorBlockHeader, functionName: 'entry_point', + contractAddress: parentAddress, }); expect(result.returnValues).toEqual([new Fr(privateIncrement)]); @@ -695,6 +716,28 @@ describe('Private Execution test suite', () => { result.nestedExecutionResults[0].publicInputs.callContext, ); }); + + it('syncs private state for child in nested calls', async () => { + const childArtifact = getFunctionArtifactByName(ChildContractArtifact, 'value'); + const parentAddress = await AztecAddress.random(); + const childAddress = await AztecAddress.random(); + const childSelector = await FunctionSelector.fromNameAndParameters(childArtifact.name, childArtifact.parameters); + + await mockContractInstance(ChildContractArtifact, childAddress); + + contractStore.getFunctionCall.mockClear(); + + const args = [childAddress, childSelector]; + await runSimulator({ + args, + artifact: ParentContractArtifact, + anchorBlockHeader, + functionName: 'entry_point', + contractAddress: parentAddress, + }); + + expect(contractStore.getFunctionCall).toHaveBeenCalledWith('sync_private_state', [], childAddress); + }); }); describe('consuming messages', () => { diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 2fbf7d62f9a1..9b9438049700 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -7,6 +7,7 @@ import { type CircuitSimulator, toACVMWitness } from '@aztec/simulator/client'; import { type FunctionAbi, type FunctionArtifact, + type FunctionCall, FunctionSelector, type NoteSelector, countArgumentsSize, @@ -84,6 +85,8 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP private readonly callContext: CallContext, /** Header of a block whose state is used during private execution (not the block the transaction is included in). */ protected override readonly anchorBlockHeader: BlockHeader, + /** Needed to trigger contract synchronization before nested calls */ + private readonly utilityExecutor: (call: FunctionCall) => Promise, /** List of transient auth witnesses to be used during this simulation */ authWitnesses: AuthWitness[], capsules: Capsule[], @@ -550,6 +553,8 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP await verifyCurrentClassId(targetContractAddress, this.aztecNode, this.contractStore, this.anchorBlockHeader); + await this.contractStore.syncPrivateState(targetContractAddress, functionSelector, this.utilityExecutor); + const targetArtifact = await this.contractStore.getFunctionArtifactWithDebugMetadata( targetContractAddress, functionSelector, @@ -564,6 +569,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP derivedTxContext, derivedCallContext, this.anchorBlockHeader, + this.utilityExecutor, this.authWitnesses, this.capsules, this.executionCache, diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index 5f859709e77c..858f19370d5e 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -990,6 +990,11 @@ export class PXE { const syncTime = syncTimer.ms(); const functionTimer = new Timer(); const contractFunctionSimulator = this.#getSimulatorForTx(); + + await this.contractStore.syncPrivateState(call.to, call.selector, privateSyncCall => + this.#simulateUtility(contractFunctionSimulator, privateSyncCall), + ); + const executionResult = await this.#simulateUtility(contractFunctionSimulator, call, authwits ?? [], scopes); const functionTime = functionTimer.ms(); @@ -1031,21 +1036,25 @@ export class PXE { * Defaults to the latest known block to PXE + 1. * @returns - The packed events with block and tx metadata. */ - public async getPrivateEvents( - eventSelector: EventSelector, - filter: PrivateEventFilter, - ): Promise { - // We need to manually trigger private state sync to have a guarantee that all the events are available. - const call = await this.contractStore.getFunctionCall('sync_private_state', [], filter.contractAddress); - await this.simulateUtility(call); - - const sanitizedFilter = await new PrivateEventFilterValidator(this.anchorBlockStore).validate(filter); - - this.log.debug( - `Getting private events for ${sanitizedFilter.contractAddress.toString()} from ${sanitizedFilter.fromBlock} to ${sanitizedFilter.toBlock}`, - ); + public getPrivateEvents(eventSelector: EventSelector, filter: PrivateEventFilter): Promise { + return this.#putInJobQueue(async () => { + await this.blockStateSynchronizer.sync(); + const contractFunctionSimulator = this.#getSimulatorForTx(); - return this.privateEventStore.getPrivateEvents(eventSelector, sanitizedFilter); + await this.contractStore.syncPrivateState( + filter.contractAddress, + null, + async privateSyncCall => await this.#simulateUtility(contractFunctionSimulator, privateSyncCall), + ); + + const sanitizedFilter = await new PrivateEventFilterValidator(this.anchorBlockStore).validate(filter); + + this.log.debug( + `Getting private events for ${sanitizedFilter.contractAddress.toString()} from ${sanitizedFilter.fromBlock} to ${sanitizedFilter.toBlock}`, + ); + + return this.privateEventStore.getPrivateEvents(eventSelector, sanitizedFilter); + }); } /** diff --git a/yarn-project/pxe/src/storage/contract_store/contract_store.ts b/yarn-project/pxe/src/storage/contract_store/contract_store.ts index 1f70d2d8c455..7a8d05a580d2 100644 --- a/yarn-project/pxe/src/storage/contract_store/contract_store.ts +++ b/yarn-project/pxe/src/storage/contract_store/contract_store.ts @@ -3,6 +3,7 @@ import type { Fr } from '@aztec/foundation/curves/bn254'; import { toArray } from '@aztec/foundation/iterable'; import type { MembershipWitness } from '@aztec/foundation/trees'; import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store'; +import { isProtocolContract } from '@aztec/protocol-contracts'; import { type ContractArtifact, type FunctionAbi, @@ -316,4 +317,23 @@ export class ContractStore { returnTypes: functionDao.returnTypes, }; } + + // Synchronize target contract data + public async syncPrivateState( + contractAddress: AztecAddress, + functionToInvokeAfterSync: FunctionSelector | null, + utilityExecutor: (privateSyncCall: FunctionCall) => Promise, + ) { + // Protocol contracts don't have private state to sync + if (!isProtocolContract(contractAddress)) { + const syncPrivateStateFunctionCall = await this.getFunctionCall('sync_private_state', [], contractAddress); + if (functionToInvokeAfterSync && functionToInvokeAfterSync.equals(syncPrivateStateFunctionCall.selector)) { + throw new Error( + 'Forbidden `sync_private_state` invocation. `sync_private_state` can only be invoked by PXE, manual execution can lead to inconsistencies.', + ); + } + + return utilityExecutor(syncPrivateStateFunctionCall); + } + } } diff --git a/yarn-project/simulator/src/private/circuit_recording/circuit_recorder.ts b/yarn-project/simulator/src/private/circuit_recording/circuit_recorder.ts index dabb743a05f7..96d1b55f5b56 100644 --- a/yarn-project/simulator/src/private/circuit_recording/circuit_recorder.ts +++ b/yarn-project/simulator/src/private/circuit_recording/circuit_recorder.ts @@ -108,16 +108,16 @@ export class CircuitRecorder { * contracts as protocol circuits artifacts always contain a single entrypoint function called 'main'. */ start(input: ACVMWitness, circuitBytecode: Buffer, circuitName: string, functionName: string): Promise { - const parentRef = this.recording; if (this.newCircuit) { + const parentRef = this.recording; this.recording = new CircuitRecording( circuitName, functionName, sha512(circuitBytecode).toString('hex'), Object.fromEntries(input), ); + this.recording.setParent(parentRef); } - this.recording!.setParent(parentRef); return Promise.resolve(); } @@ -173,22 +173,22 @@ export class CircuitRecorder { if (result instanceof Promise) { return result.then(async r => { // Once we leave the nested circuit, we decrease the stack depth and set newCircuit to false - // since we are going back to the "parent" circuit which can never be new + // so that the parent circuit continues with its existing recording + // Note: recording restoration is handled by finish() if (isExternalCall) { this.stackDepth--; this.newCircuit = false; - this.recording = this.recording!.parent; } await this.recordCall(name, args, r, timer.ms(), this.stackDepth); return r; }) as ReturnType; } // Once we leave the nested circuit, we decrease the stack depth and set newCircuit to false - // since we are going back to the "parent" circuit which can never be new + // so that the parent circuit continues with its existing recording + // Note: recording restoration is handled by finish() if (isExternalCall) { this.stackDepth--; this.newCircuit = false; - this.recording = this.recording!.parent; } void this.recordCall(name, args, result, timer.ms(), this.stackDepth); return result; @@ -239,6 +239,12 @@ export class CircuitRecorder { if (!result!.parent) { this.newCircuit = true; this.recording = undefined; + } else { + // For nested circuits (utility calls, nested contract calls), restore to parent recording + // Note: we don't set newCircuit=false here because: + // - For privateCallPrivateFunction, the callback wrapper will set it to false + // - For utility calls, we want newCircuit to remain true so the next circuit creates its own recording + this.recording = result!.parent; } return Promise.resolve(result!); } @@ -247,14 +253,9 @@ export class CircuitRecorder { * Finalizes the recording by resetting the state and returning the recording object with an attached error. * @param error - The error that occurred during circuit execution */ - finishWithError(error: unknown): Promise { - const result = this.recording; - // If this is the top-level circuit recording, we reset the state for the next simulator call - if (!result!.parent) { - this.newCircuit = true; - this.recording = undefined; - } - result!.error = JSON.stringify(error); - return Promise.resolve(result!); + async finishWithError(error: unknown): Promise { + const result = await this.finish(); + result.error = JSON.stringify(error); + return result; } } diff --git a/yarn-project/txe/src/constants.ts b/yarn-project/txe/src/constants.ts new file mode 100644 index 000000000000..24230b9217a4 --- /dev/null +++ b/yarn-project/txe/src/constants.ts @@ -0,0 +1,3 @@ +import { AztecAddress } from '@aztec/stdlib/aztec-address'; + +export const DEFAULT_ADDRESS = AztecAddress.fromNumber(42); 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 a254131b8a54..3d2cf25889b7 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 @@ -49,7 +49,7 @@ import { PublicContractsDB, PublicProcessor, } from '@aztec/simulator/server'; -import { type ContractArtifact, EventSelector, FunctionSelector, FunctionType } from '@aztec/stdlib/abi'; +import { type ContractArtifact, EventSelector, FunctionCall, FunctionSelector, FunctionType } from '@aztec/stdlib/abi'; import { AuthWitness } from '@aztec/stdlib/auth-witness'; import { PublicSimulatorConfig } from '@aztec/stdlib/avm'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; @@ -80,8 +80,8 @@ import { import type { UInt64 } from '@aztec/stdlib/types'; import { ForkCheckpoint } from '@aztec/world-state'; +import { DEFAULT_ADDRESS } from '../constants.js'; import type { TXEStateMachine } from '../state_machine/index.js'; -import { DEFAULT_ADDRESS } from '../txe_session.js'; import type { TXEAccountStore } from '../util/txe_account_store.js'; import type { TXEContractStore } from '../util/txe_contract_store.js'; import { TXEPublicContractDataSource } from '../util/txe_public_contract_data_source.js'; @@ -294,14 +294,19 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl throw new Error(message); } + // Sync notes before executing private function to discover notes from previous transactions + const utilityExecutor = async (call: FunctionCall) => { + await this.executeUtilityCall(call); + }; + + await this.contractStore.syncPrivateState(targetContractAddress, functionSelector, utilityExecutor); + const blockNumber = await this.txeGetNextBlockNumber(); const callContext = new CallContext(from, targetContractAddress, functionSelector, isStaticCall); const gasLimits = new Gas(DEFAULT_DA_GAS_LIMIT, DEFAULT_L2_GAS_LIMIT); - const teardownGasLimits = new Gas(DEFAULT_TEARDOWN_DA_GAS_LIMIT, DEFAULT_TEARDOWN_L2_GAS_LIMIT); - const gasSettings = new GasSettings(gasLimits, teardownGasLimits, GasFees.empty(), GasFees.empty()); const txContext = new TxContext(this.chainId, this.version, gasSettings); @@ -320,6 +325,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl callContext, /** Header of a block whose state is used during private execution (not the block the transaction is included in). */ blockHeader, + utilityExecutor, /** List of transient auth witnesses to be used during this simulation */ Array.from(this.authwits.values()), /** List of transient auth witnesses to be used during this simulation */ @@ -628,12 +634,26 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl throw new Error(`Cannot call ${functionSelector} as there is no artifact found at ${targetContractAddress}.`); } - const call = { - name: artifact.name, - selector: functionSelector, - to: targetContractAddress, - }; + // Sync notes before executing utility function to discover notes from previous transactions + await this.contractStore.syncPrivateState(targetContractAddress, functionSelector, async call => { + await this.executeUtilityCall(call); + }); + + const call = new FunctionCall( + artifact.name, + targetContractAddress, + functionSelector, + FunctionType.UTILITY, + false, + false, + args, + [], + ); + + return this.executeUtilityCall(call); + } + private async executeUtilityCall(call: FunctionCall): Promise { const entryPointArtifact = await this.contractStore.getFunctionArtifactWithDebugMetadata(call.to, call.selector); if (entryPointArtifact.functionType !== FunctionType.UTILITY) { throw new Error(`Cannot run ${entryPointArtifact.functionType} function as utility`); @@ -663,7 +683,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl this.privateEventStore, ); const acirExecutionResult = await new WASMSimulator() - .executeUserCircuit(toACVMWitness(0, args), entryPointArtifact, new Oracle(oracle).toACIRCallback()) + .executeUserCircuit(toACVMWitness(0, call.args), entryPointArtifact, new Oracle(oracle).toACIRCallback()) .catch((err: Error) => { err.message = resolveAssertionMessageFromError(err, entryPointArtifact); throw new ExecutionError( diff --git a/yarn-project/txe/src/txe_session.ts b/yarn-project/txe/src/txe_session.ts index 0f9f9bd1cefa..2a4cae464ae5 100644 --- a/yarn-project/txe/src/txe_session.ts +++ b/yarn-project/txe/src/txe_session.ts @@ -20,10 +20,19 @@ import { HashedValuesCache, type IPrivateExecutionOracle, type IUtilityExecutionOracle, + Oracle, PrivateExecutionOracle, UtilityExecutionOracle, } from '@aztec/pxe/simulator'; -import { FunctionSelector } from '@aztec/stdlib/abi'; +import { + ExecutionError, + WASMSimulator, + createSimulationError, + extractCallStack, + resolveAssertionMessageFromError, + toACVMWitness, +} from '@aztec/simulator/client'; +import { FunctionCall, FunctionSelector, FunctionType } from '@aztec/stdlib/abi'; import type { AuthWitness } from '@aztec/stdlib/auth-witness'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { GasSettings } from '@aztec/stdlib/gas'; @@ -34,6 +43,7 @@ import { CallContext, GlobalVariables, TxContext } from '@aztec/stdlib/tx'; import { z } from 'zod'; +import { DEFAULT_ADDRESS } 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'; @@ -103,8 +113,6 @@ export interface TXESessionStateHandler { enterUtilityState(contractAddress?: AztecAddress): Promise; } -export const DEFAULT_ADDRESS = AztecAddress.fromNumber(42); - /** * A `TXESession` corresponds to a Noir `#[test]` function, and handles all of its oracle calls, stores test-specific * state, etc., independent of all other tests running in parallel. @@ -282,11 +290,6 @@ export class TXESession implements TXESessionStateHandler { ): Promise { this.exitTopLevelState(); - // There is no automatic message discovery and contract-driven syncing process in inlined private or utility - // contexts, which means that known nullifiers are also not searched for, since it is during the tagging sync that - // we perform this. We therefore search for known nullifiers now, as otherwise notes that were nullified would not - // be removed from the database. - // TODO(#12553): make the synchronizer sync here instead and remove this await new NoteService( this.noteStore, this.stateMachine.node, @@ -311,11 +314,13 @@ export class TXESession implements TXESessionStateHandler { const noteCache = new ExecutionNoteCache(protocolNullifier); const taggingIndexCache = new ExecutionTaggingIndexCache(); + const utilityExecutor = this.utilityExecutorForContractSync(anchorBlock); this.oracleHandler = new PrivateExecutionOracle( Fr.ZERO, new TxContext(this.chainId, this.version, GasSettings.empty()), new CallContext(AztecAddress.ZERO, contractAddress, FunctionSelector.empty(), false), anchorBlock!, + utilityExecutor, [], [], new HashedValuesCache(), @@ -470,4 +475,48 @@ export class TXESession implements TXESessionStateHandler { throw new Error(`Expected to be in state 'UTILITY', but got '${this.state.name}' instead`); } } + + private utilityExecutorForContractSync(anchorBlock: any) { + return async (call: FunctionCall) => { + const entryPointArtifact = await this.contractStore.getFunctionArtifactWithDebugMetadata(call.to, call.selector); + if (entryPointArtifact.functionType !== FunctionType.UTILITY) { + throw new Error(`Cannot run ${entryPointArtifact.functionType} function as utility`); + } + + try { + const oracle = new UtilityExecutionOracle( + call.to, + [], + [], + anchorBlock!, + this.contractStore, + this.noteStore, + this.keyStore, + this.addressStore, + this.stateMachine.node, + this.stateMachine.anchorBlockStore, + this.recipientTaggingStore, + this.senderAddressBookStore, + this.capsuleStore, + this.privateEventStore, + ); + await new WASMSimulator() + .executeUserCircuit(toACVMWitness(0, call.args), entryPointArtifact, new Oracle(oracle).toACIRCallback()) + .catch((err: Error) => { + err.message = resolveAssertionMessageFromError(err, entryPointArtifact); + throw new ExecutionError( + err.message, + { + contractAddress: call.to, + functionSelector: call.selector, + }, + extractCallStack(err, entryPointArtifact.debug), + { cause: err }, + ); + }); + } catch (err) { + throw createSimulationError(err instanceof Error ? err : new Error('Unknown error contract data sync')); + } + }; + } }