@aztec/simulator 0.23.0 → 0.26.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 (177) hide show
  1. package/dest/acvm/deserialize.d.ts +5 -0
  2. package/dest/acvm/deserialize.d.ts.map +1 -1
  3. package/dest/acvm/deserialize.js +8 -1
  4. package/dest/acvm/oracle/oracle.d.ts +7 -6
  5. package/dest/acvm/oracle/oracle.d.ts.map +1 -1
  6. package/dest/acvm/oracle/oracle.js +28 -15
  7. package/dest/acvm/oracle/typed_oracle.d.ts +9 -11
  8. package/dest/acvm/oracle/typed_oracle.d.ts.map +1 -1
  9. package/dest/acvm/oracle/typed_oracle.js +11 -11
  10. package/dest/avm/avm_context.d.ts +4 -4
  11. package/dest/avm/avm_context.d.ts.map +1 -1
  12. package/dest/avm/avm_context.js +6 -6
  13. package/dest/avm/avm_execution_environment.d.ts +3 -2
  14. package/dest/avm/avm_execution_environment.d.ts.map +1 -1
  15. package/dest/avm/avm_execution_environment.js +6 -5
  16. package/dest/avm/avm_memory_types.d.ts +127 -37
  17. package/dest/avm/avm_memory_types.d.ts.map +1 -1
  18. package/dest/avm/avm_memory_types.js +98 -106
  19. package/dest/avm/avm_simulator.d.ts +6 -4
  20. package/dest/avm/avm_simulator.d.ts.map +1 -1
  21. package/dest/avm/avm_simulator.js +17 -19
  22. package/dest/avm/errors.d.ts +3 -1
  23. package/dest/avm/errors.d.ts.map +1 -1
  24. package/dest/avm/errors.js +9 -3
  25. package/dest/avm/fixtures/index.d.ts +21 -5
  26. package/dest/avm/fixtures/index.d.ts.map +1 -1
  27. package/dest/avm/fixtures/index.js +28 -9
  28. package/dest/avm/journal/host_storage.d.ts +1 -1
  29. package/dest/avm/journal/host_storage.d.ts.map +1 -1
  30. package/dest/avm/journal/host_storage.js +1 -1
  31. package/dest/avm/journal/journal.d.ts +78 -50
  32. package/dest/avm/journal/journal.d.ts.map +1 -1
  33. package/dest/avm/journal/journal.js +125 -169
  34. package/dest/avm/journal/nullifiers.d.ts +85 -0
  35. package/dest/avm/journal/nullifiers.d.ts.map +1 -0
  36. package/dest/avm/journal/nullifiers.js +147 -0
  37. package/dest/avm/journal/public_storage.d.ts +88 -0
  38. package/dest/avm/journal/public_storage.d.ts.map +1 -0
  39. package/dest/avm/journal/public_storage.js +135 -0
  40. package/dest/avm/journal/trace.d.ts +43 -0
  41. package/dest/avm/journal/trace.d.ts.map +1 -0
  42. package/dest/avm/journal/trace.js +204 -0
  43. package/dest/avm/journal/trace_types.d.ts +26 -0
  44. package/dest/avm/journal/trace_types.d.ts.map +1 -0
  45. package/dest/avm/journal/trace_types.js +6 -0
  46. package/dest/avm/opcodes/accrued_substate.d.ts +37 -4
  47. package/dest/avm/opcodes/accrued_substate.d.ts.map +1 -1
  48. package/dest/avm/opcodes/accrued_substate.js +109 -12
  49. package/dest/avm/opcodes/addressing_mode.d.ts +24 -0
  50. package/dest/avm/opcodes/addressing_mode.d.ts.map +1 -0
  51. package/dest/avm/opcodes/addressing_mode.js +62 -0
  52. package/dest/avm/opcodes/environment_getters.d.ts +14 -13
  53. package/dest/avm/opcodes/environment_getters.d.ts.map +1 -1
  54. package/dest/avm/opcodes/environment_getters.js +1 -1
  55. package/dest/avm/opcodes/external_calls.js +5 -5
  56. package/dest/avm/opcodes/hashing.d.ts +48 -0
  57. package/dest/avm/opcodes/hashing.d.ts.map +1 -0
  58. package/dest/avm/opcodes/hashing.js +127 -0
  59. package/dest/avm/opcodes/instruction.d.ts +4 -4
  60. package/dest/avm/opcodes/instruction.d.ts.map +1 -1
  61. package/dest/avm/opcodes/instruction.js +1 -1
  62. package/dest/avm/opcodes/memory.d.ts.map +1 -1
  63. package/dest/avm/opcodes/memory.js +5 -3
  64. package/dest/avm/opcodes/storage.d.ts.map +1 -1
  65. package/dest/avm/opcodes/storage.js +3 -3
  66. package/dest/avm/serialization/bytecode_serialization.d.ts.map +1 -1
  67. package/dest/avm/serialization/bytecode_serialization.js +28 -22
  68. package/dest/avm/serialization/instruction_serialization.d.ts +21 -16
  69. package/dest/avm/serialization/instruction_serialization.d.ts.map +1 -1
  70. package/dest/avm/serialization/instruction_serialization.js +23 -18
  71. package/dest/avm/temporary_executor_migration.d.ts +25 -0
  72. package/dest/avm/temporary_executor_migration.d.ts.map +1 -0
  73. package/dest/avm/temporary_executor_migration.js +71 -0
  74. package/dest/client/client_execution_context.d.ts +13 -7
  75. package/dest/client/client_execution_context.d.ts.map +1 -1
  76. package/dest/client/client_execution_context.js +52 -27
  77. package/dest/client/db_oracle.d.ts +7 -0
  78. package/dest/client/db_oracle.d.ts.map +1 -1
  79. package/dest/client/db_oracle.js +1 -1
  80. package/dest/client/execution_note_cache.js +1 -1
  81. package/dest/client/execution_result.d.ts +4 -2
  82. package/dest/client/execution_result.d.ts.map +1 -1
  83. package/dest/client/execution_result.js +1 -1
  84. package/dest/client/private_execution.d.ts.map +1 -1
  85. package/dest/client/private_execution.js +4 -4
  86. package/dest/client/simulator.d.ts +11 -6
  87. package/dest/client/simulator.d.ts.map +1 -1
  88. package/dest/client/simulator.js +21 -12
  89. package/dest/client/unconstrained_execution.js +2 -2
  90. package/dest/client/view_data_oracle.d.ts +9 -2
  91. package/dest/client/view_data_oracle.d.ts.map +1 -1
  92. package/dest/client/view_data_oracle.js +13 -5
  93. package/dest/public/db.d.ts +17 -4
  94. package/dest/public/db.d.ts.map +1 -1
  95. package/dest/public/execution.d.ts +9 -4
  96. package/dest/public/execution.d.ts.map +1 -1
  97. package/dest/public/execution.js +18 -5
  98. package/dest/public/executor.d.ts +7 -0
  99. package/dest/public/executor.d.ts.map +1 -1
  100. package/dest/public/executor.js +40 -6
  101. package/dest/public/public_execution_context.d.ts +5 -4
  102. package/dest/public/public_execution_context.d.ts.map +1 -1
  103. package/dest/public/public_execution_context.js +24 -13
  104. package/dest/public/state_actions.d.ts +1 -1
  105. package/dest/public/state_actions.d.ts.map +1 -1
  106. package/dest/public/state_actions.js +6 -7
  107. package/dest/test/utils.js +4 -4
  108. package/dest/utils.d.ts +5 -20
  109. package/dest/utils.d.ts.map +1 -1
  110. package/dest/utils.js +4 -21
  111. package/package.json +9 -6
  112. package/src/acvm/acvm.ts +156 -0
  113. package/src/acvm/acvm_types.ts +11 -0
  114. package/src/acvm/deserialize.ts +44 -0
  115. package/src/acvm/index.ts +5 -0
  116. package/src/acvm/oracle/debug.ts +109 -0
  117. package/src/acvm/oracle/index.ts +17 -0
  118. package/src/acvm/oracle/oracle.ts +356 -0
  119. package/src/acvm/oracle/typed_oracle.ts +225 -0
  120. package/src/acvm/serialize.ts +75 -0
  121. package/src/avm/avm_context.ts +63 -0
  122. package/src/avm/avm_execution_environment.ts +98 -0
  123. package/src/avm/avm_machine_state.ts +93 -0
  124. package/src/avm/avm_memory_types.ts +324 -0
  125. package/src/avm/avm_message_call_result.ts +29 -0
  126. package/src/avm/avm_simulator.ts +87 -0
  127. package/src/avm/errors.ts +57 -0
  128. package/src/avm/fixtures/index.ts +115 -0
  129. package/src/avm/journal/host_storage.ts +14 -0
  130. package/src/avm/journal/index.ts +2 -0
  131. package/src/avm/journal/journal.ts +231 -0
  132. package/src/avm/journal/nullifiers.ts +170 -0
  133. package/src/avm/journal/public_storage.ts +149 -0
  134. package/src/avm/journal/trace.ts +223 -0
  135. package/src/avm/journal/trace_types.ts +79 -0
  136. package/src/avm/opcodes/.eslintrc.cjs +8 -0
  137. package/src/avm/opcodes/accrued_substate.ts +214 -0
  138. package/src/avm/opcodes/addressing_mode.ts +66 -0
  139. package/src/avm/opcodes/arithmetic.ts +79 -0
  140. package/src/avm/opcodes/bitwise.ts +129 -0
  141. package/src/avm/opcodes/comparators.ts +69 -0
  142. package/src/avm/opcodes/control_flow.ts +129 -0
  143. package/src/avm/opcodes/environment_getters.ts +201 -0
  144. package/src/avm/opcodes/external_calls.ts +122 -0
  145. package/src/avm/opcodes/hashing.ts +170 -0
  146. package/src/avm/opcodes/index.ts +10 -0
  147. package/src/avm/opcodes/instruction.ts +64 -0
  148. package/src/avm/opcodes/instruction_impl.ts +52 -0
  149. package/src/avm/opcodes/memory.ts +194 -0
  150. package/src/avm/opcodes/storage.ts +79 -0
  151. package/src/avm/serialization/buffer_cursor.ts +109 -0
  152. package/src/avm/serialization/bytecode_serialization.ts +179 -0
  153. package/src/avm/serialization/instruction_serialization.ts +170 -0
  154. package/src/avm/temporary_executor_migration.ts +109 -0
  155. package/src/client/client_execution_context.ts +502 -0
  156. package/src/client/db_oracle.ts +192 -0
  157. package/src/client/execution_note_cache.ts +90 -0
  158. package/src/client/execution_result.ts +89 -0
  159. package/src/client/index.ts +3 -0
  160. package/src/client/pick_notes.ts +125 -0
  161. package/src/client/private_execution.ts +79 -0
  162. package/src/client/simulator.ts +317 -0
  163. package/src/client/unconstrained_execution.ts +49 -0
  164. package/src/client/view_data_oracle.ts +253 -0
  165. package/src/common/errors.ts +61 -0
  166. package/src/common/index.ts +3 -0
  167. package/src/common/packed_args_cache.ts +55 -0
  168. package/src/common/side_effect_counter.ts +12 -0
  169. package/src/index.ts +3 -0
  170. package/src/public/db.ts +100 -0
  171. package/src/public/execution.ts +161 -0
  172. package/src/public/executor.ts +178 -0
  173. package/src/public/index.ts +9 -0
  174. package/src/public/public_execution_context.ts +241 -0
  175. package/src/public/state_actions.ts +100 -0
  176. package/src/test/utils.ts +38 -0
  177. package/src/utils.ts +18 -0
@@ -0,0 +1,231 @@
1
+ import { UnencryptedL2Log } from '@aztec/circuit-types';
2
+ import { AztecAddress, EthAddress, L2ToL1Message } from '@aztec/circuits.js';
3
+ import { EventSelector } from '@aztec/foundation/abi';
4
+ import { Fr } from '@aztec/foundation/fields';
5
+
6
+ import { HostStorage } from './host_storage.js';
7
+ import { Nullifiers } from './nullifiers.js';
8
+ import { PublicStorage } from './public_storage.js';
9
+ import { WorldStateAccessTrace } from './trace.js';
10
+ import { TracedL1toL2MessageCheck, TracedNoteHashCheck, TracedNullifierCheck } from './trace_types.js';
11
+
12
+ /**
13
+ * Data held within the journal
14
+ */
15
+ export type JournalData = {
16
+ noteHashChecks: TracedNoteHashCheck[];
17
+ newNoteHashes: Fr[];
18
+ nullifierChecks: TracedNullifierCheck[];
19
+ newNullifiers: Fr[];
20
+ l1ToL2MessageChecks: TracedL1toL2MessageCheck[];
21
+
22
+ newL1Messages: L2ToL1Message[];
23
+ newLogs: UnencryptedL2Log[];
24
+
25
+ /** contract address -\> key -\> value */
26
+ currentStorageValue: Map<bigint, Map<bigint, Fr>>;
27
+
28
+ /** contract address -\> key -\> value[] (stored in order of access) */
29
+ storageWrites: Map<bigint, Map<bigint, Fr[]>>;
30
+ /** contract address -\> key -\> value[] (stored in order of access) */
31
+ storageReads: Map<bigint, Map<bigint, Fr[]>>;
32
+ };
33
+
34
+ /**
35
+ * A class to manage persistable AVM state for contract calls.
36
+ * Maintains a cache of the current world state,
37
+ * a trace of all world state accesses, and a list of accrued substate items.
38
+ *
39
+ * The simulator should make any world state and accrued substate queries through this object.
40
+ *
41
+ * Manages merging of successful/reverted child state into current state.
42
+ */
43
+ export class AvmPersistableStateManager {
44
+ /** Reference to node storage */
45
+ public readonly hostStorage: HostStorage;
46
+
47
+ /** World State */
48
+ /** Public storage, including cached writes */
49
+ private publicStorage: PublicStorage;
50
+ /** Nullifier set, including cached/recently-emitted nullifiers */
51
+ private nullifiers: Nullifiers;
52
+
53
+ /** World State Access Trace */
54
+ private trace: WorldStateAccessTrace;
55
+
56
+ /** Accrued Substate **/
57
+ private newL1Messages: L2ToL1Message[] = [];
58
+ private newLogs: UnencryptedL2Log[] = [];
59
+
60
+ constructor(hostStorage: HostStorage, parent?: AvmPersistableStateManager) {
61
+ this.hostStorage = hostStorage;
62
+ this.publicStorage = new PublicStorage(hostStorage.publicStateDb, parent?.publicStorage);
63
+ this.nullifiers = new Nullifiers(hostStorage.commitmentsDb, parent?.nullifiers);
64
+ this.trace = new WorldStateAccessTrace(parent?.trace);
65
+ }
66
+
67
+ /**
68
+ * Create a new state manager forked from this one
69
+ */
70
+ public fork() {
71
+ return new AvmPersistableStateManager(this.hostStorage, this);
72
+ }
73
+
74
+ /**
75
+ * Write to public storage, journal/trace the write.
76
+ *
77
+ * @param storageAddress - the address of the contract whose storage is being written to
78
+ * @param slot - the slot in the contract's storage being written to
79
+ * @param value - the value being written to the slot
80
+ */
81
+ public writeStorage(storageAddress: Fr, slot: Fr, value: Fr) {
82
+ // Cache storage writes for later reference/reads
83
+ this.publicStorage.write(storageAddress, slot, value);
84
+ // Trace all storage writes (even reverted ones)
85
+ this.trace.tracePublicStorageWrite(storageAddress, slot, value);
86
+ }
87
+
88
+ /**
89
+ * Read from public storage, trace the read.
90
+ *
91
+ * @param storageAddress - the address of the contract whose storage is being read from
92
+ * @param slot - the slot in the contract's storage being read from
93
+ * @returns the latest value written to slot, or 0 if never written to before
94
+ */
95
+ public async readStorage(storageAddress: Fr, slot: Fr): Promise<Fr> {
96
+ const [_exists, value] = await this.publicStorage.read(storageAddress, slot);
97
+ // We want to keep track of all performed reads (even reverted ones)
98
+ this.trace.tracePublicStorageRead(storageAddress, slot, value);
99
+ return Promise.resolve(value);
100
+ }
101
+
102
+ // TODO(4886): We currently don't silo note hashes.
103
+ /**
104
+ * Check if a note hash exists at the given leaf index, trace the check.
105
+ *
106
+ * @param storageAddress - the address of the contract whose storage is being read from
107
+ * @param noteHash - the unsiloed note hash being checked
108
+ * @param leafIndex - the leaf index being checked
109
+ * @returns true if the note hash exists at the given leaf index, false otherwise
110
+ */
111
+ public async checkNoteHashExists(storageAddress: Fr, noteHash: Fr, leafIndex: Fr): Promise<boolean> {
112
+ const gotLeafIndex = await this.hostStorage.commitmentsDb.getCommitmentIndex(noteHash);
113
+ const exists = gotLeafIndex === leafIndex.toBigInt();
114
+ this.trace.traceNoteHashCheck(storageAddress, noteHash, exists, leafIndex);
115
+ return Promise.resolve(exists);
116
+ }
117
+
118
+ /**
119
+ * Write a note hash, trace the write.
120
+ * @param noteHash - the unsiloed note hash to write
121
+ */
122
+ public writeNoteHash(noteHash: Fr) {
123
+ this.trace.traceNewNoteHash(/*storageAddress*/ Fr.ZERO, noteHash);
124
+ }
125
+
126
+ /**
127
+ * Check if a nullifier exists, trace the check.
128
+ * @param storageAddress - address of the contract that the nullifier is associated with
129
+ * @param nullifier - the unsiloed nullifier to check
130
+ * @returns exists - whether the nullifier exists in the nullifier set
131
+ */
132
+ public async checkNullifierExists(storageAddress: Fr, nullifier: Fr): Promise<boolean> {
133
+ const [exists, isPending, leafIndex] = await this.nullifiers.checkExists(storageAddress, nullifier);
134
+ this.trace.traceNullifierCheck(storageAddress, nullifier, exists, isPending, leafIndex);
135
+ return Promise.resolve(exists);
136
+ }
137
+
138
+ /**
139
+ * Write a nullifier to the nullifier set, trace the write.
140
+ * @param storageAddress - address of the contract that the nullifier is associated with
141
+ * @param nullifier - the unsiloed nullifier to write
142
+ */
143
+ public async writeNullifier(storageAddress: Fr, nullifier: Fr) {
144
+ // Cache pending nullifiers for later access
145
+ await this.nullifiers.append(storageAddress, nullifier);
146
+ // Trace all nullifier creations (even reverted ones)
147
+ this.trace.traceNewNullifier(storageAddress, nullifier);
148
+ }
149
+
150
+ /**
151
+ * Check if an L1 to L2 message exists, trace the check.
152
+ * @param msgHash - the message hash to check existence of
153
+ * @param msgLeafIndex - the message leaf index to use in the check
154
+ * @returns exists - whether the message exists in the L1 to L2 Messages tree
155
+ */
156
+ public async checkL1ToL2MessageExists(msgHash: Fr, msgLeafIndex: Fr): Promise<boolean> {
157
+ let exists = false;
158
+ try {
159
+ const gotMessage = await this.hostStorage.commitmentsDb.getL1ToL2MembershipWitness(msgHash);
160
+ exists = gotMessage !== undefined && gotMessage.index == msgLeafIndex.toBigInt();
161
+ } catch {
162
+ // error getting message - doesn't exist!
163
+ exists = false;
164
+ }
165
+ this.trace.traceL1ToL2MessageCheck(msgHash, msgLeafIndex, exists);
166
+ return Promise.resolve(exists);
167
+ }
168
+
169
+ /**
170
+ * Write an L2 to L1 message.
171
+ * @param recipient - L1 contract address to send the message to.
172
+ * @param content - Message content.
173
+ */
174
+ public writeL1Message(recipient: EthAddress | Fr, content: Fr) {
175
+ const recipientAddress = recipient instanceof EthAddress ? recipient : EthAddress.fromField(recipient);
176
+ this.newL1Messages.push(new L2ToL1Message(recipientAddress, content));
177
+ }
178
+
179
+ public writeLog(contractAddress: Fr, event: Fr, log: Fr[]) {
180
+ this.newLogs.push(
181
+ new UnencryptedL2Log(
182
+ AztecAddress.fromField(contractAddress),
183
+ EventSelector.fromField(event),
184
+ Buffer.concat(log.map(f => f.toBuffer())),
185
+ ),
186
+ );
187
+ }
188
+
189
+ /**
190
+ * Accept nested world state modifications, merging in its trace and accrued substate
191
+ */
192
+ public acceptNestedCallState(nestedJournal: AvmPersistableStateManager) {
193
+ // Merge Public Storage
194
+ this.publicStorage.acceptAndMerge(nestedJournal.publicStorage);
195
+
196
+ // Merge World State Access Trace
197
+ this.trace.acceptAndMerge(nestedJournal.trace);
198
+
199
+ // Accrued Substate
200
+ this.newL1Messages = this.newL1Messages.concat(nestedJournal.newL1Messages);
201
+ this.newLogs = this.newLogs.concat(nestedJournal.newLogs);
202
+ }
203
+
204
+ /**
205
+ * Reject nested world state, merging in its trace, but not accepting any state modifications
206
+ */
207
+ public rejectNestedCallState(nestedJournal: AvmPersistableStateManager) {
208
+ // Merge World State Access Trace
209
+ this.trace.acceptAndMerge(nestedJournal.trace);
210
+ }
211
+
212
+ /**
213
+ * Access the current state of the journal
214
+ *
215
+ * @returns a JournalData object
216
+ */
217
+ public flush(): JournalData {
218
+ return {
219
+ noteHashChecks: this.trace.noteHashChecks,
220
+ newNoteHashes: this.trace.newNoteHashes,
221
+ nullifierChecks: this.trace.nullifierChecks,
222
+ newNullifiers: this.trace.newNullifiers,
223
+ l1ToL2MessageChecks: this.trace.l1ToL2MessageChecks,
224
+ newL1Messages: this.newL1Messages,
225
+ newLogs: this.newLogs,
226
+ currentStorageValue: this.publicStorage.getCache().cachePerContract,
227
+ storageReads: this.trace.publicStorageReads,
228
+ storageWrites: this.trace.publicStorageWrites,
229
+ };
230
+ }
231
+ }
@@ -0,0 +1,170 @@
1
+ import { siloNullifier } from '@aztec/circuits.js/hash';
2
+ import { Fr } from '@aztec/foundation/fields';
3
+
4
+ import type { CommitmentsDB } from '../../index.js';
5
+
6
+ /**
7
+ * A class to manage new nullifier staging and existence checks during a contract call's AVM simulation.
8
+ * Maintains a nullifier cache, and ensures that existence checks fall back to the correct source.
9
+ * When a contract call completes, its cached nullifier set can be merged into its parent's.
10
+ */
11
+ export class Nullifiers {
12
+ /** Cached nullifiers. */
13
+ private cache: NullifierCache;
14
+ /** Parent's nullifier cache. Checked on cache-miss. */
15
+ private readonly parentCache: NullifierCache | undefined;
16
+ /** Reference to node storage. Checked on parent cache-miss. */
17
+ private readonly hostNullifiers: CommitmentsDB;
18
+
19
+ constructor(hostNullifiers: CommitmentsDB, parent?: Nullifiers) {
20
+ this.hostNullifiers = hostNullifiers;
21
+ this.parentCache = parent?.cache;
22
+ this.cache = new NullifierCache();
23
+ }
24
+
25
+ /**
26
+ * Get a nullifier's existence status.
27
+ * 1. Check cache.
28
+ * 2. Check parent's cache.
29
+ * 3. Fall back to the host state.
30
+ * 4. Not found! Nullifier does not exist.
31
+ *
32
+ * @param storageAddress - the address of the contract whose storage is being read from
33
+ * @param nullifier - the nullifier to check for
34
+ * @returns exists: whether the nullifier exists at all,
35
+ * isPending: whether the nullifier was found in a cache,
36
+ * leafIndex: the nullifier's leaf index if it exists and is not pending (comes from host state).
37
+ */
38
+ public async checkExists(
39
+ storageAddress: Fr,
40
+ nullifier: Fr,
41
+ ): Promise<[/*exists=*/ boolean, /*isPending=*/ boolean, /*leafIndex=*/ Fr]> {
42
+ // First check this cache
43
+ let existsAsPending = this.cache.exists(storageAddress, nullifier);
44
+ // Then check parent's cache
45
+ if (!existsAsPending && this.parentCache) {
46
+ existsAsPending = this.parentCache?.exists(storageAddress, nullifier);
47
+ }
48
+ // Finally try the host's Aztec state (a trip to the database)
49
+ // If the value is found in the database, it will be associated with a leaf index!
50
+ let leafIndex: bigint | undefined = undefined;
51
+ if (!existsAsPending) {
52
+ // silo the nullifier before checking for its existence in the host
53
+ leafIndex = await this.hostNullifiers.getNullifierIndex(siloNullifier(storageAddress, nullifier));
54
+ }
55
+ const exists = existsAsPending || leafIndex !== undefined;
56
+ leafIndex = leafIndex === undefined ? BigInt(0) : leafIndex;
57
+ return Promise.resolve([exists, existsAsPending, new Fr(leafIndex)]);
58
+ }
59
+
60
+ /**
61
+ * Stage a new nullifier (append it to the cache).
62
+ *
63
+ * @param storageAddress - the address of the contract that the nullifier is associated with
64
+ * @param nullifier - the nullifier to stage
65
+ */
66
+ public async append(storageAddress: Fr, nullifier: Fr) {
67
+ const [exists, ,] = await this.checkExists(storageAddress, nullifier);
68
+ if (exists) {
69
+ throw new NullifierCollisionError(
70
+ `Nullifier ${nullifier} at contract ${storageAddress} already exists in parent cache or host.`,
71
+ );
72
+ }
73
+ this.cache.append(storageAddress, nullifier);
74
+ }
75
+
76
+ /**
77
+ * Merges another nullifier cache into this one.
78
+ *
79
+ * @param incomingNullifiers - the incoming cached nullifiers to merge into this instance's
80
+ */
81
+ public acceptAndMerge(incomingNullifiers: Nullifiers) {
82
+ this.cache.acceptAndMerge(incomingNullifiers.cache);
83
+ }
84
+ }
85
+
86
+ /**
87
+ * A class to cache nullifiers created during a contract call's AVM simulation.
88
+ * "append" updates a map, "exists" checks that map.
89
+ * An instance of this class can merge another instance's cached nullifiers into its own.
90
+ */
91
+ export class NullifierCache {
92
+ /**
93
+ * Map for staging nullifiers.
94
+ * One inner-set per contract storage address,
95
+ * each entry being a nullifier.
96
+ */
97
+ private cachePerContract: Map<bigint, Set<bigint>> = new Map();
98
+
99
+ /**
100
+ * Check whether a nullifier exists in the cache.
101
+ *
102
+ * @param storageAddress - the address of the contract that the nullifier is associated with
103
+ * @param nullifier - the nullifier to check existence of
104
+ * @returns whether the nullifier is found in the cache
105
+ */
106
+ public exists(storageAddress: Fr, nullifier: Fr): boolean {
107
+ const exists = this.cachePerContract.get(storageAddress.toBigInt())?.has(nullifier.toBigInt());
108
+ return exists ? true : false;
109
+ }
110
+
111
+ /**
112
+ * Stage a new nullifier (append it to the cache).
113
+ *
114
+ * @param storageAddress - the address of the contract that the nullifier is associated with
115
+ * @param nullifier - the nullifier to stage
116
+ */
117
+ public append(storageAddress: Fr, nullifier: Fr) {
118
+ let nullifiersForContract = this.cachePerContract.get(storageAddress.toBigInt());
119
+ // If this contract's nullifier set has no cached nullifiers, create a new Set to store them
120
+ if (!nullifiersForContract) {
121
+ nullifiersForContract = new Set();
122
+ this.cachePerContract.set(storageAddress.toBigInt(), nullifiersForContract);
123
+ }
124
+ if (nullifiersForContract.has(nullifier.toBigInt())) {
125
+ throw new NullifierCollisionError(
126
+ `Nullifier ${nullifier} at contract ${storageAddress} already exists in cache.`,
127
+ );
128
+ }
129
+ nullifiersForContract.add(nullifier.toBigInt());
130
+ }
131
+
132
+ /**
133
+ * Merge another cache's nullifiers into this instance's.
134
+ *
135
+ * Cached nullifiers in "incoming" must not collide with any present in "this".
136
+ *
137
+ * In practice, "this" is a parent call's pending nullifiers, and "incoming" is a nested call's.
138
+ *
139
+ * @param incomingNullifiers - the incoming cached nullifiers to merge into this instance's
140
+ */
141
+ public acceptAndMerge(incomingNullifiers: NullifierCache) {
142
+ // Iterate over all contracts with staged writes in the child.
143
+ for (const [incomingAddress, incomingCacheAtContract] of incomingNullifiers.cachePerContract) {
144
+ const thisCacheAtContract = this.cachePerContract.get(incomingAddress);
145
+ if (!thisCacheAtContract) {
146
+ // This contract has no nullifiers cached here
147
+ // so just accept incoming cache as-is for this contract.
148
+ this.cachePerContract.set(incomingAddress, incomingCacheAtContract);
149
+ } else {
150
+ // "Incoming" and "this" both have cached nullifiers for this contract.
151
+ // Merge in incoming nullifiers, erroring if there are any duplicates.
152
+ for (const nullifier of incomingCacheAtContract) {
153
+ if (thisCacheAtContract.has(nullifier)) {
154
+ throw new NullifierCollisionError(
155
+ `Failed to accept child call's nullifiers. Nullifier ${nullifier} already exists at contract ${incomingAddress}.`,
156
+ );
157
+ }
158
+ thisCacheAtContract.add(nullifier);
159
+ }
160
+ }
161
+ }
162
+ }
163
+ }
164
+
165
+ export class NullifierCollisionError extends Error {
166
+ constructor(message: string, ...rest: any[]) {
167
+ super(message, ...rest);
168
+ this.name = 'NullifierCollisionError';
169
+ }
170
+ }
@@ -0,0 +1,149 @@
1
+ import { Fr } from '@aztec/foundation/fields';
2
+
3
+ import type { PublicStateDB } from '../../index.js';
4
+
5
+ /**
6
+ * A class to manage public storage reads and writes during a contract call's AVM simulation.
7
+ * Maintains a storage write cache, and ensures that reads fall back to the correct source.
8
+ * When a contract call completes, its storage cache can be merged into its parent's.
9
+ */
10
+ export class PublicStorage {
11
+ /** Cached storage writes. */
12
+ private cache: PublicStorageCache;
13
+ /** Parent's storage cache. Checked on cache-miss. */
14
+ private readonly parentCache: PublicStorageCache | undefined;
15
+ /** Reference to node storage. Checked on parent cache-miss. */
16
+ private readonly hostPublicStorage: PublicStateDB;
17
+
18
+ constructor(hostPublicStorage: PublicStateDB, parent?: PublicStorage) {
19
+ this.hostPublicStorage = hostPublicStorage;
20
+ this.parentCache = parent?.cache;
21
+ this.cache = new PublicStorageCache();
22
+ }
23
+
24
+ /**
25
+ * Get the pending storage.
26
+ */
27
+ public getCache() {
28
+ return this.cache;
29
+ }
30
+
31
+ /**
32
+ * Read a value from storage.
33
+ * 1. Check cache.
34
+ * 2. Check parent's cache.
35
+ * 3. Fall back to the host state.
36
+ * 4. Not found! Value has never been written to before. Flag it as non-existent and return value zero.
37
+ *
38
+ * @param storageAddress - the address of the contract whose storage is being read from
39
+ * @param slot - the slot in the contract's storage being read from
40
+ * @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
41
+ */
42
+ public async read(storageAddress: Fr, slot: Fr): Promise<[/*exists=*/ boolean, /*value=*/ Fr]> {
43
+ // First try check this storage cache
44
+ let value = this.cache.read(storageAddress, slot);
45
+ // Then try parent's storage cache (if it exists / written to earlier in this TX)
46
+ if (!value && this.parentCache) {
47
+ value = this.parentCache?.read(storageAddress, slot);
48
+ }
49
+ // Finally try the host's Aztec state (a trip to the database)
50
+ if (!value) {
51
+ value = await this.hostPublicStorage.storageRead(storageAddress, slot);
52
+ }
53
+ // if value is undefined, that means this slot has never been written to!
54
+ const exists = value !== undefined;
55
+ const valueOrZero = exists ? value : Fr.ZERO;
56
+ return Promise.resolve([exists, valueOrZero]);
57
+ }
58
+
59
+ /**
60
+ * Stage a storage write.
61
+ *
62
+ * @param storageAddress - the address of the contract whose storage is being written to
63
+ * @param slot - the slot in the contract's storage being written to
64
+ * @param value - the value being written to the slot
65
+ */
66
+ public write(storageAddress: Fr, key: Fr, value: Fr) {
67
+ this.cache.write(storageAddress, key, value);
68
+ }
69
+
70
+ /**
71
+ * Merges another PublicStorage's cache (pending writes) into this one.
72
+ *
73
+ * @param incomingPublicStorage - the incoming public storage to merge into this instance's
74
+ */
75
+ public acceptAndMerge(incomingPublicStorage: PublicStorage) {
76
+ this.cache.acceptAndMerge(incomingPublicStorage.cache);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * A class to cache writes to public storage during a contract call's AVM simulation.
82
+ * "Writes" update a map, "reads" check that map or return undefined.
83
+ * An instance of this class can merge another instance's staged writes into its own.
84
+ */
85
+ class PublicStorageCache {
86
+ /**
87
+ * Map for staging storage writes.
88
+ * One inner-map per contract storage address,
89
+ * mapping storage slot to latest staged write value.
90
+ */
91
+ public cachePerContract: Map<bigint, Map<bigint, Fr>> = new Map();
92
+ // FIXME: storage ^ should be private, but its value is used in tests for "currentStorageValue"
93
+
94
+ /**
95
+ * Read a staged value from storage, if it has been previously written to.
96
+ *
97
+ * @param storageAddress - the address of the contract whose storage is being read from
98
+ * @param slot - the slot in the contract's storage being read from
99
+ * @returns the latest value written to slot, or undefined if no value has been written
100
+ */
101
+ public read(storageAddress: Fr, slot: Fr): Fr | undefined {
102
+ return this.cachePerContract.get(storageAddress.toBigInt())?.get(slot.toBigInt());
103
+ }
104
+
105
+ /**
106
+ * Stage a storage write.
107
+ *
108
+ * @param storageAddress - the address of the contract whose storage is being written to
109
+ * @param slot - the slot in the contract's storage being written to
110
+ * @param value - the value being written to the slot
111
+ */
112
+ public write(storageAddress: Fr, slot: Fr, value: Fr) {
113
+ let cacheAtContract = this.cachePerContract.get(storageAddress.toBigInt());
114
+ if (!cacheAtContract) {
115
+ // If this contract's storage has no staged modifications, create a new inner map to store them
116
+ cacheAtContract = new Map();
117
+ this.cachePerContract.set(storageAddress.toBigInt(), cacheAtContract);
118
+ }
119
+ cacheAtContract.set(slot.toBigInt(), value);
120
+ }
121
+
122
+ /**
123
+ * Merges another cache's staged writes into this instance's cache.
124
+ *
125
+ * Staged modifications in "incoming" take precedence over those
126
+ * present in "this" as they are assumed to occur after this' writes.
127
+ *
128
+ * In practice, "this" is a parent call's storage cache, and "incoming" is a nested call's.
129
+ *
130
+ * @param incomingStorageCache - the incoming storage write cache to merge into this instance's
131
+ */
132
+ public acceptAndMerge(incomingStorageCache: PublicStorageCache) {
133
+ // Iterate over all incoming contracts with staged writes.
134
+ for (const [incomingAddress, incomingCacheAtContract] of incomingStorageCache.cachePerContract) {
135
+ const thisCacheAtContract = this.cachePerContract.get(incomingAddress);
136
+ if (!thisCacheAtContract) {
137
+ // The contract has no storage writes staged here
138
+ // so just accept the incoming cache as-is for this contract.
139
+ this.cachePerContract.set(incomingAddress, incomingCacheAtContract);
140
+ } else {
141
+ // "Incoming" and "this" both have staged writes for this contract.
142
+ // Merge in incoming staged writes, giving them precedence over this'.
143
+ for (const [slot, value] of incomingCacheAtContract) {
144
+ thisCacheAtContract.set(slot, value);
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }