@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/simulator",
3
- "version": "0.67.1",
3
+ "version": "0.68.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -22,7 +22,7 @@
22
22
  "clean": "rm -rf ./dest .tsbuildinfo",
23
23
  "formatting": "run -T prettier --check ./src && run -T eslint ./src",
24
24
  "formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src",
25
- "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests"
25
+ "test": "HARDWARE_CONCURRENCY=${HARDWARE_CONCURRENCY:-16} RAYON_NUM_THREADS=${RAYON_NUM_THREADS:-4} NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}"
26
26
  },
27
27
  "inherits": [
28
28
  "../package.common.json"
@@ -54,9 +54,9 @@
54
54
  ],
55
55
  "reporters": [
56
56
  [
57
- "default",
57
+ "jest-silent-reporter",
58
58
  {
59
- "summaryThreshold": 9999
59
+ "useDots": true
60
60
  }
61
61
  ]
62
62
  ],
@@ -66,14 +66,14 @@
66
66
  ]
67
67
  },
68
68
  "dependencies": {
69
- "@aztec/circuit-types": "0.67.1",
70
- "@aztec/circuits.js": "0.67.1",
71
- "@aztec/foundation": "0.67.1",
72
- "@aztec/noir-protocol-circuits-types": "0.67.1",
73
- "@aztec/protocol-contracts": "0.67.1",
74
- "@aztec/telemetry-client": "0.67.1",
75
- "@aztec/types": "0.67.1",
76
- "@aztec/world-state": "0.67.1",
69
+ "@aztec/circuit-types": "0.68.1",
70
+ "@aztec/circuits.js": "0.68.1",
71
+ "@aztec/foundation": "0.68.1",
72
+ "@aztec/noir-protocol-circuits-types": "0.68.1",
73
+ "@aztec/protocol-contracts": "0.68.1",
74
+ "@aztec/telemetry-client": "0.68.1",
75
+ "@aztec/types": "0.68.1",
76
+ "@aztec/world-state": "0.68.1",
77
77
  "@noir-lang/acvm_js": "portal:../../noir/packages/acvm_js",
78
78
  "@noir-lang/noirc_abi": "portal:../../noir/packages/noirc_abi",
79
79
  "@noir-lang/types": "portal:../../noir/packages/types",
@@ -375,8 +375,8 @@ export class Oracle {
375
375
  this.typedOracle.notifySetMinRevertibleSideEffectCounter(frToNumber(fromACVMField(minRevertibleSideEffectCounter)));
376
376
  }
377
377
 
378
- async getAppTaggingSecretAsSender([sender]: ACVMField[], [recipient]: ACVMField[]): Promise<ACVMField[]> {
379
- const taggingSecret = await this.typedOracle.getAppTaggingSecretAsSender(
378
+ async getIndexedTaggingSecretAsSender([sender]: ACVMField[], [recipient]: ACVMField[]): Promise<ACVMField[]> {
379
+ const taggingSecret = await this.typedOracle.getIndexedTaggingSecretAsSender(
380
380
  AztecAddress.fromString(sender),
381
381
  AztecAddress.fromString(recipient),
382
382
  );
@@ -237,8 +237,8 @@ export abstract class TypedOracle {
237
237
  throw new OracleMethodNotAvailableError('debugLog');
238
238
  }
239
239
 
240
- getAppTaggingSecretAsSender(_sender: AztecAddress, _recipient: AztecAddress): Promise<IndexedTaggingSecret> {
241
- throw new OracleMethodNotAvailableError('getAppTaggingSecretAsSender');
240
+ getIndexedTaggingSecretAsSender(_sender: AztecAddress, _recipient: AztecAddress): Promise<IndexedTaggingSecret> {
241
+ throw new OracleMethodNotAvailableError('getIndexedTaggingSecretAsSender');
242
242
  }
243
243
 
244
244
  incrementAppTaggingSecretIndexAsSender(_sender: AztecAddress, _recipient: AztecAddress): Promise<void> {
@@ -15,7 +15,12 @@ import { type FunctionsOf } from '@aztec/foundation/types';
15
15
 
16
16
  import { strict as assert } from 'assert';
17
17
 
18
- import { InstructionExecutionError, InvalidTagValueError, TagCheckError } from './errors.js';
18
+ import {
19
+ InstructionExecutionError,
20
+ InvalidTagValueError,
21
+ MemorySliceOutOfRangeError,
22
+ TagCheckError,
23
+ } from './errors.js';
19
24
  import { Addressing, AddressingMode } from './opcodes/addressing_mode.js';
20
25
 
21
26
  /** MemoryValue gathers the common operations for all memory types. */
@@ -232,13 +237,14 @@ export class TaggedMemory implements TaggedMemoryInterface {
232
237
  // Whether to track and validate memory accesses for each instruction.
233
238
  static readonly TRACK_MEMORY_ACCESSES = process.env.NODE_ENV === 'test';
234
239
 
235
- // FIXME: memory should be 2^32, but TS max array size is: 2^32 - 1
236
- static readonly MAX_MEMORY_SIZE = Number((1n << 32n) - 1n);
237
- private _mem: MemoryValue[];
240
+ // Memory is modelled by a map with key type being number.
241
+ // We however restrict the keys to be non-negative integers smaller than
242
+ // MAX_MEMORY_SIZE.
243
+ static readonly MAX_MEMORY_SIZE = Number(1n << 32n);
244
+ private _mem: Map<number, MemoryValue>;
238
245
 
239
246
  constructor() {
240
- // We do not initialize memory size here because otherwise tests blow up when diffing.
241
- this._mem = [];
247
+ this._mem = new Map<number, MemoryValue>();
242
248
  }
243
249
 
244
250
  public getMaxMemorySize(): number {
@@ -257,8 +263,9 @@ export class TaggedMemory implements TaggedMemoryInterface {
257
263
  }
258
264
 
259
265
  public getAs<T>(offset: number): T {
266
+ assert(Number.isInteger(offset));
260
267
  assert(offset < TaggedMemory.MAX_MEMORY_SIZE);
261
- const word = this._mem[offset];
268
+ const word = this._mem.get(offset);
262
269
  TaggedMemory.log.trace(`get(${offset}) = ${word}`);
263
270
  if (word === undefined) {
264
271
  TaggedMemory.log.debug(`WARNING: Memory at offset ${offset} is undefined!`);
@@ -268,54 +275,63 @@ export class TaggedMemory implements TaggedMemoryInterface {
268
275
  }
269
276
 
270
277
  public getSlice(offset: number, size: number): MemoryValue[] {
271
- assert(offset + size <= TaggedMemory.MAX_MEMORY_SIZE);
272
- const value = this._mem.slice(offset, offset + size);
273
- TaggedMemory.log.trace(`getSlice(${offset}, ${size}) = ${value}`);
274
- for (let i = 0; i < value.length; i++) {
275
- if (value[i] === undefined) {
276
- value[i] = new Field(0);
277
- }
278
+ assert(Number.isInteger(offset) && Number.isInteger(size));
279
+
280
+ if (offset + size > TaggedMemory.MAX_MEMORY_SIZE) {
281
+ throw new MemorySliceOutOfRangeError(offset, size);
278
282
  }
279
- assert(value.length === size, `Expected slice of size ${size}, got ${value.length}.`);
280
- return value;
283
+
284
+ const slice = new Array<MemoryValue>(size);
285
+
286
+ for (let i = 0; i < size; i++) {
287
+ slice[i] = this._mem.get(offset + i) ?? new Field(0);
288
+ }
289
+
290
+ TaggedMemory.log.trace(`getSlice(${offset}, ${size}) = ${slice}`);
291
+ return slice;
281
292
  }
282
293
 
283
294
  public getSliceAs<T>(offset: number, size: number): T[] {
284
- assert(offset + size <= TaggedMemory.MAX_MEMORY_SIZE);
285
295
  return this.getSlice(offset, size) as T[];
286
296
  }
287
297
 
288
298
  public getSliceTags(offset: number, size: number): TypeTag[] {
289
- assert(offset + size <= TaggedMemory.MAX_MEMORY_SIZE);
290
- return this._mem.slice(offset, offset + size).map(TaggedMemory.getTag);
299
+ return this.getSlice(offset, size).map(TaggedMemory.getTag);
291
300
  }
292
301
 
293
302
  public set(offset: number, v: MemoryValue) {
303
+ assert(Number.isInteger(offset));
294
304
  assert(offset < TaggedMemory.MAX_MEMORY_SIZE);
295
- this._mem[offset] = v;
305
+ this._mem.set(offset, v);
296
306
  TaggedMemory.log.trace(`set(${offset}, ${v})`);
297
307
  }
298
308
 
299
- public setSlice(offset: number, vs: MemoryValue[]) {
300
- assert(offset + vs.length <= TaggedMemory.MAX_MEMORY_SIZE);
301
- // We may need to extend the memory size, otherwise splice doesn't insert.
302
- if (offset + vs.length > this._mem.length) {
303
- this._mem.length = offset + vs.length;
309
+ public setSlice(offset: number, slice: MemoryValue[]) {
310
+ assert(Number.isInteger(offset));
311
+
312
+ if (offset + slice.length > TaggedMemory.MAX_MEMORY_SIZE) {
313
+ throw new MemorySliceOutOfRangeError(offset, slice.length);
304
314
  }
305
- this._mem.splice(offset, vs.length, ...vs);
306
- TaggedMemory.log.trace(`setSlice(${offset}, ${vs})`);
315
+
316
+ slice.forEach((element, idx) => {
317
+ this._mem.set(offset + idx, element);
318
+ });
319
+ TaggedMemory.log.trace(`setSlice(${offset}, ${slice})`);
307
320
  }
308
321
 
309
322
  public getTag(offset: number): TypeTag {
310
- return TaggedMemory.getTag(this._mem[offset]);
323
+ assert(Number.isInteger(offset));
324
+ assert(offset < TaggedMemory.MAX_MEMORY_SIZE);
325
+ return TaggedMemory.getTag(this._mem.get(offset));
311
326
  }
312
327
 
313
328
  /**
314
329
  * Check that the memory at the given offset matches the specified tag.
315
330
  */
316
331
  public checkTag(tag: TypeTag, offset: number) {
317
- if (this.getTag(offset) !== tag) {
318
- throw TagCheckError.forOffset(offset, TypeTag[this.getTag(offset)], TypeTag[tag]);
332
+ const gotTag = this.getTag(offset);
333
+ if (gotTag !== tag) {
334
+ throw TagCheckError.forOffset(offset, TypeTag[gotTag], TypeTag[tag]);
319
335
  }
320
336
  }
321
337
 
@@ -334,13 +350,13 @@ export class TaggedMemory implements TaggedMemoryInterface {
334
350
  public static checkIsValidTag(tagNumber: number) {
335
351
  if (
336
352
  ![
353
+ TypeTag.FIELD,
337
354
  TypeTag.UINT1,
338
355
  TypeTag.UINT8,
339
356
  TypeTag.UINT16,
340
357
  TypeTag.UINT32,
341
358
  TypeTag.UINT64,
342
359
  TypeTag.UINT128,
343
- TypeTag.FIELD,
344
360
  ].includes(tagNumber)
345
361
  ) {
346
362
  throw new InvalidTagValueError(tagNumber);
@@ -380,21 +396,23 @@ export class TaggedMemory implements TaggedMemoryInterface {
380
396
  public static getTag(v: MemoryValue | undefined): TypeTag {
381
397
  let tag = TypeTag.INVALID;
382
398
 
399
+ // Not sure why, but using instanceof here doesn't work and leads odd behavior,
400
+ // but using constructor.name does the job...
383
401
  if (v === undefined) {
384
402
  tag = TypeTag.FIELD; // uninitialized memory is Field(0)
385
- } else if (v instanceof Field) {
403
+ } else if (v.constructor.name == 'Field') {
386
404
  tag = TypeTag.FIELD;
387
- } else if (v instanceof Uint1) {
405
+ } else if (v.constructor.name == 'Uint1') {
388
406
  tag = TypeTag.UINT1;
389
- } else if (v instanceof Uint8) {
407
+ } else if (v.constructor.name == 'Uint8') {
390
408
  tag = TypeTag.UINT8;
391
- } else if (v instanceof Uint16) {
409
+ } else if (v.constructor.name == 'Uint16') {
392
410
  tag = TypeTag.UINT16;
393
- } else if (v instanceof Uint32) {
411
+ } else if (v.constructor.name == 'Uint32') {
394
412
  tag = TypeTag.UINT32;
395
- } else if (v instanceof Uint64) {
413
+ } else if (v.constructor.name == 'Uint64') {
396
414
  tag = TypeTag.UINT64;
397
- } else if (v instanceof Uint128) {
415
+ } else if (v.constructor.name == 'Uint128') {
398
416
  tag = TypeTag.UINT128;
399
417
  }
400
418
 
@@ -97,25 +97,22 @@ export class AvmSimulator {
97
97
  * Fetch the bytecode and execute it in the current context.
98
98
  */
99
99
  public async execute(): Promise<AvmContractCallResult> {
100
- const bytecode = await this.context.persistableState.getBytecode(this.context.environment.address);
101
- if (!bytecode) {
102
- // revert, consuming all gas
103
- const message = `No bytecode found at: ${this.context.environment.address}. Reverting...`;
104
- const fnName = await this.context.persistableState.getPublicFunctionDebugName(this.context.environment);
105
- const revertReason = new AvmRevertReason(
106
- message,
107
- /*failingFunction=*/ {
108
- contractAddress: this.context.environment.address,
109
- functionName: fnName,
110
- },
111
- /*noirCallStack=*/ [],
100
+ let bytecode: Buffer | undefined;
101
+ try {
102
+ bytecode = await this.context.persistableState.getBytecode(this.context.environment.address);
103
+ } catch (err: any) {
104
+ if (!(err instanceof AvmExecutionError || err instanceof SideEffectLimitReachedError)) {
105
+ this.log.error(`Unknown error thrown by AVM during bytecode retrieval: ${err}`);
106
+ throw err;
107
+ }
108
+ return await this.handleFailureToRetrieveBytecode(
109
+ `Bytecode retrieval for contract '${this.context.environment.address}' failed with ${err}. Reverting...`,
112
110
  );
113
- this.log.warn(message);
114
- return new AvmContractCallResult(
115
- /*reverted=*/ true,
116
- /*output=*/ [],
117
- /*gasLeft=*/ { l2Gas: 0, daGas: 0 },
118
- revertReason,
111
+ }
112
+
113
+ if (!bytecode) {
114
+ return await this.handleFailureToRetrieveBytecode(
115
+ `No bytecode found at: ${this.context.environment.address}. Reverting...`,
119
116
  );
120
117
  }
121
118
 
@@ -189,14 +186,25 @@ export class AvmSimulator {
189
186
  return results;
190
187
  } catch (err: any) {
191
188
  this.log.verbose('Exceptional halt (revert by something other than REVERT opcode)');
192
- if (!(err instanceof AvmExecutionError || err instanceof SideEffectLimitReachedError)) {
189
+ // FIXME: weird that we have to do this OutOfGasError check because:
190
+ // 1. OutOfGasError is an AvmExecutionError, so that check should cover both
191
+ // 2. We should at least be able to do instanceof OutOfGasError instead of checking the constructor name
192
+ if (
193
+ !(
194
+ err.constructor.name == 'OutOfGasError' ||
195
+ err instanceof AvmExecutionError ||
196
+ err instanceof SideEffectLimitReachedError
197
+ )
198
+ ) {
193
199
  this.log.error(`Unknown error thrown by AVM: ${err}`);
194
200
  throw err;
195
201
  }
196
202
 
197
203
  const revertReason = await revertReasonFromExceptionalHalt(err, this.context);
204
+ // Exceptional halts consume all allocated gas
205
+ const noGasLeft = { l2Gas: 0, daGas: 0 };
198
206
  // Note: "exceptional halts" cannot return data, hence [].
199
- const results = new AvmContractCallResult(/*reverted=*/ true, /*output=*/ [], machineState.gasLeft, revertReason);
207
+ const results = new AvmContractCallResult(/*reverted=*/ true, /*output=*/ [], noGasLeft, revertReason);
200
208
  this.log.debug(`Context execution results: ${results.toString()}`);
201
209
 
202
210
  this.tallyPrintFunction();
@@ -205,6 +213,26 @@ export class AvmSimulator {
205
213
  }
206
214
  }
207
215
 
216
+ private async handleFailureToRetrieveBytecode(message: string): Promise<AvmContractCallResult> {
217
+ // revert, consuming all gas
218
+ const fnName = await this.context.persistableState.getPublicFunctionDebugName(this.context.environment);
219
+ const revertReason = new AvmRevertReason(
220
+ message,
221
+ /*failingFunction=*/ {
222
+ contractAddress: this.context.environment.address,
223
+ functionName: fnName,
224
+ },
225
+ /*noirCallStack=*/ [],
226
+ );
227
+ this.log.warn(message);
228
+ return new AvmContractCallResult(
229
+ /*reverted=*/ true,
230
+ /*output=*/ [],
231
+ /*gasLeft=*/ { l2Gas: 0, daGas: 0 }, // consumes all allocated gas
232
+ revertReason,
233
+ );
234
+ }
235
+
208
236
  private tallyInstruction(pc: number, opcode: string, gasUsed: Gas) {
209
237
  const opcodeTally = this.opcodeTallies.get(opcode) || ({ count: 0, gas: { l2Gas: 0, daGas: 0 } } as OpcodeTally);
210
238
  opcodeTally.count++;
@@ -1,5 +1,5 @@
1
- import { type IndexedTreeId, MerkleTreeId, type MerkleTreeReadOperations, getTreeHeight } from '@aztec/circuit-types';
2
- import { NullifierLeafPreimage, PublicDataTreeLeafPreimage } from '@aztec/circuits.js';
1
+ import { type IndexedTreeId, MerkleTreeId, type MerkleTreeReadOperations } from '@aztec/circuit-types';
2
+ import { AppendOnlyTreeSnapshot, NullifierLeafPreimage, PublicDataTreeLeafPreimage } from '@aztec/circuits.js';
3
3
  import { poseidon2Hash } from '@aztec/foundation/crypto';
4
4
  import { Fr } from '@aztec/foundation/fields';
5
5
  import { type IndexedTreeLeafPreimage, type TreeLeafPreimage } from '@aztec/foundation/trees';
@@ -398,35 +398,36 @@ export class AvmEphemeralForest {
398
398
  // First, search the indexed updates (no DB fallback) to find
399
399
  // the leafIndex of the leaf with the largest key <= the specified key.
400
400
  const minIndexedLeafIndex = this._getLeafIndexOrNextLowestInIndexedUpdates(treeId, key);
401
+
402
+ // Then we search on the external DB
403
+ const leafOrLowLeafWitnessFromExternalDb: GetLeafResult<T> = await this._getLeafOrLowLeafWitnessInExternalDb(
404
+ treeId,
405
+ bigIntKey,
406
+ );
407
+
408
+ // If the indexed updates are empty, we can return the leaf from the DB
401
409
  if (minIndexedLeafIndex === -1n) {
402
- // No leaf is present in the indexed updates that is <= the key,
403
- // so retrieve the leaf or low leaf from the underlying DB.
404
- const leafOrLowLeafPreimage: GetLeafResult<T> = await this._getLeafOrLowLeafWitnessInExternalDb(
405
- treeId,
406
- bigIntKey,
407
- );
408
- return [leafOrLowLeafPreimage, /*pathAbsentInEphemeralTree=*/ true];
410
+ return [leafOrLowLeafWitnessFromExternalDb, /*pathAbsentInEphemeralTree=*/ true];
411
+ }
412
+
413
+ // Otherwise, we return the closest one. First fetch the leaf from the indexed updates.
414
+ const minIndexedUpdate: T = this.getIndexedUpdate(treeId, minIndexedLeafIndex);
415
+
416
+ // And get both keys
417
+ const keyFromIndexed = minIndexedUpdate.getKey();
418
+ const keyFromExternal = leafOrLowLeafWitnessFromExternalDb.preimage.getKey();
419
+
420
+ if (keyFromExternal > keyFromIndexed) {
421
+ // this.log.debug(`Using leaf from external DB for ${MerkleTreeId[treeId]}`);
422
+ return [leafOrLowLeafWitnessFromExternalDb, /*pathAbsentInEphemeralTree=*/ true];
409
423
  } else {
410
- // A leaf was found in the indexed updates that is <= the key
411
- const minPreimage: T = this.getIndexedUpdate(treeId, minIndexedLeafIndex);
412
- if (minPreimage.getKey() === bigIntKey) {
413
- // the index found is an exact match, no need to search further
414
- const leafInfo = { preimage: minPreimage, index: minIndexedLeafIndex, alreadyPresent: true };
415
- return [leafInfo, /*pathAbsentInEphemeralTree=*/ false];
416
- } else {
417
- // We are starting with the leaf with largest key <= the specified key
418
- // Starting at that "min leaf", search for specified key in both the indexed updates
419
- // and the underlying DB. If not found, return its low leaf.
420
- const [leafOrLowLeafInfo, pathAbsentInEphemeralTree] = await this._searchForLeafOrLowLeaf<ID, T>(
421
- treeId,
422
- bigIntKey,
423
- minPreimage,
424
- minIndexedLeafIndex,
425
- );
426
- // We did not find it - this is unexpected... the leaf OR low leaf should always be present
427
- assert(leafOrLowLeafInfo !== undefined, 'Could not find leaf or low leaf. This should not happen!');
428
- return [leafOrLowLeafInfo, pathAbsentInEphemeralTree];
429
- }
424
+ // this.log.debug(`Using leaf from indexed DB for ${MerkleTreeId[treeId]}`);
425
+ const leafInfo = {
426
+ preimage: minIndexedUpdate,
427
+ index: minIndexedLeafIndex,
428
+ alreadyPresent: keyFromIndexed === bigIntKey,
429
+ };
430
+ return [leafInfo, /*pathAbsentInEphemeralTree=*/ false];
430
431
  }
431
432
  }
432
433
 
@@ -480,69 +481,6 @@ export class AvmEphemeralForest {
480
481
  return { preimage: leafPreimage as T, index: leafIndex, alreadyPresent };
481
482
  }
482
483
 
483
- /**
484
- * Search for the leaf for the specified key.
485
- * Some leaf with key <= the specified key is expected to be present in the ephemeral tree's "indexed updates".
486
- * While searching, this function bounces between our local indexedUpdates and the external DB.
487
- *
488
- * @param key - The key for which we are look up the leaf or low leaf for.
489
- * @param minPreimage - The leaf with the largest key <= the specified key. Expected to be present in local indexedUpdates.
490
- * @param minIndex - The index of the leaf with the largest key <= the specified key.
491
- * @param T - The type of the preimage (PublicData or Nullifier)
492
- * @returns [
493
- * GetLeafResult | undefined - The leaf or low leaf info (preimage & leaf index),
494
- * pathAbsentInEphemeralTree - whether its sibling path is absent in the ephemeral tree (useful during insertions)
495
- * ]
496
- *
497
- * @details We look for the low element by bouncing between our local indexedUpdates map or the external DB
498
- * The conditions we are looking for are:
499
- * (1) Exact Match: curr.nextKey == key (this is only valid for public data tree)
500
- * (2) Sandwich Match: curr.nextKey > key and curr.key < key
501
- * (3) Max Condition: curr.next_index == 0 and curr.key < key
502
- * Note the min condition does not need to be handled since indexed trees are prefilled with at least the 0 element
503
- */
504
- private async _searchForLeafOrLowLeaf<ID extends IndexedTreeId, T extends IndexedTreeLeafPreimage>(
505
- treeId: ID,
506
- key: bigint,
507
- minPreimage: T,
508
- minIndex: bigint,
509
- ): Promise<[GetLeafResult<T> | undefined, /*pathAbsentInEphemeralTree=*/ boolean]> {
510
- let found = false;
511
- let curr = minPreimage as T;
512
- let result: GetLeafResult<T> | undefined = undefined;
513
- // Temp to avoid infinite loops - the limit is the number of leaves we may have to read
514
- const LIMIT = 2n ** BigInt(getTreeHeight(treeId)) - 1n;
515
- let counter = 0n;
516
- let lowPublicDataIndex = minIndex;
517
- let pathAbsentInEphemeralTree = false;
518
- while (!found && counter < LIMIT) {
519
- const bigIntKey = key;
520
- if (curr.getKey() === bigIntKey) {
521
- // We found an exact match - therefore this is an update
522
- found = true;
523
- result = { preimage: curr, index: lowPublicDataIndex, alreadyPresent: true };
524
- } else if (curr.getKey() < bigIntKey && (curr.getNextIndex() === 0n || curr.getNextKey() > bigIntKey)) {
525
- // We found it via sandwich or max condition, this is a low nullifier
526
- found = true;
527
- result = { preimage: curr, index: lowPublicDataIndex, alreadyPresent: false };
528
- }
529
- // Update the the values for the next iteration
530
- else {
531
- lowPublicDataIndex = curr.getNextIndex();
532
- if (this.hasLocalUpdates(treeId, lowPublicDataIndex)) {
533
- curr = this.getIndexedUpdate(treeId, lowPublicDataIndex)!;
534
- pathAbsentInEphemeralTree = false;
535
- } else {
536
- const preimage: IndexedTreeLeafPreimage = (await this.treeDb.getLeafPreimage(treeId, lowPublicDataIndex))!;
537
- curr = preimage as T;
538
- pathAbsentInEphemeralTree = true;
539
- }
540
- }
541
- counter++;
542
- }
543
- return [result, pathAbsentInEphemeralTree];
544
- }
545
-
546
484
  /**
547
485
  * This hashes the preimage to a field element
548
486
  */
@@ -550,6 +488,11 @@ export class AvmEphemeralForest {
550
488
  const input = preimage.toHashInputs().map(x => Fr.fromBuffer(x));
551
489
  return poseidon2Hash(input);
552
490
  }
491
+
492
+ getTreeSnapshot(id: MerkleTreeId): AppendOnlyTreeSnapshot {
493
+ const tree = this.treeMap.get(id)!;
494
+ return new AppendOnlyTreeSnapshot(tree.getRoot(), Number(tree.leafCount));
495
+ }
553
496
  }
554
497
 
555
498
  /****************************************************/
package/src/avm/errors.ts CHANGED
@@ -102,10 +102,21 @@ export class TagCheckError extends AvmExecutionError {
102
102
  * Error is thrown when a relative memory address resolved to an offset which
103
103
  * is out of range, i.e, greater than maxUint32.
104
104
  */
105
- export class AddressOutOfRangeError extends AvmExecutionError {
105
+ export class RelativeAddressOutOfRangeError extends AvmExecutionError {
106
106
  constructor(baseAddr: number, relOffset: number) {
107
107
  super(`Address out of range. Base address ${baseAddr}, relative offset ${relOffset}`);
108
- this.name = 'AddressOutOfRangeError';
108
+ this.name = 'RelativeAddressOutOfRangeError';
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Error is thrown when a memory slice contains addresses which are
114
+ * out of range, i.e, greater than maxUint32.
115
+ */
116
+ export class MemorySliceOutOfRangeError extends AvmExecutionError {
117
+ constructor(baseAddr: number, size: number) {
118
+ super(`Memory slice is out of range. Base address ${baseAddr}, size ${size}`);
119
+ this.name = 'MemorySliceOutOfRangeError';
109
120
  }
110
121
  }
111
122
 
@@ -1,10 +1,10 @@
1
- import { isNoirCallStackUnresolved } from '@aztec/circuit-types';
1
+ import { TxHash, isNoirCallStackUnresolved } from '@aztec/circuit-types';
2
2
  import { GasFees, GlobalVariables, MAX_L2_GAS_PER_TX_PUBLIC_PORTION } from '@aztec/circuits.js';
3
3
  import { 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 { AvmTestContractArtifact } from '@aztec/noir-contracts.js';
7
+ import { AvmTestContractArtifact } from '@aztec/noir-contracts.js/AvmTest';
8
8
 
9
9
  import { strict as assert } from 'assert';
10
10
  import { mock } from 'jest-mock-extended';
@@ -45,6 +45,7 @@ export function initPersistableStateManager(overrides?: {
45
45
  nullifiers?: NullifierManager;
46
46
  doMerkleOperations?: boolean;
47
47
  merkleTrees?: AvmEphemeralForest;
48
+ txHash?: TxHash;
48
49
  }): AvmPersistableStateManager {
49
50
  const worldStateDB = overrides?.worldStateDB || mock<WorldStateDB>();
50
51
  return new AvmPersistableStateManager(
@@ -54,6 +55,7 @@ export function initPersistableStateManager(overrides?: {
54
55
  overrides?.nullifiers || new NullifierManager(worldStateDB),
55
56
  overrides?.doMerkleOperations || false,
56
57
  overrides?.merkleTrees || mock<AvmEphemeralForest>(),
58
+ overrides?.txHash || new TxHash(new Fr(27).toBuffer()),
57
59
  );
58
60
  }
59
61