@aztec/simulator 0.67.1 → 0.68.1

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 (143) hide show
  1. package/dest/acvm/oracle/oracle.d.ts +1 -1
  2. package/dest/acvm/oracle/oracle.d.ts.map +1 -1
  3. package/dest/acvm/oracle/oracle.js +3 -3
  4. package/dest/acvm/oracle/typed_oracle.d.ts +1 -1
  5. package/dest/acvm/oracle/typed_oracle.d.ts.map +1 -1
  6. package/dest/acvm/oracle/typed_oracle.js +3 -3
  7. package/dest/avm/avm_memory_types.d.ts +1 -1
  8. package/dest/avm/avm_memory_types.d.ts.map +1 -1
  9. package/dest/avm/avm_memory_types.js +45 -38
  10. package/dest/avm/avm_simulator.d.ts +1 -0
  11. package/dest/avm/avm_simulator.d.ts.map +1 -1
  12. package/dest/avm/avm_simulator.js +38 -18
  13. package/dest/avm/avm_tree.d.ts +2 -23
  14. package/dest/avm/avm_tree.d.ts.map +1 -1
  15. package/dest/avm/avm_tree.js +27 -82
  16. package/dest/avm/errors.d.ts +8 -1
  17. package/dest/avm/errors.d.ts.map +1 -1
  18. package/dest/avm/errors.js +13 -3
  19. package/dest/avm/fixtures/index.d.ts +2 -0
  20. package/dest/avm/fixtures/index.d.ts.map +1 -1
  21. package/dest/avm/fixtures/index.js +4 -4
  22. package/dest/avm/journal/journal.d.ts +15 -7
  23. package/dest/avm/journal/journal.d.ts.map +1 -1
  24. package/dest/avm/journal/journal.js +30 -22
  25. package/dest/avm/journal/nullifiers.d.ts +0 -4
  26. package/dest/avm/journal/nullifiers.d.ts.map +1 -1
  27. package/dest/avm/journal/nullifiers.js +1 -11
  28. package/dest/avm/journal/public_storage.d.ts +1 -49
  29. package/dest/avm/journal/public_storage.d.ts.map +1 -1
  30. package/dest/avm/journal/public_storage.js +1 -19
  31. package/dest/avm/opcodes/addressing_mode.js +3 -3
  32. package/dest/avm/opcodes/conversion.d.ts +4 -4
  33. package/dest/avm/opcodes/conversion.d.ts.map +1 -1
  34. package/dest/avm/opcodes/conversion.js +22 -18
  35. package/dest/avm/opcodes/ec_add.d.ts.map +1 -1
  36. package/dest/avm/opcodes/ec_add.js +5 -4
  37. package/dest/avm/opcodes/external_calls.js +2 -2
  38. package/dest/avm/opcodes/hashing.d.ts.map +1 -1
  39. package/dest/avm/opcodes/hashing.js +5 -5
  40. package/dest/avm/opcodes/misc.d.ts.map +1 -1
  41. package/dest/avm/opcodes/misc.js +3 -3
  42. package/dest/avm/opcodes/multi_scalar_mul.d.ts.map +1 -1
  43. package/dest/avm/opcodes/multi_scalar_mul.js +9 -6
  44. package/dest/avm/test_utils.d.ts +1 -0
  45. package/dest/avm/test_utils.d.ts.map +1 -1
  46. package/dest/avm/test_utils.js +4 -1
  47. package/dest/client/client_execution_context.d.ts.map +1 -1
  48. package/dest/client/client_execution_context.js +2 -1
  49. package/dest/client/db_oracle.d.ts +7 -3
  50. package/dest/client/db_oracle.d.ts.map +1 -1
  51. package/dest/client/index.d.ts +1 -0
  52. package/dest/client/index.d.ts.map +1 -1
  53. package/dest/client/index.js +2 -1
  54. package/dest/client/view_data_oracle.d.ts +2 -2
  55. package/dest/client/view_data_oracle.d.ts.map +1 -1
  56. package/dest/client/view_data_oracle.js +5 -4
  57. package/dest/providers/acvm_wasm.js +2 -2
  58. package/dest/providers/acvm_wasm_with_blobs.d.ts +7 -0
  59. package/dest/providers/acvm_wasm_with_blobs.d.ts.map +1 -0
  60. package/dest/providers/acvm_wasm_with_blobs.js +15 -0
  61. package/dest/providers/index.d.ts +1 -1
  62. package/dest/providers/index.d.ts.map +1 -1
  63. package/dest/providers/index.js +2 -2
  64. package/dest/public/bytecode_errors.d.ts +4 -0
  65. package/dest/public/bytecode_errors.d.ts.map +1 -0
  66. package/dest/public/bytecode_errors.js +7 -0
  67. package/dest/public/enqueued_call_side_effect_trace.d.ts +10 -4
  68. package/dest/public/enqueued_call_side_effect_trace.d.ts.map +1 -1
  69. package/dest/public/enqueued_call_side_effect_trace.js +63 -13
  70. package/dest/public/execution.d.ts +2 -2
  71. package/dest/public/execution.d.ts.map +1 -1
  72. package/dest/public/execution.js +1 -1
  73. package/dest/public/executor_metrics.d.ts +2 -0
  74. package/dest/public/executor_metrics.d.ts.map +1 -1
  75. package/dest/public/executor_metrics.js +11 -1
  76. package/dest/public/fee_payment.d.ts.map +1 -1
  77. package/dest/public/fee_payment.js +4 -3
  78. package/dest/public/fixtures/index.d.ts +17 -11
  79. package/dest/public/fixtures/index.d.ts.map +1 -1
  80. package/dest/public/fixtures/index.js +103 -35
  81. package/dest/public/public_db_sources.d.ts.map +1 -1
  82. package/dest/public/public_db_sources.js +7 -6
  83. package/dest/public/public_processor.d.ts +15 -7
  84. package/dest/public/public_processor.d.ts.map +1 -1
  85. package/dest/public/public_processor.js +119 -75
  86. package/dest/public/public_processor_metrics.d.ts +10 -2
  87. package/dest/public/public_processor_metrics.d.ts.map +1 -1
  88. package/dest/public/public_processor_metrics.js +49 -2
  89. package/dest/public/public_tx_context.d.ts +5 -0
  90. package/dest/public/public_tx_context.d.ts.map +1 -1
  91. package/dest/public/public_tx_context.js +40 -20
  92. package/dest/public/public_tx_simulator.d.ts +2 -1
  93. package/dest/public/public_tx_simulator.d.ts.map +1 -1
  94. package/dest/public/public_tx_simulator.js +35 -6
  95. package/dest/public/side_effect_errors.js +2 -2
  96. package/dest/public/side_effect_trace_interface.d.ts +2 -1
  97. package/dest/public/side_effect_trace_interface.d.ts.map +1 -1
  98. package/dest/public/transitional_adapters.d.ts.map +1 -1
  99. package/dest/public/transitional_adapters.js +2 -35
  100. package/dest/public/unique_class_ids.d.ts +37 -0
  101. package/dest/public/unique_class_ids.d.ts.map +1 -0
  102. package/dest/public/unique_class_ids.js +66 -0
  103. package/package.json +12 -12
  104. package/src/acvm/oracle/oracle.ts +2 -2
  105. package/src/acvm/oracle/typed_oracle.ts +2 -2
  106. package/src/avm/avm_memory_types.ts +56 -38
  107. package/src/avm/avm_simulator.ts +48 -20
  108. package/src/avm/avm_tree.ts +35 -92
  109. package/src/avm/errors.ts +13 -2
  110. package/src/avm/fixtures/index.ts +4 -2
  111. package/src/avm/journal/journal.ts +39 -29
  112. package/src/avm/journal/nullifiers.ts +0 -11
  113. package/src/avm/journal/public_storage.ts +2 -21
  114. package/src/avm/opcodes/addressing_mode.ts +2 -2
  115. package/src/avm/opcodes/conversion.ts +21 -16
  116. package/src/avm/opcodes/ec_add.ts +4 -3
  117. package/src/avm/opcodes/external_calls.ts +1 -1
  118. package/src/avm/opcodes/hashing.ts +6 -4
  119. package/src/avm/opcodes/misc.ts +4 -3
  120. package/src/avm/opcodes/multi_scalar_mul.ts +10 -5
  121. package/src/avm/test_utils.ts +4 -0
  122. package/src/client/client_execution_context.ts +2 -0
  123. package/src/client/db_oracle.ts +8 -3
  124. package/src/client/index.ts +1 -0
  125. package/src/client/view_data_oracle.ts +6 -3
  126. package/src/providers/acvm_wasm.ts +1 -1
  127. package/src/providers/acvm_wasm_with_blobs.ts +25 -0
  128. package/src/providers/index.ts +1 -1
  129. package/src/public/bytecode_errors.ts +6 -0
  130. package/src/public/enqueued_call_side_effect_trace.ts +83 -19
  131. package/src/public/execution.ts +1 -2
  132. package/src/public/executor_metrics.ts +13 -0
  133. package/src/public/fee_payment.ts +3 -2
  134. package/src/public/fixtures/index.ts +152 -46
  135. package/src/public/public_db_sources.ts +6 -5
  136. package/src/public/public_processor.ts +171 -88
  137. package/src/public/public_processor_metrics.ts +64 -2
  138. package/src/public/public_tx_context.ts +57 -21
  139. package/src/public/public_tx_simulator.ts +34 -6
  140. package/src/public/side_effect_errors.ts +1 -1
  141. package/src/public/side_effect_trace_interface.ts +2 -1
  142. package/src/public/transitional_adapters.ts +0 -51
  143. package/src/public/unique_class_ids.ts +80 -0
@@ -24,6 +24,7 @@ import {
24
24
  MAX_L2_TO_L1_MSGS_PER_TX,
25
25
  MAX_NOTE_HASHES_PER_TX,
26
26
  MAX_NULLIFIERS_PER_TX,
27
+ MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS,
27
28
  MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
28
29
  MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
29
30
  MAX_UNENCRYPTED_LOGS_PER_TX,
@@ -42,7 +43,6 @@ import {
42
43
  PublicDataWrite,
43
44
  ScopedL2ToL1Message,
44
45
  ScopedLogHash,
45
- type ScopedNoteHash,
46
46
  SerializableContractInstance,
47
47
  type TreeSnapshots,
48
48
  } from '@aztec/circuits.js';
@@ -59,6 +59,7 @@ import { type AvmExecutionEnvironment } from '../avm/avm_execution_environment.j
59
59
  import { type EnqueuedPublicCallExecutionResultWithSideEffects, type PublicFunctionCallResult } from './execution.js';
60
60
  import { SideEffectLimitReachedError } from './side_effect_errors.js';
61
61
  import { type PublicSideEffectTraceInterface } from './side_effect_trace_interface.js';
62
+ import { UniqueClassIds } from './unique_class_ids.js';
62
63
 
63
64
  const emptyPublicDataPath = () => new Array(PUBLIC_DATA_TREE_HEIGHT).fill(Fr.zero());
64
65
  const emptyNoteHashPath = () => new Array(NOTE_HASH_TREE_HEIGHT).fill(Fr.zero());
@@ -74,7 +75,7 @@ export type SideEffects = {
74
75
  enqueuedCalls: PublicCallRequest[];
75
76
 
76
77
  publicDataWrites: PublicDataUpdateRequest[];
77
- noteHashes: ScopedNoteHash[];
78
+ noteHashes: NoteHash[];
78
79
  nullifiers: Nullifier[];
79
80
  l2ToL1Msgs: ScopedL2ToL1Message[];
80
81
 
@@ -111,7 +112,7 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI
111
112
  private publicDataWrites: PublicDataUpdateRequest[] = [];
112
113
  private protocolPublicDataWritesLength: number = 0;
113
114
  private userPublicDataWritesLength: number = 0;
114
- private noteHashes: ScopedNoteHash[] = [];
115
+ private noteHashes: NoteHash[] = [];
115
116
  private nullifiers: Nullifier[] = [];
116
117
  private l2ToL1Messages: ScopedL2ToL1Message[] = [];
117
118
  private unencryptedLogs: UnencryptedL2Log[] = [];
@@ -129,6 +130,8 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI
129
130
  * otherwise the public kernel can fail to prove because TX limits are breached.
130
131
  */
131
132
  private readonly previousSideEffectArrayLengths: SideEffectArrayLengths = SideEffectArrayLengths.empty(),
133
+ /** We need to track the set of class IDs used for bytecode retrieval to deduplicate and enforce limits. */
134
+ private gotBytecodeFromClassIds: UniqueClassIds = new UniqueClassIds(),
132
135
  ) {
133
136
  this.log.debug(`Creating trace instance with startSideEffectCounter: ${startSideEffectCounter}`);
134
137
  this.sideEffectCounter = startSideEffectCounter;
@@ -146,6 +149,7 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI
146
149
  this.previousSideEffectArrayLengths.l2ToL1Msgs + this.l2ToL1Messages.length,
147
150
  this.previousSideEffectArrayLengths.unencryptedLogs + this.unencryptedLogs.length,
148
151
  ),
152
+ this.gotBytecodeFromClassIds.fork(),
149
153
  );
150
154
  }
151
155
 
@@ -153,7 +157,7 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI
153
157
  // sanity check to avoid merging the same forked trace twice
154
158
  assert(
155
159
  !forkedTrace.alreadyMergedIntoParent,
156
- 'Cannot merge a forked trace that has already been merged into its parent!',
160
+ 'Bug! Cannot merge a forked trace that has already been merged into its parent!',
157
161
  );
158
162
  forkedTrace.alreadyMergedIntoParent = true;
159
163
 
@@ -172,10 +176,21 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI
172
176
  }
173
177
 
174
178
  private mergeHints(forkedTrace: this) {
179
+ this.gotBytecodeFromClassIds.acceptAndMerge(forkedTrace.gotBytecodeFromClassIds);
180
+
175
181
  this.avmCircuitHints.enqueuedCalls.items.push(...forkedTrace.avmCircuitHints.enqueuedCalls.items);
176
182
 
177
183
  this.avmCircuitHints.contractInstances.items.push(...forkedTrace.avmCircuitHints.contractInstances.items);
178
- this.avmCircuitHints.contractBytecodeHints.items.push(...forkedTrace.avmCircuitHints.contractBytecodeHints.items);
184
+
185
+ // merge in contract bytecode hints
186
+ // UniqueClassIds should prevent duplication
187
+ for (const [contractClassId, bytecodeHint] of forkedTrace.avmCircuitHints.contractBytecodeHints) {
188
+ assert(
189
+ !this.avmCircuitHints.contractBytecodeHints.has(contractClassId),
190
+ 'Bug preventing duplication of contract bytecode hints',
191
+ );
192
+ this.avmCircuitHints.contractBytecodeHints.set(contractClassId, bytecodeHint);
193
+ }
179
194
 
180
195
  this.avmCircuitHints.publicDataReads.items.push(...forkedTrace.avmCircuitHints.publicDataReads.items);
181
196
  this.avmCircuitHints.publicDataWrites.items.push(...forkedTrace.avmCircuitHints.publicDataWrites.items);
@@ -194,6 +209,10 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI
194
209
  this.sideEffectCounter++;
195
210
  }
196
211
 
212
+ public getNoteHashCount() {
213
+ return this.previousSideEffectArrayLengths.noteHashes + this.noteHashes.length;
214
+ }
215
+
197
216
  public tracePublicStorageRead(
198
217
  contractAddress: AztecAddress,
199
218
  slot: Fr,
@@ -272,19 +291,12 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI
272
291
  // NOTE: counter does not increment for note hash checks (because it doesn't rely on pending note hashes)
273
292
  }
274
293
 
275
- public traceNewNoteHash(
276
- contractAddress: AztecAddress,
277
- noteHash: Fr,
278
- leafIndex: Fr = Fr.zero(),
279
- path: Fr[] = emptyNoteHashPath(),
280
- ) {
294
+ public traceNewNoteHash(noteHash: Fr, leafIndex: Fr = Fr.zero(), path: Fr[] = emptyNoteHashPath()) {
281
295
  if (this.noteHashes.length + this.previousSideEffectArrayLengths.noteHashes >= MAX_NOTE_HASHES_PER_TX) {
282
296
  throw new SideEffectLimitReachedError('note hash', MAX_NOTE_HASHES_PER_TX);
283
297
  }
284
298
 
285
- // TODO(dbanks12): make unique and silo instead of scoping
286
- //const siloedNoteHash = siloNoteHash(contractAddress, noteHash);
287
- this.noteHashes.push(new NoteHash(noteHash, this.sideEffectCounter).scope(contractAddress));
299
+ this.noteHashes.push(new NoteHash(noteHash, this.sideEffectCounter));
288
300
  this.log.debug(`NEW_NOTE_HASH cnt: ${this.sideEffectCounter}`);
289
301
  this.avmCircuitHints.noteHashWrites.items.push(new AvmAppendTreeHint(leafIndex, noteHash, path));
290
302
  this.incrementSideEffectCounter();
@@ -409,6 +421,14 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI
409
421
  lowLeafIndex: Fr = Fr.zero(),
410
422
  lowLeafPath: Fr[] = emptyNullifierPath(),
411
423
  ) {
424
+ // FIXME: The way we are hinting contract bytecodes is fundamentally broken.
425
+ // We are mapping contract class ID to a bytecode hint
426
+ // But a bytecode hint is tied to a contract INSTANCE.
427
+ // What if you encounter another contract instance with the same class ID?
428
+ // We can't hint that instance too since there is already an entry in the hints set that class ID.
429
+ // But without that instance hinted, the circuit can't prove that the called contract address
430
+ // actually corresponds to any class ID.
431
+
412
432
  const membershipHint = new AvmNullifierReadTreeHint(lowLeafPreimage, lowLeafIndex, lowLeafPath);
413
433
  const instance = new AvmContractInstanceHint(
414
434
  contractAddress,
@@ -420,13 +440,57 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI
420
440
  contractInstance.publicKeys,
421
441
  membershipHint,
422
442
  );
423
- // We need to deduplicate the contract instances based on addresses
424
- this.avmCircuitHints.contractBytecodeHints.items.push(
425
- new AvmContractBytecodeHints(bytecode, instance, contractClass),
426
- );
443
+
444
+ // Always hint the contract instance separately from the bytecode hint.
445
+ // Since the bytecode hints are keyed by class ID, we need to hint the instance separately
446
+ // since there might be multiple instances hinted for the same class ID.
447
+ this.avmCircuitHints.contractInstances.items.push(instance);
427
448
  this.log.debug(
428
- `Bytecode retrieval for contract execution traced: exists=${exists}, instance=${jsonStringify(contractInstance)}`,
449
+ `Tracing contract instance for bytecode retrieval: exists=${exists}, instance=${jsonStringify(contractInstance)}`,
450
+ );
451
+
452
+ if (!exists) {
453
+ // this ensures there are no duplicates
454
+ this.log.debug(`Contract address ${contractAddress} does not exist. Not tracing bytecode & class ID.`);
455
+ return;
456
+ }
457
+ // We already hinted this bytecode. No need to
458
+ // Don't we still need to hint if the class ID already exists?
459
+ // Because the circuit needs to prove that the called contract address corresponds to the class ID.
460
+ // To do so, the circuit needs to know the class ID in the
461
+ if (this.gotBytecodeFromClassIds.has(contractInstance.contractClassId.toString())) {
462
+ // this ensures there are no duplicates
463
+ this.log.debug(
464
+ `Contract class id ${contractInstance.contractClassId.toString()} already exists in previous hints`,
465
+ );
466
+ return;
467
+ }
468
+
469
+ // If we could actually allow contract calls after the limit was reached, we would hint even if we have
470
+ // surpassed the limit of unique class IDs (still trace the failed bytecode retrieval)
471
+ // because the circuit needs to know the class ID to know when the limit is hit.
472
+ // BUT, the issue with this approach is that the sequencer could lie and say "this call was to a new class ID",
473
+ // and the circuit cannot prove that it's not true without deriving the class ID from bytecode,
474
+ // proving that it corresponds to the called contract address, and proving that the class ID wasn't already
475
+ // present/used. That would require more bytecode hashing which is exactly what this limit exists to avoid.
476
+ if (this.gotBytecodeFromClassIds.size() >= MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS) {
477
+ this.log.debug(
478
+ `Bytecode retrieval failure for contract class ID ${contractInstance.contractClassId.toString()} (limit reached)`,
479
+ );
480
+ throw new SideEffectLimitReachedError(
481
+ 'contract calls to unique class IDs',
482
+ MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS,
483
+ );
484
+ }
485
+
486
+ this.log.debug(`Tracing bytecode & contract class for bytecode retrieval: class=${jsonStringify(contractClass)}`);
487
+ this.avmCircuitHints.contractBytecodeHints.set(
488
+ contractInstance.contractClassId.toString(),
489
+ new AvmContractBytecodeHints(bytecode, instance, contractClass),
429
490
  );
491
+ // After adding the bytecode hint, mark the classId as retrieved to avoid duplication.
492
+ // The above map alone isn't sufficient because we need to check the parent trace's (and its parent) as well.
493
+ this.gotBytecodeFromClassIds.add(contractInstance.contractClassId.toString());
430
494
  }
431
495
 
432
496
  /**
@@ -20,7 +20,6 @@ import {
20
20
  RevertCode,
21
21
  type ScopedL2ToL1Message,
22
22
  type ScopedLogHash,
23
- type ScopedNoteHash,
24
23
  type TreeLeafReadRequest,
25
24
  } from '@aztec/circuits.js';
26
25
  import { computeVarArgsHash } from '@aztec/circuits.js/hash';
@@ -29,7 +28,7 @@ export interface PublicSideEffects {
29
28
  /** The contract storage update requests performed. */
30
29
  publicDataWrites: PublicDataUpdateRequest[];
31
30
  /** The new note hashes to be inserted into the note hashes tree. */
32
- noteHashes: ScopedNoteHash[];
31
+ noteHashes: NoteHash[];
33
32
  /** The new nullifiers to be inserted into the nullifier tree. */
34
33
  nullifiers: Nullifier[];
35
34
  /** The new l2 to l1 messages generated to be inserted into the messages tree. */
@@ -13,6 +13,7 @@ export class ExecutorMetrics {
13
13
  private fnCount: UpDownCounter;
14
14
  private fnDuration: Histogram;
15
15
  private manaPerSecond: Histogram;
16
+ private privateEffectsInsertions: Histogram;
16
17
 
17
18
  constructor(client: TelemetryClient, name = 'PublicExecutor') {
18
19
  this.tracer = client.getTracer(name);
@@ -33,6 +34,12 @@ export class ExecutorMetrics {
33
34
  unit: 'mana/s',
34
35
  valueType: ValueType.INT,
35
36
  });
37
+
38
+ this.privateEffectsInsertions = meter.createHistogram(Metrics.PUBLIC_EXECUTION_PRIVATE_EFFECTS_INSERTION, {
39
+ description: 'Private effects insertion time',
40
+ unit: 'us',
41
+ valueType: ValueType.INT,
42
+ });
36
43
  }
37
44
 
38
45
  recordFunctionSimulation(durationMs: number, manaUsed: number, fnName: string) {
@@ -55,4 +62,10 @@ export class ExecutorMetrics {
55
62
  [Attributes.OK]: false,
56
63
  });
57
64
  }
65
+
66
+ recordPrivateEffectsInsertion(durationUs: number, type: 'revertible' | 'non-revertible') {
67
+ this.privateEffectsInsertions.record(Math.ceil(durationUs), {
68
+ [Attributes.REVERTIBILITY]: type,
69
+ });
70
+ }
58
71
  }
@@ -1,13 +1,14 @@
1
1
  import { computePublicDataTreeLeafSlot, deriveStorageSlotInMap } from '@aztec/circuits.js/hash';
2
2
  import { type AztecAddress } from '@aztec/foundation/aztec-address';
3
3
  import { Fr } from '@aztec/foundation/fields';
4
- import { ProtocolContractAddress, ProtocolContractArtifact } from '@aztec/protocol-contracts';
4
+ import { ProtocolContractAddress } from '@aztec/protocol-contracts';
5
+ import { FeeJuiceArtifact } from '@aztec/protocol-contracts/fee-juice';
5
6
 
6
7
  /**
7
8
  * Computes the storage slot within the Fee Juice contract for the balance of the fee payer.
8
9
  */
9
10
  export function computeFeePayerBalanceStorageSlot(feePayer: AztecAddress) {
10
- return deriveStorageSlotInMap(ProtocolContractArtifact.FeeJuice.storageLayout.balances.slot, feePayer);
11
+ return deriveStorageSlotInMap(FeeJuiceArtifact.storageLayout.balances.slot, feePayer);
11
12
  }
12
13
 
13
14
  /**
@@ -1,9 +1,10 @@
1
- import { MerkleTreeId, PublicExecutionRequest, Tx } from '@aztec/circuit-types';
1
+ import { MerkleTreeId, type MerkleTreeWriteOperations, PublicExecutionRequest, Tx } from '@aztec/circuit-types';
2
2
  import {
3
3
  type AvmCircuitInputs,
4
4
  BlockHeader,
5
5
  CallContext,
6
6
  type ContractClassPublic,
7
+ type ContractDataSource,
7
8
  type ContractInstanceWithAddress,
8
9
  DEFAULT_GAS_LIMIT,
9
10
  DEPLOYER_CONTRACT_ADDRESS,
@@ -13,6 +14,7 @@ import {
13
14
  GasSettings,
14
15
  GlobalVariables,
15
16
  MAX_L2_GAS_PER_TX_PUBLIC_PORTION,
17
+ MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS,
16
18
  PartialPrivateTailPublicInputsForPublic,
17
19
  PrivateKernelTailCircuitPublicInputs,
18
20
  type PublicFunction,
@@ -29,60 +31,55 @@ import { type ContractArtifact, type FunctionArtifact } from '@aztec/foundation/
29
31
  import { AztecAddress } from '@aztec/foundation/aztec-address';
30
32
  import { Fr, Point } from '@aztec/foundation/fields';
31
33
  import { openTmpStore } from '@aztec/kv-store/lmdb';
32
- import { AvmTestContractArtifact } from '@aztec/noir-contracts.js';
33
- import { PublicTxSimulator, WorldStateDB } from '@aztec/simulator';
34
+ import { AvmTestContractArtifact } from '@aztec/noir-contracts.js/AvmTest';
35
+ import {
36
+ AvmEphemeralForest,
37
+ AvmSimulator,
38
+ PublicEnqueuedCallSideEffectTrace,
39
+ PublicTxSimulator,
40
+ WorldStateDB,
41
+ } from '@aztec/simulator';
34
42
  import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
35
43
  import { MerkleTrees } from '@aztec/world-state';
36
44
 
37
45
  import { strict as assert } from 'assert';
38
46
 
47
+ import { initContext, initExecutionEnvironment, initPersistableStateManager } from '../../avm/fixtures/index.js';
48
+
49
+ const TIMESTAMP = new Fr(99833);
50
+
39
51
  export async function simulateAvmTestContractGenerateCircuitInputs(
40
52
  functionName: string,
41
- calldata: Fr[] = [],
53
+ args: Fr[] = [],
42
54
  expectRevert: boolean = false,
55
+ contractDataSource = new MockedAvmTestContractDataSource(),
43
56
  assertionErrString?: string,
44
57
  ): Promise<AvmCircuitInputs> {
45
- const sender = AztecAddress.random();
46
- const functionSelector = getAvmTestContractFunctionSelector(functionName);
47
- calldata = [functionSelector.toField(), ...calldata];
58
+ const globals = GlobalVariables.empty();
59
+ globals.timestamp = TIMESTAMP;
48
60
 
49
- const globalVariables = GlobalVariables.empty();
50
- globalVariables.gasFees = GasFees.empty();
51
- globalVariables.timestamp = new Fr(99833);
52
-
53
- const telemetry = new NoopTelemetryClient();
54
- const merkleTrees = await (await MerkleTrees.new(openTmpStore(), telemetry)).fork();
55
- const contractDataSource = new MockedAvmTestContractDataSource();
61
+ const merkleTrees = await (await MerkleTrees.new(openTmpStore(), new NoopTelemetryClient())).fork();
62
+ await contractDataSource.deployContracts(merkleTrees);
56
63
  const worldStateDB = new WorldStateDB(merkleTrees, contractDataSource);
57
64
 
58
- const contractInstance = contractDataSource.contractInstance;
59
- const contractAddressNullifier = siloNullifier(
60
- AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
61
- contractInstance.address.toField(),
62
- );
63
- await merkleTrees.batchInsert(MerkleTreeId.NULLIFIER_TREE, [contractAddressNullifier.toBuffer()], 0);
64
- // other contract address used by the bulk test's GETCONTRACTINSTANCE test
65
- const otherContractAddressNullifier = siloNullifier(
66
- AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
67
- contractDataSource.otherContractInstance.address.toField(),
68
- );
69
- await merkleTrees.batchInsert(MerkleTreeId.NULLIFIER_TREE, [otherContractAddressNullifier.toBuffer()], 0);
70
-
71
65
  const simulator = new PublicTxSimulator(
72
66
  merkleTrees,
73
67
  worldStateDB,
74
68
  new NoopTelemetryClient(),
75
- globalVariables,
69
+ globals,
76
70
  /*doMerkleOperations=*/ true,
77
71
  );
78
72
 
73
+ const sender = AztecAddress.random();
74
+ const functionSelector = getAvmTestContractFunctionSelector(functionName);
75
+ args = [functionSelector.toField(), ...args];
79
76
  const callContext = new CallContext(
80
77
  sender,
81
- contractInstance.address,
78
+ contractDataSource.firstContractInstance.address,
82
79
  contractDataSource.fnSelector,
83
80
  /*isStaticCall=*/ false,
84
81
  );
85
- const executionRequest = new PublicExecutionRequest(callContext, calldata);
82
+ const executionRequest = new PublicExecutionRequest(callContext, args);
86
83
 
87
84
  const tx: Tx = createTxForPublicCall(executionRequest);
88
85
 
@@ -103,6 +100,46 @@ export async function simulateAvmTestContractGenerateCircuitInputs(
103
100
  return avmCircuitInputs;
104
101
  }
105
102
 
103
+ export async function simulateAvmTestContractCall(
104
+ functionName: string,
105
+ args: Fr[] = [],
106
+ expectRevert: boolean = false,
107
+ contractDataSource = new MockedAvmTestContractDataSource(),
108
+ ) {
109
+ const globals = GlobalVariables.empty();
110
+ globals.timestamp = TIMESTAMP;
111
+
112
+ const merkleTrees = await (await MerkleTrees.new(openTmpStore(), new NoopTelemetryClient())).fork();
113
+ await contractDataSource.deployContracts(merkleTrees);
114
+ const worldStateDB = new WorldStateDB(merkleTrees, contractDataSource);
115
+
116
+ const trace = new PublicEnqueuedCallSideEffectTrace();
117
+ const ephemeralTrees = await AvmEphemeralForest.create(worldStateDB.getMerkleInterface());
118
+ const persistableState = initPersistableStateManager({
119
+ worldStateDB,
120
+ trace,
121
+ merkleTrees: ephemeralTrees,
122
+ doMerkleOperations: true,
123
+ });
124
+
125
+ const sender = AztecAddress.random();
126
+ const functionSelector = getAvmTestContractFunctionSelector(functionName);
127
+ args = [functionSelector.toField(), ...args];
128
+ const environment = initExecutionEnvironment({
129
+ calldata: args,
130
+ globals,
131
+ address: contractDataSource.firstContractInstance.address,
132
+ sender,
133
+ });
134
+ const context = initContext({ env: environment, persistableState });
135
+
136
+ // First we simulate (though it's not needed in this simple case).
137
+ const simulator = new AvmSimulator(context);
138
+ const results = await simulator.execute();
139
+
140
+ expect(results.reverted).toBe(expectRevert);
141
+ }
142
+
106
143
  /**
107
144
  * Craft a carrier transaction for a public call for simulation by PublicTxSimulator.
108
145
  */
@@ -125,7 +162,7 @@ export function createTxForPublicCall(
125
162
  }
126
163
 
127
164
  const teardownGasLimits = isTeardown ? gasLimits : Gas.empty();
128
- const gasSettings = new GasSettings(gasLimits, teardownGasLimits, GasFees.empty());
165
+ const gasSettings = new GasSettings(gasLimits, teardownGasLimits, GasFees.empty(), GasFees.empty());
129
166
  const txContext = new TxContext(Fr.zero(), Fr.zero(), gasSettings);
130
167
  const constantData = new TxConstantData(BlockHeader.empty(), txContext, Fr.zero(), Fr.zero());
131
168
 
@@ -144,23 +181,47 @@ export function createTxForPublicCall(
144
181
  return tx;
145
182
  }
146
183
 
147
- export class MockedAvmTestContractDataSource {
184
+ export class MockedAvmTestContractDataSource implements ContractDataSource {
148
185
  private fnName = 'public_dispatch';
186
+ public fnSelector: FunctionSelector = getAvmTestContractFunctionSelector(this.fnName);
149
187
  private bytecode: Buffer;
150
- public fnSelector: FunctionSelector;
151
188
  private publicFn: PublicFunction;
152
- private contractClass: ContractClassPublic;
153
- public contractInstance: ContractInstanceWithAddress;
154
189
  private bytecodeCommitment: Fr;
190
+
191
+ // maps contract class ID to class
192
+ private contractClasses: Map<string, ContractClassPublic> = new Map();
193
+ // maps contract instance address to instance
194
+ public contractInstances: Map<string, ContractInstanceWithAddress> = new Map();
195
+
196
+ public firstContractInstance: ContractInstanceWithAddress = SerializableContractInstance.default().withAddress(
197
+ AztecAddress.fromNumber(0),
198
+ );
199
+ public instanceSameClassAsFirstContract: ContractInstanceWithAddress =
200
+ SerializableContractInstance.default().withAddress(AztecAddress.fromNumber(0));
155
201
  public otherContractInstance: ContractInstanceWithAddress;
156
202
 
157
- constructor() {
203
+ constructor(private skipContractDeployments: boolean = false) {
158
204
  this.bytecode = getAvmTestContractBytecode(this.fnName);
159
205
  this.fnSelector = getAvmTestContractFunctionSelector(this.fnName);
160
206
  this.publicFn = { bytecode: this.bytecode, selector: this.fnSelector };
161
- this.contractClass = makeContractClassPublic(0, this.publicFn);
162
- this.contractInstance = makeContractInstanceFromClassId(this.contractClass.id);
163
207
  this.bytecodeCommitment = computePublicBytecodeCommitment(this.bytecode);
208
+
209
+ // create enough unique classes to hit the limit (plus two extra)
210
+ for (let i = 0; i < MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS + 1; i++) {
211
+ const contractClass = makeContractClassPublic(/*seed=*/ i, this.publicFn);
212
+ const contractInstance = makeContractInstanceFromClassId(contractClass.id, /*seed=*/ i);
213
+ this.contractClasses.set(contractClass.id.toString(), contractClass);
214
+ this.contractInstances.set(contractInstance.address.toString(), contractInstance);
215
+ if (i === 0) {
216
+ this.firstContractInstance = contractInstance;
217
+ }
218
+ }
219
+ // a contract with the same class but different instance/address as the first contract
220
+ this.instanceSameClassAsFirstContract = makeContractInstanceFromClassId(
221
+ this.firstContractInstance.contractClassId,
222
+ /*seed=*/ 1000,
223
+ );
224
+
164
225
  // The values here should match those in `avm_simulator.test.ts`
165
226
  // Used for GETCONTRACTINSTANCE test
166
227
  this.otherContractInstance = new SerializableContractInstance({
@@ -178,6 +239,46 @@ export class MockedAvmTestContractDataSource {
178
239
  }).withAddress(AztecAddress.fromNumber(0x4444));
179
240
  }
180
241
 
242
+ async deployContracts(merkleTrees: MerkleTreeWriteOperations) {
243
+ if (!this.skipContractDeployments) {
244
+ for (const contractInstance of this.contractInstances.values()) {
245
+ const contractAddressNullifier = siloNullifier(
246
+ AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
247
+ contractInstance.address.toField(),
248
+ );
249
+ await merkleTrees.batchInsert(MerkleTreeId.NULLIFIER_TREE, [contractAddressNullifier.toBuffer()], 0);
250
+ }
251
+
252
+ const instanceSameClassAsFirstContractNullifier = siloNullifier(
253
+ AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
254
+ this.instanceSameClassAsFirstContract.address.toField(),
255
+ );
256
+ await merkleTrees.batchInsert(
257
+ MerkleTreeId.NULLIFIER_TREE,
258
+ [instanceSameClassAsFirstContractNullifier.toBuffer()],
259
+ 0,
260
+ );
261
+
262
+ // other contract address used by the bulk test's GETCONTRACTINSTANCE test
263
+ const otherContractAddressNullifier = siloNullifier(
264
+ AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
265
+ this.otherContractInstance.address.toField(),
266
+ );
267
+ await merkleTrees.batchInsert(MerkleTreeId.NULLIFIER_TREE, [otherContractAddressNullifier.toBuffer()], 0);
268
+ }
269
+ }
270
+
271
+ public static async create(
272
+ merkleTrees: MerkleTreeWriteOperations,
273
+ skipContractDeployments: boolean = false,
274
+ ): Promise<MockedAvmTestContractDataSource> {
275
+ const dataSource = new MockedAvmTestContractDataSource(skipContractDeployments);
276
+ if (!skipContractDeployments) {
277
+ await dataSource.deployContracts(merkleTrees);
278
+ }
279
+ return dataSource;
280
+ }
281
+
181
282
  getPublicFunction(_address: AztecAddress, _selector: FunctionSelector): Promise<PublicFunction> {
182
283
  return Promise.resolve(this.publicFn);
183
284
  }
@@ -186,8 +287,8 @@ export class MockedAvmTestContractDataSource {
186
287
  throw new Error('Method not implemented.');
187
288
  }
188
289
 
189
- getContractClass(_id: Fr): Promise<ContractClassPublic> {
190
- return Promise.resolve(this.contractClass);
290
+ getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
291
+ return Promise.resolve(this.contractClasses.get(id.toString()));
191
292
  }
192
293
 
193
294
  getBytecodeCommitment(_id: Fr): Promise<Fr> {
@@ -198,12 +299,17 @@ export class MockedAvmTestContractDataSource {
198
299
  return Promise.resolve();
199
300
  }
200
301
 
201
- getContract(address: AztecAddress): Promise<ContractInstanceWithAddress> {
202
- if (address.equals(this.contractInstance.address)) {
203
- return Promise.resolve(this.contractInstance);
204
- } else {
205
- return Promise.resolve(this.otherContractInstance);
302
+ getContract(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
303
+ if (!this.skipContractDeployments) {
304
+ if (address.equals(this.otherContractInstance.address)) {
305
+ return Promise.resolve(this.otherContractInstance);
306
+ } else if (address.equals(this.instanceSameClassAsFirstContract.address)) {
307
+ return Promise.resolve(this.instanceSameClassAsFirstContract);
308
+ } else {
309
+ return Promise.resolve(this.contractInstances.get(address.toString()));
310
+ }
206
311
  }
312
+ return Promise.resolve(undefined);
207
313
  }
208
314
 
209
315
  getContractClassIds(): Promise<Fr[]> {
@@ -218,7 +324,7 @@ export class MockedAvmTestContractDataSource {
218
324
  return Promise.resolve(this.fnName);
219
325
  }
220
326
 
221
- addContractArtifact(_address: AztecAddress, _contract: ContractArtifact): Promise<void> {
327
+ registerContractFunctionNames(_address: AztecAddress, _names: Record<string, string>): Promise<void> {
222
328
  return Promise.resolve();
223
329
  }
224
330
  }
@@ -22,7 +22,8 @@ import {
22
22
  import { computeL1ToL2MessageNullifier, computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash';
23
23
  import { createLogger } from '@aztec/foundation/log';
24
24
  import { Timer } from '@aztec/foundation/timer';
25
- import { ContractClassRegisteredEvent, ContractInstanceDeployedEvent } from '@aztec/protocol-contracts';
25
+ import { ContractClassRegisteredEvent } from '@aztec/protocol-contracts/class-registerer';
26
+ import { ContractInstanceDeployedEvent } from '@aztec/protocol-contracts/instance-deployer';
26
27
  import {
27
28
  type CommitmentsDB,
28
29
  MessageLoadOracleInputs,
@@ -207,7 +208,7 @@ export class WorldStateDB extends ContractsDataSourcePublicDB implements PublicS
207
208
  nullifier: Fr,
208
209
  ): Promise<NullifierMembershipWitness | undefined> {
209
210
  const timer = new Timer();
210
- const index = await this.db.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer());
211
+ const index = (await this.db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]))[0];
211
212
  if (!index) {
212
213
  return undefined;
213
214
  }
@@ -240,7 +241,7 @@ export class WorldStateDB extends ContractsDataSourcePublicDB implements PublicS
240
241
  ): Promise<MessageLoadOracleInputs<typeof L1_TO_L2_MSG_TREE_HEIGHT>> {
241
242
  const timer = new Timer();
242
243
 
243
- const messageIndex = await this.db.findLeafIndex(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, messageHash);
244
+ const messageIndex = (await this.db.findLeafIndices(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, [messageHash]))[0];
244
245
  if (messageIndex === undefined) {
245
246
  throw new Error(`No L1 to L2 message found for message hash ${messageHash.toString()}`);
246
247
  }
@@ -279,7 +280,7 @@ export class WorldStateDB extends ContractsDataSourcePublicDB implements PublicS
279
280
 
280
281
  public async getCommitmentIndex(commitment: Fr): Promise<bigint | undefined> {
281
282
  const timer = new Timer();
282
- const index = await this.db.findLeafIndex(MerkleTreeId.NOTE_HASH_TREE, commitment);
283
+ const index = (await this.db.findLeafIndices(MerkleTreeId.NOTE_HASH_TREE, [commitment]))[0];
283
284
  this.logger.debug(`[DB] Fetched commitment index`, {
284
285
  eventName: 'public-db-access',
285
286
  duration: timer.ms(),
@@ -301,7 +302,7 @@ export class WorldStateDB extends ContractsDataSourcePublicDB implements PublicS
301
302
 
302
303
  public async getNullifierIndex(nullifier: Fr): Promise<bigint | undefined> {
303
304
  const timer = new Timer();
304
- const index = await this.db.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer());
305
+ const index = (await this.db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]))[0];
305
306
  this.logger.debug(`[DB] Fetched nullifier index`, {
306
307
  eventName: 'public-db-access',
307
308
  duration: timer.ms(),