@aztec/txe 0.0.1-commit.fff30aa → 0.0.1-dev

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,4 @@
1
- import {
2
- CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS,
3
- MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT,
4
- MAX_PROCESSABLE_L2_GAS,
5
- NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
6
- } from '@aztec/constants';
1
+ import { CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/constants';
7
2
  import { BlockNumber } from '@aztec/foundation/branded-types';
8
3
  import { Schnorr } from '@aztec/foundation/crypto/schnorr';
9
4
  import { Fr } from '@aztec/foundation/curves/bn254';
@@ -15,13 +10,14 @@ import {
15
10
  CapsuleService,
16
11
  CapsuleStore,
17
12
  type ContractStore,
18
- type ContractSyncService,
13
+ type ExecutionHooks,
19
14
  NoteStore,
20
15
  ORACLE_VERSION_MAJOR,
21
16
  PrivateEventStore,
22
17
  RecipientTaggingStore,
23
18
  SenderAddressBookStore,
24
19
  SenderTaggingStore,
20
+ composeHooks,
25
21
  enrichPublicSimulationError,
26
22
  } from '@aztec/pxe/server';
27
23
  import {
@@ -55,13 +51,7 @@ import { AuthWitness } from '@aztec/stdlib/auth-witness';
55
51
  import { PublicSimulatorConfig } from '@aztec/stdlib/avm';
56
52
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
57
53
  import { type ContractInstanceWithAddress, computePartialAddress } from '@aztec/stdlib/contract';
58
- import {
59
- FALLBACK_TEARDOWN_DA_GAS_LIMIT,
60
- FALLBACK_TEARDOWN_L2_GAS_LIMIT,
61
- Gas,
62
- GasFees,
63
- GasSettings,
64
- } from '@aztec/stdlib/gas';
54
+ import { Gas, GasFees, GasSettings } from '@aztec/stdlib/gas';
65
55
  import { computeCalldataHash, computeProtocolNullifier, siloNullifier } from '@aztec/stdlib/hash';
66
56
  import {
67
57
  PartialPrivateTailPublicInputsForPublic,
@@ -116,7 +106,6 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
116
106
  private version: Fr,
117
107
  private chainId: Fr,
118
108
  private authwits: Map<string, AuthWitness>,
119
- private readonly contractSyncService: ContractSyncService,
120
109
  ) {
121
110
  this.logger = createLogger('txe:top_level_context');
122
111
  this.logger.debug('Entering Top Level Context');
@@ -186,7 +175,12 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
186
175
 
187
176
  const txEffects = block!.body.txEffects[0];
188
177
 
189
- return { txHash: txEffects.txHash, noteHashes: txEffects.noteHashes, nullifiers: txEffects.nullifiers };
178
+ return {
179
+ txHash: txEffects.txHash,
180
+ noteHashes: txEffects.noteHashes,
181
+ nullifiers: txEffects.nullifiers,
182
+ privateLogs: txEffects.privateLogs,
183
+ };
190
184
  }
191
185
 
192
186
  async syncContractNonOracleMethod(contractAddress: AztecAddress, scope: AztecAddress, jobId: string) {
@@ -315,13 +309,16 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
315
309
  }
316
310
 
317
311
  async privateCallNewFlow(
318
- from: AztecAddress,
312
+ from: AztecAddress | undefined,
319
313
  targetContractAddress: AztecAddress = AztecAddress.zero(),
320
314
  functionSelector: FunctionSelector = FunctionSelector.empty(),
321
315
  args: Fr[],
322
316
  argsHash: Fr = Fr.zero(),
323
317
  isStaticCall: boolean = false,
318
+ additionalScopes: AztecAddress[] = [],
324
319
  jobId: string,
320
+ authorizedUtilityCallTargets: AztecAddress[],
321
+ gasSettings: GasSettings,
325
322
  ) {
326
323
  this.logger.verbose(
327
324
  `Executing external function ${await this.contractStore.getDebugFunctionName(targetContractAddress, functionSelector)}@${targetContractAddress} isStaticCall=${isStaticCall}`,
@@ -335,9 +332,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
335
332
  throw new Error(message);
336
333
  }
337
334
 
338
- // When `from` is the zero address (e.g. when deploying a new account contract), we return an
339
- // empty scope list which acts as deny-all: no notes are visible and no keys are accessible.
340
- const effectiveScopes = from.isZero() ? [] : [from];
335
+ const scopes = from === undefined ? additionalScopes : [from, ...additionalScopes];
341
336
 
342
337
  // Sync notes before executing private function to discover notes from previous transactions
343
338
  const utilityExecutor = async (call: FunctionCall, execScopes: AztecAddress[]) => {
@@ -351,16 +346,13 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
351
346
  utilityExecutor,
352
347
  blockHeader,
353
348
  jobId,
354
- effectiveScopes,
349
+ scopes,
355
350
  );
356
351
 
357
352
  const blockNumber = await this.getNextBlockNumber();
358
353
 
359
- const callContext = new CallContext(from, targetContractAddress, functionSelector, isStaticCall);
360
-
361
- const gasLimits = new Gas(MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT, MAX_PROCESSABLE_L2_GAS);
362
- const teardownGasLimits = new Gas(FALLBACK_TEARDOWN_DA_GAS_LIMIT, FALLBACK_TEARDOWN_L2_GAS_LIMIT);
363
- const gasSettings = new GasSettings(gasLimits, teardownGasLimits, GasFees.empty(), GasFees.empty());
354
+ const msgSender = from ?? AztecAddress.NULL_MSG_SENDER;
355
+ const callContext = new CallContext(msgSender, targetContractAddress, functionSelector, isStaticCall);
364
356
 
365
357
  const txContext = new TxContext(this.chainId, this.version, gasSettings);
366
358
 
@@ -394,19 +386,22 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
394
386
  senderTaggingStore: this.senderTaggingStore,
395
387
  recipientTaggingStore: this.recipientTaggingStore,
396
388
  senderAddressBookStore: this.senderAddressBookStore,
397
- capsuleService: new CapsuleService(this.capsuleStore, effectiveScopes),
389
+ capsuleService: new CapsuleService(this.capsuleStore, scopes),
398
390
  privateEventStore: this.privateEventStore,
399
391
  contractSyncService: this.stateMachine.contractSyncService,
400
392
  jobId,
401
393
  totalPublicCalldataCount: 0,
402
394
  sideEffectCounter: minRevertibleSideEffectCounter,
403
- scopes: effectiveScopes,
395
+ scopes,
404
396
  // In TXE, the typical transaction entrypoint is skipped, so we need to simulate the actions that such a
405
397
  // contract would perform, including setting senderForTags.
406
398
  senderForTags: from,
407
399
  simulator,
408
400
  messageContextService: this.stateMachine.messageContextService,
409
401
  l2TipsStore: this.stateMachine.node,
402
+ hooks: composeHooks({
403
+ authorizeUtilityCall: this.buildAuthorizeUtilityCallHook('private', authorizedUtilityCallTargets),
404
+ }),
410
405
  });
411
406
 
412
407
  // Note: This is a slight modification of simulator.run without any of the checks. Maybe we should modify simulator.run with a boolean value to skip checks.
@@ -514,11 +509,17 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
514
509
  }
515
510
  }
516
511
 
512
+ // Walk the nested private-call tree and collect every offchain effect the transaction emitted.
513
+ // PXE stores these on each `PrivateCallExecutionResult` and they never reach TXE via the
514
+ // `aztec_utl_emitOffchainEffect` foreign-call path (that path only fires at the top-level), so
515
+ // we pull them out here and the RPC wrapper will hand them to `TXESession` for buffering.
516
+ const offchainEffects = collectNested([executionResult], r => r.offchainEffects.map(e => e.data));
517
+
517
518
  if (isStaticCall) {
518
519
  await checkpoint!.revert();
519
520
 
520
521
  await forkedWorldTrees.close();
521
- return executionResult.returnValues ?? [];
522
+ return { returnValues: executionResult.returnValues ?? [], offchainEffects };
522
523
  }
523
524
 
524
525
  const txEffect = TxEffect.empty();
@@ -540,14 +541,15 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
540
541
 
541
542
  await forkedWorldTrees.close();
542
543
 
543
- return executionResult.returnValues ?? [];
544
+ return { returnValues: executionResult.returnValues ?? [], offchainEffects };
544
545
  }
545
546
 
546
547
  async publicCallNewFlow(
547
- from: AztecAddress,
548
+ from: AztecAddress | undefined,
548
549
  targetContractAddress: AztecAddress,
549
550
  calldata: Fr[],
550
551
  isStaticCall: boolean,
552
+ gasSettings: GasSettings,
551
553
  ) {
552
554
  this.logger.verbose(
553
555
  `Executing public function ${await this.contractStore.getDebugFunctionName(targetContractAddress, FunctionSelector.fromField(calldata[0]))}@${targetContractAddress} isStaticCall=${isStaticCall}`,
@@ -555,12 +557,6 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
555
557
 
556
558
  const blockNumber = await this.getNextBlockNumber();
557
559
 
558
- const gasLimits = new Gas(MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT, MAX_PROCESSABLE_L2_GAS);
559
-
560
- const teardownGasLimits = new Gas(FALLBACK_TEARDOWN_DA_GAS_LIMIT, FALLBACK_TEARDOWN_L2_GAS_LIMIT);
561
-
562
- const gasSettings = new GasSettings(gasLimits, teardownGasLimits, GasFees.empty(), GasFees.empty());
563
-
564
560
  const txContext = new TxContext(this.chainId, this.version, gasSettings);
565
561
 
566
562
  const anchorBlockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
@@ -612,7 +608,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
612
608
  // may require producing reverts.
613
609
  const revertibleAccumulatedData = PrivateToPublicAccumulatedData.empty();
614
610
  revertibleAccumulatedData.publicCallRequests[0] = new PublicCallRequest(
615
- from,
611
+ from ?? AztecAddress.NULL_MSG_SENDER,
616
612
  targetContractAddress,
617
613
  isStaticCall,
618
614
  calldataHash,
@@ -703,6 +699,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
703
699
  functionSelector: FunctionSelector,
704
700
  args: Fr[],
705
701
  jobId: string,
702
+ authorizedUtilityCallTargets: AztecAddress[],
706
703
  ) {
707
704
  const artifact = await this.contractStore.getFunctionArtifact(targetContractAddress, functionSelector);
708
705
  if (!artifact) {
@@ -733,10 +730,15 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
733
730
  returnTypes: [],
734
731
  });
735
732
 
736
- return this.executeUtilityCall(call, await this.keyStore.getAccounts(), jobId);
733
+ return this.executeUtilityCall(call, await this.keyStore.getAccounts(), jobId, authorizedUtilityCallTargets);
737
734
  }
738
735
 
739
- private async executeUtilityCall(call: FunctionCall, scopes: AztecAddress[], jobId: string): Promise<Fr[]> {
736
+ private async executeUtilityCall(
737
+ call: FunctionCall,
738
+ scopes: AztecAddress[],
739
+ jobId: string,
740
+ authorizedUtilityCallTargets: AztecAddress[] = [],
741
+ ): Promise<Fr[]> {
740
742
  const entryPointArtifact = await this.contractStore.getFunctionArtifactWithDebugMetadata(call.to, call.selector);
741
743
  if (entryPointArtifact.functionType !== FunctionType.UTILITY) {
742
744
  throw new Error(`Cannot run ${entryPointArtifact.functionType} function as utility`);
@@ -749,6 +751,10 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
749
751
 
750
752
  try {
751
753
  const anchorBlockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
754
+ const simulator = new WASMSimulator();
755
+ const utilityExecutor = async (syncCall: FunctionCall, execScopes: AztecAddress[]) => {
756
+ await this.executeUtilityCall(syncCall, execScopes, jobId);
757
+ };
752
758
  const oracle = new UtilityExecutionOracle({
753
759
  contractAddress: call.to,
754
760
  authWitnesses: [],
@@ -764,12 +770,17 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
764
770
  capsuleService: new CapsuleService(this.capsuleStore, scopes),
765
771
  privateEventStore: this.privateEventStore,
766
772
  messageContextService: this.stateMachine.messageContextService,
767
- contractSyncService: this.contractSyncService,
773
+ contractSyncService: this.stateMachine.contractSyncService,
768
774
  l2TipsStore: this.stateMachine.node,
769
775
  jobId,
770
776
  scopes,
777
+ simulator,
778
+ hooks: composeHooks({
779
+ authorizeUtilityCall: this.buildAuthorizeUtilityCallHook('utility', authorizedUtilityCallTargets),
780
+ }),
781
+ utilityExecutor,
771
782
  });
772
- const acirExecutionResult = await new WASMSimulator()
783
+ const acirExecutionResult = await simulator
773
784
  .executeUserCircuit(toACVMWitness(0, call.args), entryPointArtifact, new Oracle(oracle).toACIRCallback())
774
785
  .catch((err: Error) => {
775
786
  err.message = resolveAssertionMessageFromError(err, entryPointArtifact);
@@ -800,4 +811,17 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
800
811
  const header = await this.stateMachine.node.getBlockHeader('latest');
801
812
  return header ? header.globalVariables.blockNumber : BlockNumber.ZERO;
802
813
  }
814
+
815
+ private buildAuthorizeUtilityCallHook(
816
+ callerContext: 'private' | 'utility',
817
+ authorizedTargets: AztecAddress[],
818
+ ): ExecutionHooks['authorizeUtilityCall'] | undefined {
819
+ if (authorizedTargets.length === 0) {
820
+ return undefined;
821
+ }
822
+ return req =>
823
+ Promise.resolve({
824
+ authorized: req.callerContext === callerContext && authorizedTargets.some(t => t.equals(req.target)),
825
+ });
826
+ }
803
827
  }
@@ -1,7 +1,15 @@
1
1
  import type { ContractInstanceWithAddress } from '@aztec/aztec.js/contracts';
2
2
  import { Fr, Point } from '@aztec/aztec.js/fields';
3
- import { MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX } from '@aztec/constants';
3
+ import {
4
+ ARCHIVE_HEIGHT,
5
+ MAX_NOTE_HASHES_PER_TX,
6
+ MAX_NULLIFIERS_PER_TX,
7
+ MAX_PRIVATE_LOGS_PER_TX,
8
+ PRIVATE_LOG_CIPHERTEXT_LEN,
9
+ PRIVATE_LOG_SIZE_IN_FIELDS,
10
+ } from '@aztec/constants';
4
11
  import { BlockNumber } from '@aztec/foundation/branded-types';
12
+ import { MembershipWitness } from '@aztec/foundation/trees';
5
13
  import {
6
14
  type IMiscOracle,
7
15
  type IPrivateExecutionOracle,
@@ -11,8 +19,10 @@ import {
11
19
  import { type ContractArtifact, EventSelector, FunctionSelector, NoteSelector } from '@aztec/stdlib/abi';
12
20
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
13
21
  import { BlockHash } from '@aztec/stdlib/block';
22
+ import { GasSettings } from '@aztec/stdlib/gas';
14
23
 
15
24
  import type { IAvmExecutionOracle, ITxeExecutionOracle } from './oracle/interfaces.js';
25
+ import { TXE_ORACLE_VERSION_MAJOR } from './txe_oracle_version.js';
16
26
  import type { TXESessionStateHandler } from './txe_session.js';
17
27
  import {
18
28
  type ForeignCallArray,
@@ -102,6 +112,26 @@ export class RPCTranslator {
102
112
  return this.oracleHandler;
103
113
  }
104
114
 
115
+ // eslint-disable-next-line camelcase
116
+ aztec_txe_assertCompatibleOracleVersion(foreignMajor: ForeignCallSingle, foreignMinor: ForeignCallSingle) {
117
+ const major = fromSingle(foreignMajor).toNumber();
118
+ const minor = fromSingle(foreignMinor).toNumber();
119
+
120
+ if (major !== TXE_ORACLE_VERSION_MAJOR) {
121
+ const hint =
122
+ major > TXE_ORACLE_VERSION_MAJOR
123
+ ? 'The test was compiled with a newer version of Aztec.nr than your test environment supports. Upgrade your test environment to a compatible version.'
124
+ : '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.';
125
+ throw new Error(
126
+ `Incompatible test environment version: ${hint} See https://docs.aztec.network/errors/12 (expected test oracle major version ${TXE_ORACLE_VERSION_MAJOR}, got ${major})`,
127
+ );
128
+ }
129
+
130
+ this.stateHandler.setTxeOracleVersion({ major, minor });
131
+
132
+ return toForeignCallResult([]);
133
+ }
134
+
105
135
  // TXE session state transition functions - these get handled by the state handler
106
136
 
107
137
  // eslint-disable-next-line camelcase
@@ -117,6 +147,7 @@ export class RPCTranslator {
117
147
  foreignContractAddressValue: ForeignCallSingle,
118
148
  foreignAnchorBlockNumberIsSome: ForeignCallSingle,
119
149
  foreignAnchorBlockNumberValue: ForeignCallSingle,
150
+ foreignGasSettings: ForeignCallArray,
120
151
  ) {
121
152
  const contractAddress = fromSingle(foreignContractAddressIsSome).toBool()
122
153
  ? AztecAddress.fromField(fromSingle(foreignContractAddressValue))
@@ -126,7 +157,13 @@ export class RPCTranslator {
126
157
  ? BlockNumber(fromSingle(foreignAnchorBlockNumberValue).toNumber())
127
158
  : undefined;
128
159
 
129
- const privateContextInputs = await this.stateHandler.enterPrivateState(contractAddress, anchorBlockNumber);
160
+ const gasSettings = GasSettings.fromFields(fromArray(foreignGasSettings));
161
+
162
+ const privateContextInputs = await this.stateHandler.enterPrivateState(
163
+ contractAddress,
164
+ anchorBlockNumber,
165
+ gasSettings,
166
+ );
130
167
 
131
168
  return toForeignCallResult(privateContextInputs.toFields().map(toSingle));
132
169
  }
@@ -289,15 +326,81 @@ export class RPCTranslator {
289
326
 
290
327
  // eslint-disable-next-line camelcase
291
328
  async aztec_txe_getLastTxEffects() {
292
- const { txHash, noteHashes, nullifiers } = await this.handlerAsTxe().getLastTxEffects();
329
+ const { txHash, noteHashes, nullifiers, privateLogs } = await this.handlerAsTxe().getLastTxEffects();
330
+
331
+ if (privateLogs.length > MAX_PRIVATE_LOGS_PER_TX) {
332
+ throw new Error(`${privateLogs.length} private logs exceed max ${MAX_PRIVATE_LOGS_PER_TX}`);
333
+ }
334
+
335
+ // Same workaround as `aztec_txe_getPrivateEvents`: Noir cannot yet return nested structs with arrays, so we return
336
+ // a flat multidimensional array plus per-log lengths and the total count, and reassemble into a
337
+ // `BoundedVec<BoundedVec<T>>` on the Noir side. Each log contributes only its emitted fields. The rest
338
+ // is zero-padded to `PRIVATE_LOG_SIZE_IN_FIELDS`.
339
+ const emittedLogs = privateLogs.map(log => log.getEmittedFields());
340
+
341
+ const rawLogStorage = emittedLogs
342
+ .map(fields => fields.concat(Array(PRIVATE_LOG_SIZE_IN_FIELDS - fields.length).fill(new Fr(0))))
343
+ .concat(
344
+ Array(MAX_PRIVATE_LOGS_PER_TX - emittedLogs.length).fill(Array(PRIVATE_LOG_SIZE_IN_FIELDS).fill(new Fr(0))),
345
+ )
346
+ .flat();
347
+
348
+ const logLengths = emittedLogs
349
+ .map(fields => new Fr(fields.length))
350
+ .concat(Array(MAX_PRIVATE_LOGS_PER_TX - emittedLogs.length).fill(new Fr(0)));
351
+
352
+ const logCount = new Fr(emittedLogs.length);
293
353
 
294
354
  return toForeignCallResult([
295
355
  toSingle(txHash.hash),
296
356
  ...arrayToBoundedVec(toArray(noteHashes), MAX_NOTE_HASHES_PER_TX),
297
357
  ...arrayToBoundedVec(toArray(nullifiers), MAX_NULLIFIERS_PER_TX),
358
+ toArray(rawLogStorage),
359
+ toArray(logLengths),
360
+ toSingle(logCount),
298
361
  ]);
299
362
  }
300
363
 
364
+ // eslint-disable-next-line camelcase
365
+ aztec_txe_getLastCallOffchainEffects() {
366
+ // This oracle returns all offchain effect payloads (messages, authwit requests, etc.) emitted by the last top-level call,
367
+ // MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY is arbitrarily set at 64 because we need a bound. Nothing inherent about it.
368
+ const MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY = 64;
369
+ // Must match MAX_OFFCHAIN_EFFECT_LEN in txe_oracles.nr.
370
+ const MAX_OFFCHAIN_EFFECT_LEN = 2 + PRIVATE_LOG_CIPHERTEXT_LEN;
371
+
372
+ const { effects } = this.stateHandler.getLastCallOffchainEffects();
373
+
374
+ if (effects.length > MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY) {
375
+ throw new Error(`${effects.length} offchain effects exceed max ${MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY}`);
376
+ }
377
+ if (effects.some(e => e.length > MAX_OFFCHAIN_EFFECT_LEN)) {
378
+ throw new Error(`Some offchain effect has length larger than max ${MAX_OFFCHAIN_EFFECT_LEN}`);
379
+ }
380
+
381
+ const rawArrayStorage = effects
382
+ .map(e => e.concat(Array(MAX_OFFCHAIN_EFFECT_LEN - e.length).fill(new Fr(0))))
383
+ .concat(
384
+ Array(MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY - effects.length).fill(Array(MAX_OFFCHAIN_EFFECT_LEN).fill(new Fr(0))),
385
+ )
386
+ .flat();
387
+
388
+ const effectLengths = effects
389
+ .map(e => new Fr(e.length))
390
+ .concat(Array(MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY - effects.length).fill(new Fr(0)));
391
+
392
+ const count = new Fr(effects.length);
393
+
394
+ return toForeignCallResult([toArray(rawArrayStorage), toArray(effectLengths), toSingle(count)]);
395
+ }
396
+
397
+ // eslint-disable-next-line camelcase
398
+ aztec_txe_getLastCallContext() {
399
+ const { txHash, anchorBlockTimestamp } = this.stateHandler.getLastCallContext();
400
+ const isSome = txHash.isZero() ? 0 : 1;
401
+ return toForeignCallResult([toSingle(isSome), toSingle(txHash), toSingle(new Fr(anchorBlockTimestamp))]);
402
+ }
403
+
301
404
  // eslint-disable-next-line camelcase
302
405
  async aztec_txe_getPrivateEvents(
303
406
  foreignSelector: ForeignCallSingle,
@@ -704,6 +807,7 @@ export class RPCTranslator {
704
807
  return toForeignCallResult(witness.toNoirRepresentation());
705
808
  }
706
809
 
810
+ // TODO(https://linear.app/aztec-labs/issue/F-651): drop this
707
811
  // eslint-disable-next-line camelcase
708
812
  async aztec_utl_getBlockHashMembershipWitness(
709
813
  foreignAnchorBlockHash: ForeignCallSingle,
@@ -722,6 +826,20 @@ export class RPCTranslator {
722
826
  return toForeignCallResult(witness.toNoirRepresentation());
723
827
  }
724
828
 
829
+ // TODO(https://linear.app/aztec-labs/issue/F-651): rename to aztec_utl_getBlockHashMembershipWitness
830
+ // eslint-disable-next-line camelcase
831
+ async aztec_utl_getBlockHashMembershipWitnessV2(
832
+ foreignAnchorBlockHash: ForeignCallSingle,
833
+ foreignBlockHash: ForeignCallSingle,
834
+ ) {
835
+ const anchorBlockHash = new BlockHash(fromSingle(foreignAnchorBlockHash));
836
+ const blockHash = new BlockHash(fromSingle(foreignBlockHash));
837
+
838
+ const witness = await this.handlerAsUtility().getBlockHashMembershipWitness(anchorBlockHash, blockHash);
839
+ const effective = witness ?? MembershipWitness.empty(ARCHIVE_HEIGHT);
840
+ return toForeignCallResult([toSingle(new Fr(witness !== undefined ? 1 : 0)), ...effective.toNoirRepresentation()]);
841
+ }
842
+
725
843
  // eslint-disable-next-line camelcase
726
844
  async aztec_utl_getLowNullifierMembershipWitness(
727
845
  foreignBlockHash: ForeignCallSingle,
@@ -1071,8 +1189,13 @@ export class RPCTranslator {
1071
1189
  }
1072
1190
 
1073
1191
  // eslint-disable-next-line camelcase
1074
- aztec_utl_emitOffchainEffect(_foreignData: ForeignCallArray) {
1075
- throw new Error('Offchain effects are not yet supported in the TestEnvironment');
1192
+ aztec_utl_emitOffchainEffect(foreignData: ForeignCallArray) {
1193
+ // Record the raw payload against the currently-executing top-level call. The Noir side
1194
+ // (via `env.offchain_messages()`) is responsible for decoding the protocol-reserved prefix
1195
+ // (`OFFCHAIN_MESSAGE_IDENTIFIER`, recipient) and turning each payload into an `OffchainMessage` struct suitable
1196
+ // for `offchain_receive`.
1197
+ this.stateHandler.recordOffchainEffect(fromArray(foreignData));
1198
+ return Promise.resolve(toForeignCallResult([]));
1076
1199
  }
1077
1200
 
1078
1201
  // AVM opcodes
@@ -1267,32 +1390,64 @@ export class RPCTranslator {
1267
1390
 
1268
1391
  // eslint-disable-next-line camelcase
1269
1392
  async aztec_txe_privateCallNewFlow(
1270
- foreignFrom: ForeignCallSingle,
1393
+ foreignFromIsSome: ForeignCallSingle,
1394
+ foreignFromValue: ForeignCallSingle,
1271
1395
  foreignTargetContractAddress: ForeignCallSingle,
1272
1396
  foreignFunctionSelector: ForeignCallSingle,
1273
1397
  foreignArgs: ForeignCallArray,
1274
1398
  foreignArgsHash: ForeignCallSingle,
1275
1399
  foreignIsStaticCall: ForeignCallSingle,
1400
+ foreignAdditionalScopes: ForeignCallArray,
1401
+ foreignAuthorizedUtilityCallTargets: ForeignCallArray,
1402
+ foreignGasSettings: ForeignCallArray,
1276
1403
  ) {
1277
- const from = addressFromSingle(foreignFrom);
1404
+ const from = fromSingle(foreignFromIsSome).toBool() ? addressFromSingle(foreignFromValue) : undefined;
1278
1405
  const targetContractAddress = addressFromSingle(foreignTargetContractAddress);
1279
1406
  const functionSelector = FunctionSelector.fromField(fromSingle(foreignFunctionSelector));
1280
1407
  const args = fromArray(foreignArgs);
1281
1408
  const argsHash = fromSingle(foreignArgsHash);
1282
1409
  const isStaticCall = fromSingle(foreignIsStaticCall).toBool();
1283
-
1284
- const returnValues = await this.handlerAsTxe().privateCallNewFlow(
1285
- from,
1286
- targetContractAddress,
1287
- functionSelector,
1288
- args,
1289
- argsHash,
1290
- isStaticCall,
1291
- this.stateHandler.getCurrentJob(),
1410
+ const additionalScopes = fromArray(foreignAdditionalScopes).map(field => AztecAddress.fromField(field));
1411
+ const authorizedUtilityCallTargets = fromArray(foreignAuthorizedUtilityCallTargets).map(field =>
1412
+ AztecAddress.fromField(field),
1292
1413
  );
1414
+ const gasSettings = GasSettings.fromFields(fromArray(foreignGasSettings));
1415
+
1416
+ const returnValues = await this.stateHandler.withTopLevelCallTracking(async () => {
1417
+ const { returnValues, offchainEffects } = await this.handlerAsTxe().privateCallNewFlow(
1418
+ from,
1419
+ targetContractAddress,
1420
+ functionSelector,
1421
+ args,
1422
+ argsHash,
1423
+ isStaticCall,
1424
+ additionalScopes,
1425
+ this.stateHandler.getCurrentJob(),
1426
+ authorizedUtilityCallTargets,
1427
+ gasSettings,
1428
+ );
1429
+
1430
+ // Private execution collects offchain effects inside PXE's PrivateExecutionOracle rather than
1431
+ // round-tripping them through `aztec_utl_emitOffchainEffect`, so the session buffer is empty
1432
+ // at this point. Drain the effects from the execution tree into the session buffer so the
1433
+ // next `env.offchain_messages()` call in the test sees them.
1434
+ for (const data of offchainEffects) {
1435
+ this.stateHandler.recordOffchainEffect(data);
1436
+ }
1437
+
1438
+ // TODO(F-335): Avoid doing the following call here.
1439
+ await this.stateHandler.cycleJob();
1440
+
1441
+ if (isStaticCall) {
1442
+ // Static calls revert their checkpoint and mine no block, so there is no tx hash to tag
1443
+ // offchain effects with. Querying `getLastTxEffects()` here would return an unrelated
1444
+ // predecessor tx.
1445
+ return { result: returnValues };
1446
+ }
1447
+ const { txHash } = await this.handlerAsTxe().getLastTxEffects();
1448
+ return { result: returnValues, txHash: txHash.hash };
1449
+ });
1293
1450
 
1294
- // TODO(F-335): Avoid doing the following call here.
1295
- await this.stateHandler.cycleJob();
1296
1451
  return toForeignCallResult([toArray(returnValues)]);
1297
1452
  }
1298
1453
 
@@ -1301,39 +1456,68 @@ export class RPCTranslator {
1301
1456
  foreignTargetContractAddress: ForeignCallSingle,
1302
1457
  foreignFunctionSelector: ForeignCallSingle,
1303
1458
  foreignArgs: ForeignCallArray,
1459
+ foreignAuthorizedUtilityCallTargets: ForeignCallArray,
1304
1460
  ) {
1305
1461
  const targetContractAddress = addressFromSingle(foreignTargetContractAddress);
1306
1462
  const functionSelector = FunctionSelector.fromField(fromSingle(foreignFunctionSelector));
1307
1463
  const args = fromArray(foreignArgs);
1308
-
1309
- const returnValues = await this.handlerAsTxe().executeUtilityFunction(
1310
- targetContractAddress,
1311
- functionSelector,
1312
- args,
1313
- this.stateHandler.getCurrentJob(),
1464
+ const authorizedUtilityCallTargets = fromArray(foreignAuthorizedUtilityCallTargets).map(field =>
1465
+ AztecAddress.fromField(field),
1314
1466
  );
1315
1467
 
1316
- // TODO(F-335): Avoid doing the following call here.
1317
- await this.stateHandler.cycleJob();
1468
+ const returnValues = await this.stateHandler.withTopLevelCallTracking(async () => {
1469
+ const returnValues = await this.handlerAsTxe().executeUtilityFunction(
1470
+ targetContractAddress,
1471
+ functionSelector,
1472
+ args,
1473
+ this.stateHandler.getCurrentJob(),
1474
+ authorizedUtilityCallTargets,
1475
+ );
1476
+
1477
+ // TODO(F-335): Avoid doing the following call here.
1478
+ await this.stateHandler.cycleJob();
1479
+
1480
+ return { result: returnValues };
1481
+ });
1482
+
1318
1483
  return toForeignCallResult([toArray(returnValues)]);
1319
1484
  }
1320
1485
 
1321
1486
  // eslint-disable-next-line camelcase
1322
1487
  async aztec_txe_publicCallNewFlow(
1323
- foreignFrom: ForeignCallSingle,
1488
+ foreignFromIsSome: ForeignCallSingle,
1489
+ foreignFromValue: ForeignCallSingle,
1324
1490
  foreignAddress: ForeignCallSingle,
1325
1491
  foreignCalldata: ForeignCallArray,
1326
1492
  foreignIsStaticCall: ForeignCallSingle,
1493
+ foreignGasSettings: ForeignCallArray,
1327
1494
  ) {
1328
- const from = addressFromSingle(foreignFrom);
1495
+ const from = fromSingle(foreignFromIsSome).toBool() ? addressFromSingle(foreignFromValue) : undefined;
1329
1496
  const address = addressFromSingle(foreignAddress);
1330
1497
  const calldata = fromArray(foreignCalldata);
1331
1498
  const isStaticCall = fromSingle(foreignIsStaticCall).toBool();
1499
+ const gasSettings = GasSettings.fromFields(fromArray(foreignGasSettings));
1500
+
1501
+ const returnValues = await this.stateHandler.withTopLevelCallTracking(async () => {
1502
+ const returnValues = await this.handlerAsTxe().publicCallNewFlow(
1503
+ from,
1504
+ address,
1505
+ calldata,
1506
+ isStaticCall,
1507
+ gasSettings,
1508
+ );
1509
+
1510
+ // TODO(F-335): Avoid doing the following call here.
1511
+ await this.stateHandler.cycleJob();
1332
1512
 
1333
- const returnValues = await this.handlerAsTxe().publicCallNewFlow(from, address, calldata, isStaticCall);
1513
+ if (isStaticCall) {
1514
+ // See equivalent branch in `aztec_txe_privateCallNewFlow`.
1515
+ return { result: returnValues };
1516
+ }
1517
+ const { txHash } = await this.handlerAsTxe().getLastTxEffects();
1518
+ return { result: returnValues, txHash: txHash.hash };
1519
+ });
1334
1520
 
1335
- // TODO(F-335): Avoid doing the following call here.
1336
- await this.stateHandler.cycleJob();
1337
1521
  return toForeignCallResult([toArray(returnValues)]);
1338
1522
  }
1339
1523
 
@@ -57,7 +57,7 @@ export class TXEStateMachine {
57
57
  CHAIN_ID,
58
58
  new TXEGlobalVariablesBuilder(),
59
59
  new MockEpochCache(),
60
- getPackageVersion() ?? '',
60
+ getPackageVersion(),
61
61
  new TestCircuitVerifier(),
62
62
  undefined,
63
63
  log,
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The TXE oracle version constants are used to check that the oracle interface used for tests is in sync between
3
+ * TXE and Aztec.nr. This is separate from the contract oracle version in `pxe/src/oracle_version.ts`, which covers
4
+ * oracles used during contract execution by PXE.
5
+ *
6
+ * The Noir counterparts are in `noir-projects/aztec-nr/aztec/src/test/helpers/txe_oracles.nr`.
7
+ */
8
+ export const TXE_ORACLE_VERSION_MAJOR = 1;
9
+ export const TXE_ORACLE_VERSION_MINOR = 0;