@aztec/simulator 0.24.0 → 0.26.2
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/acvm/deserialize.d.ts +5 -0
- package/dest/acvm/deserialize.d.ts.map +1 -1
- package/dest/acvm/deserialize.js +8 -1
- package/dest/acvm/oracle/oracle.d.ts +5 -4
- package/dest/acvm/oracle/oracle.d.ts.map +1 -1
- package/dest/acvm/oracle/oracle.js +24 -11
- package/dest/acvm/oracle/typed_oracle.d.ts +7 -9
- package/dest/acvm/oracle/typed_oracle.d.ts.map +1 -1
- package/dest/acvm/oracle/typed_oracle.js +9 -9
- package/dest/avm/avm_context.d.ts +4 -4
- package/dest/avm/avm_context.d.ts.map +1 -1
- package/dest/avm/avm_context.js +6 -6
- package/dest/avm/avm_memory_types.d.ts +11 -2
- package/dest/avm/avm_memory_types.d.ts.map +1 -1
- package/dest/avm/avm_memory_types.js +11 -1
- package/dest/avm/avm_simulator.d.ts +6 -4
- package/dest/avm/avm_simulator.d.ts.map +1 -1
- package/dest/avm/avm_simulator.js +17 -18
- package/dest/avm/fixtures/index.d.ts +17 -5
- package/dest/avm/fixtures/index.d.ts.map +1 -1
- package/dest/avm/fixtures/index.js +19 -8
- package/dest/avm/journal/host_storage.d.ts.map +1 -1
- package/dest/avm/journal/host_storage.js +1 -1
- package/dest/avm/journal/journal.d.ts +78 -50
- package/dest/avm/journal/journal.d.ts.map +1 -1
- package/dest/avm/journal/journal.js +125 -169
- package/dest/avm/journal/nullifiers.d.ts +85 -0
- package/dest/avm/journal/nullifiers.d.ts.map +1 -0
- package/dest/avm/journal/nullifiers.js +147 -0
- package/dest/avm/journal/public_storage.d.ts +88 -0
- package/dest/avm/journal/public_storage.d.ts.map +1 -0
- package/dest/avm/journal/public_storage.js +135 -0
- package/dest/avm/journal/trace.d.ts +43 -0
- package/dest/avm/journal/trace.d.ts.map +1 -0
- package/dest/avm/journal/trace.js +204 -0
- package/dest/avm/journal/trace_types.d.ts +26 -0
- package/dest/avm/journal/trace_types.d.ts.map +1 -0
- package/dest/avm/journal/trace_types.js +6 -0
- package/dest/avm/opcodes/accrued_substate.d.ts +37 -4
- package/dest/avm/opcodes/accrued_substate.d.ts.map +1 -1
- package/dest/avm/opcodes/accrued_substate.js +109 -12
- package/dest/avm/opcodes/comparators.d.ts.map +1 -1
- package/dest/avm/opcodes/comparators.js +5 -8
- package/dest/avm/opcodes/environment_getters.d.ts +14 -13
- package/dest/avm/opcodes/environment_getters.d.ts.map +1 -1
- package/dest/avm/opcodes/environment_getters.js +1 -1
- package/dest/avm/opcodes/external_calls.js +5 -5
- package/dest/avm/opcodes/hashing.d.ts +48 -0
- package/dest/avm/opcodes/hashing.d.ts.map +1 -0
- package/dest/avm/opcodes/hashing.js +127 -0
- package/dest/avm/opcodes/memory.d.ts.map +1 -1
- package/dest/avm/opcodes/memory.js +1 -1
- package/dest/avm/opcodes/storage.d.ts.map +1 -1
- package/dest/avm/opcodes/storage.js +3 -3
- package/dest/avm/serialization/bytecode_serialization.d.ts.map +1 -1
- package/dest/avm/serialization/bytecode_serialization.js +12 -8
- package/dest/avm/serialization/instruction_serialization.d.ts +10 -7
- package/dest/avm/serialization/instruction_serialization.d.ts.map +1 -1
- package/dest/avm/serialization/instruction_serialization.js +12 -9
- package/dest/avm/temporary_executor_migration.d.ts.map +1 -1
- package/dest/avm/temporary_executor_migration.js +5 -5
- package/dest/client/client_execution_context.d.ts +9 -5
- package/dest/client/client_execution_context.d.ts.map +1 -1
- package/dest/client/client_execution_context.js +46 -24
- package/dest/client/db_oracle.d.ts +7 -0
- package/dest/client/db_oracle.d.ts.map +1 -1
- package/dest/client/db_oracle.js +1 -1
- package/dest/client/execution_note_cache.js +1 -1
- package/dest/client/execution_result.d.ts +2 -2
- package/dest/client/execution_result.d.ts.map +1 -1
- package/dest/client/private_execution.d.ts.map +1 -1
- package/dest/client/private_execution.js +4 -4
- package/dest/client/simulator.d.ts +1 -1
- package/dest/client/simulator.d.ts.map +1 -1
- package/dest/client/simulator.js +3 -2
- package/dest/client/view_data_oracle.d.ts +9 -2
- package/dest/client/view_data_oracle.d.ts.map +1 -1
- package/dest/client/view_data_oracle.js +13 -5
- package/dest/public/db.d.ts +17 -4
- package/dest/public/db.d.ts.map +1 -1
- package/dest/public/execution.d.ts +9 -4
- package/dest/public/execution.d.ts.map +1 -1
- package/dest/public/execution.js +17 -4
- package/dest/public/executor.d.ts.map +1 -1
- package/dest/public/executor.js +18 -9
- package/dest/public/public_execution_context.d.ts +5 -4
- package/dest/public/public_execution_context.d.ts.map +1 -1
- package/dest/public/public_execution_context.js +23 -12
- package/dest/public/state_actions.js +2 -2
- package/dest/test/utils.js +4 -4
- package/dest/utils.js +2 -3
- package/package.json +6 -5
- package/src/acvm/deserialize.ts +8 -0
- package/src/acvm/oracle/oracle.ts +30 -6
- package/src/acvm/oracle/typed_oracle.ts +13 -5
- package/src/avm/avm_context.ts +5 -5
- package/src/avm/avm_memory_types.ts +18 -3
- package/src/avm/avm_simulator.ts +22 -24
- package/src/avm/fixtures/index.ts +34 -9
- package/src/avm/journal/host_storage.ts +5 -11
- package/src/avm/journal/journal.ts +147 -182
- package/src/avm/journal/nullifiers.ts +170 -0
- package/src/avm/journal/public_storage.ts +149 -0
- package/src/avm/journal/trace.ts +223 -0
- package/src/avm/journal/trace_types.ts +79 -0
- package/src/avm/opcodes/accrued_substate.ts +132 -10
- package/src/avm/opcodes/comparators.ts +4 -7
- package/src/avm/opcodes/environment_getters.ts +15 -13
- package/src/avm/opcodes/external_calls.ts +4 -4
- package/src/avm/opcodes/hashing.ts +170 -0
- package/src/avm/opcodes/memory.ts +1 -0
- package/src/avm/opcodes/storage.ts +5 -2
- package/src/avm/serialization/bytecode_serialization.ts +13 -6
- package/src/avm/serialization/instruction_serialization.ts +6 -3
- package/src/avm/temporary_executor_migration.ts +4 -3
- package/src/client/client_execution_context.ts +53 -23
- package/src/client/db_oracle.ts +8 -0
- package/src/client/execution_note_cache.ts +1 -1
- package/src/client/execution_result.ts +2 -2
- package/src/client/private_execution.ts +5 -4
- package/src/client/simulator.ts +2 -1
- package/src/client/view_data_oracle.ts +14 -4
- package/src/public/db.ts +19 -4
- package/src/public/execution.ts +30 -6
- package/src/public/executor.ts +29 -9
- package/src/public/public_execution_context.ts +36 -12
- package/src/public/state_actions.ts +1 -1
- package/src/test/utils.ts +3 -3
- package/src/utils.ts +1 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
2
|
+
|
|
3
|
+
import type { PublicStateDB } from '../../index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A class to manage public storage reads and writes during a contract call's AVM simulation.
|
|
7
|
+
* Maintains a storage write cache, and ensures that reads fall back to the correct source.
|
|
8
|
+
* When a contract call completes, its storage cache can be merged into its parent's.
|
|
9
|
+
*/
|
|
10
|
+
export class PublicStorage {
|
|
11
|
+
/** Cached storage writes. */
|
|
12
|
+
private cache: PublicStorageCache;
|
|
13
|
+
/** Parent's storage cache. Checked on cache-miss. */
|
|
14
|
+
private readonly parentCache: PublicStorageCache | undefined;
|
|
15
|
+
/** Reference to node storage. Checked on parent cache-miss. */
|
|
16
|
+
private readonly hostPublicStorage: PublicStateDB;
|
|
17
|
+
|
|
18
|
+
constructor(hostPublicStorage: PublicStateDB, parent?: PublicStorage) {
|
|
19
|
+
this.hostPublicStorage = hostPublicStorage;
|
|
20
|
+
this.parentCache = parent?.cache;
|
|
21
|
+
this.cache = new PublicStorageCache();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get the pending storage.
|
|
26
|
+
*/
|
|
27
|
+
public getCache() {
|
|
28
|
+
return this.cache;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Read a value from storage.
|
|
33
|
+
* 1. Check cache.
|
|
34
|
+
* 2. Check parent's cache.
|
|
35
|
+
* 3. Fall back to the host state.
|
|
36
|
+
* 4. Not found! Value has never been written to before. Flag it as non-existent and return value zero.
|
|
37
|
+
*
|
|
38
|
+
* @param storageAddress - the address of the contract whose storage is being read from
|
|
39
|
+
* @param slot - the slot in the contract's storage being read from
|
|
40
|
+
* @returns exists: whether the slot has EVER been written to before, value: the latest value written to slot, or 0 if never written to before
|
|
41
|
+
*/
|
|
42
|
+
public async read(storageAddress: Fr, slot: Fr): Promise<[/*exists=*/ boolean, /*value=*/ Fr]> {
|
|
43
|
+
// First try check this storage cache
|
|
44
|
+
let value = this.cache.read(storageAddress, slot);
|
|
45
|
+
// Then try parent's storage cache (if it exists / written to earlier in this TX)
|
|
46
|
+
if (!value && this.parentCache) {
|
|
47
|
+
value = this.parentCache?.read(storageAddress, slot);
|
|
48
|
+
}
|
|
49
|
+
// Finally try the host's Aztec state (a trip to the database)
|
|
50
|
+
if (!value) {
|
|
51
|
+
value = await this.hostPublicStorage.storageRead(storageAddress, slot);
|
|
52
|
+
}
|
|
53
|
+
// if value is undefined, that means this slot has never been written to!
|
|
54
|
+
const exists = value !== undefined;
|
|
55
|
+
const valueOrZero = exists ? value : Fr.ZERO;
|
|
56
|
+
return Promise.resolve([exists, valueOrZero]);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Stage a storage write.
|
|
61
|
+
*
|
|
62
|
+
* @param storageAddress - the address of the contract whose storage is being written to
|
|
63
|
+
* @param slot - the slot in the contract's storage being written to
|
|
64
|
+
* @param value - the value being written to the slot
|
|
65
|
+
*/
|
|
66
|
+
public write(storageAddress: Fr, key: Fr, value: Fr) {
|
|
67
|
+
this.cache.write(storageAddress, key, value);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Merges another PublicStorage's cache (pending writes) into this one.
|
|
72
|
+
*
|
|
73
|
+
* @param incomingPublicStorage - the incoming public storage to merge into this instance's
|
|
74
|
+
*/
|
|
75
|
+
public acceptAndMerge(incomingPublicStorage: PublicStorage) {
|
|
76
|
+
this.cache.acceptAndMerge(incomingPublicStorage.cache);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A class to cache writes to public storage during a contract call's AVM simulation.
|
|
82
|
+
* "Writes" update a map, "reads" check that map or return undefined.
|
|
83
|
+
* An instance of this class can merge another instance's staged writes into its own.
|
|
84
|
+
*/
|
|
85
|
+
class PublicStorageCache {
|
|
86
|
+
/**
|
|
87
|
+
* Map for staging storage writes.
|
|
88
|
+
* One inner-map per contract storage address,
|
|
89
|
+
* mapping storage slot to latest staged write value.
|
|
90
|
+
*/
|
|
91
|
+
public cachePerContract: Map<bigint, Map<bigint, Fr>> = new Map();
|
|
92
|
+
// FIXME: storage ^ should be private, but its value is used in tests for "currentStorageValue"
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Read a staged value from storage, if it has been previously written to.
|
|
96
|
+
*
|
|
97
|
+
* @param storageAddress - the address of the contract whose storage is being read from
|
|
98
|
+
* @param slot - the slot in the contract's storage being read from
|
|
99
|
+
* @returns the latest value written to slot, or undefined if no value has been written
|
|
100
|
+
*/
|
|
101
|
+
public read(storageAddress: Fr, slot: Fr): Fr | undefined {
|
|
102
|
+
return this.cachePerContract.get(storageAddress.toBigInt())?.get(slot.toBigInt());
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Stage a storage write.
|
|
107
|
+
*
|
|
108
|
+
* @param storageAddress - the address of the contract whose storage is being written to
|
|
109
|
+
* @param slot - the slot in the contract's storage being written to
|
|
110
|
+
* @param value - the value being written to the slot
|
|
111
|
+
*/
|
|
112
|
+
public write(storageAddress: Fr, slot: Fr, value: Fr) {
|
|
113
|
+
let cacheAtContract = this.cachePerContract.get(storageAddress.toBigInt());
|
|
114
|
+
if (!cacheAtContract) {
|
|
115
|
+
// If this contract's storage has no staged modifications, create a new inner map to store them
|
|
116
|
+
cacheAtContract = new Map();
|
|
117
|
+
this.cachePerContract.set(storageAddress.toBigInt(), cacheAtContract);
|
|
118
|
+
}
|
|
119
|
+
cacheAtContract.set(slot.toBigInt(), value);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Merges another cache's staged writes into this instance's cache.
|
|
124
|
+
*
|
|
125
|
+
* Staged modifications in "incoming" take precedence over those
|
|
126
|
+
* present in "this" as they are assumed to occur after this' writes.
|
|
127
|
+
*
|
|
128
|
+
* In practice, "this" is a parent call's storage cache, and "incoming" is a nested call's.
|
|
129
|
+
*
|
|
130
|
+
* @param incomingStorageCache - the incoming storage write cache to merge into this instance's
|
|
131
|
+
*/
|
|
132
|
+
public acceptAndMerge(incomingStorageCache: PublicStorageCache) {
|
|
133
|
+
// Iterate over all incoming contracts with staged writes.
|
|
134
|
+
for (const [incomingAddress, incomingCacheAtContract] of incomingStorageCache.cachePerContract) {
|
|
135
|
+
const thisCacheAtContract = this.cachePerContract.get(incomingAddress);
|
|
136
|
+
if (!thisCacheAtContract) {
|
|
137
|
+
// The contract has no storage writes staged here
|
|
138
|
+
// so just accept the incoming cache as-is for this contract.
|
|
139
|
+
this.cachePerContract.set(incomingAddress, incomingCacheAtContract);
|
|
140
|
+
} else {
|
|
141
|
+
// "Incoming" and "this" both have staged writes for this contract.
|
|
142
|
+
// Merge in incoming staged writes, giving them precedence over this'.
|
|
143
|
+
for (const [slot, value] of incomingCacheAtContract) {
|
|
144
|
+
thisCacheAtContract.set(slot, value);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
2
|
+
|
|
3
|
+
import { TracedL1toL2MessageCheck, TracedNoteHashCheck, TracedNullifierCheck } from './trace_types.js';
|
|
4
|
+
|
|
5
|
+
export class WorldStateAccessTrace {
|
|
6
|
+
public accessCounter: number;
|
|
7
|
+
//public contractCalls: Array<TracedContractCall> = [];
|
|
8
|
+
|
|
9
|
+
//public publicStorageReads: Array<TracedPublicStorageRead> = [];
|
|
10
|
+
public publicStorageReads: Map<bigint, Map<bigint, Fr[]>> = new Map();
|
|
11
|
+
//public publicStorageWrites: Array<TracedPublicStorageWrite> = [];
|
|
12
|
+
public publicStorageWrites: Map<bigint, Map<bigint, Fr[]>> = new Map();
|
|
13
|
+
|
|
14
|
+
public noteHashChecks: TracedNoteHashCheck[] = [];
|
|
15
|
+
//public newNoteHashes: TracedNoteHash[] = [];
|
|
16
|
+
public newNoteHashes: Fr[] = [];
|
|
17
|
+
public nullifierChecks: TracedNullifierCheck[] = [];
|
|
18
|
+
//public newNullifiers: TracedNullifier[] = [];
|
|
19
|
+
public newNullifiers: Fr[] = [];
|
|
20
|
+
public l1ToL2MessageChecks: TracedL1toL2MessageCheck[] = [];
|
|
21
|
+
//public archiveChecks: TracedArchiveLeafCheck[] = [];
|
|
22
|
+
|
|
23
|
+
constructor(parentTrace?: WorldStateAccessTrace) {
|
|
24
|
+
this.accessCounter = parentTrace ? parentTrace.accessCounter : 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public getAccessCounter() {
|
|
28
|
+
return this.accessCounter;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public tracePublicStorageRead(storageAddress: Fr, slot: Fr, value: Fr /*, _exists: boolean*/) {
|
|
32
|
+
// TODO(4805): check if some threshold is reached for max storage reads
|
|
33
|
+
// (need access to parent length, or trace needs to be initialized with parent's contents)
|
|
34
|
+
//const traced: TracedPublicStorageRead = {
|
|
35
|
+
// callPointer: Fr.ZERO,
|
|
36
|
+
// storageAddress,
|
|
37
|
+
// slot,
|
|
38
|
+
// value,
|
|
39
|
+
// exists,
|
|
40
|
+
// counter: new Fr(this.accessCounter),
|
|
41
|
+
// endLifetime: Fr.ZERO,
|
|
42
|
+
//};
|
|
43
|
+
//this.publicStorageReads.push(traced);
|
|
44
|
+
this.journalRead(storageAddress, slot, value);
|
|
45
|
+
this.incrementAccessCounter();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public tracePublicStorageWrite(storageAddress: Fr, slot: Fr, value: Fr) {
|
|
49
|
+
// TODO(4805): check if some threshold is reached for max storage writes
|
|
50
|
+
// (need access to parent length, or trace needs to be initialized with parent's contents)
|
|
51
|
+
//const traced: TracedPublicStorageWrite = {
|
|
52
|
+
// callPointer: Fr.ZERO,
|
|
53
|
+
// storageAddress,
|
|
54
|
+
// slot,
|
|
55
|
+
// value,
|
|
56
|
+
// counter: new Fr(this.accessCounter),
|
|
57
|
+
// endLifetime: Fr.ZERO,
|
|
58
|
+
//};
|
|
59
|
+
//this.publicStorageWrites.push(traced);
|
|
60
|
+
this.journalWrite(storageAddress, slot, value);
|
|
61
|
+
this.incrementAccessCounter();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public traceNoteHashCheck(storageAddress: Fr, noteHash: Fr, exists: boolean, leafIndex: Fr) {
|
|
65
|
+
const traced: TracedNoteHashCheck = {
|
|
66
|
+
callPointer: Fr.ZERO, // FIXME
|
|
67
|
+
storageAddress,
|
|
68
|
+
noteHash,
|
|
69
|
+
exists,
|
|
70
|
+
counter: new Fr(this.accessCounter),
|
|
71
|
+
endLifetime: Fr.ZERO,
|
|
72
|
+
leafIndex,
|
|
73
|
+
};
|
|
74
|
+
this.noteHashChecks.push(traced);
|
|
75
|
+
this.incrementAccessCounter();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public traceNewNoteHash(_storageAddress: Fr, noteHash: Fr) {
|
|
79
|
+
// TODO(4805): check if some threshold is reached for max new note hash
|
|
80
|
+
//const traced: TracedNoteHash = {
|
|
81
|
+
// callPointer: Fr.ZERO,
|
|
82
|
+
// storageAddress,
|
|
83
|
+
// noteHash,
|
|
84
|
+
// counter: new Fr(this.accessCounter),
|
|
85
|
+
// endLifetime: Fr.ZERO,
|
|
86
|
+
//};
|
|
87
|
+
//this.newNoteHashes.push(traced);
|
|
88
|
+
this.newNoteHashes.push(noteHash);
|
|
89
|
+
this.incrementAccessCounter();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public traceNullifierCheck(storageAddress: Fr, nullifier: Fr, exists: boolean, isPending: boolean, leafIndex: Fr) {
|
|
93
|
+
// TODO(4805): check if some threshold is reached for max new nullifier
|
|
94
|
+
const traced: TracedNullifierCheck = {
|
|
95
|
+
callPointer: Fr.ZERO, // FIXME
|
|
96
|
+
storageAddress,
|
|
97
|
+
nullifier,
|
|
98
|
+
exists,
|
|
99
|
+
counter: new Fr(this.accessCounter),
|
|
100
|
+
endLifetime: Fr.ZERO,
|
|
101
|
+
isPending,
|
|
102
|
+
leafIndex,
|
|
103
|
+
};
|
|
104
|
+
this.nullifierChecks.push(traced);
|
|
105
|
+
this.incrementAccessCounter();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public traceNewNullifier(_storageAddress: Fr, nullifier: Fr) {
|
|
109
|
+
// TODO(4805): check if some threshold is reached for max new nullifier
|
|
110
|
+
//const traced: TracedNullifier = {
|
|
111
|
+
// callPointer: Fr.ZERO,
|
|
112
|
+
// storageAddress,
|
|
113
|
+
// nullifier,
|
|
114
|
+
// counter: new Fr(this.accessCounter),
|
|
115
|
+
// endLifetime: Fr.ZERO,
|
|
116
|
+
//};
|
|
117
|
+
//this.newNullifiers.push(traced);
|
|
118
|
+
this.newNullifiers.push(nullifier);
|
|
119
|
+
this.incrementAccessCounter();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public traceL1ToL2MessageCheck(msgHash: Fr, msgLeafIndex: Fr, exists: boolean) {
|
|
123
|
+
// TODO(4805): check if some threshold is reached for max message reads
|
|
124
|
+
const traced: TracedL1toL2MessageCheck = {
|
|
125
|
+
//callPointer: Fr.ZERO, // FIXME
|
|
126
|
+
leafIndex: msgLeafIndex,
|
|
127
|
+
msgHash: msgHash,
|
|
128
|
+
exists: exists,
|
|
129
|
+
//endLifetime: Fr.ZERO, // FIXME
|
|
130
|
+
};
|
|
131
|
+
this.l1ToL2MessageChecks.push(traced);
|
|
132
|
+
this.incrementAccessCounter();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private incrementAccessCounter() {
|
|
136
|
+
this.accessCounter++;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Merges another trace into this one
|
|
141
|
+
*
|
|
142
|
+
* - Public state journals (r/w logs), with the accessing being appended in chronological order
|
|
143
|
+
* - Utxo objects are concatenated
|
|
144
|
+
*
|
|
145
|
+
* @param incomingTrace - the incoming trace to merge into this instance
|
|
146
|
+
*/
|
|
147
|
+
public acceptAndMerge(incomingTrace: WorldStateAccessTrace) {
|
|
148
|
+
// Merge storage read and write journals
|
|
149
|
+
mergeContractJournalMaps(this.publicStorageReads, incomingTrace.publicStorageReads);
|
|
150
|
+
mergeContractJournalMaps(this.publicStorageWrites, incomingTrace.publicStorageWrites);
|
|
151
|
+
// Merge new note hashes and nullifiers
|
|
152
|
+
this.noteHashChecks = this.noteHashChecks.concat(incomingTrace.noteHashChecks);
|
|
153
|
+
this.newNoteHashes = this.newNoteHashes.concat(incomingTrace.newNoteHashes);
|
|
154
|
+
this.nullifierChecks = this.nullifierChecks.concat(incomingTrace.nullifierChecks);
|
|
155
|
+
this.newNullifiers = this.newNullifiers.concat(incomingTrace.newNullifiers);
|
|
156
|
+
this.l1ToL2MessageChecks = this.l1ToL2MessageChecks.concat(incomingTrace.l1ToL2MessageChecks);
|
|
157
|
+
// it is assumed that the incoming trace was initialized with this as parent, so accept counter
|
|
158
|
+
this.accessCounter = incomingTrace.accessCounter;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* We want to keep track of all performed reads in the journal
|
|
163
|
+
* This information is hinted to the avm circuit
|
|
164
|
+
|
|
165
|
+
* @param contractAddress -
|
|
166
|
+
* @param key -
|
|
167
|
+
* @param value -
|
|
168
|
+
*/
|
|
169
|
+
journalUpdate(map: Map<bigint, Map<bigint, Fr[]>>, contractAddress: Fr, key: Fr, value: Fr): void {
|
|
170
|
+
let contractMap = map.get(contractAddress.toBigInt());
|
|
171
|
+
if (!contractMap) {
|
|
172
|
+
contractMap = new Map<bigint, Array<Fr>>();
|
|
173
|
+
map.set(contractAddress.toBigInt(), contractMap);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let accessArray = contractMap.get(key.toBigInt());
|
|
177
|
+
if (!accessArray) {
|
|
178
|
+
accessArray = new Array<Fr>();
|
|
179
|
+
contractMap.set(key.toBigInt(), accessArray);
|
|
180
|
+
}
|
|
181
|
+
accessArray.push(value);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Create an instance of journalUpdate that appends to the read array
|
|
185
|
+
private journalRead = this.journalUpdate.bind(this, this.publicStorageReads);
|
|
186
|
+
// Create an instance of journalUpdate that appends to the writes array
|
|
187
|
+
private journalWrite = this.journalUpdate.bind(this, this.publicStorageWrites);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Merges two contract journalling maps together
|
|
192
|
+
* For read maps, we just append the childMap arrays into the host map arrays, as the order is important
|
|
193
|
+
*
|
|
194
|
+
* @param hostMap - The map to be merged into
|
|
195
|
+
* @param childMap - The map to be merged from
|
|
196
|
+
*/
|
|
197
|
+
function mergeContractJournalMaps(hostMap: Map<bigint, Map<bigint, Fr[]>>, childMap: Map<bigint, Map<bigint, Fr[]>>) {
|
|
198
|
+
for (const [key, value] of childMap) {
|
|
199
|
+
const map1Value = hostMap.get(key);
|
|
200
|
+
if (!map1Value) {
|
|
201
|
+
hostMap.set(key, value);
|
|
202
|
+
} else {
|
|
203
|
+
mergeStorageJournalMaps(map1Value, value);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Merge two storage journalling maps together (for a particular contract).
|
|
210
|
+
*
|
|
211
|
+
* @param hostMap - The map to be merge into
|
|
212
|
+
* @param childMap - The map to be merged from
|
|
213
|
+
*/
|
|
214
|
+
function mergeStorageJournalMaps(hostMap: Map<bigint, Fr[]>, childMap: Map<bigint, Fr[]>) {
|
|
215
|
+
for (const [key, value] of childMap) {
|
|
216
|
+
const readArr = hostMap.get(key);
|
|
217
|
+
if (!readArr) {
|
|
218
|
+
hostMap.set(key, value);
|
|
219
|
+
} else {
|
|
220
|
+
hostMap.set(key, readArr?.concat(...value));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
2
|
+
|
|
3
|
+
//export type TracedContractCall = {
|
|
4
|
+
// callPointer: Fr;
|
|
5
|
+
// address: Fr;
|
|
6
|
+
// storageAddress: Fr;
|
|
7
|
+
// endLifetime: Fr;
|
|
8
|
+
//};
|
|
9
|
+
//
|
|
10
|
+
//export type TracedPublicStorageRead = {
|
|
11
|
+
// callPointer: Fr;
|
|
12
|
+
// storageAddress: Fr;
|
|
13
|
+
// exists: boolean;
|
|
14
|
+
// slot: Fr;
|
|
15
|
+
// value: Fr;
|
|
16
|
+
// counter: Fr;
|
|
17
|
+
// endLifetime: Fr;
|
|
18
|
+
//};
|
|
19
|
+
//
|
|
20
|
+
//export type TracedPublicStorageWrite = {
|
|
21
|
+
// callPointer: Fr;
|
|
22
|
+
// storageAddress: Fr;
|
|
23
|
+
// slot: Fr;
|
|
24
|
+
// value: Fr;
|
|
25
|
+
// counter: Fr;
|
|
26
|
+
// endLifetime: Fr;
|
|
27
|
+
//};
|
|
28
|
+
//
|
|
29
|
+
export type TracedNoteHashCheck = {
|
|
30
|
+
callPointer: Fr;
|
|
31
|
+
storageAddress: Fr;
|
|
32
|
+
leafIndex: Fr;
|
|
33
|
+
noteHash: Fr;
|
|
34
|
+
exists: boolean;
|
|
35
|
+
counter: Fr;
|
|
36
|
+
endLifetime: Fr;
|
|
37
|
+
};
|
|
38
|
+
//
|
|
39
|
+
//export type TracedNoteHash = {
|
|
40
|
+
// callPointer: Fr;
|
|
41
|
+
// storageAddress: Fr;
|
|
42
|
+
// noteHash: Fr;
|
|
43
|
+
// counter: Fr;
|
|
44
|
+
// endLifetime: Fr;
|
|
45
|
+
//};
|
|
46
|
+
|
|
47
|
+
export type TracedNullifierCheck = {
|
|
48
|
+
callPointer: Fr;
|
|
49
|
+
storageAddress: Fr;
|
|
50
|
+
nullifier: Fr;
|
|
51
|
+
exists: boolean;
|
|
52
|
+
counter: Fr;
|
|
53
|
+
endLifetime: Fr;
|
|
54
|
+
// the fields below are relevant only to the public kernel
|
|
55
|
+
// and are therefore omitted from VM inputs
|
|
56
|
+
isPending: boolean;
|
|
57
|
+
leafIndex: Fr;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
//export type TracedNullifier = {
|
|
61
|
+
// callPointer: Fr;
|
|
62
|
+
// storageAddress: Fr;
|
|
63
|
+
// nullifier: Fr;
|
|
64
|
+
// counter: Fr;
|
|
65
|
+
// endLifetime: Fr;
|
|
66
|
+
//};
|
|
67
|
+
|
|
68
|
+
export type TracedL1toL2MessageCheck = {
|
|
69
|
+
//callPointer: Fr;
|
|
70
|
+
leafIndex: Fr;
|
|
71
|
+
msgHash: Fr;
|
|
72
|
+
exists: boolean;
|
|
73
|
+
//endLifetime: Fr;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
//export type TracedArchiveLeafCheck = {
|
|
77
|
+
// leafIndex: Fr;
|
|
78
|
+
// leaf: Fr;
|
|
79
|
+
//};
|
|
@@ -1,8 +1,49 @@
|
|
|
1
1
|
import type { AvmContext } from '../avm_context.js';
|
|
2
|
+
import { Uint8 } from '../avm_memory_types.js';
|
|
3
|
+
import { InstructionExecutionError } from '../errors.js';
|
|
4
|
+
import { NullifierCollisionError } from '../journal/nullifiers.js';
|
|
2
5
|
import { Opcode, OperandType } from '../serialization/instruction_serialization.js';
|
|
6
|
+
import { Addressing } from './addressing_mode.js';
|
|
3
7
|
import { Instruction } from './instruction.js';
|
|
4
8
|
import { StaticCallStorageAlterError } from './storage.js';
|
|
5
9
|
|
|
10
|
+
export class NoteHashExists extends Instruction {
|
|
11
|
+
static type: string = 'NOTEHASHEXISTS';
|
|
12
|
+
static readonly opcode: Opcode = Opcode.NOTEHASHEXISTS;
|
|
13
|
+
// Informs (de)serialization. See Instruction.deserialize.
|
|
14
|
+
static readonly wireFormat = [
|
|
15
|
+
OperandType.UINT8,
|
|
16
|
+
OperandType.UINT8,
|
|
17
|
+
OperandType.UINT32,
|
|
18
|
+
OperandType.UINT32,
|
|
19
|
+
OperandType.UINT32,
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
constructor(
|
|
23
|
+
private indirect: number,
|
|
24
|
+
private noteHashOffset: number,
|
|
25
|
+
private leafIndexOffset: number,
|
|
26
|
+
private existsOffset: number,
|
|
27
|
+
) {
|
|
28
|
+
super();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async execute(context: AvmContext): Promise<void> {
|
|
32
|
+
// Note that this instruction accepts any type in memory, and converts to Field.
|
|
33
|
+
const noteHash = context.machineState.memory.get(this.noteHashOffset).toFr();
|
|
34
|
+
const leafIndex = context.machineState.memory.get(this.leafIndexOffset).toFr();
|
|
35
|
+
|
|
36
|
+
const exists = await context.persistableState.checkNoteHashExists(
|
|
37
|
+
context.environment.storageAddress,
|
|
38
|
+
noteHash,
|
|
39
|
+
leafIndex,
|
|
40
|
+
);
|
|
41
|
+
context.machineState.memory.set(this.existsOffset, exists ? new Uint8(1) : new Uint8(0));
|
|
42
|
+
|
|
43
|
+
context.machineState.incrementPc();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
6
47
|
export class EmitNoteHash extends Instruction {
|
|
7
48
|
static type: string = 'EMITNOTEHASH';
|
|
8
49
|
static readonly opcode: Opcode = Opcode.EMITNOTEHASH;
|
|
@@ -19,7 +60,27 @@ export class EmitNoteHash extends Instruction {
|
|
|
19
60
|
}
|
|
20
61
|
|
|
21
62
|
const noteHash = context.machineState.memory.get(this.noteHashOffset).toFr();
|
|
22
|
-
context.
|
|
63
|
+
context.persistableState.writeNoteHash(noteHash);
|
|
64
|
+
|
|
65
|
+
context.machineState.incrementPc();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export class NullifierExists extends Instruction {
|
|
70
|
+
static type: string = 'NULLIFIEREXISTS';
|
|
71
|
+
static readonly opcode: Opcode = Opcode.NULLIFIEREXISTS;
|
|
72
|
+
// Informs (de)serialization. See Instruction.deserialize.
|
|
73
|
+
static readonly wireFormat = [OperandType.UINT8, OperandType.UINT8, OperandType.UINT32, OperandType.UINT32];
|
|
74
|
+
|
|
75
|
+
constructor(private indirect: number, private nullifierOffset: number, private existsOffset: number) {
|
|
76
|
+
super();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async execute(context: AvmContext): Promise<void> {
|
|
80
|
+
const nullifier = context.machineState.memory.get(this.nullifierOffset).toFr();
|
|
81
|
+
const exists = await context.persistableState.checkNullifierExists(context.environment.storageAddress, nullifier);
|
|
82
|
+
|
|
83
|
+
context.machineState.memory.set(this.existsOffset, exists ? new Uint8(1) : new Uint8(0));
|
|
23
84
|
|
|
24
85
|
context.machineState.incrementPc();
|
|
25
86
|
}
|
|
@@ -41,7 +102,49 @@ export class EmitNullifier extends Instruction {
|
|
|
41
102
|
}
|
|
42
103
|
|
|
43
104
|
const nullifier = context.machineState.memory.get(this.nullifierOffset).toFr();
|
|
44
|
-
|
|
105
|
+
try {
|
|
106
|
+
await context.persistableState.writeNullifier(context.environment.storageAddress, nullifier);
|
|
107
|
+
} catch (e) {
|
|
108
|
+
if (e instanceof NullifierCollisionError) {
|
|
109
|
+
// Error is known/expected, raise as InstructionExecutionError that the will lead the simulator to revert this call
|
|
110
|
+
throw new InstructionExecutionError(
|
|
111
|
+
`Attempted to emit duplicate nullifier ${nullifier} (storage address: ${context.environment.storageAddress}).`,
|
|
112
|
+
);
|
|
113
|
+
} else {
|
|
114
|
+
throw e;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
context.machineState.incrementPc();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export class L1ToL2MessageExists extends Instruction {
|
|
123
|
+
static type: string = 'L1TOL2MSGEXISTS';
|
|
124
|
+
static readonly opcode: Opcode = Opcode.L1TOL2MSGEXISTS;
|
|
125
|
+
// Informs (de)serialization. See Instruction.deserialize.
|
|
126
|
+
static readonly wireFormat = [
|
|
127
|
+
OperandType.UINT8,
|
|
128
|
+
OperandType.UINT8,
|
|
129
|
+
OperandType.UINT32,
|
|
130
|
+
OperandType.UINT32,
|
|
131
|
+
OperandType.UINT32,
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
constructor(
|
|
135
|
+
private indirect: number,
|
|
136
|
+
private msgHashOffset: number,
|
|
137
|
+
private msgLeafIndexOffset: number,
|
|
138
|
+
private existsOffset: number,
|
|
139
|
+
) {
|
|
140
|
+
super();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async execute(context: AvmContext): Promise<void> {
|
|
144
|
+
const msgHash = context.machineState.memory.get(this.msgHashOffset).toFr();
|
|
145
|
+
const msgLeafIndex = context.machineState.memory.get(this.msgLeafIndexOffset).toFr();
|
|
146
|
+
const exists = await context.persistableState.checkL1ToL2MessageExists(msgHash, msgLeafIndex);
|
|
147
|
+
context.machineState.memory.set(this.existsOffset, exists ? new Uint8(1) : new Uint8(0));
|
|
45
148
|
|
|
46
149
|
context.machineState.incrementPc();
|
|
47
150
|
}
|
|
@@ -51,9 +154,20 @@ export class EmitUnencryptedLog extends Instruction {
|
|
|
51
154
|
static type: string = 'EMITUNENCRYPTEDLOG';
|
|
52
155
|
static readonly opcode: Opcode = Opcode.EMITUNENCRYPTEDLOG;
|
|
53
156
|
// Informs (de)serialization. See Instruction.deserialize.
|
|
54
|
-
static readonly wireFormat = [
|
|
55
|
-
|
|
56
|
-
|
|
157
|
+
static readonly wireFormat = [
|
|
158
|
+
OperandType.UINT8,
|
|
159
|
+
OperandType.UINT8,
|
|
160
|
+
OperandType.UINT32,
|
|
161
|
+
OperandType.UINT32,
|
|
162
|
+
OperandType.UINT32,
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
constructor(
|
|
166
|
+
private indirect: number,
|
|
167
|
+
private eventSelectorOffset: number,
|
|
168
|
+
private logOffset: number,
|
|
169
|
+
private logSize: number,
|
|
170
|
+
) {
|
|
57
171
|
super();
|
|
58
172
|
}
|
|
59
173
|
|
|
@@ -62,8 +176,15 @@ export class EmitUnencryptedLog extends Instruction {
|
|
|
62
176
|
throw new StaticCallStorageAlterError();
|
|
63
177
|
}
|
|
64
178
|
|
|
65
|
-
const
|
|
66
|
-
|
|
179
|
+
const [eventSelectorOffset, logOffset] = Addressing.fromWire(this.indirect).resolve(
|
|
180
|
+
[this.eventSelectorOffset, this.logOffset],
|
|
181
|
+
context.machineState.memory,
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const contractAddress = context.environment.address;
|
|
185
|
+
const event = context.machineState.memory.get(eventSelectorOffset).toFr();
|
|
186
|
+
const log = context.machineState.memory.getSlice(logOffset, this.logSize).map(f => f.toFr());
|
|
187
|
+
context.persistableState.writeLog(contractAddress, event, log);
|
|
67
188
|
|
|
68
189
|
context.machineState.incrementPc();
|
|
69
190
|
}
|
|
@@ -75,7 +196,7 @@ export class SendL2ToL1Message extends Instruction {
|
|
|
75
196
|
// Informs (de)serialization. See Instruction.deserialize.
|
|
76
197
|
static readonly wireFormat = [OperandType.UINT8, OperandType.UINT8, OperandType.UINT32, OperandType.UINT32];
|
|
77
198
|
|
|
78
|
-
constructor(private indirect: number, private
|
|
199
|
+
constructor(private indirect: number, private recipientOffset: number, private contentOffset: number) {
|
|
79
200
|
super();
|
|
80
201
|
}
|
|
81
202
|
|
|
@@ -84,8 +205,9 @@ export class SendL2ToL1Message extends Instruction {
|
|
|
84
205
|
throw new StaticCallStorageAlterError();
|
|
85
206
|
}
|
|
86
207
|
|
|
87
|
-
const
|
|
88
|
-
context.
|
|
208
|
+
const recipient = context.machineState.memory.get(this.recipientOffset).toFr();
|
|
209
|
+
const content = context.machineState.memory.get(this.contentOffset).toFr();
|
|
210
|
+
context.persistableState.writeL1Message(recipient, content);
|
|
89
211
|
|
|
90
212
|
context.machineState.incrementPc();
|
|
91
213
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { AvmContext } from '../avm_context.js';
|
|
2
|
-
import { IntegralValue, TaggedMemory } from '../avm_memory_types.js';
|
|
3
2
|
import { Opcode } from '../serialization/instruction_serialization.js';
|
|
4
3
|
import { ThreeOperandInstruction } from './instruction_impl.js';
|
|
5
4
|
|
|
@@ -35,10 +34,9 @@ export class Lt extends ThreeOperandInstruction {
|
|
|
35
34
|
|
|
36
35
|
async execute(context: AvmContext): Promise<void> {
|
|
37
36
|
context.machineState.memory.checkTags(this.inTag, this.aOffset, this.bOffset);
|
|
38
|
-
TaggedMemory.checkIsIntegralTag(this.inTag);
|
|
39
37
|
|
|
40
|
-
const a = context.machineState.memory.
|
|
41
|
-
const b = context.machineState.memory.
|
|
38
|
+
const a = context.machineState.memory.get(this.aOffset);
|
|
39
|
+
const b = context.machineState.memory.get(this.bOffset);
|
|
42
40
|
|
|
43
41
|
// Result will be of the same type as 'a'.
|
|
44
42
|
const dest = a.build(a.lt(b) ? 1n : 0n);
|
|
@@ -58,10 +56,9 @@ export class Lte extends ThreeOperandInstruction {
|
|
|
58
56
|
|
|
59
57
|
async execute(context: AvmContext): Promise<void> {
|
|
60
58
|
context.machineState.memory.checkTags(this.inTag, this.aOffset, this.bOffset);
|
|
61
|
-
TaggedMemory.checkIsIntegralTag(this.inTag);
|
|
62
59
|
|
|
63
|
-
const a = context.machineState.memory.
|
|
64
|
-
const b = context.machineState.memory.
|
|
60
|
+
const a = context.machineState.memory.get(this.aOffset);
|
|
61
|
+
const b = context.machineState.memory.get(this.bOffset);
|
|
65
62
|
|
|
66
63
|
// Result will be of the same type as 'a'.
|
|
67
64
|
const dest = a.build(a.equals(b) || a.lt(b) ? 1n : 0n);
|