@aztec/txe 0.69.1 → 0.70.0

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.
@@ -58,30 +58,32 @@ import {
58
58
  import { AztecAddress } from '@aztec/foundation/aztec-address';
59
59
  import { poseidon2Hash } from '@aztec/foundation/crypto';
60
60
  import { Fr } from '@aztec/foundation/fields';
61
- import { type Logger, applyStringFormatting } from '@aztec/foundation/log';
61
+ import { type LogFn, type Logger, applyStringFormatting, createDebugOnlyLogger } from '@aztec/foundation/log';
62
62
  import { Timer } from '@aztec/foundation/timer';
63
63
  import { type KeyStore } from '@aztec/key-store';
64
64
  import { ContractDataOracle, SimulatorOracle, enrichPublicSimulationError } from '@aztec/pxe';
65
65
  import {
66
- ExecutionError,
67
66
  ExecutionNoteCache,
68
67
  type MessageLoadOracleInputs,
69
68
  type NoteData,
70
69
  Oracle,
71
- type PackedValuesCache,
72
- type PublicTxResult,
73
- PublicTxSimulator,
74
70
  type TypedOracle,
75
- acvm,
76
- createSimulationError,
71
+ WASMSimulator,
77
72
  extractCallStack,
78
73
  extractPrivateCircuitPublicInputs,
79
74
  pickNotes,
80
- resolveAssertionMessageFromError,
81
75
  toACVMWitness,
82
76
  witnessMapToFields,
83
- } from '@aztec/simulator';
84
- import { createTxForPublicCall } from '@aztec/simulator/public/fixtures';
77
+ } from '@aztec/simulator/client';
78
+ import { createTxForPublicCalls } from '@aztec/simulator/public/fixtures';
79
+ import {
80
+ ExecutionError,
81
+ type HashedValuesCache,
82
+ type PublicTxResult,
83
+ PublicTxSimulator,
84
+ createSimulationError,
85
+ resolveAssertionMessageFromError,
86
+ } from '@aztec/simulator/server';
85
87
  import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
86
88
  import { MerkleTreeSnapshotOperationsFacade, type MerkleTrees } from '@aztec/world-state';
87
89
 
@@ -108,7 +110,6 @@ export class TXE implements TypedOracle {
108
110
 
109
111
  private uniqueNoteHashesFromPublic: Fr[] = [];
110
112
  private siloedNullifiersFromPublic: Fr[] = [];
111
- private siloedNullifiersFromPrivate: Set<string> = new Set();
112
113
  private privateLogs: PrivateLog[] = [];
113
114
  private publicLogs: UnencryptedL2Log[] = [];
114
115
 
@@ -116,19 +117,33 @@ export class TXE implements TypedOracle {
116
117
 
117
118
  private node = new TXENode(this.blockNumber);
118
119
 
120
+ private simulationProvider = new WASMSimulator();
121
+
122
+ private noteCache: ExecutionNoteCache;
123
+
124
+ debug: LogFn;
125
+
119
126
  constructor(
120
127
  private logger: Logger,
121
128
  private trees: MerkleTrees,
122
- private packedValuesCache: PackedValuesCache,
123
- private noteCache: ExecutionNoteCache,
129
+ private executionCache: HashedValuesCache,
124
130
  private keyStore: KeyStore,
125
131
  private txeDatabase: TXEDatabase,
126
132
  ) {
133
+ this.noteCache = new ExecutionNoteCache(this.getTxRequestHash());
127
134
  this.contractDataOracle = new ContractDataOracle(txeDatabase);
128
135
  this.contractAddress = AztecAddress.random();
129
136
  // Default msg_sender (for entrypoints) is now Fr.max_value rather than 0 addr (see #7190 & #7404)
130
137
  this.msgSender = AztecAddress.fromField(Fr.MAX_FIELD_VALUE);
131
- this.simulatorOracle = new SimulatorOracle(this.contractDataOracle, txeDatabase, keyStore, this.node);
138
+ this.simulatorOracle = new SimulatorOracle(
139
+ this.contractDataOracle,
140
+ txeDatabase,
141
+ keyStore,
142
+ this.node,
143
+ this.simulationProvider,
144
+ );
145
+
146
+ this.debug = createDebugOnlyLogger('aztec:kv-pxe-database');
132
147
  }
133
148
 
134
149
  // Utils
@@ -260,18 +275,14 @@ export class TXE implements TypedOracle {
260
275
  );
261
276
  }
262
277
 
263
- async addNullifiersFromPrivate(contractAddress: AztecAddress, nullifiers: Fr[]) {
278
+ async checkNullifiersNotInTree(contractAddress: AztecAddress, nullifiers: Fr[]) {
264
279
  const siloedNullifiers = nullifiers.map(nullifier => siloNullifier(contractAddress, nullifier));
265
280
  const db = await this.trees.getLatest();
266
281
  const nullifierIndexesInTree = await db.findLeafIndices(
267
282
  MerkleTreeId.NULLIFIER_TREE,
268
283
  siloedNullifiers.map(n => n.toBuffer()),
269
284
  );
270
- const notInTree = nullifierIndexesInTree.every(index => index === undefined);
271
- const notInCache = siloedNullifiers.every(n => !this.siloedNullifiersFromPrivate.has(n.toString()));
272
- if (notInTree && notInCache) {
273
- siloedNullifiers.forEach(n => this.siloedNullifiersFromPrivate.add(n.toString()));
274
- } else {
285
+ if (nullifierIndexesInTree.some(index => index !== undefined)) {
275
286
  throw new Error(`Rejecting tx for emitting duplicate nullifiers`);
276
287
  }
277
288
  }
@@ -360,16 +371,16 @@ export class TXE implements TypedOracle {
360
371
  return Fr.random();
361
372
  }
362
373
 
363
- packArgumentsArray(args: Fr[]) {
364
- return Promise.resolve(this.packedValuesCache.pack(args));
374
+ storeArrayInExecutionCache(values: Fr[]) {
375
+ return Promise.resolve(this.executionCache.store(values));
365
376
  }
366
377
 
367
- packReturns(returns: Fr[]) {
368
- return Promise.resolve(this.packedValuesCache.pack(returns));
378
+ storeInExecutionCache(values: Fr[]) {
379
+ return Promise.resolve(this.executionCache.store(values));
369
380
  }
370
381
 
371
- unpackReturns(returnsHash: Fr) {
372
- return Promise.resolve(this.packedValuesCache.unpack(returnsHash));
382
+ loadFromExecutionCache(returnsHash: Fr) {
383
+ return Promise.resolve(this.executionCache.getPreimage(returnsHash));
373
384
  }
374
385
 
375
386
  getKeyValidationRequest(pkMHash: Fr): Promise<KeyValidationRequest> {
@@ -550,12 +561,18 @@ export class TXE implements TypedOracle {
550
561
  }
551
562
 
552
563
  async notifyNullifiedNote(innerNullifier: Fr, noteHash: Fr, counter: number) {
553
- await this.addNullifiersFromPrivate(this.contractAddress, [innerNullifier]);
564
+ await this.checkNullifiersNotInTree(this.contractAddress, [innerNullifier]);
554
565
  this.noteCache.nullifyNote(this.contractAddress, innerNullifier, noteHash);
555
566
  this.sideEffectCounter = counter + 1;
556
567
  return Promise.resolve();
557
568
  }
558
569
 
570
+ async notifyCreatedNullifier(innerNullifier: Fr): Promise<void> {
571
+ await this.checkNullifiersNotInTree(this.contractAddress, [innerNullifier]);
572
+ this.noteCache.nullifierCreated(this.contractAddress, innerNullifier);
573
+ return Promise.resolve();
574
+ }
575
+
559
576
  async checkNullifierExists(innerNullifier: Fr): Promise<boolean> {
560
577
  const nullifier = siloNullifier(this.contractAddress, innerNullifier!);
561
578
  const db = await this.trees.getLatest();
@@ -617,6 +634,7 @@ export class TXE implements TypedOracle {
617
634
 
618
635
  async commitState() {
619
636
  const blockNumber = await this.getBlockNumber();
637
+ const { usedTxRequestHashForNonces } = this.noteCache.finish();
620
638
  if (this.committedBlocks.has(blockNumber)) {
621
639
  throw new Error('Already committed state');
622
640
  } else {
@@ -625,30 +643,25 @@ export class TXE implements TypedOracle {
625
643
 
626
644
  const txEffect = TxEffect.empty();
627
645
 
646
+ const nonceGenerator = usedTxRequestHashForNonces ? this.getTxRequestHash() : this.noteCache.getAllNullifiers()[0];
647
+
628
648
  let i = 0;
629
649
  txEffect.noteHashes = [
630
650
  ...this.noteCache
631
651
  .getAllNotes()
632
652
  .map(pendingNote =>
633
653
  computeUniqueNoteHash(
634
- computeNoteHashNonce(new Fr(this.blockNumber + 6969), i++),
654
+ computeNoteHashNonce(nonceGenerator, i++),
635
655
  siloNoteHash(pendingNote.note.contractAddress, pendingNote.noteHashForConsumption),
636
656
  ),
637
657
  ),
638
658
  ...this.uniqueNoteHashesFromPublic,
639
659
  ];
640
- txEffect.nullifiers = [
641
- new Fr(blockNumber + 6969),
642
- ...Array.from(this.siloedNullifiersFromPrivate).map(n => Fr.fromString(n)),
643
- ];
644
-
645
- // Using block number itself, (without adding 6969) gets killed at 1 as it says the slot is already used,
646
- // it seems like we commit a 1 there to the trees before ? To see what I mean, uncomment these lines below
647
- // let index = await (await this.trees.getLatest()).findLeafIndex(MerkleTreeId.NULLIFIER_TREE, Fr.ONE.toBuffer());
648
- // console.log('INDEX OF ONE', index);
649
- // index = await (await this.trees.getLatest()).findLeafIndex(MerkleTreeId.NULLIFIER_TREE, Fr.random().toBuffer());
650
- // console.log('INDEX OF RANDOM', index);
651
660
 
661
+ txEffect.nullifiers = this.noteCache.getAllNullifiers();
662
+ if (usedTxRequestHashForNonces) {
663
+ txEffect.nullifiers.unshift(this.getTxRequestHash());
664
+ }
652
665
  this.node.setTxEffect(blockNumber, new TxHash(new Fr(blockNumber)), txEffect);
653
666
  this.node.setNullifiersIndexesWithBlock(blockNumber, txEffect.nullifiers);
654
667
  this.node.addNoteLogsByTags(this.blockNumber, this.privateLogs);
@@ -659,10 +672,14 @@ export class TXE implements TypedOracle {
659
672
 
660
673
  this.privateLogs = [];
661
674
  this.publicLogs = [];
662
- this.siloedNullifiersFromPrivate = new Set();
663
675
  this.uniqueNoteHashesFromPublic = [];
664
676
  this.siloedNullifiersFromPublic = [];
665
- this.noteCache = new ExecutionNoteCache(new Fr(1));
677
+ this.noteCache = new ExecutionNoteCache(this.getTxRequestHash());
678
+ }
679
+
680
+ getTxRequestHash() {
681
+ // Using block number itself is invalid since indexed trees come prefilled with the first slots.
682
+ return new Fr(this.blockNumber + 6969);
666
683
  }
667
684
 
668
685
  emitContractClassLog(_log: UnencryptedL2Log, _counter: number): Fr {
@@ -697,21 +714,23 @@ export class TXE implements TypedOracle {
697
714
  const initialWitness = await this.getInitialWitness(artifact, argsHash, sideEffectCounter, isStaticCall);
698
715
  const acvmCallback = new Oracle(this);
699
716
  const timer = new Timer();
700
- const acirExecutionResult = await acvm(acir, initialWitness, acvmCallback).catch((err: Error) => {
701
- err.message = resolveAssertionMessageFromError(err, artifact);
702
-
703
- const execError = new ExecutionError(
704
- err.message,
705
- {
706
- contractAddress: targetContractAddress,
707
- functionSelector,
708
- },
709
- extractCallStack(err, artifact.debug),
710
- { cause: err },
711
- );
712
- this.logger.debug(`Error executing private function ${targetContractAddress}:${functionSelector}`);
713
- throw createSimulationError(execError);
714
- });
717
+ const acirExecutionResult = await this.simulationProvider
718
+ .executeUserCircuit(acir, initialWitness, acvmCallback)
719
+ .catch((err: Error) => {
720
+ err.message = resolveAssertionMessageFromError(err, artifact);
721
+
722
+ const execError = new ExecutionError(
723
+ err.message,
724
+ {
725
+ contractAddress: targetContractAddress,
726
+ functionSelector,
727
+ },
728
+ extractCallStack(err, artifact.debug),
729
+ { cause: err },
730
+ );
731
+ this.logger.debug(`Error executing private function ${targetContractAddress}:${functionSelector}`);
732
+ throw createSimulationError(execError);
733
+ });
715
734
  const duration = timer.ms();
716
735
  const publicInputs = extractPrivateCircuitPublicInputs(artifact, acirExecutionResult.partialWitness);
717
736
 
@@ -734,24 +753,6 @@ export class TXE implements TypedOracle {
734
753
  publicInputs.privateLogs.filter(privateLog => !privateLog.isEmpty()).map(privateLog => privateLog.log),
735
754
  );
736
755
 
737
- const executionNullifiers = publicInputs.nullifiers
738
- .filter(nullifier => !nullifier.isEmpty())
739
- .map(nullifier => nullifier.value);
740
- // We inject nullifiers into siloedNullifiersFromPrivate from notifyNullifiedNote,
741
- // so top level calls to destroyNote work as expected. As such, we are certain
742
- // that we would insert duplicates if we just took the nullifiers from the public inputs and
743
- // blindly inserted them into siloedNullifiersFromPrivate. To avoid this, we extract the first
744
- // (and only the first!) duplicated nullifier from the public inputs, so we can just push
745
- // the ones that were not created by deleting a note
746
- const firstDuplicateIndexes = executionNullifiers
747
- .map((nullifier, index) => {
748
- const siloedNullifier = siloNullifier(targetContractAddress, nullifier);
749
- return this.siloedNullifiersFromPrivate.has(siloedNullifier.toString()) ? index : -1;
750
- })
751
- .filter(index => index !== -1);
752
- const nonNoteNullifiers = executionNullifiers.filter((_, index) => !firstDuplicateIndexes.includes(index));
753
- await this.addNullifiersFromPrivate(targetContractAddress, nonNoteNullifiers);
754
-
755
756
  this.setContractAddress(currentContractAddress);
756
757
  this.setMsgSender(currentMessageSender);
757
758
  this.setFunctionSelector(currentFunctionSelector);
@@ -762,7 +763,7 @@ export class TXE implements TypedOracle {
762
763
  async getInitialWitness(abi: FunctionAbi, argsHash: Fr, sideEffectCounter: number, isStaticCall: boolean) {
763
764
  const argumentsSize = countArgumentsSize(abi);
764
765
 
765
- const args = this.packedValuesCache.unpack(argsHash);
766
+ const args = this.executionCache.getPreimage(argsHash);
766
767
 
767
768
  if (args.length !== argumentsSize) {
768
769
  throw new Error('Invalid arguments size');
@@ -836,7 +837,12 @@ export class TXE implements TypedOracle {
836
837
  // When setting up a teardown call, we tell it that
837
838
  // private execution used Gas(1, 1) so it can compute a tx fee.
838
839
  const gasUsedByPrivate = isTeardown ? new Gas(1, 1) : Gas.empty();
839
- const tx = createTxForPublicCall(executionRequest, gasUsedByPrivate, isTeardown);
840
+ const tx = createTxForPublicCalls(
841
+ /*setupExecutionRequests=*/ [],
842
+ /*appExecutionRequests=*/ isTeardown ? [] : [executionRequest],
843
+ /*teardownExecutionRequests=*/ isTeardown ? executionRequest : undefined,
844
+ gasUsedByPrivate,
845
+ );
840
846
 
841
847
  const result = await simulator.simulate(tx);
842
848
 
@@ -868,8 +874,8 @@ export class TXE implements TypedOracle {
868
874
  isStaticCall,
869
875
  );
870
876
 
871
- const args = [this.functionSelector.toField(), ...this.packedValuesCache.unpack(argsHash)];
872
- const newArgsHash = this.packedValuesCache.pack(args);
877
+ const args = [this.functionSelector.toField(), ...this.executionCache.getPreimage(argsHash)];
878
+ const newArgsHash = this.executionCache.store(args);
873
879
 
874
880
  const executionResult = await this.executePublicFunction(args, callContext, isTeardown);
875
881
 
@@ -1051,4 +1057,37 @@ export class TXE implements TypedOracle {
1051
1057
 
1052
1058
  return preimage.value;
1053
1059
  }
1060
+
1061
+ /**
1062
+ * Used by contracts during execution to store arbitrary data in the local PXE database. The data is siloed/scoped
1063
+ * to a specific `contract`.
1064
+ * @param contract - The contract address to store the data under.
1065
+ * @param key - A field element representing the key to store the data under.
1066
+ * @param values - An array of field elements representing the data to store.
1067
+ */
1068
+ store(contract: AztecAddress, key: Fr, values: Fr[]): Promise<void> {
1069
+ if (!contract.equals(this.contractAddress)) {
1070
+ // TODO(#10727): instead of this check check that this.contractAddress is allowed to process notes for contract
1071
+ throw new Error(
1072
+ `Contract address ${contract} does not match the oracle's contract address ${this.contractAddress}`,
1073
+ );
1074
+ }
1075
+ return this.txeDatabase.store(this.contractAddress, key, values);
1076
+ }
1077
+
1078
+ /**
1079
+ * Used by contracts during execution to load arbitrary data from the local PXE database. The data is siloed/scoped
1080
+ * to a specific `contract`.
1081
+ * @param contract - The contract address to load the data from.
1082
+ * @param key - A field element representing the key under which to load the data..
1083
+ * @returns An array of field elements representing the stored data or `null` if no data is stored under the key.
1084
+ */
1085
+ load(contract: AztecAddress, key: Fr): Promise<Fr[] | null> {
1086
+ if (!contract.equals(this.contractAddress)) {
1087
+ // TODO(#10727): instead of this check check that this.contractAddress is allowed to process notes for contract
1088
+ this.debug(`Data not found for contract ${contract.toString()} and key ${key.toString()}`);
1089
+ return Promise.resolve(null);
1090
+ }
1091
+ return this.txeDatabase.load(this.contractAddress, key);
1092
+ }
1054
1093
  }
@@ -18,7 +18,8 @@ import { openTmpStore } from '@aztec/kv-store/lmdb';
18
18
  import { protocolContractNames } from '@aztec/protocol-contracts';
19
19
  import { getCanonicalProtocolContract } from '@aztec/protocol-contracts/bundle';
20
20
  import { enrichPublicSimulationError } from '@aztec/pxe';
21
- import { ExecutionNoteCache, PackedValuesCache, type TypedOracle } from '@aztec/simulator';
21
+ import { type TypedOracle } from '@aztec/simulator/client';
22
+ import { HashedValuesCache } from '@aztec/simulator/server';
22
23
  import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
23
24
  import { MerkleTrees } from '@aztec/world-state';
24
25
 
@@ -42,9 +43,7 @@ export class TXEService {
42
43
  static async init(logger: Logger) {
43
44
  const store = openTmpStore(true);
44
45
  const trees = await MerkleTrees.new(store, new NoopTelemetryClient(), logger);
45
- const packedValuesCache = new PackedValuesCache();
46
- const txHash = new Fr(1); // The txHash is used for computing the revertible nullifiers for non-revertible note hashes. It can be any value for testing.
47
- const noteCache = new ExecutionNoteCache(txHash);
46
+ const executionCache = new HashedValuesCache();
48
47
  const keyStore = new KeyStore(store);
49
48
  const txeDatabase = new TXEDatabase(store);
50
49
  // Register protocol contracts.
@@ -54,7 +53,7 @@ export class TXEService {
54
53
  await txeDatabase.addContractInstance(instance);
55
54
  }
56
55
  logger.debug(`TXE service initialized`);
57
- const txe = new TXE(logger, trees, packedValuesCache, noteCache, keyStore, txeDatabase);
56
+ const txe = new TXE(logger, trees, executionCache, keyStore, txeDatabase);
58
57
  const service = new TXEService(logger, txe);
59
58
  await service.advanceBlocksBy(toSingle(new Fr(1n)));
60
59
  return service;
@@ -272,25 +271,20 @@ export class TXEService {
272
271
  return toForeignCallResult([toSingle(new Fr(blockNumber))]);
273
272
  }
274
273
 
275
- async packArgumentsArray(args: ForeignCallArray) {
276
- const packed = await this.typedOracle.packArgumentsArray(fromArray(args));
277
- return toForeignCallResult([toSingle(packed)]);
278
- }
279
-
280
- async packArguments(_length: ForeignCallSingle, values: ForeignCallArray) {
281
- const packed = await this.typedOracle.packArgumentsArray(fromArray(values));
282
- return toForeignCallResult([toSingle(packed)]);
274
+ async storeArrayInExecutionCache(args: ForeignCallArray) {
275
+ const hash = await this.typedOracle.storeArrayInExecutionCache(fromArray(args));
276
+ return toForeignCallResult([toSingle(hash)]);
283
277
  }
284
278
 
285
279
  // Since the argument is a slice, noir automatically adds a length field to oracle call.
286
- async packReturns(_length: ForeignCallSingle, values: ForeignCallArray) {
287
- const packed = await this.typedOracle.packReturns(fromArray(values));
288
- return toForeignCallResult([toSingle(packed)]);
280
+ async storeInExecutionCache(_length: ForeignCallSingle, values: ForeignCallArray) {
281
+ const returnsHash = await this.typedOracle.storeInExecutionCache(fromArray(values));
282
+ return toForeignCallResult([toSingle(returnsHash)]);
289
283
  }
290
284
 
291
- async unpackReturns(returnsHash: ForeignCallSingle) {
292
- const unpacked = await this.typedOracle.unpackReturns(fromSingle(returnsHash));
293
- return toForeignCallResult([toArray(unpacked)]);
285
+ async loadFromExecutionCache(hash: ForeignCallSingle) {
286
+ const returns = await this.typedOracle.loadFromExecutionCache(fromSingle(hash));
287
+ return toForeignCallResult([toArray(returns)]);
294
288
  }
295
289
 
296
290
  // Since the argument is a slice, noir automatically adds a length field to oracle call.
@@ -334,15 +328,6 @@ export class TXEService {
334
328
  return toForeignCallResult([toArray(witness.toFields())]);
335
329
  }
336
330
 
337
- async getSiblingPath(blockNumber: ForeignCallSingle, treeId: ForeignCallSingle, leafIndex: ForeignCallSingle) {
338
- const result = await this.typedOracle.getSiblingPath(
339
- fromSingle(blockNumber).toNumber(),
340
- fromSingle(treeId).toNumber(),
341
- fromSingle(leafIndex),
342
- );
343
- return toForeignCallResult([toArray(result)]);
344
- }
345
-
346
331
  async getNotes(
347
332
  storageSlot: ForeignCallSingle,
348
333
  numSelects: ForeignCallSingle,
@@ -434,6 +419,11 @@ export class TXEService {
434
419
  return toForeignCallResult([toSingle(new Fr(0))]);
435
420
  }
436
421
 
422
+ async notifyCreatedNullifier(innerNullifier: ForeignCallSingle) {
423
+ await this.typedOracle.notifyCreatedNullifier(fromSingle(innerNullifier));
424
+ return toForeignCallResult([toSingle(new Fr(0))]);
425
+ }
426
+
437
427
  async checkNullifierExists(innerNullifier: ForeignCallSingle) {
438
428
  const exists = await this.typedOracle.checkNullifierExists(fromSingle(innerNullifier));
439
429
  return toForeignCallResult([toSingle(new Fr(exists))]);
@@ -598,6 +588,37 @@ export class TXEService {
598
588
  return toForeignCallResult([]);
599
589
  }
600
590
 
591
+ async store(contract: ForeignCallSingle, key: ForeignCallSingle, values: ForeignCallArray) {
592
+ const processedContract = AztecAddress.fromField(fromSingle(contract));
593
+ const processedKey = fromSingle(key);
594
+ const processedValues = fromArray(values);
595
+ await this.typedOracle.store(processedContract, processedKey, processedValues);
596
+ return toForeignCallResult([]);
597
+ }
598
+
599
+ /**
600
+ * Load data from pxe db.
601
+ * @param contract - The contract address.
602
+ * @param key - The key to load.
603
+ * @param tSize - The size of the serialized object to return.
604
+ * @returns The data found flag and the serialized object concatenated in one array.
605
+ */
606
+ async load(contract: ForeignCallSingle, key: ForeignCallSingle, tSize: ForeignCallSingle) {
607
+ const processedContract = AztecAddress.fromField(fromSingle(contract));
608
+ const processedKey = fromSingle(key);
609
+ const values = await this.typedOracle.load(processedContract, processedKey);
610
+ // We are going to return a Noir Option struct to represent the possibility of null values. Options are a struct
611
+ // with two fields: `some` (a boolean) and `value` (a field array in this case).
612
+ if (values === null) {
613
+ // No data was found so we set `some` to 0 and pad `value` with zeros get the correct return size.
614
+ const processedTSize = fromSingle(tSize).toNumber();
615
+ return toForeignCallResult([toSingle(new Fr(0)), toArray(Array(processedTSize).fill(new Fr(0)))]);
616
+ } else {
617
+ // Data was found so we set `some` to 1 and return it along with `value`.
618
+ return toForeignCallResult([toSingle(new Fr(1)), toArray(values)]);
619
+ }
620
+ }
621
+
601
622
  // AVM opcodes
602
623
 
603
624
  avmOpcodeEmitUnencryptedLog(_message: ForeignCallArray) {
@@ -7,7 +7,7 @@ import {
7
7
  type PublicDataTreeLeafPreimage,
8
8
  } from '@aztec/circuits.js';
9
9
  import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash';
10
- import { WorldStateDB } from '@aztec/simulator';
10
+ import { WorldStateDB } from '@aztec/simulator/server';
11
11
 
12
12
  export class TXEWorldStateDB extends WorldStateDB {
13
13
  constructor(private merkleDb: MerkleTreeWriteOperations, dataSource: ContractDataSource) {