@aztec/simulator 0.62.0 → 0.63.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 (216) hide show
  1. package/dest/acvm/acvm.d.ts +2 -16
  2. package/dest/acvm/acvm.d.ts.map +1 -1
  3. package/dest/acvm/acvm.js +2 -70
  4. package/dest/acvm/oracle/oracle.d.ts +4 -4
  5. package/dest/acvm/oracle/oracle.d.ts.map +1 -1
  6. package/dest/acvm/oracle/oracle.js +10 -11
  7. package/dest/acvm/oracle/typed_oracle.d.ts +5 -5
  8. package/dest/acvm/oracle/typed_oracle.d.ts.map +1 -1
  9. package/dest/acvm/oracle/typed_oracle.js +8 -8
  10. package/dest/avm/avm_gas.d.ts.map +1 -1
  11. package/dest/avm/avm_gas.js +2 -1
  12. package/dest/avm/avm_machine_state.d.ts +27 -8
  13. package/dest/avm/avm_machine_state.d.ts.map +1 -1
  14. package/dest/avm/avm_machine_state.js +6 -10
  15. package/dest/avm/avm_memory_types.d.ts +8 -0
  16. package/dest/avm/avm_memory_types.d.ts.map +1 -1
  17. package/dest/avm/avm_memory_types.js +5 -1
  18. package/dest/avm/avm_simulator.d.ts +2 -19
  19. package/dest/avm/avm_simulator.d.ts.map +1 -1
  20. package/dest/avm/avm_simulator.js +12 -14
  21. package/dest/avm/avm_tree.d.ts +249 -0
  22. package/dest/avm/avm_tree.d.ts.map +1 -0
  23. package/dest/avm/avm_tree.js +637 -0
  24. package/dest/avm/errors.d.ts +4 -17
  25. package/dest/avm/errors.d.ts.map +1 -1
  26. package/dest/avm/errors.js +21 -50
  27. package/dest/avm/fixtures/index.d.ts +7 -2
  28. package/dest/avm/fixtures/index.d.ts.map +1 -1
  29. package/dest/avm/fixtures/index.js +12 -12
  30. package/dest/avm/index.d.ts +1 -0
  31. package/dest/avm/index.d.ts.map +1 -1
  32. package/dest/avm/index.js +2 -1
  33. package/dest/avm/journal/journal.d.ts +43 -24
  34. package/dest/avm/journal/journal.d.ts.map +1 -1
  35. package/dest/avm/journal/journal.js +172 -39
  36. package/dest/avm/journal/nullifiers.d.ts +5 -4
  37. package/dest/avm/journal/nullifiers.d.ts.map +1 -1
  38. package/dest/avm/journal/nullifiers.js +2 -3
  39. package/dest/avm/journal/public_storage.d.ts +6 -5
  40. package/dest/avm/journal/public_storage.d.ts.map +1 -1
  41. package/dest/avm/journal/public_storage.js +1 -1
  42. package/dest/avm/opcodes/accrued_substate.d.ts.map +1 -1
  43. package/dest/avm/opcodes/accrued_substate.js +4 -10
  44. package/dest/avm/opcodes/arithmetic.d.ts +4 -1
  45. package/dest/avm/opcodes/arithmetic.d.ts.map +1 -1
  46. package/dest/avm/opcodes/arithmetic.js +18 -4
  47. package/dest/avm/opcodes/bitwise.d.ts.map +1 -1
  48. package/dest/avm/opcodes/bitwise.js +1 -3
  49. package/dest/avm/opcodes/comparators.d.ts.map +1 -1
  50. package/dest/avm/opcodes/comparators.js +1 -2
  51. package/dest/avm/opcodes/contract.d.ts.map +1 -1
  52. package/dest/avm/opcodes/contract.js +2 -3
  53. package/dest/avm/opcodes/control_flow.d.ts +4 -0
  54. package/dest/avm/opcodes/control_flow.d.ts.map +1 -1
  55. package/dest/avm/opcodes/control_flow.js +21 -6
  56. package/dest/avm/opcodes/conversion.d.ts.map +1 -1
  57. package/dest/avm/opcodes/conversion.js +1 -2
  58. package/dest/avm/opcodes/ec_add.d.ts.map +1 -1
  59. package/dest/avm/opcodes/ec_add.js +5 -11
  60. package/dest/avm/opcodes/environment_getters.d.ts.map +1 -1
  61. package/dest/avm/opcodes/environment_getters.js +1 -2
  62. package/dest/avm/opcodes/external_calls.d.ts +4 -2
  63. package/dest/avm/opcodes/external_calls.d.ts.map +1 -1
  64. package/dest/avm/opcodes/external_calls.js +38 -22
  65. package/dest/avm/opcodes/hashing.d.ts.map +1 -1
  66. package/dest/avm/opcodes/hashing.js +1 -4
  67. package/dest/avm/opcodes/instruction.d.ts +4 -0
  68. package/dest/avm/opcodes/instruction.d.ts.map +1 -1
  69. package/dest/avm/opcodes/instruction.js +7 -1
  70. package/dest/avm/opcodes/memory.d.ts.map +1 -1
  71. package/dest/avm/opcodes/memory.js +1 -7
  72. package/dest/avm/opcodes/misc.js +3 -3
  73. package/dest/avm/opcodes/multi_scalar_mul.d.ts.map +1 -1
  74. package/dest/avm/opcodes/multi_scalar_mul.js +6 -5
  75. package/dest/avm/opcodes/storage.d.ts.map +1 -1
  76. package/dest/avm/opcodes/storage.js +2 -4
  77. package/dest/avm/serialization/bytecode_serialization.d.ts +1 -6
  78. package/dest/avm/serialization/bytecode_serialization.d.ts.map +1 -1
  79. package/dest/avm/serialization/bytecode_serialization.js +24 -20
  80. package/dest/client/client_execution_context.d.ts +7 -11
  81. package/dest/client/client_execution_context.d.ts.map +1 -1
  82. package/dest/client/client_execution_context.js +18 -20
  83. package/dest/client/db_oracle.d.ts +17 -10
  84. package/dest/client/db_oracle.d.ts.map +1 -1
  85. package/dest/client/db_oracle.js +1 -1
  86. package/dest/client/private_execution.d.ts.map +1 -1
  87. package/dest/client/private_execution.js +5 -4
  88. package/dest/client/unconstrained_execution.d.ts.map +1 -1
  89. package/dest/client/unconstrained_execution.js +3 -2
  90. package/dest/client/view_data_oracle.d.ts +6 -12
  91. package/dest/client/view_data_oracle.d.ts.map +1 -1
  92. package/dest/client/view_data_oracle.js +10 -12
  93. package/dest/common/errors.d.ts +15 -2
  94. package/dest/common/errors.d.ts.map +1 -1
  95. package/dest/common/errors.js +85 -4
  96. package/dest/mocks/fixtures.d.ts +9 -28
  97. package/dest/mocks/fixtures.d.ts.map +1 -1
  98. package/dest/mocks/fixtures.js +12 -57
  99. package/dest/public/dual_side_effect_trace.d.ts +34 -26
  100. package/dest/public/dual_side_effect_trace.d.ts.map +1 -1
  101. package/dest/public/dual_side_effect_trace.js +48 -36
  102. package/dest/public/enqueued_call_side_effect_trace.d.ts +96 -33
  103. package/dest/public/enqueued_call_side_effect_trace.d.ts.map +1 -1
  104. package/dest/public/enqueued_call_side_effect_trace.js +212 -138
  105. package/dest/public/execution.d.ts +50 -17
  106. package/dest/public/execution.d.ts.map +1 -1
  107. package/dest/public/execution.js +1 -29
  108. package/dest/public/executor.d.ts +28 -11
  109. package/dest/public/executor.d.ts.map +1 -1
  110. package/dest/public/executor.js +33 -33
  111. package/dest/public/index.d.ts +4 -5
  112. package/dest/public/index.d.ts.map +1 -1
  113. package/dest/public/index.js +4 -5
  114. package/dest/public/public_db_sources.d.ts +1 -0
  115. package/dest/public/public_db_sources.d.ts.map +1 -1
  116. package/dest/public/public_db_sources.js +12 -5
  117. package/dest/public/public_processor.d.ts +7 -11
  118. package/dest/public/public_processor.d.ts.map +1 -1
  119. package/dest/public/public_processor.js +60 -42
  120. package/dest/public/public_processor_metrics.d.ts +3 -3
  121. package/dest/public/public_processor_metrics.d.ts.map +1 -1
  122. package/dest/public/public_processor_metrics.js +1 -1
  123. package/dest/public/public_tx_context.d.ts +130 -0
  124. package/dest/public/public_tx_context.d.ts.map +1 -0
  125. package/dest/public/public_tx_context.js +293 -0
  126. package/dest/public/public_tx_simulator.d.ts +36 -0
  127. package/dest/public/public_tx_simulator.d.ts.map +1 -0
  128. package/dest/public/public_tx_simulator.js +148 -0
  129. package/dest/public/side_effect_trace.d.ts +30 -15
  130. package/dest/public/side_effect_trace.d.ts.map +1 -1
  131. package/dest/public/side_effect_trace.js +70 -16
  132. package/dest/public/side_effect_trace_interface.d.ts +43 -12
  133. package/dest/public/side_effect_trace_interface.d.ts.map +1 -1
  134. package/dest/public/transitional_adapters.d.ts +9 -0
  135. package/dest/public/transitional_adapters.d.ts.map +1 -0
  136. package/dest/public/transitional_adapters.js +127 -0
  137. package/dest/public/utils.d.ts +5 -0
  138. package/dest/public/utils.d.ts.map +1 -0
  139. package/dest/public/utils.js +30 -0
  140. package/package.json +12 -9
  141. package/src/acvm/acvm.ts +3 -94
  142. package/src/acvm/oracle/oracle.ts +9 -14
  143. package/src/acvm/oracle/typed_oracle.ts +8 -8
  144. package/src/avm/avm_gas.ts +1 -0
  145. package/src/avm/avm_machine_state.ts +28 -12
  146. package/src/avm/avm_memory_types.ts +5 -0
  147. package/src/avm/avm_simulator.ts +13 -16
  148. package/src/avm/avm_tree.ts +785 -0
  149. package/src/avm/errors.ts +25 -48
  150. package/src/avm/fixtures/index.ts +16 -12
  151. package/src/avm/index.ts +1 -0
  152. package/src/avm/journal/journal.ts +291 -52
  153. package/src/avm/journal/nullifiers.ts +7 -7
  154. package/src/avm/journal/public_storage.ts +5 -5
  155. package/src/avm/opcodes/accrued_substate.ts +3 -9
  156. package/src/avm/opcodes/arithmetic.ts +26 -4
  157. package/src/avm/opcodes/bitwise.ts +0 -2
  158. package/src/avm/opcodes/comparators.ts +0 -1
  159. package/src/avm/opcodes/contract.ts +1 -2
  160. package/src/avm/opcodes/control_flow.ts +24 -5
  161. package/src/avm/opcodes/conversion.ts +0 -1
  162. package/src/avm/opcodes/ec_add.ts +6 -9
  163. package/src/avm/opcodes/environment_getters.ts +0 -1
  164. package/src/avm/opcodes/external_calls.ts +39 -21
  165. package/src/avm/opcodes/hashing.ts +0 -3
  166. package/src/avm/opcodes/instruction.ts +7 -0
  167. package/src/avm/opcodes/memory.ts +0 -6
  168. package/src/avm/opcodes/misc.ts +2 -2
  169. package/src/avm/opcodes/multi_scalar_mul.ts +5 -4
  170. package/src/avm/opcodes/storage.ts +1 -3
  171. package/src/avm/serialization/bytecode_serialization.ts +31 -22
  172. package/src/client/client_execution_context.ts +22 -23
  173. package/src/client/db_oracle.ts +22 -11
  174. package/src/client/private_execution.ts +5 -4
  175. package/src/client/unconstrained_execution.ts +2 -1
  176. package/src/client/view_data_oracle.ts +14 -13
  177. package/src/common/errors.ts +119 -3
  178. package/src/mocks/fixtures.ts +15 -106
  179. package/src/public/dual_side_effect_trace.ts +138 -50
  180. package/src/public/enqueued_call_side_effect_trace.ts +352 -212
  181. package/src/public/execution.ts +58 -42
  182. package/src/public/executor.ts +52 -67
  183. package/src/public/index.ts +7 -5
  184. package/src/public/public_db_sources.ts +12 -4
  185. package/src/public/public_processor.ts +111 -73
  186. package/src/public/public_processor_metrics.ts +3 -3
  187. package/src/public/public_tx_context.ts +411 -0
  188. package/src/public/public_tx_simulator.ts +232 -0
  189. package/src/public/side_effect_trace.ts +154 -28
  190. package/src/public/side_effect_trace_interface.ts +92 -14
  191. package/src/public/transitional_adapters.ts +347 -0
  192. package/src/public/utils.ts +32 -0
  193. package/dest/public/enqueued_call_simulator.d.ts +0 -43
  194. package/dest/public/enqueued_call_simulator.d.ts.map +0 -1
  195. package/dest/public/enqueued_call_simulator.js +0 -156
  196. package/dest/public/enqueued_calls_processor.d.ts +0 -43
  197. package/dest/public/enqueued_calls_processor.d.ts.map +0 -1
  198. package/dest/public/enqueued_calls_processor.js +0 -209
  199. package/dest/public/hints_builder.d.ts +0 -29
  200. package/dest/public/hints_builder.d.ts.map +0 -1
  201. package/dest/public/hints_builder.js +0 -75
  202. package/dest/public/public_kernel.d.ts +0 -30
  203. package/dest/public/public_kernel.d.ts.map +0 -1
  204. package/dest/public/public_kernel.js +0 -67
  205. package/dest/public/public_kernel_circuit_simulator.d.ts +0 -25
  206. package/dest/public/public_kernel_circuit_simulator.d.ts.map +0 -1
  207. package/dest/public/public_kernel_circuit_simulator.js +0 -2
  208. package/dest/public/public_kernel_tail_simulator.d.ts +0 -15
  209. package/dest/public/public_kernel_tail_simulator.d.ts.map +0 -1
  210. package/dest/public/public_kernel_tail_simulator.js +0 -39
  211. package/src/public/enqueued_call_simulator.ts +0 -360
  212. package/src/public/enqueued_calls_processor.ts +0 -372
  213. package/src/public/hints_builder.ts +0 -168
  214. package/src/public/public_kernel.ts +0 -100
  215. package/src/public/public_kernel_circuit_simulator.ts +0 -32
  216. package/src/public/public_kernel_tail_simulator.ts +0 -97
@@ -1,11 +1,18 @@
1
+ import { type IndexedTreeId, MerkleTreeId, type MerkleTreeWriteOperations } from '@aztec/circuit-types';
1
2
  import {
2
- AztecAddress,
3
+ type AztecAddress,
3
4
  type Gas,
5
+ type NullifierLeafPreimage,
6
+ type PublicCallRequest,
7
+ PublicDataTreeLeaf,
8
+ PublicDataTreeLeafPreimage,
4
9
  SerializableContractInstance,
5
10
  computePublicBytecodeCommitment,
6
11
  } from '@aztec/circuits.js';
12
+ import { computePublicDataTreeLeafSlot, siloNoteHash, siloNullifier } from '@aztec/circuits.js/hash';
7
13
  import { Fr } from '@aztec/foundation/fields';
8
14
  import { createDebugLogger } from '@aztec/foundation/log';
15
+ import { type IndexedTreeLeafPreimage } from '@aztec/foundation/trees';
9
16
 
10
17
  import assert from 'assert';
11
18
 
@@ -29,17 +36,31 @@ import { PublicStorage } from './public_storage.js';
29
36
  export class AvmPersistableStateManager {
30
37
  private readonly log = createDebugLogger('aztec:avm_simulator:state_manager');
31
38
 
39
+ /** Interface to perform merkle tree operations */
40
+ public merkleTrees: MerkleTreeWriteOperations;
41
+
42
+ /** Make sure a forked state is never merged twice. */
43
+ private alreadyMergedIntoParent = false;
44
+
32
45
  constructor(
33
46
  /** Reference to node storage */
34
47
  private readonly worldStateDB: WorldStateDB,
35
48
  /** Side effect trace */
36
- private readonly trace: PublicSideEffectTraceInterface,
37
- /** Public storage, including cached writes */
38
49
  // TODO(5818): make private once no longer accessed in executor
39
- public readonly publicStorage: PublicStorage,
50
+ public readonly trace: PublicSideEffectTraceInterface,
51
+ /** Public storage, including cached writes */
52
+ private readonly publicStorage: PublicStorage = new PublicStorage(worldStateDB),
40
53
  /** Nullifier set, including cached/recently-emitted nullifiers */
41
- private readonly nullifiers: NullifierManager,
42
- ) {}
54
+ private readonly nullifiers: NullifierManager = new NullifierManager(worldStateDB),
55
+ private readonly doMerkleOperations: boolean = false,
56
+ merkleTrees?: MerkleTreeWriteOperations,
57
+ ) {
58
+ if (merkleTrees) {
59
+ this.merkleTrees = merkleTrees;
60
+ } else {
61
+ this.merkleTrees = worldStateDB.getMerkleInterface();
62
+ }
63
+ }
43
64
 
44
65
  /**
45
66
  * Create a new state manager with some preloaded pending siloed nullifiers
@@ -67,9 +88,40 @@ export class AvmPersistableStateManager {
67
88
  this.trace.fork(),
68
89
  this.publicStorage.fork(),
69
90
  this.nullifiers.fork(),
91
+ this.doMerkleOperations,
70
92
  );
71
93
  }
72
94
 
95
+ /**
96
+ * Accept forked world state modifications & traced side effects / hints
97
+ */
98
+ public merge(forkedState: AvmPersistableStateManager) {
99
+ this._merge(forkedState, /*reverted=*/ false);
100
+ }
101
+
102
+ /**
103
+ * Reject forked world state modifications & traced side effects, keep traced hints
104
+ */
105
+ public reject(forkedState: AvmPersistableStateManager) {
106
+ this._merge(forkedState, /*reverted=*/ true);
107
+ }
108
+
109
+ /**
110
+ * Commit cached storage writes to the DB.
111
+ * Keeps public storage up to date from tx to tx within a block.
112
+ */
113
+ public async commitStorageWritesToDB() {
114
+ await this.publicStorage.commitToDB();
115
+ }
116
+
117
+ private _merge(forkedState: AvmPersistableStateManager, reverted: boolean) {
118
+ // sanity check to avoid merging the same forked trace twice
119
+ assert(!this.alreadyMergedIntoParent, 'Cannot merge forked state that has already been merged into its parent!');
120
+ this.publicStorage.acceptAndMerge(forkedState.publicStorage);
121
+ this.nullifiers.acceptAndMerge(forkedState.nullifiers);
122
+ this.trace.merge(forkedState.trace, reverted);
123
+ }
124
+
73
125
  /**
74
126
  * Write to public storage, journal/trace the write.
75
127
  *
@@ -77,11 +129,46 @@ export class AvmPersistableStateManager {
77
129
  * @param slot - the slot in the contract's storage being written to
78
130
  * @param value - the value being written to the slot
79
131
  */
80
- public writeStorage(contractAddress: Fr, slot: Fr, value: Fr) {
132
+ public async writeStorage(contractAddress: AztecAddress, slot: Fr, value: Fr): Promise<void> {
81
133
  this.log.debug(`Storage write (address=${contractAddress}, slot=${slot}): value=${value}`);
82
134
  // Cache storage writes for later reference/reads
83
135
  this.publicStorage.write(contractAddress, slot, value);
84
- this.trace.tracePublicStorageWrite(contractAddress, slot, value);
136
+ const leafSlot = computePublicDataTreeLeafSlot(contractAddress, slot);
137
+ if (this.doMerkleOperations) {
138
+ const result = await this.merkleTrees.batchInsert(
139
+ MerkleTreeId.PUBLIC_DATA_TREE,
140
+ [new PublicDataTreeLeaf(leafSlot, value).toBuffer()],
141
+ 0,
142
+ );
143
+ assert(result !== undefined, 'Public data tree insertion error. You might want to disable skipMerkleOperations.');
144
+ this.log.debug(`Inserted public data tree leaf at leafSlot ${leafSlot}, value: ${value}`);
145
+
146
+ const lowLeafInfo = result.lowLeavesWitnessData![0];
147
+ const lowLeafPreimage = lowLeafInfo.leafPreimage as PublicDataTreeLeafPreimage;
148
+ const lowLeafIndex = lowLeafInfo.index;
149
+ const lowLeafPath = lowLeafInfo.siblingPath.toFields();
150
+
151
+ const insertionPath = result.newSubtreeSiblingPath.toFields();
152
+ const newLeafPreimage = new PublicDataTreeLeafPreimage(
153
+ leafSlot,
154
+ value,
155
+ lowLeafPreimage.nextSlot,
156
+ lowLeafPreimage.nextIndex,
157
+ );
158
+ // FIXME: Why do we need to hint both preimages for public data writes, but not for nullifier insertions?
159
+ this.trace.tracePublicStorageWrite(
160
+ contractAddress,
161
+ slot,
162
+ value,
163
+ lowLeafPreimage,
164
+ new Fr(lowLeafIndex),
165
+ lowLeafPath,
166
+ newLeafPreimage,
167
+ insertionPath,
168
+ );
169
+ } else {
170
+ this.trace.tracePublicStorageWrite(contractAddress, slot, value);
171
+ }
85
172
  }
86
173
 
87
174
  /**
@@ -91,12 +178,46 @@ export class AvmPersistableStateManager {
91
178
  * @param slot - the slot in the contract's storage being read from
92
179
  * @returns the latest value written to slot, or 0 if never written to before
93
180
  */
94
- public async readStorage(contractAddress: Fr, slot: Fr): Promise<Fr> {
181
+ public async readStorage(contractAddress: AztecAddress, slot: Fr): Promise<Fr> {
95
182
  const { value, exists, cached } = await this.publicStorage.read(contractAddress, slot);
96
183
  this.log.debug(
97
184
  `Storage read (address=${contractAddress}, slot=${slot}): value=${value}, exists=${exists}, cached=${cached}`,
98
185
  );
99
- this.trace.tracePublicStorageRead(contractAddress, slot, value, exists, cached);
186
+
187
+ const leafSlot = computePublicDataTreeLeafSlot(contractAddress, slot);
188
+
189
+ if (this.doMerkleOperations) {
190
+ // Get leaf if present, low leaf if absent
191
+ // If leaf is present, hint/trace it. Otherwise, hint/trace the low leaf.
192
+ const [leafIndex, leafPreimage, leafPath, _alreadyPresent] = await getLeafOrLowLeaf<PublicDataTreeLeafPreimage>(
193
+ MerkleTreeId.PUBLIC_DATA_TREE,
194
+ leafSlot.toBigInt(),
195
+ this.merkleTrees,
196
+ );
197
+ // FIXME: cannot have this assertion until "caching" is done via ephemeral merkle writes
198
+ //assert(alreadyPresent == exists, 'WorldStateDB contains public data leaf, but merkle tree does not.... This is a bug!');
199
+ this.log.debug(
200
+ `leafPreimage.nextSlot: ${leafPreimage.nextSlot}, leafPreimage.nextIndex: ${Number(leafPreimage.nextIndex)}`,
201
+ );
202
+ this.log.debug(`leafPreimage.slot: ${leafPreimage.slot}, leafPreimage.value: ${leafPreimage.value}`);
203
+
204
+ if (!exists) {
205
+ // Sanity check that the leaf slot is skipped by low leaf when it doesn't exist
206
+ assert(
207
+ leafSlot.toBigInt() > leafPreimage.slot.toBigInt() && leafSlot.toBigInt() < leafPreimage.nextSlot.toBigInt(),
208
+ 'Public data tree low leaf should skip the target leaf slot when the target leaf does not exist.',
209
+ );
210
+ }
211
+ this.log.debug(
212
+ `Tracing storage leaf preimage slot=${slot}, leafSlot=${leafSlot}, value=${value}, nextKey=${leafPreimage.nextSlot}, nextIndex=${leafPreimage.nextIndex}`,
213
+ );
214
+ // On non-existence, AVM circuit will need to recognize that leafPreimage.slot != leafSlot,
215
+ // prove that this is a low leaf that skips leafSlot, and then prove memebership of the leaf.
216
+ this.trace.tracePublicStorageRead(contractAddress, slot, value, leafPreimage, new Fr(leafIndex), leafPath);
217
+ } else {
218
+ this.trace.tracePublicStorageRead(contractAddress, slot, value);
219
+ }
220
+
100
221
  return Promise.resolve(value);
101
222
  }
102
223
 
@@ -107,7 +228,7 @@ export class AvmPersistableStateManager {
107
228
  * @param slot - the slot in the contract's storage being read from
108
229
  * @returns the latest value written to slot, or 0 if never written to before
109
230
  */
110
- public async peekStorage(contractAddress: Fr, slot: Fr): Promise<Fr> {
231
+ public async peekStorage(contractAddress: AztecAddress, slot: Fr): Promise<Fr> {
111
232
  const { value, exists, cached } = await this.publicStorage.read(contractAddress, slot);
112
233
  this.log.debug(
113
234
  `Storage peek (address=${contractAddress}, slot=${slot}): value=${value}, exists=${exists}, cached=${cached}`,
@@ -124,15 +245,20 @@ export class AvmPersistableStateManager {
124
245
  * @param leafIndex - the leaf index being checked
125
246
  * @returns true if the note hash exists at the given leaf index, false otherwise
126
247
  */
127
- public async checkNoteHashExists(contractAddress: Fr, noteHash: Fr, leafIndex: Fr): Promise<boolean> {
248
+ public async checkNoteHashExists(contractAddress: AztecAddress, noteHash: Fr, leafIndex: Fr): Promise<boolean> {
128
249
  const gotLeafValue = (await this.worldStateDB.getCommitmentValue(leafIndex.toBigInt())) ?? Fr.ZERO;
129
250
  const exists = gotLeafValue.equals(noteHash);
130
251
  this.log.debug(
131
252
  `noteHashes(${contractAddress})@${noteHash} ?? leafIndex: ${leafIndex} | gotLeafValue: ${gotLeafValue}, exists: ${exists}.`,
132
253
  );
133
- // TODO(8287): We still return exists here, but we need to transmit both the requested noteHash and the gotLeafValue
134
- // such that the VM can constrain the equality and decide on exists based on that.
135
- this.trace.traceNoteHashCheck(contractAddress, gotLeafValue, leafIndex, exists);
254
+ if (this.doMerkleOperations) {
255
+ // TODO(8287): We still return exists here, but we need to transmit both the requested noteHash and the gotLeafValue
256
+ // such that the VM can constrain the equality and decide on exists based on that.
257
+ const path = await this.merkleTrees.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex.toBigInt());
258
+ this.trace.traceNoteHashCheck(contractAddress, gotLeafValue, leafIndex, exists, path.toFields());
259
+ } else {
260
+ this.trace.traceNoteHashCheck(contractAddress, gotLeafValue, leafIndex, exists);
261
+ }
136
262
  return Promise.resolve(exists);
137
263
  }
138
264
 
@@ -140,9 +266,22 @@ export class AvmPersistableStateManager {
140
266
  * Write a note hash, trace the write.
141
267
  * @param noteHash - the unsiloed note hash to write
142
268
  */
143
- public writeNoteHash(contractAddress: Fr, noteHash: Fr) {
269
+ public async writeNoteHash(contractAddress: AztecAddress, noteHash: Fr): Promise<void> {
144
270
  this.log.debug(`noteHashes(${contractAddress}) += @${noteHash}.`);
145
- this.trace.traceNewNoteHash(contractAddress, noteHash);
271
+
272
+ if (this.doMerkleOperations) {
273
+ // TODO: We should track this globally here in the state manager
274
+ const info = await this.merkleTrees.getTreeInfo(MerkleTreeId.NOTE_HASH_TREE);
275
+ const leafIndex = new Fr(info.size + 1n);
276
+
277
+ const path = await this.merkleTrees.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex.toBigInt());
278
+ const siloedNoteHash = siloNoteHash(contractAddress, noteHash);
279
+
280
+ await this.merkleTrees.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, [siloedNoteHash]);
281
+ this.trace.traceNewNoteHash(contractAddress, noteHash, leafIndex, path.toFields());
282
+ } else {
283
+ this.trace.traceNewNoteHash(contractAddress, noteHash);
284
+ }
146
285
  }
147
286
 
148
287
  /**
@@ -151,12 +290,52 @@ export class AvmPersistableStateManager {
151
290
  * @param nullifier - the unsiloed nullifier to check
152
291
  * @returns exists - whether the nullifier exists in the nullifier set
153
292
  */
154
- public async checkNullifierExists(contractAddress: Fr, nullifier: Fr): Promise<boolean> {
155
- const [exists, isPending, leafIndex] = await this.nullifiers.checkExists(contractAddress, nullifier);
156
- this.log.debug(
157
- `nullifiers(${contractAddress})@${nullifier} ?? leafIndex: ${leafIndex}, exists: ${exists}, pending: ${isPending}.`,
158
- );
159
- this.trace.traceNullifierCheck(contractAddress, nullifier, leafIndex, exists, isPending);
293
+ public async checkNullifierExists(contractAddress: AztecAddress, nullifier: Fr): Promise<boolean> {
294
+ const [exists, isPending, _] = await this.nullifiers.checkExists(contractAddress, nullifier);
295
+
296
+ const siloedNullifier = siloNullifier(contractAddress, nullifier);
297
+
298
+ if (this.doMerkleOperations) {
299
+ // Get leaf if present, low leaf if absent
300
+ // If leaf is present, hint/trace it. Otherwise, hint/trace the low leaf.
301
+ const [leafIndex, leafPreimage, leafPath, alreadyPresent] = await getLeafOrLowLeaf<NullifierLeafPreimage>(
302
+ MerkleTreeId.NULLIFIER_TREE,
303
+ siloedNullifier.toBigInt(),
304
+ this.merkleTrees,
305
+ );
306
+ assert(
307
+ alreadyPresent == exists,
308
+ 'WorldStateDB contains nullifier leaf, but merkle tree does not.... This is a bug!',
309
+ );
310
+
311
+ this.log.debug(
312
+ `nullifiers(${contractAddress})@${nullifier} ?? leafIndex: ${leafIndex}, exists: ${exists}, pending: ${isPending}.`,
313
+ );
314
+
315
+ if (!exists) {
316
+ // Sanity check that the leaf value is skipped by low leaf when it doesn't exist
317
+ assert(
318
+ siloedNullifier.toBigInt() > leafPreimage.nullifier.toBigInt() &&
319
+ siloedNullifier.toBigInt() < leafPreimage.nextNullifier.toBigInt(),
320
+ 'Nullifier tree low leaf should skip the target leaf nullifier when the target leaf does not exist.',
321
+ );
322
+ }
323
+
324
+ this.trace.traceNullifierCheck(
325
+ contractAddress,
326
+ nullifier, // FIXME: Should this be siloed?
327
+ exists,
328
+ leafPreimage,
329
+ new Fr(leafIndex),
330
+ leafPath,
331
+ );
332
+ } else {
333
+ this.trace.traceNullifierCheck(
334
+ contractAddress,
335
+ nullifier, // FIXME: Should this be siloed?
336
+ exists,
337
+ );
338
+ }
160
339
  return Promise.resolve(exists);
161
340
  }
162
341
 
@@ -165,12 +344,44 @@ export class AvmPersistableStateManager {
165
344
  * @param contractAddress - address of the contract that the nullifier is associated with
166
345
  * @param nullifier - the unsiloed nullifier to write
167
346
  */
168
- public async writeNullifier(contractAddress: Fr, nullifier: Fr) {
347
+ public async writeNullifier(contractAddress: AztecAddress, nullifier: Fr) {
169
348
  this.log.debug(`nullifiers(${contractAddress}) += ${nullifier}.`);
170
349
  // Cache pending nullifiers for later access
171
350
  await this.nullifiers.append(contractAddress, nullifier);
172
- // Trace all nullifier creations (even reverted ones)
173
- this.trace.traceNewNullifier(contractAddress, nullifier);
351
+
352
+ const siloedNullifier = siloNullifier(contractAddress, nullifier);
353
+
354
+ if (this.doMerkleOperations) {
355
+ // Trace all nullifier creations, even duplicate insertions that fail
356
+ const alreadyPresent = await this.merkleTrees.getPreviousValueIndex(
357
+ MerkleTreeId.NULLIFIER_TREE,
358
+ siloedNullifier.toBigInt(),
359
+ );
360
+ if (alreadyPresent) {
361
+ this.log.verbose(`Nullifier already present in tree: ${nullifier} at index ${alreadyPresent.index}.`);
362
+ }
363
+ const insertionResult = await this.merkleTrees.batchInsert(
364
+ MerkleTreeId.NULLIFIER_TREE,
365
+ [siloedNullifier.toBuffer()],
366
+ 0,
367
+ );
368
+ const lowLeafInfo = insertionResult.lowLeavesWitnessData![0];
369
+ const lowLeafPreimage = lowLeafInfo.leafPreimage as NullifierLeafPreimage;
370
+ const lowLeafIndex = lowLeafInfo.index;
371
+ const lowLeafPath = lowLeafInfo.siblingPath.toFields();
372
+ const insertionPath = insertionResult.newSubtreeSiblingPath.toFields();
373
+
374
+ this.trace.traceNewNullifier(
375
+ contractAddress,
376
+ nullifier,
377
+ lowLeafPreimage,
378
+ new Fr(lowLeafIndex),
379
+ lowLeafPath,
380
+ insertionPath,
381
+ );
382
+ } else {
383
+ this.trace.traceNewNullifier(contractAddress, nullifier);
384
+ }
174
385
  }
175
386
 
176
387
  /**
@@ -179,15 +390,25 @@ export class AvmPersistableStateManager {
179
390
  * @param msgLeafIndex - the message leaf index to use in the check
180
391
  * @returns exists - whether the message exists in the L1 to L2 Messages tree
181
392
  */
182
- public async checkL1ToL2MessageExists(contractAddress: Fr, msgHash: Fr, msgLeafIndex: Fr): Promise<boolean> {
393
+ public async checkL1ToL2MessageExists(
394
+ contractAddress: AztecAddress,
395
+ msgHash: Fr,
396
+ msgLeafIndex: Fr,
397
+ ): Promise<boolean> {
183
398
  const valueAtIndex = (await this.worldStateDB.getL1ToL2LeafValue(msgLeafIndex.toBigInt())) ?? Fr.ZERO;
184
399
  const exists = valueAtIndex.equals(msgHash);
185
400
  this.log.debug(
186
401
  `l1ToL2Messages(@${msgLeafIndex}) ?? exists: ${exists}, expected: ${msgHash}, found: ${valueAtIndex}.`,
187
402
  );
188
- // TODO(8287): We still return exists here, but we need to transmit both the requested msgHash and the value
189
- // such that the VM can constrain the equality and decide on exists based on that.
190
- this.trace.traceL1ToL2MessageCheck(contractAddress, valueAtIndex, msgLeafIndex, exists);
403
+
404
+ if (this.doMerkleOperations) {
405
+ // TODO(8287): We still return exists here, but we need to transmit both the requested msgHash and the value
406
+ // such that the VM can constrain the equality and decide on exists based on that.
407
+ const path = await this.merkleTrees.getSiblingPath(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, msgLeafIndex.toBigInt());
408
+ this.trace.traceL1ToL2MessageCheck(contractAddress, valueAtIndex, msgLeafIndex, exists, path.toFields());
409
+ } else {
410
+ this.trace.traceL1ToL2MessageCheck(contractAddress, valueAtIndex, msgLeafIndex, exists);
411
+ }
191
412
  return Promise.resolve(exists);
192
413
  }
193
414
 
@@ -197,7 +418,7 @@ export class AvmPersistableStateManager {
197
418
  * @param recipient - L1 contract address to send the message to.
198
419
  * @param content - Message content.
199
420
  */
200
- public writeL2ToL1Message(contractAddress: Fr, recipient: Fr, content: Fr) {
421
+ public writeL2ToL1Message(contractAddress: AztecAddress, recipient: Fr, content: Fr) {
201
422
  this.log.debug(`L2ToL1Messages(${contractAddress}) += (recipient: ${recipient}, content: ${content}).`);
202
423
  this.trace.traceNewL2ToL1Message(contractAddress, recipient, content);
203
424
  }
@@ -208,7 +429,7 @@ export class AvmPersistableStateManager {
208
429
  * @param event - log event selector
209
430
  * @param log - log contents
210
431
  */
211
- public writeUnencryptedLog(contractAddress: Fr, log: Fr[]) {
432
+ public writeUnencryptedLog(contractAddress: AztecAddress, log: Fr[]) {
212
433
  this.log.debug(`UnencryptedL2Log(${contractAddress}) += event with ${log.length} fields.`);
213
434
  this.trace.traceUnencryptedLog(contractAddress, log);
214
435
  }
@@ -218,11 +439,12 @@ export class AvmPersistableStateManager {
218
439
  * @param contractAddress - address of the contract instance to retrieve.
219
440
  * @returns the contract instance or undefined if it does not exist.
220
441
  */
221
- public async getContractInstance(contractAddress: Fr): Promise<SerializableContractInstance | undefined> {
442
+ public async getContractInstance(contractAddress: AztecAddress): Promise<SerializableContractInstance | undefined> {
222
443
  this.log.debug(`Getting contract instance for address ${contractAddress}`);
223
- const instanceWithAddress = await this.worldStateDB.getContractInstance(AztecAddress.fromField(contractAddress));
444
+ const instanceWithAddress = await this.worldStateDB.getContractInstance(contractAddress);
224
445
  const exists = instanceWithAddress !== undefined;
225
446
 
447
+ // TODO: nullifier check!
226
448
  if (exists) {
227
449
  const instance = new SerializableContractInstance(instanceWithAddress);
228
450
  this.log.debug(
@@ -238,14 +460,6 @@ export class AvmPersistableStateManager {
238
460
  }
239
461
  }
240
462
 
241
- /**
242
- * Accept nested world state modifications
243
- */
244
- public acceptNestedCallState(nestedState: AvmPersistableStateManager) {
245
- this.publicStorage.acceptAndMerge(nestedState.publicStorage);
246
- this.nullifiers.acceptAndMerge(nestedState.nullifiers);
247
- }
248
-
249
463
  /**
250
464
  * Get a contract's bytecode from the contracts DB, also trace the contract class and instance
251
465
  */
@@ -286,20 +500,14 @@ export class AvmPersistableStateManager {
286
500
  }
287
501
  }
288
502
 
289
- /**
290
- * Accept the nested call's state and trace the nested call
291
- */
292
- public async processNestedCall(
293
- nestedState: AvmPersistableStateManager,
503
+ public async traceNestedCall(
504
+ forkedState: AvmPersistableStateManager,
294
505
  nestedEnvironment: AvmExecutionEnvironment,
295
506
  startGasLeft: Gas,
296
507
  endGasLeft: Gas,
297
508
  bytecode: Buffer,
298
509
  avmCallResults: AvmContractCallResult,
299
510
  ) {
300
- if (!avmCallResults.reverted) {
301
- this.acceptNestedCallState(nestedState);
302
- }
303
511
  const functionName = await getPublicFunctionDebugName(
304
512
  this.worldStateDB,
305
513
  nestedEnvironment.address,
@@ -307,10 +515,10 @@ export class AvmPersistableStateManager {
307
515
  nestedEnvironment.calldata,
308
516
  );
309
517
 
310
- this.log.verbose(`[AVM] Calling nested function ${functionName}`);
518
+ this.log.verbose(`[AVM] Tracing nested external contract call ${functionName}`);
311
519
 
312
520
  this.trace.traceNestedCall(
313
- nestedState.trace,
521
+ forkedState.trace,
314
522
  nestedEnvironment,
315
523
  startGasLeft,
316
524
  endGasLeft,
@@ -319,4 +527,35 @@ export class AvmPersistableStateManager {
319
527
  functionName,
320
528
  );
321
529
  }
530
+
531
+ public traceEnqueuedCall(publicCallRequest: PublicCallRequest, calldata: Fr[], reverted: boolean) {
532
+ this.trace.traceEnqueuedCall(publicCallRequest, calldata, reverted);
533
+ }
534
+ }
535
+
536
+ /**
537
+ * Get leaf if present, low leaf if absent
538
+ */
539
+ export async function getLeafOrLowLeaf<TreePreimageType extends IndexedTreeLeafPreimage>(
540
+ treeId: IndexedTreeId,
541
+ key: bigint,
542
+ merkleTrees: MerkleTreeWriteOperations,
543
+ ) {
544
+ // "key" is siloed slot (leafSlot) or siloed nullifier
545
+ const previousValueIndex = await merkleTrees.getPreviousValueIndex(treeId, key);
546
+ assert(
547
+ previousValueIndex !== undefined,
548
+ `${MerkleTreeId[treeId]} low leaf index should always be found (even if target leaf does not exist)`,
549
+ );
550
+ const { index: leafIndex, alreadyPresent } = previousValueIndex;
551
+
552
+ const leafPreimage = await merkleTrees.getLeafPreimage(treeId, leafIndex);
553
+ assert(
554
+ leafPreimage !== undefined,
555
+ `${MerkleTreeId[treeId]} low leaf preimage should never be undefined (even if target leaf does not exist)`,
556
+ );
557
+
558
+ const leafPath = await merkleTrees.getSiblingPath(treeId, leafIndex);
559
+
560
+ return [leafIndex, leafPreimage as TreePreimageType, leafPath.toFields(), alreadyPresent] as const;
322
561
  }
@@ -1,4 +1,4 @@
1
- import { AztecAddress } from '@aztec/circuits.js';
1
+ import { type AztecAddress } from '@aztec/circuits.js';
2
2
  import { siloNullifier } from '@aztec/circuits.js/hash';
3
3
  import { Fr } from '@aztec/foundation/fields';
4
4
 
@@ -41,7 +41,7 @@ export class NullifierManager {
41
41
  * @param nullifier - the nullifier to check for
42
42
  * @returns exists: whether the nullifier exists in cache here or in parent's
43
43
  */
44
- private checkExistsHereOrParent(contractAddress: Fr, nullifier: Fr): boolean {
44
+ private checkExistsHereOrParent(contractAddress: AztecAddress, nullifier: Fr): boolean {
45
45
  // First check this cache
46
46
  let existsAsPending = this.cache.exists(contractAddress, nullifier);
47
47
  // Then try parent's nullifier cache
@@ -66,7 +66,7 @@ export class NullifierManager {
66
66
  * leafIndex: the nullifier's leaf index if it exists and is not pending (comes from host state).
67
67
  */
68
68
  public async checkExists(
69
- contractAddress: Fr,
69
+ contractAddress: AztecAddress,
70
70
  nullifier: Fr,
71
71
  ): Promise<[/*exists=*/ boolean, /*isPending=*/ boolean, /*leafIndex=*/ Fr]> {
72
72
  // Check this cache and parent's (recursively)
@@ -89,7 +89,7 @@ export class NullifierManager {
89
89
  * @param contractAddress - the address of the contract that the nullifier is associated with
90
90
  * @param nullifier - the nullifier to stage
91
91
  */
92
- public async append(contractAddress: Fr, nullifier: Fr) {
92
+ public async append(contractAddress: AztecAddress, nullifier: Fr) {
93
93
  const [exists, ,] = await this.checkExists(contractAddress, nullifier);
94
94
  if (exists) {
95
95
  throw new NullifierCollisionError(
@@ -139,10 +139,10 @@ export class NullifierCache {
139
139
  * @param nullifier - the nullifier to check existence of
140
140
  * @returns whether the nullifier is found in the cache
141
141
  */
142
- public exists(contractAddress: Fr, nullifier: Fr): boolean {
142
+ public exists(contractAddress: AztecAddress, nullifier: Fr): boolean {
143
143
  const exists =
144
144
  this.cachePerContract.get(contractAddress.toBigInt())?.has(nullifier.toBigInt()) ||
145
- this.siloedNullifiers.has(siloNullifier(AztecAddress.fromField(contractAddress), nullifier).toBigInt());
145
+ this.siloedNullifiers.has(siloNullifier(contractAddress, nullifier).toBigInt());
146
146
  return !!exists;
147
147
  }
148
148
 
@@ -152,7 +152,7 @@ export class NullifierCache {
152
152
  * @param contractAddress - the address of the contract that the nullifier is associated with
153
153
  * @param nullifier - the nullifier to stage
154
154
  */
155
- public append(contractAddress: Fr, nullifier: Fr) {
155
+ public append(contractAddress: AztecAddress, nullifier: Fr) {
156
156
  if (this.exists(contractAddress, nullifier)) {
157
157
  throw new NullifierCollisionError(
158
158
  `Nullifier ${nullifier} at contract ${contractAddress} already exists in cache.`,
@@ -49,7 +49,7 @@ export class PublicStorage {
49
49
  * @param slot - the slot in the contract's storage being read from
50
50
  * @returns value: the latest value written according to this cache or the parent's. undefined on cache miss.
51
51
  */
52
- public readHereOrParent(contractAddress: Fr, slot: Fr): Fr | undefined {
52
+ public readHereOrParent(contractAddress: AztecAddress, slot: Fr): Fr | undefined {
53
53
  // First try check this storage cache
54
54
  let value = this.cache.read(contractAddress, slot);
55
55
  // Then try parent's storage cache
@@ -71,7 +71,7 @@ export class PublicStorage {
71
71
  * @param slot - the slot in the contract's storage being read from
72
72
  * @returns exists: whether the slot has EVER been written to before, value: the latest value written to slot, or 0 if never written to before
73
73
  */
74
- public async read(contractAddress: Fr, slot: Fr): Promise<PublicStorageReadResult> {
74
+ public async read(contractAddress: AztecAddress, slot: Fr): Promise<PublicStorageReadResult> {
75
75
  let cached = false;
76
76
  // Check this cache and parent's (recursively)
77
77
  let value = this.readHereOrParent(contractAddress, slot);
@@ -97,7 +97,7 @@ export class PublicStorage {
97
97
  * @param slot - the slot in the contract's storage being written to
98
98
  * @param value - the value being written to the slot
99
99
  */
100
- public write(contractAddress: Fr, slot: Fr, value: Fr) {
100
+ public write(contractAddress: AztecAddress, slot: Fr, value: Fr) {
101
101
  this.cache.write(contractAddress, slot, value);
102
102
  }
103
103
 
@@ -143,7 +143,7 @@ class PublicStorageCache {
143
143
  * @param slot - the slot in the contract's storage being read from
144
144
  * @returns the latest value written to slot, or undefined if no value has been written
145
145
  */
146
- public read(contractAddress: Fr, slot: Fr): Fr | undefined {
146
+ public read(contractAddress: AztecAddress, slot: Fr): Fr | undefined {
147
147
  return this.cachePerContract.get(contractAddress.toBigInt())?.get(slot.toBigInt());
148
148
  }
149
149
 
@@ -154,7 +154,7 @@ class PublicStorageCache {
154
154
  * @param slot - the slot in the contract's storage being written to
155
155
  * @param value - the value being written to the slot
156
156
  */
157
- public write(contractAddress: Fr, slot: Fr, value: Fr) {
157
+ public write(contractAddress: AztecAddress, slot: Fr, value: Fr) {
158
158
  let cacheAtContract = this.cachePerContract.get(contractAddress.toBigInt());
159
159
  if (!cacheAtContract) {
160
160
  // If this contract's storage has no staged modifications, create a new inner map to store them
@@ -43,7 +43,6 @@ export class NoteHashExists extends Instruction {
43
43
  memory.set(existsOffset, exists ? new Uint1(1) : new Uint1(0));
44
44
 
45
45
  memory.assert({ reads: 2, writes: 1, addressing });
46
- context.machineState.incrementPc();
47
46
  }
48
47
  }
49
48
 
@@ -71,10 +70,9 @@ export class EmitNoteHash extends Instruction {
71
70
  }
72
71
 
73
72
  const noteHash = memory.get(noteHashOffset).toFr();
74
- context.persistableState.writeNoteHash(context.environment.address, noteHash);
73
+ await context.persistableState.writeNoteHash(context.environment.address, noteHash);
75
74
 
76
75
  memory.assert({ reads: 1, addressing });
77
- context.machineState.incrementPc();
78
76
  }
79
77
  }
80
78
 
@@ -109,13 +107,12 @@ export class NullifierExists extends Instruction {
109
107
  memory.checkTags(TypeTag.FIELD, nullifierOffset, addressOffset);
110
108
 
111
109
  const nullifier = memory.get(nullifierOffset).toFr();
112
- const address = memory.get(addressOffset).toFr();
110
+ const address = memory.get(addressOffset).toAztecAddress();
113
111
  const exists = await context.persistableState.checkNullifierExists(address, nullifier);
114
112
 
115
113
  memory.set(existsOffset, exists ? new Uint1(1) : new Uint1(0));
116
114
 
117
115
  memory.assert({ reads: 2, writes: 1, addressing });
118
- context.machineState.incrementPc();
119
116
  }
120
117
  }
121
118
 
@@ -157,7 +154,6 @@ export class EmitNullifier extends Instruction {
157
154
  }
158
155
 
159
156
  memory.assert({ reads: 1, addressing });
160
- context.machineState.incrementPc();
161
157
  }
162
158
  }
163
159
 
@@ -201,7 +197,6 @@ export class L1ToL2MessageExists extends Instruction {
201
197
  memory.set(existsOffset, exists ? new Uint1(1) : new Uint1(0));
202
198
 
203
199
  memory.assert({ reads: 2, writes: 1, addressing });
204
- context.machineState.incrementPc();
205
200
  }
206
201
  }
207
202
 
@@ -236,7 +231,6 @@ export class EmitUnencryptedLog extends Instruction {
236
231
  context.persistableState.writeUnencryptedLog(contractAddress, log);
237
232
 
238
233
  memory.assert({ reads: 1 + logSize, addressing });
239
- context.machineState.incrementPc();
240
234
  }
241
235
  }
242
236
 
@@ -261,12 +255,12 @@ export class SendL2ToL1Message extends Instruction {
261
255
  const operands = [this.recipientOffset, this.contentOffset];
262
256
  const addressing = Addressing.fromWire(this.indirect, operands.length);
263
257
  const [recipientOffset, contentOffset] = addressing.resolve(operands, memory);
258
+ memory.checkTags(TypeTag.FIELD, recipientOffset, contentOffset);
264
259
 
265
260
  const recipient = memory.get(recipientOffset).toFr();
266
261
  const content = memory.get(contentOffset).toFr();
267
262
  context.persistableState.writeL2ToL1Message(context.environment.address, recipient, content);
268
263
 
269
264
  memory.assert({ reads: 2, addressing });
270
- context.machineState.incrementPc();
271
265
  }
272
266
  }