@aztec/simulator 0.72.1 → 0.74.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 (115) 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 +73 -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 +10 -2
  15. package/dest/avm/fixtures/index.d.ts.map +1 -1
  16. package/dest/avm/fixtures/index.js +45 -5
  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 +88 -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} +6 -26
  72. package/dest/public/side_effect_trace.d.ts.map +1 -0
  73. package/dest/public/side_effect_trace.js +316 -0
  74. package/dest/public/side_effect_trace_interface.d.ts +2 -19
  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 +104 -0
  83. package/src/avm/fixtures/base_avm_simulation_tester.ts +104 -0
  84. package/src/avm/fixtures/index.ts +74 -6
  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 +173 -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} +8 -49
  108. package/src/public/side_effect_trace_interface.ts +1 -22
  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/enqueued_call_side_effect_trace.js +0 -350
  112. package/dest/public/transitional_adapters.d.ts +0 -4
  113. package/dest/public/transitional_adapters.d.ts.map +0 -1
  114. package/dest/public/transitional_adapters.js +0 -29
  115. package/src/public/transitional_adapters.ts +0 -113
@@ -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,104 @@
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 { NativeWorldStateService } from '@aztec/world-state';
7
+
8
+ import { type AvmContractCallResult } from '../../avm/avm_contract_call_result.js';
9
+ import {
10
+ getContractFunctionArtifact,
11
+ getFunctionSelector,
12
+ initContext,
13
+ initExecutionEnvironment,
14
+ resolveContractAssertionMessage,
15
+ } from '../../avm/fixtures/index.js';
16
+ import { WorldStateDB } from '../../public/public_db_sources.js';
17
+ import { SideEffectTrace } from '../../public/side_effect_trace.js';
18
+ import { AvmPersistableStateManager, AvmSimulator } from '../../server.js';
19
+ import { BaseAvmSimulationTester } from './base_avm_simulation_tester.js';
20
+ import { SimpleContractDataSource } from './simple_contract_data_source.js';
21
+
22
+ const TIMESTAMP = new Fr(99833);
23
+ const DEFAULT_GAS_FEES = new GasFees(2, 3);
24
+
25
+ /**
26
+ * A test class that extends the BaseAvmSimulationTester to enable real-app testing of the core AvmSimulator.
27
+ * It provides an interface for simulating one top-level call at a time and maintains state between
28
+ * subsequent top-level calls.
29
+ */
30
+ export class AvmSimulationTester extends BaseAvmSimulationTester {
31
+ constructor(
32
+ contractDataSource: SimpleContractDataSource,
33
+ merkleTrees: MerkleTreeWriteOperations,
34
+ skipContractDeployments = false,
35
+ private stateManager: AvmPersistableStateManager,
36
+ ) {
37
+ super(contractDataSource, merkleTrees, skipContractDeployments);
38
+ }
39
+
40
+ static async create(skipContractDeployments = false): Promise<AvmSimulationTester> {
41
+ const contractDataSource = new SimpleContractDataSource();
42
+ const merkleTrees = await (await NativeWorldStateService.tmp()).fork();
43
+ const worldStateDB = new WorldStateDB(merkleTrees, contractDataSource);
44
+ const trace = new SideEffectTrace();
45
+ const firstNullifier = new Fr(420000);
46
+ // FIXME: merkle ops should work, but I'm seeing frequent (but inconsistent) bytecode retrieval
47
+ // failures on 2nd call to simulateCall with merkle ops on
48
+ const stateManager = await AvmPersistableStateManager.create(
49
+ worldStateDB,
50
+ trace,
51
+ /*doMerkleOperations=*/ false,
52
+ firstNullifier,
53
+ );
54
+ return new AvmSimulationTester(contractDataSource, merkleTrees, skipContractDeployments, stateManager);
55
+ }
56
+
57
+ /**
58
+ * Simulate a top-level contract call.
59
+ */
60
+ async simulateCall(
61
+ sender: AztecAddress,
62
+ address: AztecAddress,
63
+ fnName: string,
64
+ args: any[],
65
+ isStaticCall = false,
66
+ ): Promise<AvmContractCallResult> {
67
+ const contractArtifact = await this.contractDataSource.getContractArtifact(address);
68
+ if (!contractArtifact) {
69
+ throw new Error(`Contract not found at address: ${address}`);
70
+ }
71
+ const fnSelector = await getFunctionSelector(fnName, contractArtifact);
72
+ const fnAbi = getContractFunctionArtifact(fnName, contractArtifact);
73
+ const encodedArgs = encodeArguments(fnAbi!, args);
74
+ const calldata = [fnSelector.toField(), ...encodedArgs];
75
+
76
+ const globals = GlobalVariables.empty();
77
+ globals.timestamp = TIMESTAMP;
78
+ globals.gasFees = DEFAULT_GAS_FEES;
79
+
80
+ const environment = initExecutionEnvironment({
81
+ calldata,
82
+ globals,
83
+ address,
84
+ sender,
85
+ isStaticCall,
86
+ });
87
+ const persistableState = this.stateManager.fork();
88
+ const context = initContext({ env: environment, persistableState });
89
+
90
+ // First we simulate (though it's not needed in this simple case).
91
+ const simulator = new AvmSimulator(context);
92
+ const result = await simulator.execute();
93
+ if (result.reverted) {
94
+ this.logger.error(`Error in ${fnName}:`);
95
+ this.logger.error(
96
+ resolveContractAssertionMessage(fnName, result.revertReason!, result.output, contractArtifact)!,
97
+ );
98
+ } else {
99
+ this.logger.info(`Simulation of function ${fnName} succeeded!`);
100
+ this.stateManager.merge(persistableState);
101
+ }
102
+ return result;
103
+ }
104
+ }
@@ -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,9 +1,10 @@
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';
7
+ import { AvmGadgetsTestContractArtifact } from '@aztec/noir-contracts.js/AvmGadgetsTest';
7
8
  import { AvmTestContractArtifact } from '@aztec/noir-contracts.js/AvmTest';
8
9
 
9
10
  import { strict as assert } from 'assert';
@@ -23,6 +24,8 @@ import { AvmPersistableStateManager } from '../journal/journal.js';
23
24
  import { NullifierManager } from '../journal/nullifiers.js';
24
25
  import { PublicStorage } from '../journal/public_storage.js';
25
26
 
27
+ export const PUBLIC_DISPATCH_FN_NAME = 'public_dispatch';
28
+
26
29
  /**
27
30
  * Create a new AVM context with default values.
28
31
  */
@@ -124,15 +127,67 @@ export function randomMemoryFields(length: number): Field[] {
124
127
  return [...Array(length)].map(_ => new Field(Fr.random()));
125
128
  }
126
129
 
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
+ export function getFunctionSelector(
131
+ functionName: string,
132
+ contractArtifact: ContractArtifact,
133
+ ): Promise<FunctionSelector> {
134
+ const fnArtifact = contractArtifact.functions.find(f => f.name === functionName)!;
135
+ assert(!!fnArtifact, `Function ${functionName} not found in ${contractArtifact.name}`);
136
+ const params = fnArtifact.parameters;
137
+ return FunctionSelector.fromNameAndParameters(fnArtifact.name, params);
138
+ }
139
+
140
+ export function getContractFunctionArtifact(
141
+ functionName: string,
142
+ contractArtifact: ContractArtifact,
143
+ ): FunctionArtifact | undefined {
144
+ const artifact = contractArtifact.functions.find(f => f.name === functionName)!;
145
+ if (!artifact) {
146
+ return undefined;
147
+ }
148
+ return artifact;
149
+ }
150
+
151
+ export function resolveContractAssertionMessage(
152
+ functionName: string,
153
+ revertReason: AvmRevertReason,
154
+ output: Fr[],
155
+ contractArtifact: ContractArtifact,
156
+ ): string | undefined {
157
+ traverseCauseChain(revertReason, cause => {
158
+ revertReason = cause as AvmRevertReason;
159
+ });
160
+
161
+ const functionArtifact = contractArtifact.functions.find(f => f.name === functionName);
162
+ if (!functionArtifact || !revertReason.noirCallStack || !isNoirCallStackUnresolved(revertReason.noirCallStack)) {
163
+ return undefined;
164
+ }
165
+
166
+ return resolveAssertionMessageFromRevertData(output, functionArtifact);
167
+ }
168
+
169
+ export function getAvmTestContractFunctionSelector(functionName: string): Promise<FunctionSelector> {
170
+ return getFunctionSelector(functionName, AvmTestContractArtifact);
171
+ }
172
+
173
+ export function getAvmGadgetsTestContractFunctionSelector(functionName: string): Promise<FunctionSelector> {
174
+ const artifact = AvmGadgetsTestContractArtifact.functions.find(f => f.name === functionName)!;
175
+ assert(!!artifact, `Function ${functionName} not found in AvmGadgetsTestContractArtifact`);
130
176
  const params = artifact.parameters;
131
177
  return FunctionSelector.fromNameAndParameters(artifact.name, params);
132
178
  }
133
179
 
134
180
  export function getAvmTestContractArtifact(functionName: string): FunctionArtifact {
135
- const artifact = AvmTestContractArtifact.functions.find(f => f.name === functionName)!;
181
+ const artifact = getContractFunctionArtifact(functionName, AvmTestContractArtifact);
182
+ assert(
183
+ !!artifact?.bytecode,
184
+ `No bytecode found for function ${functionName}. Try re-running bootstrap.sh on the repository root.`,
185
+ );
186
+ return artifact;
187
+ }
188
+
189
+ export function getAvmGadgetsTestContractArtifact(functionName: string): FunctionArtifact {
190
+ const artifact = AvmGadgetsTestContractArtifact.functions.find(f => f.name === functionName)!;
136
191
  assert(
137
192
  !!artifact?.bytecode,
138
193
  `No bytecode found for function ${functionName}. Try re-running bootstrap.sh on the repository root.`,
@@ -145,16 +200,29 @@ export function getAvmTestContractBytecode(functionName: string): Buffer {
145
200
  return artifact.bytecode;
146
201
  }
147
202
 
203
+ export function getAvmGadgetsTestContractBytecode(functionName: string): Buffer {
204
+ const artifact = getAvmGadgetsTestContractArtifact(functionName);
205
+ return artifact.bytecode;
206
+ }
207
+
148
208
  export function resolveAvmTestContractAssertionMessage(
149
209
  functionName: string,
150
210
  revertReason: AvmRevertReason,
151
211
  output: Fr[],
212
+ ): string | undefined {
213
+ return resolveContractAssertionMessage(functionName, revertReason, output, AvmTestContractArtifact);
214
+ }
215
+
216
+ export function resolveAvmGadgetsTestContractAssertionMessage(
217
+ functionName: string,
218
+ revertReason: AvmRevertReason,
219
+ output: Fr[],
152
220
  ): string | undefined {
153
221
  traverseCauseChain(revertReason, cause => {
154
222
  revertReason = cause as AvmRevertReason;
155
223
  });
156
224
 
157
- const functionArtifact = AvmTestContractArtifact.functions.find(f => f.name === functionName);
225
+ const functionArtifact = AvmGadgetsTestContractArtifact.functions.find(f => f.name === functionName);
158
226
  if (!functionArtifact || !revertReason.noirCallStack || !isNoirCallStackUnresolved(revertReason.noirCallStack)) {
159
227
  return undefined;
160
228
  }
@@ -0,0 +1,98 @@
1
+ import {
2
+ type ContractClassPublic,
3
+ type ContractDataSource,
4
+ type ContractInstanceWithAddress,
5
+ type FunctionSelector,
6
+ type PublicFunction,
7
+ computePublicBytecodeCommitment,
8
+ } from '@aztec/circuits.js';
9
+ import { type ContractArtifact } from '@aztec/foundation/abi';
10
+ import { type AztecAddress } from '@aztec/foundation/aztec-address';
11
+ import { type Fr } from '@aztec/foundation/fields';
12
+ import { createLogger } from '@aztec/foundation/log';
13
+
14
+ import { PUBLIC_DISPATCH_FN_NAME } from './index.js';
15
+
16
+ /**
17
+ * This class is used during public/avm testing to function as a database of
18
+ * contract contract classes and instances. Tests can populate it with classes
19
+ * and instances and then probe it via the ContractDataSource interface.
20
+ *
21
+ * This class does not include any real merkle trees & merkle operations.
22
+ */
23
+ export class SimpleContractDataSource implements ContractDataSource {
24
+ public logger = createLogger('simple-contract-data-source');
25
+
26
+ // maps contract class ID to class
27
+ private contractClasses: Map<string, ContractClassPublic> = new Map();
28
+ // maps contract instance address to instance
29
+ private contractInstances: Map<string, ContractInstanceWithAddress> = new Map();
30
+ // maps contract instance address to address
31
+ private contractArtifacts: Map<string, ContractArtifact> = new Map();
32
+
33
+ /////////////////////////////////////////////////////////////
34
+ // Helper functions not in the contract data source interface
35
+ getFirstContractInstance(): ContractInstanceWithAddress {
36
+ return this.contractInstances.values().next().value;
37
+ }
38
+
39
+ addContractArtifact(classId: Fr, artifact: ContractArtifact): void {
40
+ this.contractArtifacts.set(classId.toString(), artifact);
41
+ }
42
+
43
+ /////////////////////////////////////////////////////////////
44
+ // ContractDataSource function impelementations
45
+ getPublicFunction(_address: AztecAddress, _selector: FunctionSelector): Promise<PublicFunction> {
46
+ throw new Error('Method not implemented.');
47
+ }
48
+
49
+ getBlockNumber(): Promise<number> {
50
+ throw new Error('Method not implemented.');
51
+ }
52
+
53
+ getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
54
+ return Promise.resolve(this.contractClasses.get(id.toString()));
55
+ }
56
+
57
+ async getBytecodeCommitment(id: Fr): Promise<Fr | undefined> {
58
+ const contractClass = await this.getContractClass(id);
59
+ return Promise.resolve(computePublicBytecodeCommitment(contractClass!.packedBytecode));
60
+ }
61
+
62
+ getContract(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
63
+ return Promise.resolve(this.contractInstances.get(address.toString()));
64
+ }
65
+
66
+ getContractClassIds(): Promise<Fr[]> {
67
+ throw new Error('Method not implemented.');
68
+ }
69
+
70
+ async getContractArtifact(address: AztecAddress): Promise<ContractArtifact | undefined> {
71
+ const contractInstance = await this.getContract(address);
72
+ if (!contractInstance) {
73
+ this.logger.warn(`Contract not found at address: ${address}`);
74
+ return undefined;
75
+ }
76
+ this.logger.debug(`Retrieved contract artifact for address: ${address}`);
77
+ this.logger.debug(`Contract class ID: ${contractInstance.contractClassId}`);
78
+ return Promise.resolve(this.contractArtifacts.get(contractInstance!.contractClassId.toString()));
79
+ }
80
+
81
+ getContractFunctionName(_address: AztecAddress, _selector: FunctionSelector): Promise<string> {
82
+ return Promise.resolve(PUBLIC_DISPATCH_FN_NAME);
83
+ }
84
+
85
+ registerContractFunctionSignatures(_address: AztecAddress, _signatures: string[]): Promise<void> {
86
+ return Promise.resolve();
87
+ }
88
+
89
+ addContractClass(contractClass: ContractClassPublic): Promise<void> {
90
+ this.contractClasses.set(contractClass.id.toString(), contractClass);
91
+ return Promise.resolve();
92
+ }
93
+
94
+ addContractInstance(contractInstance: ContractInstanceWithAddress): Promise<void> {
95
+ this.contractInstances.set(contractInstance.address.toString(), contractInstance);
96
+ return Promise.resolve();
97
+ }
98
+ }