@aztec/simulator 0.72.1 → 0.74.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 +73 -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 +10 -2
- package/dest/avm/fixtures/index.d.ts.map +1 -1
- package/dest/avm/fixtures/index.js +45 -5
- 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 +88 -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} +6 -26
- package/dest/public/side_effect_trace.d.ts.map +1 -0
- package/dest/public/side_effect_trace.js +316 -0
- package/dest/public/side_effect_trace_interface.d.ts +2 -19
- 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 +104 -0
- package/src/avm/fixtures/base_avm_simulation_tester.ts +104 -0
- package/src/avm/fixtures/index.ts +74 -6
- 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 +173 -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} +8 -49
- package/src/public/side_effect_trace_interface.ts +1 -22
- package/src/test/utils.ts +2 -2
- package/dest/public/enqueued_call_side_effect_trace.d.ts.map +0 -1
- package/dest/public/enqueued_call_side_effect_trace.js +0 -350
- 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_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,104 @@
|
|
|
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 { NativeWorldStateService } from '@aztec/world-state';
|
|
7
|
+
|
|
8
|
+
import { type AvmContractCallResult } from '../../avm/avm_contract_call_result.js';
|
|
9
|
+
import {
|
|
10
|
+
getContractFunctionArtifact,
|
|
11
|
+
getFunctionSelector,
|
|
12
|
+
initContext,
|
|
13
|
+
initExecutionEnvironment,
|
|
14
|
+
resolveContractAssertionMessage,
|
|
15
|
+
} from '../../avm/fixtures/index.js';
|
|
16
|
+
import { WorldStateDB } from '../../public/public_db_sources.js';
|
|
17
|
+
import { SideEffectTrace } from '../../public/side_effect_trace.js';
|
|
18
|
+
import { AvmPersistableStateManager, AvmSimulator } from '../../server.js';
|
|
19
|
+
import { BaseAvmSimulationTester } from './base_avm_simulation_tester.js';
|
|
20
|
+
import { SimpleContractDataSource } from './simple_contract_data_source.js';
|
|
21
|
+
|
|
22
|
+
const TIMESTAMP = new Fr(99833);
|
|
23
|
+
const DEFAULT_GAS_FEES = new GasFees(2, 3);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A test class that extends the BaseAvmSimulationTester to enable real-app testing of the core AvmSimulator.
|
|
27
|
+
* It provides an interface for simulating one top-level call at a time and maintains state between
|
|
28
|
+
* subsequent top-level calls.
|
|
29
|
+
*/
|
|
30
|
+
export class AvmSimulationTester extends BaseAvmSimulationTester {
|
|
31
|
+
constructor(
|
|
32
|
+
contractDataSource: SimpleContractDataSource,
|
|
33
|
+
merkleTrees: MerkleTreeWriteOperations,
|
|
34
|
+
skipContractDeployments = false,
|
|
35
|
+
private stateManager: AvmPersistableStateManager,
|
|
36
|
+
) {
|
|
37
|
+
super(contractDataSource, merkleTrees, skipContractDeployments);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static async create(skipContractDeployments = false): Promise<AvmSimulationTester> {
|
|
41
|
+
const contractDataSource = new SimpleContractDataSource();
|
|
42
|
+
const merkleTrees = await (await NativeWorldStateService.tmp()).fork();
|
|
43
|
+
const worldStateDB = new WorldStateDB(merkleTrees, contractDataSource);
|
|
44
|
+
const trace = new SideEffectTrace();
|
|
45
|
+
const firstNullifier = new Fr(420000);
|
|
46
|
+
// FIXME: merkle ops should work, but I'm seeing frequent (but inconsistent) bytecode retrieval
|
|
47
|
+
// failures on 2nd call to simulateCall with merkle ops on
|
|
48
|
+
const stateManager = await AvmPersistableStateManager.create(
|
|
49
|
+
worldStateDB,
|
|
50
|
+
trace,
|
|
51
|
+
/*doMerkleOperations=*/ false,
|
|
52
|
+
firstNullifier,
|
|
53
|
+
);
|
|
54
|
+
return new AvmSimulationTester(contractDataSource, merkleTrees, skipContractDeployments, stateManager);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Simulate a top-level contract call.
|
|
59
|
+
*/
|
|
60
|
+
async simulateCall(
|
|
61
|
+
sender: AztecAddress,
|
|
62
|
+
address: AztecAddress,
|
|
63
|
+
fnName: string,
|
|
64
|
+
args: any[],
|
|
65
|
+
isStaticCall = false,
|
|
66
|
+
): Promise<AvmContractCallResult> {
|
|
67
|
+
const contractArtifact = await this.contractDataSource.getContractArtifact(address);
|
|
68
|
+
if (!contractArtifact) {
|
|
69
|
+
throw new Error(`Contract not found at address: ${address}`);
|
|
70
|
+
}
|
|
71
|
+
const fnSelector = await getFunctionSelector(fnName, contractArtifact);
|
|
72
|
+
const fnAbi = getContractFunctionArtifact(fnName, contractArtifact);
|
|
73
|
+
const encodedArgs = encodeArguments(fnAbi!, args);
|
|
74
|
+
const calldata = [fnSelector.toField(), ...encodedArgs];
|
|
75
|
+
|
|
76
|
+
const globals = GlobalVariables.empty();
|
|
77
|
+
globals.timestamp = TIMESTAMP;
|
|
78
|
+
globals.gasFees = DEFAULT_GAS_FEES;
|
|
79
|
+
|
|
80
|
+
const environment = initExecutionEnvironment({
|
|
81
|
+
calldata,
|
|
82
|
+
globals,
|
|
83
|
+
address,
|
|
84
|
+
sender,
|
|
85
|
+
isStaticCall,
|
|
86
|
+
});
|
|
87
|
+
const persistableState = this.stateManager.fork();
|
|
88
|
+
const context = initContext({ env: environment, persistableState });
|
|
89
|
+
|
|
90
|
+
// First we simulate (though it's not needed in this simple case).
|
|
91
|
+
const simulator = new AvmSimulator(context);
|
|
92
|
+
const result = await simulator.execute();
|
|
93
|
+
if (result.reverted) {
|
|
94
|
+
this.logger.error(`Error in ${fnName}:`);
|
|
95
|
+
this.logger.error(
|
|
96
|
+
resolveContractAssertionMessage(fnName, result.revertReason!, result.output, contractArtifact)!,
|
|
97
|
+
);
|
|
98
|
+
} else {
|
|
99
|
+
this.logger.info(`Simulation of function ${fnName} succeeded!`);
|
|
100
|
+
this.stateManager.merge(persistableState);
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -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,9 +1,10 @@
|
|
|
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';
|
|
7
|
+
import { AvmGadgetsTestContractArtifact } from '@aztec/noir-contracts.js/AvmGadgetsTest';
|
|
7
8
|
import { AvmTestContractArtifact } from '@aztec/noir-contracts.js/AvmTest';
|
|
8
9
|
|
|
9
10
|
import { strict as assert } from 'assert';
|
|
@@ -23,6 +24,8 @@ import { AvmPersistableStateManager } from '../journal/journal.js';
|
|
|
23
24
|
import { NullifierManager } from '../journal/nullifiers.js';
|
|
24
25
|
import { PublicStorage } from '../journal/public_storage.js';
|
|
25
26
|
|
|
27
|
+
export const PUBLIC_DISPATCH_FN_NAME = 'public_dispatch';
|
|
28
|
+
|
|
26
29
|
/**
|
|
27
30
|
* Create a new AVM context with default values.
|
|
28
31
|
*/
|
|
@@ -124,15 +127,67 @@ export function randomMemoryFields(length: number): Field[] {
|
|
|
124
127
|
return [...Array(length)].map(_ => new Field(Fr.random()));
|
|
125
128
|
}
|
|
126
129
|
|
|
127
|
-
export function
|
|
128
|
-
|
|
129
|
-
|
|
130
|
+
export function getFunctionSelector(
|
|
131
|
+
functionName: string,
|
|
132
|
+
contractArtifact: ContractArtifact,
|
|
133
|
+
): Promise<FunctionSelector> {
|
|
134
|
+
const fnArtifact = contractArtifact.functions.find(f => f.name === functionName)!;
|
|
135
|
+
assert(!!fnArtifact, `Function ${functionName} not found in ${contractArtifact.name}`);
|
|
136
|
+
const params = fnArtifact.parameters;
|
|
137
|
+
return FunctionSelector.fromNameAndParameters(fnArtifact.name, params);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function getContractFunctionArtifact(
|
|
141
|
+
functionName: string,
|
|
142
|
+
contractArtifact: ContractArtifact,
|
|
143
|
+
): FunctionArtifact | undefined {
|
|
144
|
+
const artifact = contractArtifact.functions.find(f => f.name === functionName)!;
|
|
145
|
+
if (!artifact) {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
return artifact;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function resolveContractAssertionMessage(
|
|
152
|
+
functionName: string,
|
|
153
|
+
revertReason: AvmRevertReason,
|
|
154
|
+
output: Fr[],
|
|
155
|
+
contractArtifact: ContractArtifact,
|
|
156
|
+
): string | undefined {
|
|
157
|
+
traverseCauseChain(revertReason, cause => {
|
|
158
|
+
revertReason = cause as AvmRevertReason;
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const functionArtifact = contractArtifact.functions.find(f => f.name === functionName);
|
|
162
|
+
if (!functionArtifact || !revertReason.noirCallStack || !isNoirCallStackUnresolved(revertReason.noirCallStack)) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return resolveAssertionMessageFromRevertData(output, functionArtifact);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function getAvmTestContractFunctionSelector(functionName: string): Promise<FunctionSelector> {
|
|
170
|
+
return getFunctionSelector(functionName, AvmTestContractArtifact);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function getAvmGadgetsTestContractFunctionSelector(functionName: string): Promise<FunctionSelector> {
|
|
174
|
+
const artifact = AvmGadgetsTestContractArtifact.functions.find(f => f.name === functionName)!;
|
|
175
|
+
assert(!!artifact, `Function ${functionName} not found in AvmGadgetsTestContractArtifact`);
|
|
130
176
|
const params = artifact.parameters;
|
|
131
177
|
return FunctionSelector.fromNameAndParameters(artifact.name, params);
|
|
132
178
|
}
|
|
133
179
|
|
|
134
180
|
export function getAvmTestContractArtifact(functionName: string): FunctionArtifact {
|
|
135
|
-
const artifact =
|
|
181
|
+
const artifact = getContractFunctionArtifact(functionName, AvmTestContractArtifact);
|
|
182
|
+
assert(
|
|
183
|
+
!!artifact?.bytecode,
|
|
184
|
+
`No bytecode found for function ${functionName}. Try re-running bootstrap.sh on the repository root.`,
|
|
185
|
+
);
|
|
186
|
+
return artifact;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function getAvmGadgetsTestContractArtifact(functionName: string): FunctionArtifact {
|
|
190
|
+
const artifact = AvmGadgetsTestContractArtifact.functions.find(f => f.name === functionName)!;
|
|
136
191
|
assert(
|
|
137
192
|
!!artifact?.bytecode,
|
|
138
193
|
`No bytecode found for function ${functionName}. Try re-running bootstrap.sh on the repository root.`,
|
|
@@ -145,16 +200,29 @@ export function getAvmTestContractBytecode(functionName: string): Buffer {
|
|
|
145
200
|
return artifact.bytecode;
|
|
146
201
|
}
|
|
147
202
|
|
|
203
|
+
export function getAvmGadgetsTestContractBytecode(functionName: string): Buffer {
|
|
204
|
+
const artifact = getAvmGadgetsTestContractArtifact(functionName);
|
|
205
|
+
return artifact.bytecode;
|
|
206
|
+
}
|
|
207
|
+
|
|
148
208
|
export function resolveAvmTestContractAssertionMessage(
|
|
149
209
|
functionName: string,
|
|
150
210
|
revertReason: AvmRevertReason,
|
|
151
211
|
output: Fr[],
|
|
212
|
+
): string | undefined {
|
|
213
|
+
return resolveContractAssertionMessage(functionName, revertReason, output, AvmTestContractArtifact);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function resolveAvmGadgetsTestContractAssertionMessage(
|
|
217
|
+
functionName: string,
|
|
218
|
+
revertReason: AvmRevertReason,
|
|
219
|
+
output: Fr[],
|
|
152
220
|
): string | undefined {
|
|
153
221
|
traverseCauseChain(revertReason, cause => {
|
|
154
222
|
revertReason = cause as AvmRevertReason;
|
|
155
223
|
});
|
|
156
224
|
|
|
157
|
-
const functionArtifact =
|
|
225
|
+
const functionArtifact = AvmGadgetsTestContractArtifact.functions.find(f => f.name === functionName);
|
|
158
226
|
if (!functionArtifact || !revertReason.noirCallStack || !isNoirCallStackUnresolved(revertReason.noirCallStack)) {
|
|
159
227
|
return undefined;
|
|
160
228
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ContractClassPublic,
|
|
3
|
+
type ContractDataSource,
|
|
4
|
+
type ContractInstanceWithAddress,
|
|
5
|
+
type FunctionSelector,
|
|
6
|
+
type PublicFunction,
|
|
7
|
+
computePublicBytecodeCommitment,
|
|
8
|
+
} from '@aztec/circuits.js';
|
|
9
|
+
import { type ContractArtifact } from '@aztec/foundation/abi';
|
|
10
|
+
import { type AztecAddress } from '@aztec/foundation/aztec-address';
|
|
11
|
+
import { type Fr } from '@aztec/foundation/fields';
|
|
12
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
13
|
+
|
|
14
|
+
import { PUBLIC_DISPATCH_FN_NAME } from './index.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* This class is used during public/avm testing to function as a database of
|
|
18
|
+
* contract contract classes and instances. Tests can populate it with classes
|
|
19
|
+
* and instances and then probe it via the ContractDataSource interface.
|
|
20
|
+
*
|
|
21
|
+
* This class does not include any real merkle trees & merkle operations.
|
|
22
|
+
*/
|
|
23
|
+
export class SimpleContractDataSource implements ContractDataSource {
|
|
24
|
+
public logger = createLogger('simple-contract-data-source');
|
|
25
|
+
|
|
26
|
+
// maps contract class ID to class
|
|
27
|
+
private contractClasses: Map<string, ContractClassPublic> = new Map();
|
|
28
|
+
// maps contract instance address to instance
|
|
29
|
+
private contractInstances: Map<string, ContractInstanceWithAddress> = new Map();
|
|
30
|
+
// maps contract instance address to address
|
|
31
|
+
private contractArtifacts: Map<string, ContractArtifact> = new Map();
|
|
32
|
+
|
|
33
|
+
/////////////////////////////////////////////////////////////
|
|
34
|
+
// Helper functions not in the contract data source interface
|
|
35
|
+
getFirstContractInstance(): ContractInstanceWithAddress {
|
|
36
|
+
return this.contractInstances.values().next().value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
addContractArtifact(classId: Fr, artifact: ContractArtifact): void {
|
|
40
|
+
this.contractArtifacts.set(classId.toString(), artifact);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/////////////////////////////////////////////////////////////
|
|
44
|
+
// ContractDataSource function impelementations
|
|
45
|
+
getPublicFunction(_address: AztecAddress, _selector: FunctionSelector): Promise<PublicFunction> {
|
|
46
|
+
throw new Error('Method not implemented.');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getBlockNumber(): Promise<number> {
|
|
50
|
+
throw new Error('Method not implemented.');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
|
|
54
|
+
return Promise.resolve(this.contractClasses.get(id.toString()));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async getBytecodeCommitment(id: Fr): Promise<Fr | undefined> {
|
|
58
|
+
const contractClass = await this.getContractClass(id);
|
|
59
|
+
return Promise.resolve(computePublicBytecodeCommitment(contractClass!.packedBytecode));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getContract(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
|
|
63
|
+
return Promise.resolve(this.contractInstances.get(address.toString()));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getContractClassIds(): Promise<Fr[]> {
|
|
67
|
+
throw new Error('Method not implemented.');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async getContractArtifact(address: AztecAddress): Promise<ContractArtifact | undefined> {
|
|
71
|
+
const contractInstance = await this.getContract(address);
|
|
72
|
+
if (!contractInstance) {
|
|
73
|
+
this.logger.warn(`Contract not found at address: ${address}`);
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
this.logger.debug(`Retrieved contract artifact for address: ${address}`);
|
|
77
|
+
this.logger.debug(`Contract class ID: ${contractInstance.contractClassId}`);
|
|
78
|
+
return Promise.resolve(this.contractArtifacts.get(contractInstance!.contractClassId.toString()));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getContractFunctionName(_address: AztecAddress, _selector: FunctionSelector): Promise<string> {
|
|
82
|
+
return Promise.resolve(PUBLIC_DISPATCH_FN_NAME);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
registerContractFunctionSignatures(_address: AztecAddress, _signatures: string[]): Promise<void> {
|
|
86
|
+
return Promise.resolve();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
addContractClass(contractClass: ContractClassPublic): Promise<void> {
|
|
90
|
+
this.contractClasses.set(contractClass.id.toString(), contractClass);
|
|
91
|
+
return Promise.resolve();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
addContractInstance(contractInstance: ContractInstanceWithAddress): Promise<void> {
|
|
95
|
+
this.contractInstances.set(contractInstance.address.toString(), contractInstance);
|
|
96
|
+
return Promise.resolve();
|
|
97
|
+
}
|
|
98
|
+
}
|