@aztec/simulator 0.72.1 → 0.73.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/README.md +1 -1
- package/dest/avm/avm_simulator.d.ts +0 -1
- package/dest/avm/avm_simulator.d.ts.map +1 -1
- package/dest/avm/avm_simulator.js +4 -19
- package/dest/avm/avm_tree.d.ts +9 -8
- package/dest/avm/avm_tree.d.ts.map +1 -1
- package/dest/avm/avm_tree.js +35 -30
- package/dest/avm/fixtures/avm_simulation_tester.d.ts +21 -0
- package/dest/avm/fixtures/avm_simulation_tester.d.ts.map +1 -0
- package/dest/avm/fixtures/avm_simulation_tester.js +74 -0
- package/dest/avm/fixtures/base_avm_simulation_tester.d.ts +35 -0
- package/dest/avm/fixtures/base_avm_simulation_tester.d.ts.map +1 -0
- package/dest/avm/fixtures/base_avm_simulation_tester.js +76 -0
- package/dest/avm/fixtures/index.d.ts +6 -2
- package/dest/avm/fixtures/index.d.ts.map +1 -1
- package/dest/avm/fixtures/index.js +28 -14
- package/dest/avm/fixtures/simple_contract_data_source.d.ts +31 -0
- package/dest/avm/fixtures/simple_contract_data_source.d.ts.map +1 -0
- package/dest/avm/fixtures/simple_contract_data_source.js +75 -0
- package/dest/avm/journal/journal.d.ts +3 -3
- package/dest/avm/journal/journal.d.ts.map +1 -1
- package/dest/avm/journal/journal.js +24 -21
- package/dest/avm/opcodes/accrued_substate.js +2 -2
- package/dest/avm/opcodes/hashing.js +2 -2
- package/dest/avm/test_utils.d.ts +1 -1
- package/dest/avm/test_utils.d.ts.map +1 -1
- package/dest/avm/test_utils.js +4 -3
- package/dest/client/client_execution_context.d.ts +1 -1
- package/dest/client/client_execution_context.d.ts.map +1 -1
- package/dest/client/client_execution_context.js +21 -18
- package/dest/client/execution_note_cache.d.ts +3 -3
- package/dest/client/execution_note_cache.d.ts.map +1 -1
- package/dest/client/execution_note_cache.js +10 -10
- package/dest/client/pick_notes.js +5 -5
- package/dest/client/simulator.js +4 -4
- package/dest/client/view_data_oracle.js +2 -2
- package/dest/common/hashed_values_cache.d.ts +1 -1
- package/dest/common/hashed_values_cache.d.ts.map +1 -1
- package/dest/common/hashed_values_cache.js +5 -5
- package/dest/providers/acvm_wasm.d.ts +2 -0
- package/dest/providers/acvm_wasm.d.ts.map +1 -1
- package/dest/providers/acvm_wasm.js +15 -2
- package/dest/public/execution.d.ts +1 -1
- package/dest/public/execution.d.ts.map +1 -1
- package/dest/public/execution.js +3 -3
- package/dest/public/fee_payment.d.ts +2 -2
- package/dest/public/fee_payment.d.ts.map +1 -1
- package/dest/public/fee_payment.js +3 -3
- package/dest/public/fixtures/index.d.ts +2 -37
- package/dest/public/fixtures/index.d.ts.map +1 -1
- package/dest/public/fixtures/index.js +3 -250
- package/dest/public/fixtures/public_tx_simulation_tester.d.ts +21 -0
- package/dest/public/fixtures/public_tx_simulation_tester.d.ts.map +1 -0
- package/dest/public/fixtures/public_tx_simulation_tester.js +89 -0
- package/dest/public/fixtures/utils.d.ts +17 -0
- package/dest/public/fixtures/utils.d.ts.map +1 -0
- package/dest/public/fixtures/utils.js +66 -0
- package/dest/public/index.d.ts +1 -1
- package/dest/public/index.d.ts.map +1 -1
- package/dest/public/index.js +2 -2
- package/dest/public/public_db_sources.d.ts +2 -1
- package/dest/public/public_db_sources.d.ts.map +1 -1
- package/dest/public/public_db_sources.js +27 -21
- package/dest/public/public_processor.d.ts +1 -1
- package/dest/public/public_processor.d.ts.map +1 -1
- package/dest/public/public_processor.js +19 -18
- package/dest/public/public_tx_context.d.ts +5 -5
- package/dest/public/public_tx_context.d.ts.map +1 -1
- package/dest/public/public_tx_context.js +43 -17
- package/dest/public/public_tx_simulator.js +7 -7
- package/dest/public/{enqueued_call_side_effect_trace.d.ts → side_effect_trace.d.ts} +5 -5
- package/dest/public/side_effect_trace.d.ts.map +1 -0
- package/dest/public/{enqueued_call_side_effect_trace.js → side_effect_trace.js} +7 -7
- package/dest/public/side_effect_trace_interface.d.ts +1 -1
- package/dest/public/side_effect_trace_interface.d.ts.map +1 -1
- package/dest/test/utils.d.ts +1 -1
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +3 -3
- package/package.json +9 -9
- package/src/avm/avm_simulator.ts +3 -27
- package/src/avm/avm_tree.ts +39 -37
- package/src/avm/fixtures/avm_simulation_tester.ts +105 -0
- package/src/avm/fixtures/base_avm_simulation_tester.ts +104 -0
- package/src/avm/fixtures/index.ts +46 -17
- package/src/avm/fixtures/simple_contract_data_source.ts +98 -0
- package/src/avm/journal/journal.ts +24 -21
- package/src/avm/opcodes/accrued_substate.ts +1 -1
- package/src/avm/opcodes/hashing.ts +1 -1
- package/src/avm/test_utils.ts +3 -4
- package/src/client/client_execution_context.ts +27 -21
- package/src/client/execution_note_cache.ts +19 -14
- package/src/client/pick_notes.ts +4 -4
- package/src/client/simulator.ts +3 -3
- package/src/client/view_data_oracle.ts +1 -1
- package/src/common/hashed_values_cache.ts +4 -4
- package/src/providers/acvm_wasm.ts +13 -2
- package/src/public/execution.ts +3 -3
- package/src/public/fee_payment.ts +2 -2
- package/src/public/fixtures/index.ts +2 -387
- package/src/public/fixtures/public_tx_simulation_tester.ts +174 -0
- package/src/public/fixtures/utils.ts +110 -0
- package/src/public/index.ts +1 -1
- package/src/public/public_db_sources.ts +31 -20
- package/src/public/public_processor.ts +25 -18
- package/src/public/public_tx_context.ts +86 -27
- package/src/public/public_tx_simulator.ts +6 -6
- package/src/public/{enqueued_call_side_effect_trace.ts → side_effect_trace.ts} +7 -7
- package/src/public/side_effect_trace_interface.ts +1 -1
- package/src/test/utils.ts +2 -2
- package/dest/public/enqueued_call_side_effect_trace.d.ts.map +0 -1
- package/dest/public/transitional_adapters.d.ts +0 -4
- package/dest/public/transitional_adapters.d.ts.map +0 -1
- package/dest/public/transitional_adapters.js +0 -29
- package/src/public/transitional_adapters.ts +0 -113
package/src/avm/avm_simulator.ts
CHANGED
|
@@ -28,20 +28,14 @@ type OpcodeTally = {
|
|
|
28
28
|
count: number;
|
|
29
29
|
gas: Gas;
|
|
30
30
|
};
|
|
31
|
-
type PcTally = {
|
|
32
|
-
opcode: string;
|
|
33
|
-
count: number;
|
|
34
|
-
gas: Gas;
|
|
35
|
-
};
|
|
36
31
|
|
|
37
32
|
export class AvmSimulator {
|
|
38
33
|
private log: Logger;
|
|
39
34
|
private bytecode: Buffer | undefined;
|
|
40
35
|
private opcodeTallies: Map<string, OpcodeTally> = new Map();
|
|
41
|
-
private pcTallies: Map<number, PcTally> = new Map();
|
|
42
36
|
|
|
43
37
|
private tallyPrintFunction = () => {};
|
|
44
|
-
private tallyInstructionFunction = (
|
|
38
|
+
private tallyInstructionFunction = (_b: string, _c: Gas) => {};
|
|
45
39
|
|
|
46
40
|
// Test Purposes only: Logger will not have the proper function name. Use this constructor for testing purposes
|
|
47
41
|
// only. Otherwise, use build() below.
|
|
@@ -145,7 +139,6 @@ export class AvmSimulator {
|
|
|
145
139
|
while (!machineState.getHalted()) {
|
|
146
140
|
const [instruction, bytesRead] = decodeInstructionFromBytecode(bytecode, machineState.pc, this.instructionSet);
|
|
147
141
|
const instrStartGas = machineState.gasLeft; // Save gas before executing instruction (for profiling)
|
|
148
|
-
const instrPc = machineState.pc; // Save PC before executing instruction (for profiling)
|
|
149
142
|
|
|
150
143
|
this.log.trace(
|
|
151
144
|
`[PC:${machineState.pc}] [IC:${instrCounter++}] ${instruction.toString()} (gasLeft l2=${
|
|
@@ -168,7 +161,7 @@ export class AvmSimulator {
|
|
|
168
161
|
l2Gas: instrStartGas.l2Gas - machineState.l2GasLeft,
|
|
169
162
|
daGas: instrStartGas.daGas - machineState.daGasLeft,
|
|
170
163
|
};
|
|
171
|
-
this.tallyInstructionFunction(
|
|
164
|
+
this.tallyInstructionFunction(instruction.constructor.name, gasUsed);
|
|
172
165
|
|
|
173
166
|
if (machineState.pc >= bytecode.length) {
|
|
174
167
|
this.log.warn('Passed end of program');
|
|
@@ -239,18 +232,12 @@ export class AvmSimulator {
|
|
|
239
232
|
);
|
|
240
233
|
}
|
|
241
234
|
|
|
242
|
-
private tallyInstruction(
|
|
235
|
+
private tallyInstruction(opcode: string, gasUsed: Gas) {
|
|
243
236
|
const opcodeTally = this.opcodeTallies.get(opcode) || ({ count: 0, gas: { l2Gas: 0, daGas: 0 } } as OpcodeTally);
|
|
244
237
|
opcodeTally.count++;
|
|
245
238
|
opcodeTally.gas.l2Gas += gasUsed.l2Gas;
|
|
246
239
|
opcodeTally.gas.daGas += gasUsed.daGas;
|
|
247
240
|
this.opcodeTallies.set(opcode, opcodeTally);
|
|
248
|
-
|
|
249
|
-
const pcTally = this.pcTallies.get(pc) || ({ opcode: opcode, count: 0, gas: { l2Gas: 0, daGas: 0 } } as PcTally);
|
|
250
|
-
pcTally.count++;
|
|
251
|
-
pcTally.gas.l2Gas += gasUsed.l2Gas;
|
|
252
|
-
pcTally.gas.daGas += gasUsed.daGas;
|
|
253
|
-
this.pcTallies.set(pc, pcTally);
|
|
254
241
|
}
|
|
255
242
|
|
|
256
243
|
private printOpcodeTallies() {
|
|
@@ -261,16 +248,5 @@ export class AvmSimulator {
|
|
|
261
248
|
// NOTE: don't care to clutter the logs with DA gas for now
|
|
262
249
|
this.log.debug(`${opcode} executed ${tally.count} times consuming a total of ${tally.gas.l2Gas} L2 gas`);
|
|
263
250
|
}
|
|
264
|
-
|
|
265
|
-
this.log.debug(`Printing tallies per PC sorted by #times each PC was executed...`);
|
|
266
|
-
const sortedPcs = Array.from(this.pcTallies.entries())
|
|
267
|
-
.sort((a, b) => b[1].count - a[1].count)
|
|
268
|
-
.filter((_, i) => i < 20);
|
|
269
|
-
for (const [pc, tally] of sortedPcs) {
|
|
270
|
-
// NOTE: don't care to clutter the logs with DA gas for now
|
|
271
|
-
this.log.debug(
|
|
272
|
-
`PC:${pc} containing opcode ${tally.opcode} executed ${tally.count} times consuming a total of ${tally.gas.l2Gas} L2 gas`,
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
251
|
}
|
|
276
252
|
}
|
package/src/avm/avm_tree.ts
CHANGED
|
@@ -109,7 +109,7 @@ export class AvmEphemeralForest {
|
|
|
109
109
|
*/
|
|
110
110
|
async getSiblingPath(treeId: MerkleTreeId, index: bigint): Promise<Fr[]> {
|
|
111
111
|
const tree = this.treeMap.get(treeId)!;
|
|
112
|
-
let path = tree.getSiblingPath(index);
|
|
112
|
+
let path = await tree.getSiblingPath(index);
|
|
113
113
|
if (path === undefined) {
|
|
114
114
|
// We dont have the sibling path in our tree - we have to get it from the DB
|
|
115
115
|
path = (await this.treeDb.getSiblingPath(treeId, index)).toFields();
|
|
@@ -119,7 +119,7 @@ export class AvmEphemeralForest {
|
|
|
119
119
|
const siblingIndex = index ^ 1n;
|
|
120
120
|
const node = tree.getNode(siblingIndex, tree.depth - i);
|
|
121
121
|
if (node !== undefined) {
|
|
122
|
-
const nodeHash = tree.hashTree(node, i + 1);
|
|
122
|
+
const nodeHash = await tree.hashTree(node, i + 1);
|
|
123
123
|
if (!nodeHash.equals(path[i])) {
|
|
124
124
|
path[i] = nodeHash;
|
|
125
125
|
}
|
|
@@ -138,17 +138,17 @@ export class AvmEphemeralForest {
|
|
|
138
138
|
* @param newLeafPreimage - The preimage of the new leaf to be inserted.
|
|
139
139
|
* @returns The sibling path of the new leaf (i.e. the insertion path)
|
|
140
140
|
*/
|
|
141
|
-
appendIndexedTree<ID extends IndexedTreeId, T extends IndexedTreeLeafPreimage>(
|
|
141
|
+
async appendIndexedTree<ID extends IndexedTreeId, T extends IndexedTreeLeafPreimage>(
|
|
142
142
|
treeId: ID,
|
|
143
143
|
lowLeafIndex: bigint,
|
|
144
144
|
lowLeafPreimage: T,
|
|
145
145
|
newLeafPreimage: T,
|
|
146
|
-
): Fr[] {
|
|
146
|
+
): Promise<Fr[]> {
|
|
147
147
|
const tree = this.treeMap.get(treeId)!;
|
|
148
|
-
const newLeaf = this.hashPreimage(newLeafPreimage);
|
|
148
|
+
const newLeaf = await this.hashPreimage(newLeafPreimage);
|
|
149
149
|
const insertIndex = tree.leafCount;
|
|
150
150
|
|
|
151
|
-
const lowLeaf = this.hashPreimage(lowLeafPreimage);
|
|
151
|
+
const lowLeaf = await this.hashPreimage(lowLeafPreimage);
|
|
152
152
|
// Update the low nullifier hash
|
|
153
153
|
this.setIndexedUpdates(treeId, lowLeafIndex, lowLeafPreimage);
|
|
154
154
|
tree.updateLeaf(lowLeaf, lowLeafIndex);
|
|
@@ -156,7 +156,7 @@ export class AvmEphemeralForest {
|
|
|
156
156
|
tree.appendLeaf(newLeaf);
|
|
157
157
|
this.setIndexedUpdates(treeId, insertIndex, newLeafPreimage);
|
|
158
158
|
|
|
159
|
-
return tree.getSiblingPath(insertIndex)!;
|
|
159
|
+
return (await tree.getSiblingPath(insertIndex))!;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
/**
|
|
@@ -186,7 +186,7 @@ export class AvmEphemeralForest {
|
|
|
186
186
|
const existingPublicDataSiblingPath = siblingPath;
|
|
187
187
|
updatedPreimage.value = newValue;
|
|
188
188
|
|
|
189
|
-
tree.updateLeaf(this.hashPreimage(updatedPreimage), lowLeafIndex);
|
|
189
|
+
tree.updateLeaf(await this.hashPreimage(updatedPreimage), lowLeafIndex);
|
|
190
190
|
this.setIndexedUpdates(treeId, lowLeafIndex, updatedPreimage);
|
|
191
191
|
this._updateSortedKeys(treeId, [updatedPreimage.slot], [lowLeafIndex]);
|
|
192
192
|
|
|
@@ -212,7 +212,7 @@ export class AvmEphemeralForest {
|
|
|
212
212
|
new Fr(preimage.getNextKey()),
|
|
213
213
|
preimage.getNextIndex(),
|
|
214
214
|
);
|
|
215
|
-
const insertionPath = this.appendIndexedTree(treeId, lowLeafIndex, updatedLowLeaf, newPublicDataLeaf);
|
|
215
|
+
const insertionPath = await this.appendIndexedTree(treeId, lowLeafIndex, updatedLowLeaf, newPublicDataLeaf);
|
|
216
216
|
|
|
217
217
|
// Even though the low leaf key is not updated, we still need to update the sorted keys in case we have
|
|
218
218
|
// not seen the low leaf before
|
|
@@ -281,7 +281,7 @@ export class AvmEphemeralForest {
|
|
|
281
281
|
updatedLowNullifier.nextIndex = insertionIndex;
|
|
282
282
|
|
|
283
283
|
const newNullifierLeaf = new NullifierLeafPreimage(nullifier, preimage.nextNullifier, preimage.nextIndex);
|
|
284
|
-
const insertionPath = this.appendIndexedTree(treeId, index, updatedLowNullifier, newNullifierLeaf);
|
|
284
|
+
const insertionPath = await this.appendIndexedTree(treeId, index, updatedLowNullifier, newNullifierLeaf);
|
|
285
285
|
|
|
286
286
|
// Even though the low nullifier key is not updated, we still need to update the sorted keys in case we have
|
|
287
287
|
// not seen the low nullifier before
|
|
@@ -308,11 +308,11 @@ export class AvmEphemeralForest {
|
|
|
308
308
|
* @param value - The note hash to be appended
|
|
309
309
|
* @returns The insertion result which contains the insertion path
|
|
310
310
|
*/
|
|
311
|
-
appendNoteHash(noteHash: Fr): Fr[] {
|
|
311
|
+
async appendNoteHash(noteHash: Fr): Promise<Fr[]> {
|
|
312
312
|
const tree = this.treeMap.get(MerkleTreeId.NOTE_HASH_TREE)!;
|
|
313
313
|
tree.appendLeaf(noteHash);
|
|
314
314
|
// We use leafCount - 1 here because we would have just appended a leaf
|
|
315
|
-
const insertionPath = tree.getSiblingPath(tree.leafCount - 1n);
|
|
315
|
+
const insertionPath = await tree.getSiblingPath(tree.leafCount - 1n);
|
|
316
316
|
return insertionPath!;
|
|
317
317
|
}
|
|
318
318
|
|
|
@@ -484,14 +484,15 @@ export class AvmEphemeralForest {
|
|
|
484
484
|
/**
|
|
485
485
|
* This hashes the preimage to a field element
|
|
486
486
|
*/
|
|
487
|
-
hashPreimage<T extends TreeLeafPreimage>(preimage: T): Fr {
|
|
487
|
+
hashPreimage<T extends TreeLeafPreimage>(preimage: T): Promise<Fr> {
|
|
488
488
|
const input = preimage.toHashInputs().map(x => Fr.fromBuffer(x));
|
|
489
489
|
return poseidon2Hash(input);
|
|
490
490
|
}
|
|
491
491
|
|
|
492
|
-
getTreeSnapshot(id: MerkleTreeId): AppendOnlyTreeSnapshot {
|
|
492
|
+
async getTreeSnapshot(id: MerkleTreeId): Promise<AppendOnlyTreeSnapshot> {
|
|
493
493
|
const tree = this.treeMap.get(id)!;
|
|
494
|
-
|
|
494
|
+
const root = await tree.getRoot();
|
|
495
|
+
return new AppendOnlyTreeSnapshot(root, Number(tree.leafCount));
|
|
495
496
|
}
|
|
496
497
|
}
|
|
497
498
|
|
|
@@ -551,19 +552,10 @@ const Leaf = (value: Fr): Leaf => ({
|
|
|
551
552
|
*/
|
|
552
553
|
export class EphemeralAvmTree {
|
|
553
554
|
private tree: Tree;
|
|
554
|
-
private readonly zeroHashes: Fr[];
|
|
555
555
|
public frontier: Fr[];
|
|
556
556
|
|
|
557
|
-
private constructor(public leafCount: bigint, public depth: number) {
|
|
558
|
-
|
|
559
|
-
// Can probably cache this elsewhere
|
|
560
|
-
const zeroHashes = [];
|
|
561
|
-
for (let i = 0; i < this.depth; i++) {
|
|
562
|
-
zeroHashes.push(zeroHash);
|
|
563
|
-
zeroHash = poseidon2Hash([zeroHash, zeroHash]);
|
|
564
|
-
}
|
|
565
|
-
this.tree = Leaf(zeroHash);
|
|
566
|
-
this.zeroHashes = zeroHashes;
|
|
557
|
+
private constructor(private root: Leaf, public leafCount: bigint, public depth: number, private zeroHashes: Fr[]) {
|
|
558
|
+
this.tree = root;
|
|
567
559
|
this.frontier = [];
|
|
568
560
|
}
|
|
569
561
|
|
|
@@ -573,7 +565,14 @@ export class EphemeralAvmTree {
|
|
|
573
565
|
treeDb: MerkleTreeReadOperations,
|
|
574
566
|
merkleId: MerkleTreeId,
|
|
575
567
|
): Promise<EphemeralAvmTree> {
|
|
576
|
-
|
|
568
|
+
let zeroHash = Fr.zero();
|
|
569
|
+
// Can probably cache this elsewhere
|
|
570
|
+
const zeroHashes = [];
|
|
571
|
+
for (let i = 0; i < depth; i++) {
|
|
572
|
+
zeroHashes.push(zeroHash);
|
|
573
|
+
zeroHash = await poseidon2Hash([zeroHash, zeroHash]);
|
|
574
|
+
}
|
|
575
|
+
const tree = new EphemeralAvmTree(Leaf(zeroHash), forkedLeafCount, depth, zeroHashes);
|
|
577
576
|
await tree.initializeFrontier(treeDb, merkleId);
|
|
578
577
|
return tree;
|
|
579
578
|
}
|
|
@@ -604,10 +603,10 @@ export class EphemeralAvmTree {
|
|
|
604
603
|
* @param index - The index of the leaf for which a sibling path should be returned.
|
|
605
604
|
* @returns The sibling path of the leaf, can fail if the path is not found
|
|
606
605
|
*/
|
|
607
|
-
getSiblingPath(index: bigint): Fr[] | undefined {
|
|
606
|
+
async getSiblingPath(index: bigint): Promise<Fr[] | undefined> {
|
|
608
607
|
const searchPath = this._derivePathLE(index);
|
|
609
608
|
// Handle cases where we error out
|
|
610
|
-
const { path, status } = this._getSiblingPath(searchPath, this.tree, []);
|
|
609
|
+
const { path, status } = await this._getSiblingPath(searchPath, this.tree, []);
|
|
611
610
|
if (status === SiblingStatus.ERROR) {
|
|
612
611
|
return undefined;
|
|
613
612
|
}
|
|
@@ -695,7 +694,7 @@ export class EphemeralAvmTree {
|
|
|
695
694
|
/**
|
|
696
695
|
* Computes the root of the tree
|
|
697
696
|
*/
|
|
698
|
-
public getRoot(): Fr {
|
|
697
|
+
public getRoot(): Promise<Fr> {
|
|
699
698
|
return this.hashTree(this.tree, this.depth);
|
|
700
699
|
}
|
|
701
700
|
|
|
@@ -704,10 +703,13 @@ export class EphemeralAvmTree {
|
|
|
704
703
|
* @param tree - The tree to be hashed
|
|
705
704
|
* @param depth - The depth of the tree
|
|
706
705
|
*/
|
|
707
|
-
public hashTree(tree: Tree, depth: number): Fr {
|
|
706
|
+
public async hashTree(tree: Tree, depth: number): Promise<Fr> {
|
|
708
707
|
switch (tree.tag) {
|
|
709
708
|
case TreeType.NODE: {
|
|
710
|
-
return poseidon2Hash([
|
|
709
|
+
return poseidon2Hash([
|
|
710
|
+
await this.hashTree(tree.leftTree, depth - 1),
|
|
711
|
+
await this.hashTree(tree.rightTree, depth - 1),
|
|
712
|
+
]);
|
|
711
713
|
}
|
|
712
714
|
case TreeType.LEAF: {
|
|
713
715
|
return tree.value;
|
|
@@ -807,7 +809,7 @@ export class EphemeralAvmTree {
|
|
|
807
809
|
* @param tree - The current tree
|
|
808
810
|
* @param acc - The accumulated sibling path
|
|
809
811
|
*/
|
|
810
|
-
private _getSiblingPath(searchPath: number[], tree: Tree, acc: Fr[]): AccumulatedSiblingPath {
|
|
812
|
+
private async _getSiblingPath(searchPath: number[], tree: Tree, acc: Fr[]): Promise<AccumulatedSiblingPath> {
|
|
811
813
|
// If we have reached the end of the path, we should be at a leaf or empty node
|
|
812
814
|
// If it is a leaf, we check if the value is equal to the leaf value
|
|
813
815
|
// If it is empty we check if the value is equal to zero
|
|
@@ -824,15 +826,15 @@ export class EphemeralAvmTree {
|
|
|
824
826
|
case TreeType.NODE: {
|
|
825
827
|
// Look at the next element of the path to decided if we go left or right, note this mutates!
|
|
826
828
|
return searchPath.pop() === 0
|
|
827
|
-
? this._getSiblingPath(
|
|
829
|
+
? await this._getSiblingPath(
|
|
828
830
|
searchPath,
|
|
829
831
|
tree.leftTree,
|
|
830
|
-
[this.hashTree(tree.rightTree, searchPath.length)].concat(acc),
|
|
832
|
+
[await this.hashTree(tree.rightTree, searchPath.length)].concat(acc),
|
|
831
833
|
)
|
|
832
|
-
: this._getSiblingPath(
|
|
834
|
+
: await this._getSiblingPath(
|
|
833
835
|
searchPath,
|
|
834
836
|
tree.rightTree,
|
|
835
|
-
[this.hashTree(tree.leftTree, searchPath.length)].concat(acc),
|
|
837
|
+
[await this.hashTree(tree.leftTree, searchPath.length)].concat(acc),
|
|
836
838
|
);
|
|
837
839
|
}
|
|
838
840
|
// In these two situations we are exploring a subtree we dont have information about
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { type MerkleTreeWriteOperations } from '@aztec/circuit-types';
|
|
2
|
+
import { GasFees, GlobalVariables } from '@aztec/circuits.js';
|
|
3
|
+
import { encodeArguments } from '@aztec/foundation/abi';
|
|
4
|
+
import { type AztecAddress } from '@aztec/foundation/aztec-address';
|
|
5
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
6
|
+
import { openTmpStore } from '@aztec/kv-store/lmdb';
|
|
7
|
+
import { MerkleTrees } from '@aztec/world-state';
|
|
8
|
+
|
|
9
|
+
import { type AvmContractCallResult } from '../../avm/avm_contract_call_result.js';
|
|
10
|
+
import {
|
|
11
|
+
getContractFunctionArtifact,
|
|
12
|
+
getFunctionSelector,
|
|
13
|
+
initContext,
|
|
14
|
+
initExecutionEnvironment,
|
|
15
|
+
resolveContractAssertionMessage,
|
|
16
|
+
} from '../../avm/fixtures/index.js';
|
|
17
|
+
import { WorldStateDB } from '../../public/public_db_sources.js';
|
|
18
|
+
import { SideEffectTrace } from '../../public/side_effect_trace.js';
|
|
19
|
+
import { AvmPersistableStateManager, AvmSimulator } from '../../server.js';
|
|
20
|
+
import { BaseAvmSimulationTester } from './base_avm_simulation_tester.js';
|
|
21
|
+
import { SimpleContractDataSource } from './simple_contract_data_source.js';
|
|
22
|
+
|
|
23
|
+
const TIMESTAMP = new Fr(99833);
|
|
24
|
+
const DEFAULT_GAS_FEES = new GasFees(2, 3);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A test class that extends the BaseAvmSimulationTester to enable real-app testing of the core AvmSimulator.
|
|
28
|
+
* It provides an interface for simulating one top-level call at a time and maintains state between
|
|
29
|
+
* subsequent top-level calls.
|
|
30
|
+
*/
|
|
31
|
+
export class AvmSimulationTester extends BaseAvmSimulationTester {
|
|
32
|
+
constructor(
|
|
33
|
+
contractDataSource: SimpleContractDataSource,
|
|
34
|
+
merkleTrees: MerkleTreeWriteOperations,
|
|
35
|
+
skipContractDeployments = false,
|
|
36
|
+
private stateManager: AvmPersistableStateManager,
|
|
37
|
+
) {
|
|
38
|
+
super(contractDataSource, merkleTrees, skipContractDeployments);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static async create(skipContractDeployments = false): Promise<AvmSimulationTester> {
|
|
42
|
+
const contractDataSource = new SimpleContractDataSource();
|
|
43
|
+
const merkleTrees = await (await MerkleTrees.new(openTmpStore())).fork();
|
|
44
|
+
const worldStateDB = new WorldStateDB(merkleTrees, contractDataSource);
|
|
45
|
+
const trace = new SideEffectTrace();
|
|
46
|
+
const firstNullifier = new Fr(420000);
|
|
47
|
+
// FIXME: merkle ops should work, but I'm seeing frequent (but inconsistent) bytecode retrieval
|
|
48
|
+
// failures on 2nd call to simulateCall with merkle ops on
|
|
49
|
+
const stateManager = await AvmPersistableStateManager.create(
|
|
50
|
+
worldStateDB,
|
|
51
|
+
trace,
|
|
52
|
+
/*doMerkleOperations=*/ false,
|
|
53
|
+
firstNullifier,
|
|
54
|
+
);
|
|
55
|
+
return new AvmSimulationTester(contractDataSource, merkleTrees, skipContractDeployments, stateManager);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Simulate a top-level contract call.
|
|
60
|
+
*/
|
|
61
|
+
async simulateCall(
|
|
62
|
+
sender: AztecAddress,
|
|
63
|
+
address: AztecAddress,
|
|
64
|
+
fnName: string,
|
|
65
|
+
args: any[],
|
|
66
|
+
isStaticCall = false,
|
|
67
|
+
): Promise<AvmContractCallResult> {
|
|
68
|
+
const contractArtifact = await this.contractDataSource.getContractArtifact(address);
|
|
69
|
+
if (!contractArtifact) {
|
|
70
|
+
throw new Error(`Contract not found at address: ${address}`);
|
|
71
|
+
}
|
|
72
|
+
const fnSelector = await getFunctionSelector(fnName, contractArtifact);
|
|
73
|
+
const fnAbi = getContractFunctionArtifact(fnName, contractArtifact);
|
|
74
|
+
const encodedArgs = encodeArguments(fnAbi!, args);
|
|
75
|
+
const calldata = [fnSelector.toField(), ...encodedArgs];
|
|
76
|
+
|
|
77
|
+
const globals = GlobalVariables.empty();
|
|
78
|
+
globals.timestamp = TIMESTAMP;
|
|
79
|
+
globals.gasFees = DEFAULT_GAS_FEES;
|
|
80
|
+
|
|
81
|
+
const environment = initExecutionEnvironment({
|
|
82
|
+
calldata,
|
|
83
|
+
globals,
|
|
84
|
+
address,
|
|
85
|
+
sender,
|
|
86
|
+
isStaticCall,
|
|
87
|
+
});
|
|
88
|
+
const persistableState = this.stateManager.fork();
|
|
89
|
+
const context = initContext({ env: environment, persistableState });
|
|
90
|
+
|
|
91
|
+
// First we simulate (though it's not needed in this simple case).
|
|
92
|
+
const simulator = new AvmSimulator(context);
|
|
93
|
+
const result = await simulator.execute();
|
|
94
|
+
if (result.reverted) {
|
|
95
|
+
this.logger.error(`Error in ${fnName}:`);
|
|
96
|
+
this.logger.error(
|
|
97
|
+
resolveContractAssertionMessage(fnName, result.revertReason!, result.output, contractArtifact)!,
|
|
98
|
+
);
|
|
99
|
+
} else {
|
|
100
|
+
this.logger.info(`Simulation of function ${fnName} succeeded!`);
|
|
101
|
+
this.stateManager.merge(persistableState);
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { MerkleTreeId, type MerkleTreeWriteOperations } from '@aztec/circuit-types';
|
|
2
|
+
import {
|
|
3
|
+
type ContractClassPublic,
|
|
4
|
+
type ContractInstanceWithAddress,
|
|
5
|
+
DEPLOYER_CONTRACT_ADDRESS,
|
|
6
|
+
PUBLIC_DISPATCH_SELECTOR,
|
|
7
|
+
PublicDataWrite,
|
|
8
|
+
computeInitializationHash,
|
|
9
|
+
} from '@aztec/circuits.js';
|
|
10
|
+
import { computePublicDataTreeLeafSlot, siloNullifier } from '@aztec/circuits.js/hash';
|
|
11
|
+
import { makeContractClassPublic, makeContractInstanceFromClassId } from '@aztec/circuits.js/testing';
|
|
12
|
+
import { type ContractArtifact, FunctionSelector } from '@aztec/foundation/abi';
|
|
13
|
+
import { AztecAddress } from '@aztec/foundation/aztec-address';
|
|
14
|
+
import { type Fr } from '@aztec/foundation/fields';
|
|
15
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
16
|
+
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
|
|
17
|
+
|
|
18
|
+
import { computeFeePayerBalanceStorageSlot } from '../../server.js';
|
|
19
|
+
import { PUBLIC_DISPATCH_FN_NAME, getContractFunctionArtifact } from './index.js';
|
|
20
|
+
import { type SimpleContractDataSource } from './simple_contract_data_source.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* An abstract test class that enables tests of real apps in the AVM without requiring e2e tests.
|
|
24
|
+
* It enables this by letting us (1) perform pseudo-contract-deployments (and registrations)
|
|
25
|
+
* that trigger merkle tree operations and (2) maintain a contract data source to store
|
|
26
|
+
* and retrieve contract classes and instances.
|
|
27
|
+
*
|
|
28
|
+
* This class is meant to be extended when writing tests for a specific simulation interface.
|
|
29
|
+
* For example, has been extended for testing of the core AvmSimulator, and again for the PublicTxSimulator,
|
|
30
|
+
* both of which benefit from such pseudo-deployments by populating merkle trees and a contract data source
|
|
31
|
+
* with contract information.
|
|
32
|
+
*/
|
|
33
|
+
export abstract class BaseAvmSimulationTester {
|
|
34
|
+
public logger = createLogger('avm-simulation-tester');
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
public contractDataSource: SimpleContractDataSource,
|
|
38
|
+
public merkleTrees: MerkleTreeWriteOperations,
|
|
39
|
+
/* May want to skip contract deployment tree ops to test failed contract address nullifier checks on CALL */
|
|
40
|
+
private skipContractDeployments = false,
|
|
41
|
+
) {}
|
|
42
|
+
|
|
43
|
+
async setFeePayerBalance(feePayer: AztecAddress, balance: Fr) {
|
|
44
|
+
const feeJuiceAddress = ProtocolContractAddress.FeeJuice;
|
|
45
|
+
const balanceSlot = await computeFeePayerBalanceStorageSlot(feePayer);
|
|
46
|
+
await this.setPublicStorage(feeJuiceAddress, balanceSlot, balance);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async setPublicStorage(address: AztecAddress, slot: Fr, value: Fr) {
|
|
50
|
+
const leafSlot = await computePublicDataTreeLeafSlot(address, slot);
|
|
51
|
+
// get existing preimage
|
|
52
|
+
const publicDataWrite = new PublicDataWrite(leafSlot, value);
|
|
53
|
+
await this.merkleTrees.batchInsert(MerkleTreeId.PUBLIC_DATA_TREE, [publicDataWrite.toBuffer()], 0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Derive the contract class and instance with some seed.
|
|
58
|
+
* Add both to the contract data source along with the contract artifact.
|
|
59
|
+
*/
|
|
60
|
+
async registerAndDeployContract(
|
|
61
|
+
constructorArgs: any[],
|
|
62
|
+
deployer: AztecAddress,
|
|
63
|
+
contractArtifact: ContractArtifact,
|
|
64
|
+
seed = 0,
|
|
65
|
+
): Promise<ContractInstanceWithAddress> {
|
|
66
|
+
const bytecode = getContractFunctionArtifact(PUBLIC_DISPATCH_FN_NAME, contractArtifact)!.bytecode;
|
|
67
|
+
const contractClass = await makeContractClassPublic(
|
|
68
|
+
seed,
|
|
69
|
+
/*publicDispatchFunction=*/ { bytecode, selector: new FunctionSelector(PUBLIC_DISPATCH_SELECTOR) },
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const constructorAbi = getContractFunctionArtifact('constructor', contractArtifact);
|
|
73
|
+
const initializationHash = await computeInitializationHash(constructorAbi, constructorArgs);
|
|
74
|
+
const contractInstance = await makeContractInstanceFromClassId(contractClass.id, seed, {
|
|
75
|
+
deployer,
|
|
76
|
+
initializationHash,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await this.addContractClass(contractClass, contractArtifact);
|
|
80
|
+
await this.addContractInstance(contractInstance);
|
|
81
|
+
return contractInstance;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getFirstContractInstance(): ContractInstanceWithAddress {
|
|
85
|
+
return this.contractDataSource.getFirstContractInstance();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
addContractClass(contractClass: ContractClassPublic, contractArtifact: ContractArtifact): Promise<void> {
|
|
89
|
+
this.logger.debug(`Adding contract class with Id ${contractClass.id}`);
|
|
90
|
+
this.contractDataSource.addContractArtifact(contractClass.id, contractArtifact);
|
|
91
|
+
return this.contractDataSource.addContractClass(contractClass);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async addContractInstance(contractInstance: ContractInstanceWithAddress) {
|
|
95
|
+
if (!this.skipContractDeployments) {
|
|
96
|
+
const contractAddressNullifier = await siloNullifier(
|
|
97
|
+
AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
|
|
98
|
+
contractInstance.address.toField(),
|
|
99
|
+
);
|
|
100
|
+
await this.merkleTrees.batchInsert(MerkleTreeId.NULLIFIER_TREE, [contractAddressNullifier.toBuffer()], 0);
|
|
101
|
+
}
|
|
102
|
+
await this.contractDataSource.addContractInstance(contractInstance);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isNoirCallStackUnresolved } from '@aztec/circuit-types';
|
|
2
2
|
import { GasFees, GlobalVariables, MAX_L2_GAS_PER_TX_PUBLIC_PORTION } from '@aztec/circuits.js';
|
|
3
|
-
import { type FunctionArtifact, FunctionSelector } from '@aztec/foundation/abi';
|
|
3
|
+
import { type ContractArtifact, type FunctionArtifact, FunctionSelector } from '@aztec/foundation/abi';
|
|
4
4
|
import { AztecAddress } from '@aztec/foundation/aztec-address';
|
|
5
5
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
6
|
import { Fr } from '@aztec/foundation/fields';
|
|
@@ -23,6 +23,8 @@ import { AvmPersistableStateManager } from '../journal/journal.js';
|
|
|
23
23
|
import { NullifierManager } from '../journal/nullifiers.js';
|
|
24
24
|
import { PublicStorage } from '../journal/public_storage.js';
|
|
25
25
|
|
|
26
|
+
export const PUBLIC_DISPATCH_FN_NAME = 'public_dispatch';
|
|
27
|
+
|
|
26
28
|
/**
|
|
27
29
|
* Create a new AVM context with default values.
|
|
28
30
|
*/
|
|
@@ -124,15 +126,51 @@ export function randomMemoryFields(length: number): Field[] {
|
|
|
124
126
|
return [...Array(length)].map(_ => new Field(Fr.random()));
|
|
125
127
|
}
|
|
126
128
|
|
|
127
|
-
export function
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
export function getFunctionSelector(
|
|
130
|
+
functionName: string,
|
|
131
|
+
contractArtifact: ContractArtifact,
|
|
132
|
+
): Promise<FunctionSelector> {
|
|
133
|
+
const fnArtifact = contractArtifact.functions.find(f => f.name === functionName)!;
|
|
134
|
+
assert(!!fnArtifact, `Function ${functionName} not found in ${contractArtifact.name}`);
|
|
135
|
+
const params = fnArtifact.parameters;
|
|
136
|
+
return FunctionSelector.fromNameAndParameters(fnArtifact.name, params);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function getContractFunctionArtifact(
|
|
140
|
+
functionName: string,
|
|
141
|
+
contractArtifact: ContractArtifact,
|
|
142
|
+
): FunctionArtifact | undefined {
|
|
143
|
+
const artifact = contractArtifact.functions.find(f => f.name === functionName)!;
|
|
144
|
+
if (!artifact) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
return artifact;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function resolveContractAssertionMessage(
|
|
151
|
+
functionName: string,
|
|
152
|
+
revertReason: AvmRevertReason,
|
|
153
|
+
output: Fr[],
|
|
154
|
+
contractArtifact: ContractArtifact,
|
|
155
|
+
): string | undefined {
|
|
156
|
+
traverseCauseChain(revertReason, cause => {
|
|
157
|
+
revertReason = cause as AvmRevertReason;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const functionArtifact = contractArtifact.functions.find(f => f.name === functionName);
|
|
161
|
+
if (!functionArtifact || !revertReason.noirCallStack || !isNoirCallStackUnresolved(revertReason.noirCallStack)) {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return resolveAssertionMessageFromRevertData(output, functionArtifact);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function getAvmTestContractFunctionSelector(functionName: string): Promise<FunctionSelector> {
|
|
169
|
+
return getFunctionSelector(functionName, AvmTestContractArtifact);
|
|
132
170
|
}
|
|
133
171
|
|
|
134
172
|
export function getAvmTestContractArtifact(functionName: string): FunctionArtifact {
|
|
135
|
-
const artifact =
|
|
173
|
+
const artifact = getContractFunctionArtifact(functionName, AvmTestContractArtifact);
|
|
136
174
|
assert(
|
|
137
175
|
!!artifact?.bytecode,
|
|
138
176
|
`No bytecode found for function ${functionName}. Try re-running bootstrap.sh on the repository root.`,
|
|
@@ -150,14 +188,5 @@ export function resolveAvmTestContractAssertionMessage(
|
|
|
150
188
|
revertReason: AvmRevertReason,
|
|
151
189
|
output: Fr[],
|
|
152
190
|
): string | undefined {
|
|
153
|
-
|
|
154
|
-
revertReason = cause as AvmRevertReason;
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
const functionArtifact = AvmTestContractArtifact.functions.find(f => f.name === functionName);
|
|
158
|
-
if (!functionArtifact || !revertReason.noirCallStack || !isNoirCallStackUnresolved(revertReason.noirCallStack)) {
|
|
159
|
-
return undefined;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return resolveAssertionMessageFromRevertData(output, functionArtifact);
|
|
191
|
+
return resolveContractAssertionMessage(functionName, revertReason, output, AvmTestContractArtifact);
|
|
163
192
|
}
|