@aztec/simulator 0.63.1 → 0.65.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/avm/avm_contract_call_result.d.ts +18 -1
- package/dest/avm/avm_contract_call_result.d.ts.map +1 -1
- package/dest/avm/avm_contract_call_result.js +30 -3
- package/dest/avm/avm_memory_types.d.ts.map +1 -1
- package/dest/avm/avm_memory_types.js +7 -11
- package/dest/avm/avm_simulator.d.ts +6 -1
- package/dest/avm/avm_simulator.d.ts.map +1 -1
- package/dest/avm/avm_simulator.js +29 -8
- package/dest/avm/avm_tree.d.ts +4 -18
- package/dest/avm/avm_tree.d.ts.map +1 -1
- package/dest/avm/avm_tree.js +72 -53
- package/dest/avm/errors.d.ts +7 -0
- package/dest/avm/errors.d.ts.map +1 -1
- package/dest/avm/errors.js +11 -1
- package/dest/avm/fixtures/index.d.ts +2 -2
- package/dest/avm/fixtures/index.d.ts.map +1 -1
- package/dest/avm/fixtures/index.js +1 -1
- package/dest/avm/index.d.ts +1 -0
- package/dest/avm/index.d.ts.map +1 -1
- package/dest/avm/index.js +2 -1
- package/dest/avm/journal/journal.d.ts +14 -13
- package/dest/avm/journal/journal.d.ts.map +1 -1
- package/dest/avm/journal/journal.js +81 -68
- package/dest/avm/journal/public_storage.d.ts +0 -1
- package/dest/avm/journal/public_storage.d.ts.map +1 -1
- package/dest/avm/journal/public_storage.js +6 -6
- package/dest/avm/opcodes/accrued_substate.js +2 -2
- package/dest/avm/opcodes/addressing_mode.d.ts.map +1 -1
- package/dest/avm/opcodes/addressing_mode.js +8 -2
- package/dest/avm/opcodes/contract.d.ts +2 -2
- package/dest/avm/opcodes/contract.d.ts.map +1 -1
- package/dest/avm/opcodes/contract.js +4 -4
- package/dest/avm/opcodes/control_flow.d.ts +2 -2
- package/dest/avm/opcodes/control_flow.d.ts.map +1 -1
- package/dest/avm/opcodes/control_flow.js +4 -4
- package/dest/avm/opcodes/environment_getters.d.ts +2 -2
- package/dest/avm/opcodes/environment_getters.d.ts.map +1 -1
- package/dest/avm/opcodes/environment_getters.js +4 -4
- package/dest/avm/opcodes/external_calls.d.ts.map +1 -1
- package/dest/avm/opcodes/external_calls.js +2 -4
- package/dest/avm/opcodes/memory.d.ts +4 -4
- package/dest/avm/opcodes/memory.d.ts.map +1 -1
- package/dest/avm/opcodes/memory.js +12 -12
- package/dest/avm/opcodes/misc.d.ts +2 -2
- package/dest/avm/opcodes/misc.d.ts.map +1 -1
- package/dest/avm/opcodes/misc.js +4 -4
- package/dest/index.d.ts +0 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -2
- package/dest/public/dual_side_effect_trace.d.ts +3 -9
- package/dest/public/dual_side_effect_trace.d.ts.map +1 -1
- package/dest/public/dual_side_effect_trace.js +5 -11
- package/dest/public/enqueued_call_side_effect_trace.d.ts +4 -10
- package/dest/public/enqueued_call_side_effect_trace.d.ts.map +1 -1
- package/dest/public/enqueued_call_side_effect_trace.js +6 -15
- package/dest/public/executor_metrics.d.ts +1 -2
- package/dest/public/executor_metrics.d.ts.map +1 -1
- package/dest/public/executor_metrics.js +2 -8
- package/dest/public/fixtures/index.d.ts +14 -0
- package/dest/public/fixtures/index.d.ts.map +1 -0
- package/dest/public/fixtures/index.js +101 -0
- package/dest/public/index.d.ts +1 -2
- package/dest/public/index.d.ts.map +1 -1
- package/dest/public/index.js +1 -2
- package/dest/public/public_processor.d.ts +0 -2
- package/dest/public/public_processor.d.ts.map +1 -1
- package/dest/public/public_processor.js +5 -12
- package/dest/public/public_tx_context.d.ts +6 -4
- package/dest/public/public_tx_context.d.ts.map +1 -1
- package/dest/public/public_tx_context.js +11 -7
- package/dest/public/public_tx_simulator.d.ts +56 -5
- package/dest/public/public_tx_simulator.d.ts.map +1 -1
- package/dest/public/public_tx_simulator.js +106 -26
- package/dest/public/side_effect_trace.d.ts +4 -10
- package/dest/public/side_effect_trace.d.ts.map +1 -1
- package/dest/public/side_effect_trace.js +6 -15
- package/dest/public/side_effect_trace_interface.d.ts +3 -9
- package/dest/public/side_effect_trace_interface.d.ts.map +1 -1
- package/dest/public/transitional_adapters.d.ts +1 -2
- package/dest/public/transitional_adapters.d.ts.map +1 -1
- package/dest/public/transitional_adapters.js +8 -10
- package/package.json +10 -10
- package/src/avm/avm_contract_call_result.ts +39 -2
- package/src/avm/avm_memory_types.ts +6 -10
- package/src/avm/avm_simulator.ts +58 -8
- package/src/avm/avm_tree.ts +89 -64
- package/src/avm/errors.ts +11 -0
- package/src/avm/fixtures/index.ts +4 -3
- package/src/avm/index.ts +1 -0
- package/src/avm/journal/journal.ts +118 -126
- package/src/avm/journal/public_storage.ts +5 -6
- package/src/avm/opcodes/accrued_substate.ts +1 -1
- package/src/avm/opcodes/addressing_mode.ts +7 -2
- package/src/avm/opcodes/contract.ts +2 -2
- package/src/avm/opcodes/control_flow.ts +2 -2
- package/src/avm/opcodes/environment_getters.ts +2 -2
- package/src/avm/opcodes/external_calls.ts +1 -3
- package/src/avm/opcodes/memory.ts +9 -9
- package/src/avm/opcodes/misc.ts +2 -2
- package/src/index.ts +0 -1
- package/src/public/dual_side_effect_trace.ts +4 -13
- package/src/public/enqueued_call_side_effect_trace.ts +14 -17
- package/src/public/executor_metrics.ts +1 -9
- package/src/public/fixtures/index.ts +159 -0
- package/src/public/index.ts +1 -5
- package/src/public/public_processor.ts +5 -27
- package/src/public/public_tx_context.ts +13 -8
- package/src/public/public_tx_simulator.ts +182 -50
- package/src/public/side_effect_trace.ts +9 -19
- package/src/public/side_effect_trace_interface.ts +3 -9
- package/src/public/transitional_adapters.ts +12 -12
- package/dest/mocks/fixtures.d.ts +0 -28
- package/dest/mocks/fixtures.d.ts.map +0 -1
- package/dest/mocks/fixtures.js +0 -48
- package/dest/mocks/index.d.ts +0 -2
- package/dest/mocks/index.d.ts.map +0 -1
- package/dest/mocks/index.js +0 -2
- package/dest/public/executor.d.ts +0 -47
- package/dest/public/executor.d.ts.map +0 -1
- package/dest/public/executor.js +0 -90
- package/src/mocks/fixtures.ts +0 -72
- package/src/mocks/index.ts +0 -1
- package/src/public/executor.ts +0 -154
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MerkleTreeId } from '@aztec/circuit-types';
|
|
2
2
|
import {
|
|
3
3
|
type AztecAddress,
|
|
4
4
|
type Gas,
|
|
5
5
|
type NullifierLeafPreimage,
|
|
6
6
|
type PublicCallRequest,
|
|
7
|
-
|
|
8
|
-
PublicDataTreeLeafPreimage,
|
|
7
|
+
type PublicDataTreeLeafPreimage,
|
|
9
8
|
SerializableContractInstance,
|
|
10
9
|
computePublicBytecodeCommitment,
|
|
11
10
|
} from '@aztec/circuits.js';
|
|
12
11
|
import { computePublicDataTreeLeafSlot, siloNoteHash, siloNullifier } from '@aztec/circuits.js/hash';
|
|
13
12
|
import { Fr } from '@aztec/foundation/fields';
|
|
14
13
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
15
|
-
import { type IndexedTreeLeafPreimage } from '@aztec/foundation/trees';
|
|
16
14
|
|
|
17
15
|
import assert from 'assert';
|
|
18
16
|
|
|
@@ -21,7 +19,8 @@ import { type WorldStateDB } from '../../public/public_db_sources.js';
|
|
|
21
19
|
import { type PublicSideEffectTraceInterface } from '../../public/side_effect_trace_interface.js';
|
|
22
20
|
import { type AvmContractCallResult } from '../avm_contract_call_result.js';
|
|
23
21
|
import { type AvmExecutionEnvironment } from '../avm_execution_environment.js';
|
|
24
|
-
import {
|
|
22
|
+
import { AvmEphemeralForest } from '../avm_tree.js';
|
|
23
|
+
import { NullifierCollisionError, NullifierManager } from './nullifiers.js';
|
|
25
24
|
import { PublicStorage } from './public_storage.js';
|
|
26
25
|
|
|
27
26
|
/**
|
|
@@ -36,9 +35,6 @@ import { PublicStorage } from './public_storage.js';
|
|
|
36
35
|
export class AvmPersistableStateManager {
|
|
37
36
|
private readonly log = createDebugLogger('aztec:avm_simulator:state_manager');
|
|
38
37
|
|
|
39
|
-
/** Interface to perform merkle tree operations */
|
|
40
|
-
public merkleTrees: MerkleTreeWriteOperations;
|
|
41
|
-
|
|
42
38
|
/** Make sure a forked state is never merged twice. */
|
|
43
39
|
private alreadyMergedIntoParent = false;
|
|
44
40
|
|
|
@@ -53,29 +49,43 @@ export class AvmPersistableStateManager {
|
|
|
53
49
|
/** Nullifier set, including cached/recently-emitted nullifiers */
|
|
54
50
|
private readonly nullifiers: NullifierManager = new NullifierManager(worldStateDB),
|
|
55
51
|
private readonly doMerkleOperations: boolean = false,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
this.merkleTrees = merkleTrees;
|
|
60
|
-
} else {
|
|
61
|
-
this.merkleTrees = worldStateDB.getMerkleInterface();
|
|
62
|
-
}
|
|
63
|
-
}
|
|
52
|
+
/** Ephmeral forest for merkle tree operations */
|
|
53
|
+
public readonly merkleTrees: AvmEphemeralForest,
|
|
54
|
+
) {}
|
|
64
55
|
|
|
65
56
|
/**
|
|
66
57
|
* Create a new state manager with some preloaded pending siloed nullifiers
|
|
67
58
|
*/
|
|
68
|
-
public static newWithPendingSiloedNullifiers(
|
|
59
|
+
public static async newWithPendingSiloedNullifiers(
|
|
69
60
|
worldStateDB: WorldStateDB,
|
|
70
61
|
trace: PublicSideEffectTraceInterface,
|
|
71
62
|
pendingSiloedNullifiers: Fr[],
|
|
63
|
+
doMerkleOperations: boolean = false,
|
|
72
64
|
) {
|
|
73
65
|
const parentNullifiers = NullifierManager.newWithPendingSiloedNullifiers(worldStateDB, pendingSiloedNullifiers);
|
|
66
|
+
const ephemeralForest = await AvmEphemeralForest.create(worldStateDB.getMerkleInterface());
|
|
74
67
|
return new AvmPersistableStateManager(
|
|
75
68
|
worldStateDB,
|
|
76
69
|
trace,
|
|
77
70
|
/*publicStorage=*/ new PublicStorage(worldStateDB),
|
|
78
71
|
/*nullifiers=*/ parentNullifiers.fork(),
|
|
72
|
+
doMerkleOperations,
|
|
73
|
+
ephemeralForest,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Create a new state manager
|
|
79
|
+
*/
|
|
80
|
+
public static async create(worldStateDB: WorldStateDB, trace: PublicSideEffectTraceInterface) {
|
|
81
|
+
const ephemeralForest = await AvmEphemeralForest.create(worldStateDB.getMerkleInterface());
|
|
82
|
+
return new AvmPersistableStateManager(
|
|
83
|
+
worldStateDB,
|
|
84
|
+
trace,
|
|
85
|
+
/*publicStorage=*/ new PublicStorage(worldStateDB),
|
|
86
|
+
/*nullifiers=*/ new NullifierManager(worldStateDB),
|
|
87
|
+
/*doMerkleOperations=*/ true,
|
|
88
|
+
ephemeralForest,
|
|
79
89
|
);
|
|
80
90
|
}
|
|
81
91
|
|
|
@@ -89,6 +99,7 @@ export class AvmPersistableStateManager {
|
|
|
89
99
|
this.publicStorage.fork(),
|
|
90
100
|
this.nullifiers.fork(),
|
|
91
101
|
this.doMerkleOperations,
|
|
102
|
+
this.merkleTrees.fork(),
|
|
92
103
|
);
|
|
93
104
|
}
|
|
94
105
|
|
|
@@ -116,7 +127,11 @@ export class AvmPersistableStateManager {
|
|
|
116
127
|
|
|
117
128
|
private _merge(forkedState: AvmPersistableStateManager, reverted: boolean) {
|
|
118
129
|
// sanity check to avoid merging the same forked trace twice
|
|
119
|
-
assert(
|
|
130
|
+
assert(
|
|
131
|
+
!forkedState.alreadyMergedIntoParent,
|
|
132
|
+
'Cannot merge forked state that has already been merged into its parent!',
|
|
133
|
+
);
|
|
134
|
+
forkedState.alreadyMergedIntoParent = true;
|
|
120
135
|
this.publicStorage.acceptAndMerge(forkedState.publicStorage);
|
|
121
136
|
this.nullifiers.acceptAndMerge(forkedState.nullifiers);
|
|
122
137
|
this.trace.merge(forkedState.trace, reverted);
|
|
@@ -135,27 +150,18 @@ export class AvmPersistableStateManager {
|
|
|
135
150
|
this.publicStorage.write(contractAddress, slot, value);
|
|
136
151
|
const leafSlot = computePublicDataTreeLeafSlot(contractAddress, slot);
|
|
137
152
|
if (this.doMerkleOperations) {
|
|
138
|
-
const result = await this.merkleTrees.
|
|
139
|
-
MerkleTreeId.PUBLIC_DATA_TREE,
|
|
140
|
-
[new PublicDataTreeLeaf(leafSlot, value).toBuffer()],
|
|
141
|
-
0,
|
|
142
|
-
);
|
|
153
|
+
const result = await this.merkleTrees.writePublicStorage(leafSlot, value);
|
|
143
154
|
assert(result !== undefined, 'Public data tree insertion error. You might want to disable skipMerkleOperations.');
|
|
144
155
|
this.log.debug(`Inserted public data tree leaf at leafSlot ${leafSlot}, value: ${value}`);
|
|
145
156
|
|
|
146
|
-
const lowLeafInfo = result.
|
|
147
|
-
const lowLeafPreimage =
|
|
157
|
+
const lowLeafInfo = result.lowWitness;
|
|
158
|
+
const lowLeafPreimage = result.lowWitness.preimage as PublicDataTreeLeafPreimage;
|
|
148
159
|
const lowLeafIndex = lowLeafInfo.index;
|
|
149
|
-
const lowLeafPath = lowLeafInfo.siblingPath
|
|
160
|
+
const lowLeafPath = lowLeafInfo.siblingPath;
|
|
161
|
+
|
|
162
|
+
const insertionPath = result.insertionPath;
|
|
163
|
+
const newLeafPreimage = result.newOrElementToUpdate.element as PublicDataTreeLeafPreimage;
|
|
150
164
|
|
|
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
165
|
this.trace.tracePublicStorageWrite(
|
|
160
166
|
contractAddress,
|
|
161
167
|
slot,
|
|
@@ -179,23 +185,24 @@ export class AvmPersistableStateManager {
|
|
|
179
185
|
* @returns the latest value written to slot, or 0 if never written to before
|
|
180
186
|
*/
|
|
181
187
|
public async readStorage(contractAddress: AztecAddress, slot: Fr): Promise<Fr> {
|
|
182
|
-
const { value,
|
|
183
|
-
this.log.debug(
|
|
184
|
-
`Storage read (address=${contractAddress}, slot=${slot}): value=${value}, exists=${exists}, cached=${cached}`,
|
|
185
|
-
);
|
|
188
|
+
const { value, cached } = await this.publicStorage.read(contractAddress, slot);
|
|
189
|
+
this.log.debug(`Storage read (address=${contractAddress}, slot=${slot}): value=${value}, cached=${cached}`);
|
|
186
190
|
|
|
187
191
|
const leafSlot = computePublicDataTreeLeafSlot(contractAddress, slot);
|
|
188
192
|
|
|
189
193
|
if (this.doMerkleOperations) {
|
|
190
194
|
// Get leaf if present, low leaf if absent
|
|
191
195
|
// If leaf is present, hint/trace it. Otherwise, hint/trace the low leaf.
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
);
|
|
197
|
-
//
|
|
198
|
-
//
|
|
196
|
+
const {
|
|
197
|
+
preimage,
|
|
198
|
+
index: leafIndex,
|
|
199
|
+
update: exists,
|
|
200
|
+
} = await this.merkleTrees.getLeafOrLowLeafInfo(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot);
|
|
201
|
+
// The index and preimage here is either the low leaf or the leaf itself (depending on the value of update flag)
|
|
202
|
+
// In either case, we just want the sibling path to this leaf - it's up to the avm to distinguish if it's a low leaf or not
|
|
203
|
+
const leafPath = await this.merkleTrees.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, leafIndex);
|
|
204
|
+
const leafPreimage = preimage as PublicDataTreeLeafPreimage;
|
|
205
|
+
|
|
199
206
|
this.log.debug(
|
|
200
207
|
`leafPreimage.nextSlot: ${leafPreimage.nextSlot}, leafPreimage.nextIndex: ${Number(leafPreimage.nextIndex)}`,
|
|
201
208
|
);
|
|
@@ -204,15 +211,16 @@ export class AvmPersistableStateManager {
|
|
|
204
211
|
if (!exists) {
|
|
205
212
|
// Sanity check that the leaf slot is skipped by low leaf when it doesn't exist
|
|
206
213
|
assert(
|
|
207
|
-
|
|
208
|
-
|
|
214
|
+
leafPreimage.slot.toBigInt() < leafSlot.toBigInt() &&
|
|
215
|
+
(leafPreimage.nextIndex === 0n || leafPreimage.nextSlot.toBigInt() > leafSlot.toBigInt()),
|
|
216
|
+
'Public data tree low leaf should skip the target leaf slot when the target leaf does not exist or is the max value.',
|
|
209
217
|
);
|
|
210
218
|
}
|
|
211
219
|
this.log.debug(
|
|
212
220
|
`Tracing storage leaf preimage slot=${slot}, leafSlot=${leafSlot}, value=${value}, nextKey=${leafPreimage.nextSlot}, nextIndex=${leafPreimage.nextIndex}`,
|
|
213
221
|
);
|
|
214
222
|
// 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
|
|
223
|
+
// prove that this is a low leaf that skips leafSlot, and then prove membership of the leaf.
|
|
216
224
|
this.trace.tracePublicStorageRead(contractAddress, slot, value, leafPreimage, new Fr(leafIndex), leafPath);
|
|
217
225
|
} else {
|
|
218
226
|
this.trace.tracePublicStorageRead(contractAddress, slot, value);
|
|
@@ -229,10 +237,8 @@ export class AvmPersistableStateManager {
|
|
|
229
237
|
* @returns the latest value written to slot, or 0 if never written to before
|
|
230
238
|
*/
|
|
231
239
|
public async peekStorage(contractAddress: AztecAddress, slot: Fr): Promise<Fr> {
|
|
232
|
-
const { value,
|
|
233
|
-
this.log.debug(
|
|
234
|
-
`Storage peek (address=${contractAddress}, slot=${slot}): value=${value}, exists=${exists}, cached=${cached}`,
|
|
235
|
-
);
|
|
240
|
+
const { value, cached } = await this.publicStorage.read(contractAddress, slot);
|
|
241
|
+
this.log.debug(`Storage peek (address=${contractAddress}, slot=${slot}): value=${value}, cached=${cached}`);
|
|
236
242
|
return Promise.resolve(value);
|
|
237
243
|
}
|
|
238
244
|
|
|
@@ -255,7 +261,7 @@ export class AvmPersistableStateManager {
|
|
|
255
261
|
// TODO(8287): We still return exists here, but we need to transmit both the requested noteHash and the gotLeafValue
|
|
256
262
|
// such that the VM can constrain the equality and decide on exists based on that.
|
|
257
263
|
const path = await this.merkleTrees.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex.toBigInt());
|
|
258
|
-
this.trace.traceNoteHashCheck(contractAddress, gotLeafValue, leafIndex, exists, path
|
|
264
|
+
this.trace.traceNoteHashCheck(contractAddress, gotLeafValue, leafIndex, exists, path);
|
|
259
265
|
} else {
|
|
260
266
|
this.trace.traceNoteHashCheck(contractAddress, gotLeafValue, leafIndex, exists);
|
|
261
267
|
}
|
|
@@ -266,19 +272,15 @@ export class AvmPersistableStateManager {
|
|
|
266
272
|
* Write a note hash, trace the write.
|
|
267
273
|
* @param noteHash - the unsiloed note hash to write
|
|
268
274
|
*/
|
|
269
|
-
public
|
|
275
|
+
public writeNoteHash(contractAddress: AztecAddress, noteHash: Fr): void {
|
|
270
276
|
this.log.debug(`noteHashes(${contractAddress}) += @${noteHash}.`);
|
|
271
277
|
|
|
272
278
|
if (this.doMerkleOperations) {
|
|
273
|
-
//
|
|
274
|
-
const
|
|
275
|
-
const leafIndex = new Fr(info.size + 1n);
|
|
276
|
-
|
|
277
|
-
const path = await this.merkleTrees.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex.toBigInt());
|
|
279
|
+
// Should write a helper for this
|
|
280
|
+
const leafIndex = new Fr(this.merkleTrees.treeMap.get(MerkleTreeId.NOTE_HASH_TREE)!.leafCount);
|
|
278
281
|
const siloedNoteHash = siloNoteHash(contractAddress, noteHash);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
this.trace.traceNewNoteHash(contractAddress, noteHash, leafIndex, path.toFields());
|
|
282
|
+
const insertionPath = this.merkleTrees.appendNoteHash(siloedNoteHash);
|
|
283
|
+
this.trace.traceNewNoteHash(contractAddress, noteHash, leafIndex, insertionPath);
|
|
282
284
|
} else {
|
|
283
285
|
this.trace.traceNewNoteHash(contractAddress, noteHash);
|
|
284
286
|
}
|
|
@@ -298,15 +300,15 @@ export class AvmPersistableStateManager {
|
|
|
298
300
|
if (this.doMerkleOperations) {
|
|
299
301
|
// Get leaf if present, low leaf if absent
|
|
300
302
|
// If leaf is present, hint/trace it. Otherwise, hint/trace the low leaf.
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
);
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
);
|
|
303
|
+
const {
|
|
304
|
+
preimage,
|
|
305
|
+
index: leafIndex,
|
|
306
|
+
update,
|
|
307
|
+
} = await this.merkleTrees.getLeafOrLowLeafInfo(MerkleTreeId.NULLIFIER_TREE, siloedNullifier);
|
|
308
|
+
const leafPreimage = preimage as NullifierLeafPreimage;
|
|
309
|
+
const leafPath = await this.merkleTrees.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, leafIndex);
|
|
310
|
+
|
|
311
|
+
assert(update == exists, 'WorldStateDB contains nullifier leaf, but merkle tree does not.... This is a bug!');
|
|
310
312
|
|
|
311
313
|
this.log.debug(
|
|
312
314
|
`nullifiers(${contractAddress})@${nullifier} ?? leafIndex: ${leafIndex}, exists: ${exists}, pending: ${isPending}.`,
|
|
@@ -346,40 +348,55 @@ export class AvmPersistableStateManager {
|
|
|
346
348
|
*/
|
|
347
349
|
public async writeNullifier(contractAddress: AztecAddress, nullifier: Fr) {
|
|
348
350
|
this.log.debug(`nullifiers(${contractAddress}) += ${nullifier}.`);
|
|
349
|
-
// Cache pending nullifiers for later access
|
|
350
|
-
await this.nullifiers.append(contractAddress, nullifier);
|
|
351
351
|
|
|
352
352
|
const siloedNullifier = siloNullifier(contractAddress, nullifier);
|
|
353
353
|
|
|
354
354
|
if (this.doMerkleOperations) {
|
|
355
|
+
// Maybe overkill, but we should check if the nullifier is already present in the tree before attempting to insert
|
|
356
|
+
// It might be better to catch the error from the insert operation
|
|
355
357
|
// Trace all nullifier creations, even duplicate insertions that fail
|
|
356
|
-
const
|
|
358
|
+
const { preimage, index, update } = await this.merkleTrees.getLeafOrLowLeafInfo(
|
|
357
359
|
MerkleTreeId.NULLIFIER_TREE,
|
|
358
|
-
siloedNullifier
|
|
360
|
+
siloedNullifier,
|
|
359
361
|
);
|
|
360
|
-
if (
|
|
361
|
-
this.log.verbose(`Nullifier already present in tree: ${nullifier} at index ${
|
|
362
|
+
if (update) {
|
|
363
|
+
this.log.verbose(`Nullifier already present in tree: ${nullifier} at index ${index}.`);
|
|
364
|
+
// If the nullifier is already present, we should not insert it again
|
|
365
|
+
// instead we provide the direct membership path
|
|
366
|
+
const path = await this.merkleTrees.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, index);
|
|
367
|
+
// This just becomes a nullifier read hint
|
|
368
|
+
this.trace.traceNullifierCheck(
|
|
369
|
+
contractAddress,
|
|
370
|
+
nullifier,
|
|
371
|
+
/*exists=*/ update,
|
|
372
|
+
preimage as NullifierLeafPreimage,
|
|
373
|
+
new Fr(index),
|
|
374
|
+
path,
|
|
375
|
+
);
|
|
376
|
+
throw new NullifierCollisionError(
|
|
377
|
+
`Nullifier ${nullifier} at contract ${contractAddress} already exists in parent cache or host.`,
|
|
378
|
+
);
|
|
379
|
+
} else {
|
|
380
|
+
// Cache pending nullifiers for later access
|
|
381
|
+
await this.nullifiers.append(contractAddress, nullifier);
|
|
382
|
+
// We append the new nullifier
|
|
383
|
+
const appendResult = await this.merkleTrees.appendNullifier(siloedNullifier);
|
|
384
|
+
const lowLeafPreimage = appendResult.lowWitness.preimage as NullifierLeafPreimage;
|
|
385
|
+
const lowLeafIndex = appendResult.lowWitness.index;
|
|
386
|
+
const lowLeafPath = appendResult.lowWitness.siblingPath;
|
|
387
|
+
const insertionPath = appendResult.insertionPath;
|
|
388
|
+
this.trace.traceNewNullifier(
|
|
389
|
+
contractAddress,
|
|
390
|
+
nullifier,
|
|
391
|
+
lowLeafPreimage,
|
|
392
|
+
new Fr(lowLeafIndex),
|
|
393
|
+
lowLeafPath,
|
|
394
|
+
insertionPath,
|
|
395
|
+
);
|
|
362
396
|
}
|
|
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
397
|
} else {
|
|
398
|
+
// Cache pending nullifiers for later access
|
|
399
|
+
await this.nullifiers.append(contractAddress, nullifier);
|
|
383
400
|
this.trace.traceNewNullifier(contractAddress, nullifier);
|
|
384
401
|
}
|
|
385
402
|
}
|
|
@@ -404,7 +421,11 @@ export class AvmPersistableStateManager {
|
|
|
404
421
|
if (this.doMerkleOperations) {
|
|
405
422
|
// TODO(8287): We still return exists here, but we need to transmit both the requested msgHash and the value
|
|
406
423
|
// such that the VM can constrain the equality and decide on exists based on that.
|
|
407
|
-
|
|
424
|
+
// We should defintely add a helper here
|
|
425
|
+
const path = await this.merkleTrees.treeDb.getSiblingPath(
|
|
426
|
+
MerkleTreeId.L1_TO_L2_MESSAGE_TREE,
|
|
427
|
+
msgLeafIndex.toBigInt(),
|
|
428
|
+
);
|
|
408
429
|
this.trace.traceL1ToL2MessageCheck(contractAddress, valueAtIndex, msgLeafIndex, exists, path.toFields());
|
|
409
430
|
} else {
|
|
410
431
|
this.trace.traceL1ToL2MessageCheck(contractAddress, valueAtIndex, msgLeafIndex, exists);
|
|
@@ -504,7 +525,6 @@ export class AvmPersistableStateManager {
|
|
|
504
525
|
forkedState: AvmPersistableStateManager,
|
|
505
526
|
nestedEnvironment: AvmExecutionEnvironment,
|
|
506
527
|
startGasLeft: Gas,
|
|
507
|
-
endGasLeft: Gas,
|
|
508
528
|
bytecode: Buffer,
|
|
509
529
|
avmCallResults: AvmContractCallResult,
|
|
510
530
|
) {
|
|
@@ -521,7 +541,6 @@ export class AvmPersistableStateManager {
|
|
|
521
541
|
forkedState.trace,
|
|
522
542
|
nestedEnvironment,
|
|
523
543
|
startGasLeft,
|
|
524
|
-
endGasLeft,
|
|
525
544
|
bytecode,
|
|
526
545
|
avmCallResults,
|
|
527
546
|
functionName,
|
|
@@ -532,30 +551,3 @@ export class AvmPersistableStateManager {
|
|
|
532
551
|
this.trace.traceEnqueuedCall(publicCallRequest, calldata, reverted);
|
|
533
552
|
}
|
|
534
553
|
}
|
|
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;
|
|
561
|
-
}
|
|
@@ -5,7 +5,6 @@ import type { PublicStateDB } from '../../index.js';
|
|
|
5
5
|
|
|
6
6
|
type PublicStorageReadResult = {
|
|
7
7
|
value: Fr;
|
|
8
|
-
exists: boolean;
|
|
9
8
|
cached: boolean;
|
|
10
9
|
};
|
|
11
10
|
|
|
@@ -77,17 +76,17 @@ export class PublicStorage {
|
|
|
77
76
|
let value = this.readHereOrParent(contractAddress, slot);
|
|
78
77
|
// Finally try the host's Aztec state (a trip to the database)
|
|
79
78
|
if (!value) {
|
|
80
|
-
|
|
79
|
+
// This functions returns Fr.ZERO if it has never been written to before
|
|
80
|
+
// we explicity coalesce to Fr.ZERO in case we have some implementations that cause this to return undefined
|
|
81
|
+
value = (await this.hostPublicStorage.storageRead(contractAddress, slot)) ?? Fr.ZERO;
|
|
81
82
|
// TODO(dbanks12): if value retrieved from host storage, we can cache it here
|
|
82
83
|
// any future reads to the same slot can read from cache instead of more expensive
|
|
83
84
|
// DB access
|
|
84
85
|
} else {
|
|
85
86
|
cached = true;
|
|
86
87
|
}
|
|
87
|
-
// if value is
|
|
88
|
-
|
|
89
|
-
const valueOrZero = exists ? value : Fr.ZERO;
|
|
90
|
-
return Promise.resolve({ value: valueOrZero, exists, cached });
|
|
88
|
+
// if value is Fr.ZERO here, it that means this slot has never been written to!
|
|
89
|
+
return Promise.resolve({ value, cached });
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
/**
|
|
@@ -70,7 +70,7 @@ export class EmitNoteHash extends Instruction {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
const noteHash = memory.get(noteHashOffset).toFr();
|
|
73
|
-
|
|
73
|
+
context.persistableState.writeNoteHash(context.environment.address, noteHash);
|
|
74
74
|
|
|
75
75
|
memory.assert({ reads: 1, addressing });
|
|
76
76
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { strict as assert } from 'assert';
|
|
2
2
|
|
|
3
|
-
import { type TaggedMemoryInterface } from '../avm_memory_types.js';
|
|
3
|
+
import { TaggedMemory, type TaggedMemoryInterface } from '../avm_memory_types.js';
|
|
4
|
+
import { AddressOutOfRangeError } from '../errors.js';
|
|
4
5
|
|
|
5
6
|
export enum AddressingMode {
|
|
6
7
|
DIRECT = 0,
|
|
@@ -63,7 +64,11 @@ export class Addressing {
|
|
|
63
64
|
resolved[i] = offset;
|
|
64
65
|
if (mode & AddressingMode.RELATIVE) {
|
|
65
66
|
mem.checkIsValidMemoryOffsetTag(0);
|
|
66
|
-
|
|
67
|
+
const baseAddr = Number(mem.get(0).toBigInt());
|
|
68
|
+
resolved[i] += baseAddr;
|
|
69
|
+
if (resolved[i] >= TaggedMemory.MAX_MEMORY_SIZE) {
|
|
70
|
+
throw new AddressOutOfRangeError(baseAddr, offset);
|
|
71
|
+
}
|
|
67
72
|
}
|
|
68
73
|
if (mode & AddressingMode.INDIRECT) {
|
|
69
74
|
mem.checkIsValidMemoryOffsetTag(resolved[i]);
|
|
@@ -18,18 +18,18 @@ export class GetContractInstance extends Instruction {
|
|
|
18
18
|
static readonly wireFormat: OperandType[] = [
|
|
19
19
|
OperandType.UINT8, // opcode
|
|
20
20
|
OperandType.UINT8, // indirect bits
|
|
21
|
-
OperandType.UINT8, // member enum (immediate)
|
|
22
21
|
OperandType.UINT16, // addressOffset
|
|
23
22
|
OperandType.UINT16, // dstOffset
|
|
24
23
|
OperandType.UINT16, // existsOfsset
|
|
24
|
+
OperandType.UINT8, // member enum (immediate)
|
|
25
25
|
];
|
|
26
26
|
|
|
27
27
|
constructor(
|
|
28
28
|
private indirect: number,
|
|
29
|
-
private memberEnum: number,
|
|
30
29
|
private addressOffset: number,
|
|
31
30
|
private dstOffset: number,
|
|
32
31
|
private existsOffset: number,
|
|
32
|
+
private memberEnum: number,
|
|
33
33
|
) {
|
|
34
34
|
super();
|
|
35
35
|
}
|
|
@@ -36,11 +36,11 @@ export class JumpI extends Instruction {
|
|
|
36
36
|
static readonly wireFormat: OperandType[] = [
|
|
37
37
|
OperandType.UINT8,
|
|
38
38
|
OperandType.UINT8,
|
|
39
|
-
OperandType.UINT32,
|
|
40
39
|
OperandType.UINT16,
|
|
40
|
+
OperandType.UINT32,
|
|
41
41
|
];
|
|
42
42
|
|
|
43
|
-
constructor(private indirect: number, private
|
|
43
|
+
constructor(private indirect: number, private condOffset: number, private loc: number) {
|
|
44
44
|
super();
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -60,11 +60,11 @@ export class GetEnvVar extends Instruction {
|
|
|
60
60
|
static readonly wireFormat16: OperandType[] = [
|
|
61
61
|
OperandType.UINT8, // opcode
|
|
62
62
|
OperandType.UINT8, // indirect
|
|
63
|
-
OperandType.UINT8, // variable enum (immediate)
|
|
64
63
|
OperandType.UINT16, // dstOffset
|
|
64
|
+
OperandType.UINT8, // variable enum (immediate)
|
|
65
65
|
];
|
|
66
66
|
|
|
67
|
-
constructor(private indirect: number, private
|
|
67
|
+
constructor(private indirect: number, private dstOffset: number, private varEnum: number) {
|
|
68
68
|
super();
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -2,7 +2,6 @@ import { Fr, FunctionSelector, Gas, PUBLIC_DISPATCH_SELECTOR } from '@aztec/circ
|
|
|
2
2
|
|
|
3
3
|
import type { AvmContext } from '../avm_context.js';
|
|
4
4
|
import { type AvmContractCallResult } from '../avm_contract_call_result.js';
|
|
5
|
-
import { gasLeftToGas } from '../avm_gas.js';
|
|
6
5
|
import { type Field, TypeTag, Uint1 } from '../avm_memory_types.js';
|
|
7
6
|
import { AvmSimulator } from '../avm_simulator.js';
|
|
8
7
|
import { Opcode, OperandType } from '../serialization/instruction_serialization.js';
|
|
@@ -95,7 +94,7 @@ abstract class ExternalCall extends Instruction {
|
|
|
95
94
|
memory.set(successOffset, new Uint1(success ? 1 : 0));
|
|
96
95
|
|
|
97
96
|
// Refund unused gas
|
|
98
|
-
context.machineState.refundGas(
|
|
97
|
+
context.machineState.refundGas(nestedCallResults.gasLeft);
|
|
99
98
|
|
|
100
99
|
// Merge nested call's state and trace based on whether it succeeded.
|
|
101
100
|
if (success) {
|
|
@@ -107,7 +106,6 @@ abstract class ExternalCall extends Instruction {
|
|
|
107
106
|
/*nestedState=*/ nestedContext.persistableState,
|
|
108
107
|
/*nestedEnvironment=*/ nestedContext.environment,
|
|
109
108
|
/*startGasLeft=*/ Gas.from(allocatedGas),
|
|
110
|
-
/*endGasLeft=*/ Gas.from(nestedContext.machineState.gasLeft),
|
|
111
109
|
/*bytecode=*/ simulator.getBytecode()!,
|
|
112
110
|
/*avmCallResults=*/ nestedCallResults,
|
|
113
111
|
);
|
|
@@ -12,51 +12,51 @@ export class Set extends Instruction {
|
|
|
12
12
|
public static readonly wireFormat8: OperandType[] = [
|
|
13
13
|
OperandType.UINT8, // opcode
|
|
14
14
|
OperandType.UINT8, // indirect
|
|
15
|
+
OperandType.UINT8, // dstOffset
|
|
15
16
|
OperandType.UINT8, // tag
|
|
16
17
|
OperandType.UINT8, // const (value)
|
|
17
|
-
OperandType.UINT8, // dstOffset
|
|
18
18
|
];
|
|
19
19
|
public static readonly wireFormat16: OperandType[] = [
|
|
20
20
|
OperandType.UINT8, // opcode
|
|
21
21
|
OperandType.UINT8, // indirect
|
|
22
|
+
OperandType.UINT16, // dstOffset
|
|
22
23
|
OperandType.UINT8, // tag
|
|
23
24
|
OperandType.UINT16, // const (value)
|
|
24
|
-
OperandType.UINT16, // dstOffset
|
|
25
25
|
];
|
|
26
26
|
public static readonly wireFormat32: OperandType[] = [
|
|
27
27
|
OperandType.UINT8, // opcode
|
|
28
28
|
OperandType.UINT8, // indirect
|
|
29
|
+
OperandType.UINT16, // dstOffset
|
|
29
30
|
OperandType.UINT8, // tag
|
|
30
31
|
OperandType.UINT32, // const (value)
|
|
31
|
-
OperandType.UINT16, // dstOffset
|
|
32
32
|
];
|
|
33
33
|
public static readonly wireFormat64: OperandType[] = [
|
|
34
34
|
OperandType.UINT8, // opcode
|
|
35
35
|
OperandType.UINT8, // indirect
|
|
36
|
+
OperandType.UINT16, // dstOffset
|
|
36
37
|
OperandType.UINT8, // tag
|
|
37
38
|
OperandType.UINT64, // const (value)
|
|
38
|
-
OperandType.UINT16, // dstOffset
|
|
39
39
|
];
|
|
40
40
|
public static readonly wireFormat128: OperandType[] = [
|
|
41
41
|
OperandType.UINT8, // opcode
|
|
42
42
|
OperandType.UINT8, // indirect
|
|
43
|
+
OperandType.UINT16, // dstOffset
|
|
43
44
|
OperandType.UINT8, // tag
|
|
44
45
|
OperandType.UINT128, // const (value)
|
|
45
|
-
OperandType.UINT16, // dstOffset
|
|
46
46
|
];
|
|
47
47
|
public static readonly wireFormatFF: OperandType[] = [
|
|
48
48
|
OperandType.UINT8, // opcode
|
|
49
49
|
OperandType.UINT8, // indirect
|
|
50
|
+
OperandType.UINT16, // dstOffset
|
|
50
51
|
OperandType.UINT8, // tag
|
|
51
52
|
OperandType.FF, // const (value)
|
|
52
|
-
OperandType.UINT16, // dstOffset
|
|
53
53
|
];
|
|
54
54
|
|
|
55
55
|
constructor(
|
|
56
56
|
private indirect: number,
|
|
57
|
+
private dstOffset: number,
|
|
57
58
|
private inTag: number,
|
|
58
59
|
private value: bigint | number,
|
|
59
|
-
private dstOffset: number,
|
|
60
60
|
) {
|
|
61
61
|
super();
|
|
62
62
|
}
|
|
@@ -87,14 +87,14 @@ export class Cast extends Instruction {
|
|
|
87
87
|
OperandType.UINT8,
|
|
88
88
|
];
|
|
89
89
|
static readonly wireFormat16 = [
|
|
90
|
-
OperandType.UINT8,
|
|
91
90
|
OperandType.UINT8,
|
|
92
91
|
OperandType.UINT8,
|
|
93
92
|
OperandType.UINT16,
|
|
94
93
|
OperandType.UINT16,
|
|
94
|
+
OperandType.UINT8,
|
|
95
95
|
];
|
|
96
96
|
|
|
97
|
-
constructor(private indirect: number, private
|
|
97
|
+
constructor(private indirect: number, private srcOffset: number, private dstOffset: number, private dstTag: number) {
|
|
98
98
|
super();
|
|
99
99
|
}
|
|
100
100
|
|
package/src/avm/opcodes/misc.ts
CHANGED
|
@@ -16,17 +16,17 @@ export class DebugLog extends Instruction {
|
|
|
16
16
|
OperandType.UINT8, // Opcode
|
|
17
17
|
OperandType.UINT8, // Indirect
|
|
18
18
|
OperandType.UINT16, // message memory address
|
|
19
|
-
OperandType.UINT16, // message size
|
|
20
19
|
OperandType.UINT16, // fields memory address
|
|
21
20
|
OperandType.UINT16, // fields size address
|
|
21
|
+
OperandType.UINT16, // message size
|
|
22
22
|
];
|
|
23
23
|
|
|
24
24
|
constructor(
|
|
25
25
|
private indirect: number,
|
|
26
26
|
private messageOffset: number,
|
|
27
|
-
private messageSize: number,
|
|
28
27
|
private fieldsOffset: number,
|
|
29
28
|
private fieldsSizeOffset: number,
|
|
29
|
+
private messageSize: number,
|
|
30
30
|
) {
|
|
31
31
|
super();
|
|
32
32
|
}
|