@aztec/simulator 0.72.1 → 0.73.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.
Files changed (114) hide show
  1. package/README.md +1 -1
  2. package/dest/avm/avm_simulator.d.ts +0 -1
  3. package/dest/avm/avm_simulator.d.ts.map +1 -1
  4. package/dest/avm/avm_simulator.js +4 -19
  5. package/dest/avm/avm_tree.d.ts +9 -8
  6. package/dest/avm/avm_tree.d.ts.map +1 -1
  7. package/dest/avm/avm_tree.js +35 -30
  8. package/dest/avm/fixtures/avm_simulation_tester.d.ts +21 -0
  9. package/dest/avm/fixtures/avm_simulation_tester.d.ts.map +1 -0
  10. package/dest/avm/fixtures/avm_simulation_tester.js +74 -0
  11. package/dest/avm/fixtures/base_avm_simulation_tester.d.ts +35 -0
  12. package/dest/avm/fixtures/base_avm_simulation_tester.d.ts.map +1 -0
  13. package/dest/avm/fixtures/base_avm_simulation_tester.js +76 -0
  14. package/dest/avm/fixtures/index.d.ts +6 -2
  15. package/dest/avm/fixtures/index.d.ts.map +1 -1
  16. package/dest/avm/fixtures/index.js +28 -14
  17. package/dest/avm/fixtures/simple_contract_data_source.d.ts +31 -0
  18. package/dest/avm/fixtures/simple_contract_data_source.d.ts.map +1 -0
  19. package/dest/avm/fixtures/simple_contract_data_source.js +75 -0
  20. package/dest/avm/journal/journal.d.ts +3 -3
  21. package/dest/avm/journal/journal.d.ts.map +1 -1
  22. package/dest/avm/journal/journal.js +24 -21
  23. package/dest/avm/opcodes/accrued_substate.js +2 -2
  24. package/dest/avm/opcodes/hashing.js +2 -2
  25. package/dest/avm/test_utils.d.ts +1 -1
  26. package/dest/avm/test_utils.d.ts.map +1 -1
  27. package/dest/avm/test_utils.js +4 -3
  28. package/dest/client/client_execution_context.d.ts +1 -1
  29. package/dest/client/client_execution_context.d.ts.map +1 -1
  30. package/dest/client/client_execution_context.js +21 -18
  31. package/dest/client/execution_note_cache.d.ts +3 -3
  32. package/dest/client/execution_note_cache.d.ts.map +1 -1
  33. package/dest/client/execution_note_cache.js +10 -10
  34. package/dest/client/pick_notes.js +5 -5
  35. package/dest/client/simulator.js +4 -4
  36. package/dest/client/view_data_oracle.js +2 -2
  37. package/dest/common/hashed_values_cache.d.ts +1 -1
  38. package/dest/common/hashed_values_cache.d.ts.map +1 -1
  39. package/dest/common/hashed_values_cache.js +5 -5
  40. package/dest/providers/acvm_wasm.d.ts +2 -0
  41. package/dest/providers/acvm_wasm.d.ts.map +1 -1
  42. package/dest/providers/acvm_wasm.js +15 -2
  43. package/dest/public/execution.d.ts +1 -1
  44. package/dest/public/execution.d.ts.map +1 -1
  45. package/dest/public/execution.js +3 -3
  46. package/dest/public/fee_payment.d.ts +2 -2
  47. package/dest/public/fee_payment.d.ts.map +1 -1
  48. package/dest/public/fee_payment.js +3 -3
  49. package/dest/public/fixtures/index.d.ts +2 -37
  50. package/dest/public/fixtures/index.d.ts.map +1 -1
  51. package/dest/public/fixtures/index.js +3 -250
  52. package/dest/public/fixtures/public_tx_simulation_tester.d.ts +21 -0
  53. package/dest/public/fixtures/public_tx_simulation_tester.d.ts.map +1 -0
  54. package/dest/public/fixtures/public_tx_simulation_tester.js +89 -0
  55. package/dest/public/fixtures/utils.d.ts +17 -0
  56. package/dest/public/fixtures/utils.d.ts.map +1 -0
  57. package/dest/public/fixtures/utils.js +66 -0
  58. package/dest/public/index.d.ts +1 -1
  59. package/dest/public/index.d.ts.map +1 -1
  60. package/dest/public/index.js +2 -2
  61. package/dest/public/public_db_sources.d.ts +2 -1
  62. package/dest/public/public_db_sources.d.ts.map +1 -1
  63. package/dest/public/public_db_sources.js +27 -21
  64. package/dest/public/public_processor.d.ts +1 -1
  65. package/dest/public/public_processor.d.ts.map +1 -1
  66. package/dest/public/public_processor.js +19 -18
  67. package/dest/public/public_tx_context.d.ts +5 -5
  68. package/dest/public/public_tx_context.d.ts.map +1 -1
  69. package/dest/public/public_tx_context.js +43 -17
  70. package/dest/public/public_tx_simulator.js +7 -7
  71. package/dest/public/{enqueued_call_side_effect_trace.d.ts → side_effect_trace.d.ts} +5 -5
  72. package/dest/public/side_effect_trace.d.ts.map +1 -0
  73. package/dest/public/{enqueued_call_side_effect_trace.js → side_effect_trace.js} +7 -7
  74. package/dest/public/side_effect_trace_interface.d.ts +1 -1
  75. package/dest/public/side_effect_trace_interface.d.ts.map +1 -1
  76. package/dest/test/utils.d.ts +1 -1
  77. package/dest/test/utils.d.ts.map +1 -1
  78. package/dest/test/utils.js +3 -3
  79. package/package.json +9 -9
  80. package/src/avm/avm_simulator.ts +3 -27
  81. package/src/avm/avm_tree.ts +39 -37
  82. package/src/avm/fixtures/avm_simulation_tester.ts +105 -0
  83. package/src/avm/fixtures/base_avm_simulation_tester.ts +104 -0
  84. package/src/avm/fixtures/index.ts +46 -17
  85. package/src/avm/fixtures/simple_contract_data_source.ts +98 -0
  86. package/src/avm/journal/journal.ts +24 -21
  87. package/src/avm/opcodes/accrued_substate.ts +1 -1
  88. package/src/avm/opcodes/hashing.ts +1 -1
  89. package/src/avm/test_utils.ts +3 -4
  90. package/src/client/client_execution_context.ts +27 -21
  91. package/src/client/execution_note_cache.ts +19 -14
  92. package/src/client/pick_notes.ts +4 -4
  93. package/src/client/simulator.ts +3 -3
  94. package/src/client/view_data_oracle.ts +1 -1
  95. package/src/common/hashed_values_cache.ts +4 -4
  96. package/src/providers/acvm_wasm.ts +13 -2
  97. package/src/public/execution.ts +3 -3
  98. package/src/public/fee_payment.ts +2 -2
  99. package/src/public/fixtures/index.ts +2 -387
  100. package/src/public/fixtures/public_tx_simulation_tester.ts +174 -0
  101. package/src/public/fixtures/utils.ts +110 -0
  102. package/src/public/index.ts +1 -1
  103. package/src/public/public_db_sources.ts +31 -20
  104. package/src/public/public_processor.ts +25 -18
  105. package/src/public/public_tx_context.ts +86 -27
  106. package/src/public/public_tx_simulator.ts +6 -6
  107. package/src/public/{enqueued_call_side_effect_trace.ts → side_effect_trace.ts} +7 -7
  108. package/src/public/side_effect_trace_interface.ts +1 -1
  109. package/src/test/utils.ts +2 -2
  110. package/dest/public/enqueued_call_side_effect_trace.d.ts.map +0 -1
  111. package/dest/public/transitional_adapters.d.ts +0 -4
  112. package/dest/public/transitional_adapters.d.ts.map +0 -1
  113. package/dest/public/transitional_adapters.js +0 -29
  114. package/src/public/transitional_adapters.ts +0 -113
@@ -28,20 +28,14 @@ type OpcodeTally = {
28
28
  count: number;
29
29
  gas: Gas;
30
30
  };
31
- type PcTally = {
32
- opcode: string;
33
- count: number;
34
- gas: Gas;
35
- };
36
31
 
37
32
  export class AvmSimulator {
38
33
  private log: Logger;
39
34
  private bytecode: Buffer | undefined;
40
35
  private opcodeTallies: Map<string, OpcodeTally> = new Map();
41
- private pcTallies: Map<number, PcTally> = new Map();
42
36
 
43
37
  private tallyPrintFunction = () => {};
44
- private tallyInstructionFunction = (_a: number, _b: string, _c: Gas) => {};
38
+ private tallyInstructionFunction = (_b: string, _c: Gas) => {};
45
39
 
46
40
  // Test Purposes only: Logger will not have the proper function name. Use this constructor for testing purposes
47
41
  // only. Otherwise, use build() below.
@@ -145,7 +139,6 @@ export class AvmSimulator {
145
139
  while (!machineState.getHalted()) {
146
140
  const [instruction, bytesRead] = decodeInstructionFromBytecode(bytecode, machineState.pc, this.instructionSet);
147
141
  const instrStartGas = machineState.gasLeft; // Save gas before executing instruction (for profiling)
148
- const instrPc = machineState.pc; // Save PC before executing instruction (for profiling)
149
142
 
150
143
  this.log.trace(
151
144
  `[PC:${machineState.pc}] [IC:${instrCounter++}] ${instruction.toString()} (gasLeft l2=${
@@ -168,7 +161,7 @@ export class AvmSimulator {
168
161
  l2Gas: instrStartGas.l2Gas - machineState.l2GasLeft,
169
162
  daGas: instrStartGas.daGas - machineState.daGasLeft,
170
163
  };
171
- this.tallyInstructionFunction(instrPc, instruction.constructor.name, gasUsed);
164
+ this.tallyInstructionFunction(instruction.constructor.name, gasUsed);
172
165
 
173
166
  if (machineState.pc >= bytecode.length) {
174
167
  this.log.warn('Passed end of program');
@@ -239,18 +232,12 @@ export class AvmSimulator {
239
232
  );
240
233
  }
241
234
 
242
- private tallyInstruction(pc: number, opcode: string, gasUsed: Gas) {
235
+ private tallyInstruction(opcode: string, gasUsed: Gas) {
243
236
  const opcodeTally = this.opcodeTallies.get(opcode) || ({ count: 0, gas: { l2Gas: 0, daGas: 0 } } as OpcodeTally);
244
237
  opcodeTally.count++;
245
238
  opcodeTally.gas.l2Gas += gasUsed.l2Gas;
246
239
  opcodeTally.gas.daGas += gasUsed.daGas;
247
240
  this.opcodeTallies.set(opcode, opcodeTally);
248
-
249
- const pcTally = this.pcTallies.get(pc) || ({ opcode: opcode, count: 0, gas: { l2Gas: 0, daGas: 0 } } as PcTally);
250
- pcTally.count++;
251
- pcTally.gas.l2Gas += gasUsed.l2Gas;
252
- pcTally.gas.daGas += gasUsed.daGas;
253
- this.pcTallies.set(pc, pcTally);
254
241
  }
255
242
 
256
243
  private printOpcodeTallies() {
@@ -261,16 +248,5 @@ export class AvmSimulator {
261
248
  // NOTE: don't care to clutter the logs with DA gas for now
262
249
  this.log.debug(`${opcode} executed ${tally.count} times consuming a total of ${tally.gas.l2Gas} L2 gas`);
263
250
  }
264
-
265
- this.log.debug(`Printing tallies per PC sorted by #times each PC was executed...`);
266
- const sortedPcs = Array.from(this.pcTallies.entries())
267
- .sort((a, b) => b[1].count - a[1].count)
268
- .filter((_, i) => i < 20);
269
- for (const [pc, tally] of sortedPcs) {
270
- // NOTE: don't care to clutter the logs with DA gas for now
271
- this.log.debug(
272
- `PC:${pc} containing opcode ${tally.opcode} executed ${tally.count} times consuming a total of ${tally.gas.l2Gas} L2 gas`,
273
- );
274
- }
275
251
  }
276
252
  }
@@ -109,7 +109,7 @@ export class AvmEphemeralForest {
109
109
  */
110
110
  async getSiblingPath(treeId: MerkleTreeId, index: bigint): Promise<Fr[]> {
111
111
  const tree = this.treeMap.get(treeId)!;
112
- let path = tree.getSiblingPath(index);
112
+ let path = await tree.getSiblingPath(index);
113
113
  if (path === undefined) {
114
114
  // We dont have the sibling path in our tree - we have to get it from the DB
115
115
  path = (await this.treeDb.getSiblingPath(treeId, index)).toFields();
@@ -119,7 +119,7 @@ export class AvmEphemeralForest {
119
119
  const siblingIndex = index ^ 1n;
120
120
  const node = tree.getNode(siblingIndex, tree.depth - i);
121
121
  if (node !== undefined) {
122
- const nodeHash = tree.hashTree(node, i + 1);
122
+ const nodeHash = await tree.hashTree(node, i + 1);
123
123
  if (!nodeHash.equals(path[i])) {
124
124
  path[i] = nodeHash;
125
125
  }
@@ -138,17 +138,17 @@ export class AvmEphemeralForest {
138
138
  * @param newLeafPreimage - The preimage of the new leaf to be inserted.
139
139
  * @returns The sibling path of the new leaf (i.e. the insertion path)
140
140
  */
141
- appendIndexedTree<ID extends IndexedTreeId, T extends IndexedTreeLeafPreimage>(
141
+ async appendIndexedTree<ID extends IndexedTreeId, T extends IndexedTreeLeafPreimage>(
142
142
  treeId: ID,
143
143
  lowLeafIndex: bigint,
144
144
  lowLeafPreimage: T,
145
145
  newLeafPreimage: T,
146
- ): Fr[] {
146
+ ): Promise<Fr[]> {
147
147
  const tree = this.treeMap.get(treeId)!;
148
- const newLeaf = this.hashPreimage(newLeafPreimage);
148
+ const newLeaf = await this.hashPreimage(newLeafPreimage);
149
149
  const insertIndex = tree.leafCount;
150
150
 
151
- const lowLeaf = this.hashPreimage(lowLeafPreimage);
151
+ const lowLeaf = await this.hashPreimage(lowLeafPreimage);
152
152
  // Update the low nullifier hash
153
153
  this.setIndexedUpdates(treeId, lowLeafIndex, lowLeafPreimage);
154
154
  tree.updateLeaf(lowLeaf, lowLeafIndex);
@@ -156,7 +156,7 @@ export class AvmEphemeralForest {
156
156
  tree.appendLeaf(newLeaf);
157
157
  this.setIndexedUpdates(treeId, insertIndex, newLeafPreimage);
158
158
 
159
- return tree.getSiblingPath(insertIndex)!;
159
+ return (await tree.getSiblingPath(insertIndex))!;
160
160
  }
161
161
 
162
162
  /**
@@ -186,7 +186,7 @@ export class AvmEphemeralForest {
186
186
  const existingPublicDataSiblingPath = siblingPath;
187
187
  updatedPreimage.value = newValue;
188
188
 
189
- tree.updateLeaf(this.hashPreimage(updatedPreimage), lowLeafIndex);
189
+ tree.updateLeaf(await this.hashPreimage(updatedPreimage), lowLeafIndex);
190
190
  this.setIndexedUpdates(treeId, lowLeafIndex, updatedPreimage);
191
191
  this._updateSortedKeys(treeId, [updatedPreimage.slot], [lowLeafIndex]);
192
192
 
@@ -212,7 +212,7 @@ export class AvmEphemeralForest {
212
212
  new Fr(preimage.getNextKey()),
213
213
  preimage.getNextIndex(),
214
214
  );
215
- const insertionPath = this.appendIndexedTree(treeId, lowLeafIndex, updatedLowLeaf, newPublicDataLeaf);
215
+ const insertionPath = await this.appendIndexedTree(treeId, lowLeafIndex, updatedLowLeaf, newPublicDataLeaf);
216
216
 
217
217
  // Even though the low leaf key is not updated, we still need to update the sorted keys in case we have
218
218
  // not seen the low leaf before
@@ -281,7 +281,7 @@ export class AvmEphemeralForest {
281
281
  updatedLowNullifier.nextIndex = insertionIndex;
282
282
 
283
283
  const newNullifierLeaf = new NullifierLeafPreimage(nullifier, preimage.nextNullifier, preimage.nextIndex);
284
- const insertionPath = this.appendIndexedTree(treeId, index, updatedLowNullifier, newNullifierLeaf);
284
+ const insertionPath = await this.appendIndexedTree(treeId, index, updatedLowNullifier, newNullifierLeaf);
285
285
 
286
286
  // Even though the low nullifier key is not updated, we still need to update the sorted keys in case we have
287
287
  // not seen the low nullifier before
@@ -308,11 +308,11 @@ export class AvmEphemeralForest {
308
308
  * @param value - The note hash to be appended
309
309
  * @returns The insertion result which contains the insertion path
310
310
  */
311
- appendNoteHash(noteHash: Fr): Fr[] {
311
+ async appendNoteHash(noteHash: Fr): Promise<Fr[]> {
312
312
  const tree = this.treeMap.get(MerkleTreeId.NOTE_HASH_TREE)!;
313
313
  tree.appendLeaf(noteHash);
314
314
  // We use leafCount - 1 here because we would have just appended a leaf
315
- const insertionPath = tree.getSiblingPath(tree.leafCount - 1n);
315
+ const insertionPath = await tree.getSiblingPath(tree.leafCount - 1n);
316
316
  return insertionPath!;
317
317
  }
318
318
 
@@ -484,14 +484,15 @@ export class AvmEphemeralForest {
484
484
  /**
485
485
  * This hashes the preimage to a field element
486
486
  */
487
- hashPreimage<T extends TreeLeafPreimage>(preimage: T): Fr {
487
+ hashPreimage<T extends TreeLeafPreimage>(preimage: T): Promise<Fr> {
488
488
  const input = preimage.toHashInputs().map(x => Fr.fromBuffer(x));
489
489
  return poseidon2Hash(input);
490
490
  }
491
491
 
492
- getTreeSnapshot(id: MerkleTreeId): AppendOnlyTreeSnapshot {
492
+ async getTreeSnapshot(id: MerkleTreeId): Promise<AppendOnlyTreeSnapshot> {
493
493
  const tree = this.treeMap.get(id)!;
494
- return new AppendOnlyTreeSnapshot(tree.getRoot(), Number(tree.leafCount));
494
+ const root = await tree.getRoot();
495
+ return new AppendOnlyTreeSnapshot(root, Number(tree.leafCount));
495
496
  }
496
497
  }
497
498
 
@@ -551,19 +552,10 @@ const Leaf = (value: Fr): Leaf => ({
551
552
  */
552
553
  export class EphemeralAvmTree {
553
554
  private tree: Tree;
554
- private readonly zeroHashes: Fr[];
555
555
  public frontier: Fr[];
556
556
 
557
- private constructor(public leafCount: bigint, public depth: number) {
558
- let zeroHash = Fr.zero();
559
- // Can probably cache this elsewhere
560
- const zeroHashes = [];
561
- for (let i = 0; i < this.depth; i++) {
562
- zeroHashes.push(zeroHash);
563
- zeroHash = poseidon2Hash([zeroHash, zeroHash]);
564
- }
565
- this.tree = Leaf(zeroHash);
566
- this.zeroHashes = zeroHashes;
557
+ private constructor(private root: Leaf, public leafCount: bigint, public depth: number, private zeroHashes: Fr[]) {
558
+ this.tree = root;
567
559
  this.frontier = [];
568
560
  }
569
561
 
@@ -573,7 +565,14 @@ export class EphemeralAvmTree {
573
565
  treeDb: MerkleTreeReadOperations,
574
566
  merkleId: MerkleTreeId,
575
567
  ): Promise<EphemeralAvmTree> {
576
- const tree = new EphemeralAvmTree(forkedLeafCount, depth);
568
+ let zeroHash = Fr.zero();
569
+ // Can probably cache this elsewhere
570
+ const zeroHashes = [];
571
+ for (let i = 0; i < depth; i++) {
572
+ zeroHashes.push(zeroHash);
573
+ zeroHash = await poseidon2Hash([zeroHash, zeroHash]);
574
+ }
575
+ const tree = new EphemeralAvmTree(Leaf(zeroHash), forkedLeafCount, depth, zeroHashes);
577
576
  await tree.initializeFrontier(treeDb, merkleId);
578
577
  return tree;
579
578
  }
@@ -604,10 +603,10 @@ export class EphemeralAvmTree {
604
603
  * @param index - The index of the leaf for which a sibling path should be returned.
605
604
  * @returns The sibling path of the leaf, can fail if the path is not found
606
605
  */
607
- getSiblingPath(index: bigint): Fr[] | undefined {
606
+ async getSiblingPath(index: bigint): Promise<Fr[] | undefined> {
608
607
  const searchPath = this._derivePathLE(index);
609
608
  // Handle cases where we error out
610
- const { path, status } = this._getSiblingPath(searchPath, this.tree, []);
609
+ const { path, status } = await this._getSiblingPath(searchPath, this.tree, []);
611
610
  if (status === SiblingStatus.ERROR) {
612
611
  return undefined;
613
612
  }
@@ -695,7 +694,7 @@ export class EphemeralAvmTree {
695
694
  /**
696
695
  * Computes the root of the tree
697
696
  */
698
- public getRoot(): Fr {
697
+ public getRoot(): Promise<Fr> {
699
698
  return this.hashTree(this.tree, this.depth);
700
699
  }
701
700
 
@@ -704,10 +703,13 @@ export class EphemeralAvmTree {
704
703
  * @param tree - The tree to be hashed
705
704
  * @param depth - The depth of the tree
706
705
  */
707
- public hashTree(tree: Tree, depth: number): Fr {
706
+ public async hashTree(tree: Tree, depth: number): Promise<Fr> {
708
707
  switch (tree.tag) {
709
708
  case TreeType.NODE: {
710
- return poseidon2Hash([this.hashTree(tree.leftTree, depth - 1), this.hashTree(tree.rightTree, depth - 1)]);
709
+ return poseidon2Hash([
710
+ await this.hashTree(tree.leftTree, depth - 1),
711
+ await this.hashTree(tree.rightTree, depth - 1),
712
+ ]);
711
713
  }
712
714
  case TreeType.LEAF: {
713
715
  return tree.value;
@@ -807,7 +809,7 @@ export class EphemeralAvmTree {
807
809
  * @param tree - The current tree
808
810
  * @param acc - The accumulated sibling path
809
811
  */
810
- private _getSiblingPath(searchPath: number[], tree: Tree, acc: Fr[]): AccumulatedSiblingPath {
812
+ private async _getSiblingPath(searchPath: number[], tree: Tree, acc: Fr[]): Promise<AccumulatedSiblingPath> {
811
813
  // If we have reached the end of the path, we should be at a leaf or empty node
812
814
  // If it is a leaf, we check if the value is equal to the leaf value
813
815
  // If it is empty we check if the value is equal to zero
@@ -824,15 +826,15 @@ export class EphemeralAvmTree {
824
826
  case TreeType.NODE: {
825
827
  // Look at the next element of the path to decided if we go left or right, note this mutates!
826
828
  return searchPath.pop() === 0
827
- ? this._getSiblingPath(
829
+ ? await this._getSiblingPath(
828
830
  searchPath,
829
831
  tree.leftTree,
830
- [this.hashTree(tree.rightTree, searchPath.length)].concat(acc),
832
+ [await this.hashTree(tree.rightTree, searchPath.length)].concat(acc),
831
833
  )
832
- : this._getSiblingPath(
834
+ : await this._getSiblingPath(
833
835
  searchPath,
834
836
  tree.rightTree,
835
- [this.hashTree(tree.leftTree, searchPath.length)].concat(acc),
837
+ [await this.hashTree(tree.leftTree, searchPath.length)].concat(acc),
836
838
  );
837
839
  }
838
840
  // In these two situations we are exploring a subtree we dont have information about
@@ -0,0 +1,105 @@
1
+ import { type MerkleTreeWriteOperations } from '@aztec/circuit-types';
2
+ import { GasFees, GlobalVariables } from '@aztec/circuits.js';
3
+ import { encodeArguments } from '@aztec/foundation/abi';
4
+ import { type AztecAddress } from '@aztec/foundation/aztec-address';
5
+ import { Fr } from '@aztec/foundation/fields';
6
+ import { openTmpStore } from '@aztec/kv-store/lmdb';
7
+ import { MerkleTrees } from '@aztec/world-state';
8
+
9
+ import { type AvmContractCallResult } from '../../avm/avm_contract_call_result.js';
10
+ import {
11
+ getContractFunctionArtifact,
12
+ getFunctionSelector,
13
+ initContext,
14
+ initExecutionEnvironment,
15
+ resolveContractAssertionMessage,
16
+ } from '../../avm/fixtures/index.js';
17
+ import { WorldStateDB } from '../../public/public_db_sources.js';
18
+ import { SideEffectTrace } from '../../public/side_effect_trace.js';
19
+ import { AvmPersistableStateManager, AvmSimulator } from '../../server.js';
20
+ import { BaseAvmSimulationTester } from './base_avm_simulation_tester.js';
21
+ import { SimpleContractDataSource } from './simple_contract_data_source.js';
22
+
23
+ const TIMESTAMP = new Fr(99833);
24
+ const DEFAULT_GAS_FEES = new GasFees(2, 3);
25
+
26
+ /**
27
+ * A test class that extends the BaseAvmSimulationTester to enable real-app testing of the core AvmSimulator.
28
+ * It provides an interface for simulating one top-level call at a time and maintains state between
29
+ * subsequent top-level calls.
30
+ */
31
+ export class AvmSimulationTester extends BaseAvmSimulationTester {
32
+ constructor(
33
+ contractDataSource: SimpleContractDataSource,
34
+ merkleTrees: MerkleTreeWriteOperations,
35
+ skipContractDeployments = false,
36
+ private stateManager: AvmPersistableStateManager,
37
+ ) {
38
+ super(contractDataSource, merkleTrees, skipContractDeployments);
39
+ }
40
+
41
+ static async create(skipContractDeployments = false): Promise<AvmSimulationTester> {
42
+ const contractDataSource = new SimpleContractDataSource();
43
+ const merkleTrees = await (await MerkleTrees.new(openTmpStore())).fork();
44
+ const worldStateDB = new WorldStateDB(merkleTrees, contractDataSource);
45
+ const trace = new SideEffectTrace();
46
+ const firstNullifier = new Fr(420000);
47
+ // FIXME: merkle ops should work, but I'm seeing frequent (but inconsistent) bytecode retrieval
48
+ // failures on 2nd call to simulateCall with merkle ops on
49
+ const stateManager = await AvmPersistableStateManager.create(
50
+ worldStateDB,
51
+ trace,
52
+ /*doMerkleOperations=*/ false,
53
+ firstNullifier,
54
+ );
55
+ return new AvmSimulationTester(contractDataSource, merkleTrees, skipContractDeployments, stateManager);
56
+ }
57
+
58
+ /**
59
+ * Simulate a top-level contract call.
60
+ */
61
+ async simulateCall(
62
+ sender: AztecAddress,
63
+ address: AztecAddress,
64
+ fnName: string,
65
+ args: any[],
66
+ isStaticCall = false,
67
+ ): Promise<AvmContractCallResult> {
68
+ const contractArtifact = await this.contractDataSource.getContractArtifact(address);
69
+ if (!contractArtifact) {
70
+ throw new Error(`Contract not found at address: ${address}`);
71
+ }
72
+ const fnSelector = await getFunctionSelector(fnName, contractArtifact);
73
+ const fnAbi = getContractFunctionArtifact(fnName, contractArtifact);
74
+ const encodedArgs = encodeArguments(fnAbi!, args);
75
+ const calldata = [fnSelector.toField(), ...encodedArgs];
76
+
77
+ const globals = GlobalVariables.empty();
78
+ globals.timestamp = TIMESTAMP;
79
+ globals.gasFees = DEFAULT_GAS_FEES;
80
+
81
+ const environment = initExecutionEnvironment({
82
+ calldata,
83
+ globals,
84
+ address,
85
+ sender,
86
+ isStaticCall,
87
+ });
88
+ const persistableState = this.stateManager.fork();
89
+ const context = initContext({ env: environment, persistableState });
90
+
91
+ // First we simulate (though it's not needed in this simple case).
92
+ const simulator = new AvmSimulator(context);
93
+ const result = await simulator.execute();
94
+ if (result.reverted) {
95
+ this.logger.error(`Error in ${fnName}:`);
96
+ this.logger.error(
97
+ resolveContractAssertionMessage(fnName, result.revertReason!, result.output, contractArtifact)!,
98
+ );
99
+ } else {
100
+ this.logger.info(`Simulation of function ${fnName} succeeded!`);
101
+ this.stateManager.merge(persistableState);
102
+ }
103
+ return result;
104
+ }
105
+ }
@@ -0,0 +1,104 @@
1
+ import { MerkleTreeId, type MerkleTreeWriteOperations } from '@aztec/circuit-types';
2
+ import {
3
+ type ContractClassPublic,
4
+ type ContractInstanceWithAddress,
5
+ DEPLOYER_CONTRACT_ADDRESS,
6
+ PUBLIC_DISPATCH_SELECTOR,
7
+ PublicDataWrite,
8
+ computeInitializationHash,
9
+ } from '@aztec/circuits.js';
10
+ import { computePublicDataTreeLeafSlot, siloNullifier } from '@aztec/circuits.js/hash';
11
+ import { makeContractClassPublic, makeContractInstanceFromClassId } from '@aztec/circuits.js/testing';
12
+ import { type ContractArtifact, FunctionSelector } from '@aztec/foundation/abi';
13
+ import { AztecAddress } from '@aztec/foundation/aztec-address';
14
+ import { type Fr } from '@aztec/foundation/fields';
15
+ import { createLogger } from '@aztec/foundation/log';
16
+ import { ProtocolContractAddress } from '@aztec/protocol-contracts';
17
+
18
+ import { computeFeePayerBalanceStorageSlot } from '../../server.js';
19
+ import { PUBLIC_DISPATCH_FN_NAME, getContractFunctionArtifact } from './index.js';
20
+ import { type SimpleContractDataSource } from './simple_contract_data_source.js';
21
+
22
+ /**
23
+ * An abstract test class that enables tests of real apps in the AVM without requiring e2e tests.
24
+ * It enables this by letting us (1) perform pseudo-contract-deployments (and registrations)
25
+ * that trigger merkle tree operations and (2) maintain a contract data source to store
26
+ * and retrieve contract classes and instances.
27
+ *
28
+ * This class is meant to be extended when writing tests for a specific simulation interface.
29
+ * For example, has been extended for testing of the core AvmSimulator, and again for the PublicTxSimulator,
30
+ * both of which benefit from such pseudo-deployments by populating merkle trees and a contract data source
31
+ * with contract information.
32
+ */
33
+ export abstract class BaseAvmSimulationTester {
34
+ public logger = createLogger('avm-simulation-tester');
35
+
36
+ constructor(
37
+ public contractDataSource: SimpleContractDataSource,
38
+ public merkleTrees: MerkleTreeWriteOperations,
39
+ /* May want to skip contract deployment tree ops to test failed contract address nullifier checks on CALL */
40
+ private skipContractDeployments = false,
41
+ ) {}
42
+
43
+ async setFeePayerBalance(feePayer: AztecAddress, balance: Fr) {
44
+ const feeJuiceAddress = ProtocolContractAddress.FeeJuice;
45
+ const balanceSlot = await computeFeePayerBalanceStorageSlot(feePayer);
46
+ await this.setPublicStorage(feeJuiceAddress, balanceSlot, balance);
47
+ }
48
+
49
+ async setPublicStorage(address: AztecAddress, slot: Fr, value: Fr) {
50
+ const leafSlot = await computePublicDataTreeLeafSlot(address, slot);
51
+ // get existing preimage
52
+ const publicDataWrite = new PublicDataWrite(leafSlot, value);
53
+ await this.merkleTrees.batchInsert(MerkleTreeId.PUBLIC_DATA_TREE, [publicDataWrite.toBuffer()], 0);
54
+ }
55
+
56
+ /**
57
+ * Derive the contract class and instance with some seed.
58
+ * Add both to the contract data source along with the contract artifact.
59
+ */
60
+ async registerAndDeployContract(
61
+ constructorArgs: any[],
62
+ deployer: AztecAddress,
63
+ contractArtifact: ContractArtifact,
64
+ seed = 0,
65
+ ): Promise<ContractInstanceWithAddress> {
66
+ const bytecode = getContractFunctionArtifact(PUBLIC_DISPATCH_FN_NAME, contractArtifact)!.bytecode;
67
+ const contractClass = await makeContractClassPublic(
68
+ seed,
69
+ /*publicDispatchFunction=*/ { bytecode, selector: new FunctionSelector(PUBLIC_DISPATCH_SELECTOR) },
70
+ );
71
+
72
+ const constructorAbi = getContractFunctionArtifact('constructor', contractArtifact);
73
+ const initializationHash = await computeInitializationHash(constructorAbi, constructorArgs);
74
+ const contractInstance = await makeContractInstanceFromClassId(contractClass.id, seed, {
75
+ deployer,
76
+ initializationHash,
77
+ });
78
+
79
+ await this.addContractClass(contractClass, contractArtifact);
80
+ await this.addContractInstance(contractInstance);
81
+ return contractInstance;
82
+ }
83
+
84
+ getFirstContractInstance(): ContractInstanceWithAddress {
85
+ return this.contractDataSource.getFirstContractInstance();
86
+ }
87
+
88
+ addContractClass(contractClass: ContractClassPublic, contractArtifact: ContractArtifact): Promise<void> {
89
+ this.logger.debug(`Adding contract class with Id ${contractClass.id}`);
90
+ this.contractDataSource.addContractArtifact(contractClass.id, contractArtifact);
91
+ return this.contractDataSource.addContractClass(contractClass);
92
+ }
93
+
94
+ async addContractInstance(contractInstance: ContractInstanceWithAddress) {
95
+ if (!this.skipContractDeployments) {
96
+ const contractAddressNullifier = await siloNullifier(
97
+ AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
98
+ contractInstance.address.toField(),
99
+ );
100
+ await this.merkleTrees.batchInsert(MerkleTreeId.NULLIFIER_TREE, [contractAddressNullifier.toBuffer()], 0);
101
+ }
102
+ await this.contractDataSource.addContractInstance(contractInstance);
103
+ }
104
+ }
@@ -1,6 +1,6 @@
1
1
  import { isNoirCallStackUnresolved } from '@aztec/circuit-types';
2
2
  import { GasFees, GlobalVariables, MAX_L2_GAS_PER_TX_PUBLIC_PORTION } from '@aztec/circuits.js';
3
- import { type FunctionArtifact, FunctionSelector } from '@aztec/foundation/abi';
3
+ import { type ContractArtifact, type FunctionArtifact, FunctionSelector } from '@aztec/foundation/abi';
4
4
  import { AztecAddress } from '@aztec/foundation/aztec-address';
5
5
  import { EthAddress } from '@aztec/foundation/eth-address';
6
6
  import { Fr } from '@aztec/foundation/fields';
@@ -23,6 +23,8 @@ import { AvmPersistableStateManager } from '../journal/journal.js';
23
23
  import { NullifierManager } from '../journal/nullifiers.js';
24
24
  import { PublicStorage } from '../journal/public_storage.js';
25
25
 
26
+ export const PUBLIC_DISPATCH_FN_NAME = 'public_dispatch';
27
+
26
28
  /**
27
29
  * Create a new AVM context with default values.
28
30
  */
@@ -124,15 +126,51 @@ export function randomMemoryFields(length: number): Field[] {
124
126
  return [...Array(length)].map(_ => new Field(Fr.random()));
125
127
  }
126
128
 
127
- export function getAvmTestContractFunctionSelector(functionName: string): FunctionSelector {
128
- const artifact = AvmTestContractArtifact.functions.find(f => f.name === functionName)!;
129
- assert(!!artifact, `Function ${functionName} not found in AvmTestContractArtifact`);
130
- const params = artifact.parameters;
131
- return FunctionSelector.fromNameAndParameters(artifact.name, params);
129
+ export function getFunctionSelector(
130
+ functionName: string,
131
+ contractArtifact: ContractArtifact,
132
+ ): Promise<FunctionSelector> {
133
+ const fnArtifact = contractArtifact.functions.find(f => f.name === functionName)!;
134
+ assert(!!fnArtifact, `Function ${functionName} not found in ${contractArtifact.name}`);
135
+ const params = fnArtifact.parameters;
136
+ return FunctionSelector.fromNameAndParameters(fnArtifact.name, params);
137
+ }
138
+
139
+ export function getContractFunctionArtifact(
140
+ functionName: string,
141
+ contractArtifact: ContractArtifact,
142
+ ): FunctionArtifact | undefined {
143
+ const artifact = contractArtifact.functions.find(f => f.name === functionName)!;
144
+ if (!artifact) {
145
+ return undefined;
146
+ }
147
+ return artifact;
148
+ }
149
+
150
+ export function resolveContractAssertionMessage(
151
+ functionName: string,
152
+ revertReason: AvmRevertReason,
153
+ output: Fr[],
154
+ contractArtifact: ContractArtifact,
155
+ ): string | undefined {
156
+ traverseCauseChain(revertReason, cause => {
157
+ revertReason = cause as AvmRevertReason;
158
+ });
159
+
160
+ const functionArtifact = contractArtifact.functions.find(f => f.name === functionName);
161
+ if (!functionArtifact || !revertReason.noirCallStack || !isNoirCallStackUnresolved(revertReason.noirCallStack)) {
162
+ return undefined;
163
+ }
164
+
165
+ return resolveAssertionMessageFromRevertData(output, functionArtifact);
166
+ }
167
+
168
+ export function getAvmTestContractFunctionSelector(functionName: string): Promise<FunctionSelector> {
169
+ return getFunctionSelector(functionName, AvmTestContractArtifact);
132
170
  }
133
171
 
134
172
  export function getAvmTestContractArtifact(functionName: string): FunctionArtifact {
135
- const artifact = AvmTestContractArtifact.functions.find(f => f.name === functionName)!;
173
+ const artifact = getContractFunctionArtifact(functionName, AvmTestContractArtifact);
136
174
  assert(
137
175
  !!artifact?.bytecode,
138
176
  `No bytecode found for function ${functionName}. Try re-running bootstrap.sh on the repository root.`,
@@ -150,14 +188,5 @@ export function resolveAvmTestContractAssertionMessage(
150
188
  revertReason: AvmRevertReason,
151
189
  output: Fr[],
152
190
  ): string | undefined {
153
- traverseCauseChain(revertReason, cause => {
154
- revertReason = cause as AvmRevertReason;
155
- });
156
-
157
- const functionArtifact = AvmTestContractArtifact.functions.find(f => f.name === functionName);
158
- if (!functionArtifact || !revertReason.noirCallStack || !isNoirCallStackUnresolved(revertReason.noirCallStack)) {
159
- return undefined;
160
- }
161
-
162
- return resolveAssertionMessageFromRevertData(output, functionArtifact);
191
+ return resolveContractAssertionMessage(functionName, revertReason, output, AvmTestContractArtifact);
163
192
  }