@aztec/simulator 0.32.1 → 0.33.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 +5 -3
- package/dest/acvm/oracle/index.d.ts +0 -1
- package/dest/acvm/oracle/index.d.ts.map +1 -1
- package/dest/acvm/oracle/index.js +1 -2
- package/dest/acvm/oracle/oracle.d.ts.map +1 -1
- package/dest/acvm/oracle/oracle.js +2 -3
- package/dest/avm/avm_context.d.ts +4 -14
- package/dest/avm/avm_context.d.ts.map +1 -1
- package/dest/avm/avm_context.js +10 -22
- package/dest/avm/avm_gas.d.ts +71 -0
- package/dest/avm/avm_gas.d.ts.map +1 -0
- package/dest/avm/avm_gas.js +161 -0
- package/dest/avm/avm_machine_state.d.ts +4 -2
- package/dest/avm/avm_machine_state.d.ts.map +1 -1
- package/dest/avm/avm_machine_state.js +8 -2
- package/dest/avm/avm_memory_types.d.ts +53 -1
- package/dest/avm/avm_memory_types.d.ts.map +1 -1
- package/dest/avm/avm_memory_types.js +95 -2
- package/dest/avm/avm_simulator.d.ts.map +1 -1
- package/dest/avm/avm_simulator.js +10 -8
- package/dest/avm/opcodes/accrued_substate.d.ts.map +1 -1
- package/dest/avm/opcodes/accrued_substate.js +44 -16
- package/dest/avm/opcodes/addressing_mode.d.ts +5 -3
- package/dest/avm/opcodes/addressing_mode.d.ts.map +1 -1
- package/dest/avm/opcodes/addressing_mode.js +5 -1
- package/dest/avm/opcodes/arithmetic.d.ts +7 -3
- package/dest/avm/opcodes/arithmetic.d.ts.map +1 -1
- package/dest/avm/opcodes/arithmetic.js +27 -16
- package/dest/avm/opcodes/bitwise.d.ts +21 -20
- package/dest/avm/opcodes/bitwise.d.ts.map +1 -1
- package/dest/avm/opcodes/bitwise.js +43 -65
- package/dest/avm/opcodes/comparators.d.ts +12 -9
- package/dest/avm/opcodes/comparators.d.ts.map +1 -1
- package/dest/avm/opcodes/comparators.js +22 -32
- package/dest/avm/opcodes/context_getters.d.ts +20 -0
- package/dest/avm/opcodes/context_getters.d.ts.map +1 -0
- package/dest/avm/opcodes/context_getters.js +26 -0
- package/dest/avm/opcodes/contract.d.ts +14 -0
- package/dest/avm/opcodes/contract.d.ts.map +1 -0
- package/dest/avm/opcodes/contract.js +49 -0
- package/dest/avm/opcodes/control_flow.d.ts.map +1 -1
- package/dest/avm/opcodes/control_flow.js +12 -2
- package/dest/avm/opcodes/environment_getters.d.ts +30 -33
- package/dest/avm/opcodes/environment_getters.d.ts.map +1 -1
- package/dest/avm/opcodes/environment_getters.js +34 -43
- package/dest/avm/opcodes/external_calls.d.ts +13 -19
- package/dest/avm/opcodes/external_calls.d.ts.map +1 -1
- package/dest/avm/opcodes/external_calls.js +50 -68
- package/dest/avm/opcodes/hashing.d.ts +2 -1
- package/dest/avm/opcodes/hashing.d.ts.map +1 -1
- package/dest/avm/opcodes/hashing.js +37 -18
- package/dest/avm/opcodes/index.d.ts +1 -0
- package/dest/avm/opcodes/index.d.ts.map +1 -1
- package/dest/avm/opcodes/index.js +2 -1
- package/dest/avm/opcodes/instruction.d.ts +10 -15
- package/dest/avm/opcodes/instruction.d.ts.map +1 -1
- package/dest/avm/opcodes/instruction.js +12 -22
- package/dest/avm/opcodes/instruction_impl.d.ts +14 -0
- package/dest/avm/opcodes/instruction_impl.d.ts.map +1 -1
- package/dest/avm/opcodes/instruction_impl.js +37 -16
- package/dest/avm/opcodes/memory.d.ts +4 -3
- package/dest/avm/opcodes/memory.d.ts.map +1 -1
- package/dest/avm/opcodes/memory.js +38 -19
- package/dest/avm/opcodes/storage.d.ts +5 -0
- package/dest/avm/opcodes/storage.d.ts.map +1 -1
- package/dest/avm/opcodes/storage.js +21 -7
- package/dest/avm/serialization/bytecode_serialization.d.ts.map +1 -1
- package/dest/avm/serialization/bytecode_serialization.js +7 -5
- package/dest/avm/serialization/instruction_serialization.d.ts +12 -11
- package/dest/avm/serialization/instruction_serialization.d.ts.map +1 -1
- package/dest/avm/serialization/instruction_serialization.js +13 -12
- package/dest/avm/temporary_executor_migration.d.ts +2 -0
- package/dest/avm/temporary_executor_migration.d.ts.map +1 -1
- package/dest/avm/temporary_executor_migration.js +14 -3
- package/dest/client/private_execution.d.ts.map +1 -1
- package/dest/client/private_execution.js +6 -2
- package/dest/public/executor.d.ts +10 -2
- package/dest/public/executor.d.ts.map +1 -1
- package/dest/public/executor.js +59 -20
- package/dest/public/index.d.ts +1 -1
- package/dest/public/index.d.ts.map +1 -1
- package/package.json +15 -9
- package/src/acvm/oracle/index.ts +0 -1
- package/src/acvm/oracle/oracle.ts +1 -2
- package/src/avm/avm_context.ts +11 -33
- package/src/avm/{avm_gas_cost.ts → avm_gas.ts} +75 -21
- package/src/avm/avm_machine_state.ts +9 -2
- package/src/avm/avm_memory_types.ts +130 -2
- package/src/avm/avm_simulator.ts +9 -7
- package/src/avm/opcodes/accrued_substate.ts +57 -22
- package/src/avm/opcodes/addressing_mode.ts +8 -3
- package/src/avm/opcodes/arithmetic.ts +32 -22
- package/src/avm/opcodes/bitwise.ts +49 -83
- package/src/avm/opcodes/comparators.ts +28 -43
- package/src/avm/opcodes/context_getters.ts +32 -0
- package/src/avm/opcodes/contract.ts +58 -0
- package/src/avm/opcodes/control_flow.ts +23 -5
- package/src/avm/opcodes/environment_getters.ts +35 -44
- package/src/avm/opcodes/external_calls.ts +65 -84
- package/src/avm/opcodes/hashing.ts +45 -22
- package/src/avm/opcodes/index.ts +1 -0
- package/src/avm/opcodes/instruction.ts +14 -26
- package/src/avm/opcodes/instruction_impl.ts +45 -15
- package/src/avm/opcodes/memory.ts +48 -28
- package/src/avm/opcodes/storage.ts +26 -12
- package/src/avm/serialization/bytecode_serialization.ts +6 -3
- package/src/avm/serialization/instruction_serialization.ts +1 -0
- package/src/avm/temporary_executor_migration.ts +16 -2
- package/src/client/private_execution.ts +8 -2
- package/src/public/executor.ts +75 -22
- package/src/public/index.ts +2 -2
- package/dest/acvm/oracle/debug.d.ts +0 -19
- package/dest/acvm/oracle/debug.d.ts.map +0 -1
- package/dest/acvm/oracle/debug.js +0 -95
- package/dest/avm/avm_gas_cost.d.ts +0 -322
- package/dest/avm/avm_gas_cost.d.ts.map +0 -1
- package/dest/avm/avm_gas_cost.js +0 -118
- package/src/acvm/oracle/debug.ts +0 -109
|
@@ -1,20 +1,49 @@
|
|
|
1
1
|
import { TypeTag } from './avm_memory_types.js';
|
|
2
|
+
import { InstructionExecutionError } from './errors.js';
|
|
3
|
+
import { Addressing, AddressingMode } from './opcodes/addressing_mode.js';
|
|
2
4
|
import { Opcode } from './serialization/instruction_serialization.js';
|
|
3
5
|
|
|
4
|
-
/** Gas
|
|
5
|
-
export type
|
|
6
|
+
/** Gas counters in L1, L2, and DA. */
|
|
7
|
+
export type Gas = {
|
|
6
8
|
l1Gas: number;
|
|
7
9
|
l2Gas: number;
|
|
8
10
|
daGas: number;
|
|
9
11
|
};
|
|
10
12
|
|
|
13
|
+
/** Maps a Gas struct to gasLeft properties. */
|
|
14
|
+
export function gasToGasLeft(gas: Gas) {
|
|
15
|
+
return { l1GasLeft: gas.l1Gas, l2GasLeft: gas.l2Gas, daGasLeft: gas.daGas };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Maps gasLeft properties to a gas struct. */
|
|
19
|
+
export function gasLeftToGas(gasLeft: { l1GasLeft: number; l2GasLeft: number; daGasLeft: number }) {
|
|
20
|
+
return { l1Gas: gasLeft.l1GasLeft, l2Gas: gasLeft.l2GasLeft, daGas: gasLeft.daGasLeft };
|
|
21
|
+
}
|
|
22
|
+
|
|
11
23
|
/** Creates a new instance with all values set to zero except the ones set. */
|
|
12
|
-
export function
|
|
13
|
-
return { ...
|
|
24
|
+
export function makeGas(gasCost: Partial<Gas>) {
|
|
25
|
+
return { ...EmptyGas, ...gasCost };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Sums together multiple instances of Gas. */
|
|
29
|
+
export function sumGas(...gases: Partial<Gas>[]) {
|
|
30
|
+
return gases.reduce(
|
|
31
|
+
(acc: Gas, gas) => ({
|
|
32
|
+
l1Gas: acc.l1Gas + (gas.l1Gas ?? 0),
|
|
33
|
+
l2Gas: acc.l2Gas + (gas.l2Gas ?? 0),
|
|
34
|
+
daGas: acc.daGas + (gas.daGas ?? 0),
|
|
35
|
+
}),
|
|
36
|
+
EmptyGas,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Multiplies a gas instance by a scalar. */
|
|
41
|
+
export function mulGas(gas: Partial<Gas>, scalar: number) {
|
|
42
|
+
return { l1Gas: (gas.l1Gas ?? 0) * scalar, l2Gas: (gas.l2Gas ?? 0) * scalar, daGas: (gas.daGas ?? 0) * scalar };
|
|
14
43
|
}
|
|
15
44
|
|
|
16
|
-
/**
|
|
17
|
-
export const
|
|
45
|
+
/** Zero gas across all gas dimensions. */
|
|
46
|
+
export const EmptyGas: Gas = {
|
|
18
47
|
l1Gas: 0,
|
|
19
48
|
l2Gas: 0,
|
|
20
49
|
daGas: 0,
|
|
@@ -29,12 +58,12 @@ export const DynamicGasCost = Symbol('DynamicGasCost');
|
|
|
29
58
|
/** Temporary default gas cost. We should eventually remove all usage of this variable in favor of actual gas for each opcode. */
|
|
30
59
|
const TemporaryDefaultGasCost = { l1Gas: 0, l2Gas: 10, daGas: 0 };
|
|
31
60
|
|
|
32
|
-
/**
|
|
33
|
-
export const GasCosts = {
|
|
34
|
-
[Opcode.ADD]:
|
|
35
|
-
[Opcode.SUB]:
|
|
36
|
-
[Opcode.MUL]:
|
|
37
|
-
[Opcode.DIV]:
|
|
61
|
+
/** Base gas costs for each instruction. Additional gas cost may be added on top due to memory or storage accesses, etc. */
|
|
62
|
+
export const GasCosts: Record<Opcode, Gas | typeof DynamicGasCost> = {
|
|
63
|
+
[Opcode.ADD]: TemporaryDefaultGasCost,
|
|
64
|
+
[Opcode.SUB]: TemporaryDefaultGasCost,
|
|
65
|
+
[Opcode.MUL]: TemporaryDefaultGasCost,
|
|
66
|
+
[Opcode.DIV]: TemporaryDefaultGasCost,
|
|
38
67
|
[Opcode.FDIV]: TemporaryDefaultGasCost,
|
|
39
68
|
[Opcode.EQ]: TemporaryDefaultGasCost,
|
|
40
69
|
[Opcode.LT]: TemporaryDefaultGasCost,
|
|
@@ -64,7 +93,7 @@ export const GasCosts = {
|
|
|
64
93
|
[Opcode.BLOCKL1GASLIMIT]: TemporaryDefaultGasCost,
|
|
65
94
|
[Opcode.BLOCKL2GASLIMIT]: TemporaryDefaultGasCost,
|
|
66
95
|
[Opcode.BLOCKDAGASLIMIT]: TemporaryDefaultGasCost,
|
|
67
|
-
[Opcode.CALLDATACOPY]:
|
|
96
|
+
[Opcode.CALLDATACOPY]: TemporaryDefaultGasCost,
|
|
68
97
|
// Gas
|
|
69
98
|
[Opcode.L1GASLEFT]: TemporaryDefaultGasCost,
|
|
70
99
|
[Opcode.L2GASLEFT]: TemporaryDefaultGasCost,
|
|
@@ -75,7 +104,7 @@ export const GasCosts = {
|
|
|
75
104
|
[Opcode.INTERNALCALL]: TemporaryDefaultGasCost,
|
|
76
105
|
[Opcode.INTERNALRETURN]: TemporaryDefaultGasCost,
|
|
77
106
|
// Memory
|
|
78
|
-
[Opcode.SET]:
|
|
107
|
+
[Opcode.SET]: TemporaryDefaultGasCost,
|
|
79
108
|
[Opcode.MOV]: TemporaryDefaultGasCost,
|
|
80
109
|
[Opcode.CMOV]: TemporaryDefaultGasCost,
|
|
81
110
|
// World state
|
|
@@ -89,6 +118,7 @@ export const GasCosts = {
|
|
|
89
118
|
[Opcode.HEADERMEMBER]: TemporaryDefaultGasCost,
|
|
90
119
|
[Opcode.EMITUNENCRYPTEDLOG]: TemporaryDefaultGasCost,
|
|
91
120
|
[Opcode.SENDL2TOL1MSG]: TemporaryDefaultGasCost,
|
|
121
|
+
[Opcode.GETCONTRACTINSTANCE]: TemporaryDefaultGasCost,
|
|
92
122
|
// External calls
|
|
93
123
|
[Opcode.CALL]: TemporaryDefaultGasCost,
|
|
94
124
|
[Opcode.STATICCALL]: TemporaryDefaultGasCost,
|
|
@@ -100,18 +130,42 @@ export const GasCosts = {
|
|
|
100
130
|
[Opcode.POSEIDON]: TemporaryDefaultGasCost,
|
|
101
131
|
[Opcode.SHA256]: TemporaryDefaultGasCost, // temp - may be removed, but alot of contracts rely on i: TemporaryDefaultGasCost,
|
|
102
132
|
[Opcode.PEDERSEN]: TemporaryDefaultGasCost, // temp - may be removed, but alot of contracts rely on i: TemporaryDefaultGasCost,t
|
|
103
|
-
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/** Returns the fixed base gas cost for a given opcode, or throws if set to dynamic. */
|
|
136
|
+
export function getBaseGasCost(opcode: Opcode): Gas {
|
|
137
|
+
const cost = GasCosts[opcode];
|
|
138
|
+
if (cost === DynamicGasCost) {
|
|
139
|
+
throw new Error(`Opcode ${Opcode[opcode]} has dynamic gas cost`);
|
|
140
|
+
}
|
|
141
|
+
return cost;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Returns the gas cost associated with the memory operations performed. */
|
|
145
|
+
export function getMemoryGasCost(args: { reads?: number; writes?: number; indirect?: number }) {
|
|
146
|
+
const { reads, writes, indirect } = args;
|
|
147
|
+
const indirectCount = Addressing.fromWire(indirect ?? 0).count(AddressingMode.INDIRECT);
|
|
148
|
+
const l2MemoryGasCost =
|
|
149
|
+
(reads ?? 0) * GasCostConstants.MEMORY_READ +
|
|
150
|
+
(writes ?? 0) * GasCostConstants.MEMORY_WRITE +
|
|
151
|
+
indirectCount * GasCostConstants.MEMORY_INDIRECT_READ_PENALTY;
|
|
152
|
+
return makeGas({ l2Gas: l2MemoryGasCost });
|
|
153
|
+
}
|
|
104
154
|
|
|
105
155
|
/** Constants used in base cost calculations. */
|
|
106
156
|
export const GasCostConstants = {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
ARITHMETIC_COST_PER_INDIRECT_ACCESS: 5,
|
|
157
|
+
MEMORY_READ: 10,
|
|
158
|
+
MEMORY_INDIRECT_READ_PENALTY: 10,
|
|
159
|
+
MEMORY_WRITE: 100,
|
|
111
160
|
};
|
|
112
161
|
|
|
162
|
+
/** Returns gas cost for an operation on a given type tag based on the base cost per byte. */
|
|
163
|
+
export function getGasCostForTypeTag(tag: TypeTag, baseCost: Gas) {
|
|
164
|
+
return mulGas(baseCost, getGasCostMultiplierFromTypeTag(tag));
|
|
165
|
+
}
|
|
166
|
+
|
|
113
167
|
/** Returns a multiplier based on the size of the type represented by the tag. Throws on uninitialized or invalid. */
|
|
114
|
-
|
|
168
|
+
function getGasCostMultiplierFromTypeTag(tag: TypeTag) {
|
|
115
169
|
switch (tag) {
|
|
116
170
|
case TypeTag.UINT8:
|
|
117
171
|
return 1;
|
|
@@ -127,6 +181,6 @@ export function getGasCostMultiplierFromTypeTag(tag: TypeTag) {
|
|
|
127
181
|
return 32;
|
|
128
182
|
case TypeTag.INVALID:
|
|
129
183
|
case TypeTag.UNINITIALIZED:
|
|
130
|
-
throw new
|
|
184
|
+
throw new InstructionExecutionError(`Invalid tag type for gas cost multiplier: ${TypeTag[tag]}`);
|
|
131
185
|
}
|
|
132
186
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Fr } from '@aztec/circuits.js';
|
|
2
2
|
|
|
3
|
-
import { type
|
|
3
|
+
import { type Gas, GasDimensions } from './avm_gas.js';
|
|
4
4
|
import { TaggedMemory } from './avm_memory_types.js';
|
|
5
5
|
import { AvmContractCallResults } from './avm_message_call_result.js';
|
|
6
6
|
import { OutOfGasError } from './errors.js';
|
|
@@ -59,7 +59,7 @@ export class AvmMachineState {
|
|
|
59
59
|
* Should any of the gas dimensions get depleted, it sets all gas left to zero and triggers
|
|
60
60
|
* an exceptional halt by throwing an OutOfGasError.
|
|
61
61
|
*/
|
|
62
|
-
public consumeGas(gasCost: Partial<
|
|
62
|
+
public consumeGas(gasCost: Partial<Gas>) {
|
|
63
63
|
// Assert there is enough gas on every dimension.
|
|
64
64
|
const outOfGasDimensions = GasDimensions.filter(
|
|
65
65
|
dimension => this[`${dimension}Left`] - (gasCost[dimension] ?? 0) < 0,
|
|
@@ -76,6 +76,13 @@ export class AvmMachineState {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/** Increases the gas left by the amounts specified. */
|
|
80
|
+
public refundGas(gasRefund: Partial<Gas>) {
|
|
81
|
+
for (const dimension of GasDimensions) {
|
|
82
|
+
this[`${dimension}Left`] += gasRefund[dimension] ?? 0;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
79
86
|
/**
|
|
80
87
|
* Most instructions just increment PC before they complete
|
|
81
88
|
*/
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { toBufferBE } from '@aztec/foundation/bigint-buffer';
|
|
2
2
|
import { Fr } from '@aztec/foundation/fields';
|
|
3
3
|
import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log';
|
|
4
|
+
import { type FunctionsOf } from '@aztec/foundation/types';
|
|
4
5
|
|
|
5
6
|
import { strict as assert } from 'assert';
|
|
6
7
|
|
|
7
|
-
import { TagCheckError } from './errors.js';
|
|
8
|
+
import { InstructionExecutionError, TagCheckError } from './errors.js';
|
|
9
|
+
import { Addressing, AddressingMode } from './opcodes/addressing_mode.js';
|
|
8
10
|
|
|
9
11
|
/** MemoryValue gathers the common operations for all memory types. */
|
|
10
12
|
export abstract class MemoryValue {
|
|
@@ -30,6 +32,11 @@ export abstract class MemoryValue {
|
|
|
30
32
|
return new Fr(this.toBigInt());
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
// To number. Throws if exceeds max safe int.
|
|
36
|
+
public toNumber(): number {
|
|
37
|
+
return this.toFr().toNumber();
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
public toString(): string {
|
|
34
41
|
return `${this.constructor.name}(0x${this.toBigInt().toString(16)})`;
|
|
35
42
|
}
|
|
@@ -198,10 +205,16 @@ export enum TypeTag {
|
|
|
198
205
|
INVALID,
|
|
199
206
|
}
|
|
200
207
|
|
|
208
|
+
// Lazy interface definition for tagged memory
|
|
209
|
+
export type TaggedMemoryInterface = FunctionsOf<TaggedMemory>;
|
|
210
|
+
|
|
201
211
|
// TODO: Consider automatic conversion when getting undefined values.
|
|
202
|
-
export class TaggedMemory {
|
|
212
|
+
export class TaggedMemory implements TaggedMemoryInterface {
|
|
203
213
|
static readonly log: DebugLogger = createDebugLogger('aztec:avm_simulator:memory');
|
|
204
214
|
|
|
215
|
+
// Whether to track and validate memory accesses for each instruction.
|
|
216
|
+
static readonly TRACK_MEMORY_ACCESSES = process.env.NODE_ENV === 'test';
|
|
217
|
+
|
|
205
218
|
// FIXME: memory should be 2^32, but TS doesn't allow for arrays that big.
|
|
206
219
|
static readonly MAX_MEMORY_SIZE = Number((1n << 32n) - 2n);
|
|
207
220
|
private _mem: MemoryValue[];
|
|
@@ -211,6 +224,11 @@ export class TaggedMemory {
|
|
|
211
224
|
this._mem = [];
|
|
212
225
|
}
|
|
213
226
|
|
|
227
|
+
/** Returns a MeteredTaggedMemory instance to track the number of reads and writes if TRACK_MEMORY_ACCESSES is set. */
|
|
228
|
+
public track(type: string = 'instruction') {
|
|
229
|
+
return TaggedMemory.TRACK_MEMORY_ACCESSES ? new MeteredTaggedMemory(this, type) : this;
|
|
230
|
+
}
|
|
231
|
+
|
|
214
232
|
public get(offset: number): MemoryValue {
|
|
215
233
|
assert(offset < TaggedMemory.MAX_MEMORY_SIZE);
|
|
216
234
|
const value = this.getAs<MemoryValue>(offset);
|
|
@@ -221,6 +239,9 @@ export class TaggedMemory {
|
|
|
221
239
|
assert(offset < TaggedMemory.MAX_MEMORY_SIZE);
|
|
222
240
|
const word = this._mem[offset];
|
|
223
241
|
TaggedMemory.log(`get(${offset}) = ${word}`);
|
|
242
|
+
if (word === undefined) {
|
|
243
|
+
TaggedMemory.log.warn(`Memory at offset ${offset} is undefined! This might be OK if it's stack dumping.`);
|
|
244
|
+
}
|
|
224
245
|
return word as T;
|
|
225
246
|
}
|
|
226
247
|
|
|
@@ -229,6 +250,7 @@ export class TaggedMemory {
|
|
|
229
250
|
assert(offset + size < TaggedMemory.MAX_MEMORY_SIZE);
|
|
230
251
|
const value = this._mem.slice(offset, offset + size);
|
|
231
252
|
TaggedMemory.log(`getSlice(${offset}, ${size}) = ${value}`);
|
|
253
|
+
assert(!value.some(e => e === undefined), 'Memory slice contains undefined values.');
|
|
232
254
|
assert(value.length === size, `Expected slice of size ${size}, got ${value.length}.`);
|
|
233
255
|
return value;
|
|
234
256
|
}
|
|
@@ -367,4 +389,110 @@ export class TaggedMemory {
|
|
|
367
389
|
throw new Error(`${TypeTag[tag]} is not a valid integral type.`);
|
|
368
390
|
}
|
|
369
391
|
}
|
|
392
|
+
|
|
393
|
+
/** No-op. Implemented here for compatibility with the MeteredTaggedMemory. */
|
|
394
|
+
public assert(_operations: Partial<MemoryOperations & { indirect: number }>) {}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Tagged memory wrapper with metering for each memory read and write operation. */
|
|
398
|
+
export class MeteredTaggedMemory implements TaggedMemoryInterface {
|
|
399
|
+
private reads: number = 0;
|
|
400
|
+
private writes: number = 0;
|
|
401
|
+
|
|
402
|
+
constructor(private wrapped: TaggedMemory, private type: string = 'instruction') {}
|
|
403
|
+
|
|
404
|
+
/** Returns the number of reads and writes tracked so far and resets them to zero. */
|
|
405
|
+
public reset(): MemoryOperations {
|
|
406
|
+
const stats = { reads: this.reads, writes: this.writes };
|
|
407
|
+
this.reads = 0;
|
|
408
|
+
this.writes = 0;
|
|
409
|
+
return stats;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Asserts that the exact number of memory operations have been performed.
|
|
414
|
+
* Indirect represents the flags for indirect accesses: each bit set to one counts as an extra read.
|
|
415
|
+
*/
|
|
416
|
+
public assert(operations: Partial<MemoryOperations & { indirect: number }>) {
|
|
417
|
+
const { reads: expectedReads, writes: expectedWrites, indirect } = { reads: 0, writes: 0, ...operations };
|
|
418
|
+
|
|
419
|
+
const totalExpectedReads = expectedReads + Addressing.fromWire(indirect ?? 0).count(AddressingMode.INDIRECT);
|
|
420
|
+
const { reads: actualReads, writes: actualWrites } = this.reset();
|
|
421
|
+
if (actualReads !== totalExpectedReads) {
|
|
422
|
+
throw new InstructionExecutionError(
|
|
423
|
+
`Incorrect number of memory reads for ${this.type}: expected ${totalExpectedReads} but executed ${actualReads}`,
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
if (actualWrites !== expectedWrites) {
|
|
427
|
+
throw new InstructionExecutionError(
|
|
428
|
+
`Incorrect number of memory writes for ${this.type}: expected ${expectedWrites} but executed ${actualWrites}`,
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
public track(type: string = 'instruction'): MeteredTaggedMemory {
|
|
434
|
+
return new MeteredTaggedMemory(this.wrapped, type);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
public get(offset: number): MemoryValue {
|
|
438
|
+
this.reads++;
|
|
439
|
+
return this.wrapped.get(offset);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
public getSliceAs<T>(offset: number, size: number): T[] {
|
|
443
|
+
this.reads += size;
|
|
444
|
+
return this.wrapped.getSliceAs<T>(offset, size);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
public getAs<T>(offset: number): T {
|
|
448
|
+
this.reads++;
|
|
449
|
+
return this.wrapped.getAs(offset);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
public getSlice(offset: number, size: number): MemoryValue[] {
|
|
453
|
+
this.reads += size;
|
|
454
|
+
return this.wrapped.getSlice(offset, size);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
public set(offset: number, v: MemoryValue): void {
|
|
458
|
+
this.writes++;
|
|
459
|
+
this.wrapped.set(offset, v);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
public setSlice(offset: number, vs: MemoryValue[]): void {
|
|
463
|
+
this.writes += vs.length;
|
|
464
|
+
this.wrapped.setSlice(offset, vs);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
public getSliceTags(offset: number, size: number): TypeTag[] {
|
|
468
|
+
return this.wrapped.getSliceTags(offset, size);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
public getTag(offset: number): TypeTag {
|
|
472
|
+
return this.wrapped.getTag(offset);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
public checkTag(tag: TypeTag, offset: number): void {
|
|
476
|
+
this.wrapped.checkTag(tag, offset);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
public checkIsValidMemoryOffsetTag(offset: number): void {
|
|
480
|
+
this.wrapped.checkIsValidMemoryOffsetTag(offset);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
public checkTags(tag: TypeTag, ...offsets: number[]): void {
|
|
484
|
+
this.wrapped.checkTags(tag, ...offsets);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
public checkTagsRange(tag: TypeTag, startOffset: number, size: number): void {
|
|
488
|
+
this.wrapped.checkTagsRange(tag, startOffset, size);
|
|
489
|
+
}
|
|
370
490
|
}
|
|
491
|
+
|
|
492
|
+
/** Tracks number of memory reads and writes. */
|
|
493
|
+
export type MemoryOperations = {
|
|
494
|
+
/** How many total reads are performed. Slice reads are count as one per element. */
|
|
495
|
+
reads: number;
|
|
496
|
+
/** How many total writes are performed. Slice writes are count as one per element. */
|
|
497
|
+
writes: number;
|
|
498
|
+
};
|
package/src/avm/avm_simulator.ts
CHANGED
|
@@ -50,30 +50,32 @@ export class AvmSimulator {
|
|
|
50
50
|
*/
|
|
51
51
|
public async executeInstructions(instructions: Instruction[]): Promise<AvmContractCallResults> {
|
|
52
52
|
assert(instructions.length > 0);
|
|
53
|
+
const { machineState } = this.context;
|
|
53
54
|
try {
|
|
54
55
|
// Execute instruction pointed to by the current program counter
|
|
55
56
|
// continuing until the machine state signifies a halt
|
|
56
|
-
while (!
|
|
57
|
-
const instruction = instructions[
|
|
57
|
+
while (!machineState.halted) {
|
|
58
|
+
const instruction = instructions[machineState.pc];
|
|
58
59
|
assert(
|
|
59
60
|
!!instruction,
|
|
60
61
|
'AVM attempted to execute non-existent instruction. This should never happen (invalid bytecode or AVM simulator bug)!',
|
|
61
62
|
);
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
const gasLeft = `l1=${machineState.l1GasLeft} l2=${machineState.l2GasLeft} da=${machineState.daGasLeft}`;
|
|
65
|
+
this.log.debug(`@${machineState.pc} (${gasLeft}) ${instruction.toString()}`);
|
|
64
66
|
// Execute the instruction.
|
|
65
67
|
// Normal returns and reverts will return normally here.
|
|
66
68
|
// "Exceptional halts" will throw.
|
|
67
|
-
await instruction.
|
|
69
|
+
await instruction.execute(this.context);
|
|
68
70
|
|
|
69
|
-
if (
|
|
71
|
+
if (machineState.pc >= instructions.length) {
|
|
70
72
|
this.log('Passed end of program!');
|
|
71
|
-
throw new InvalidProgramCounterError(
|
|
73
|
+
throw new InvalidProgramCounterError(machineState.pc, /*max=*/ instructions.length);
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
// Return results for processing by calling context
|
|
76
|
-
const results =
|
|
78
|
+
const results = machineState.getResults();
|
|
77
79
|
this.log(`Context execution results: ${results.toString()}`);
|
|
78
80
|
return results;
|
|
79
81
|
} catch (e) {
|
|
@@ -28,18 +28,23 @@ export class NoteHashExists extends Instruction {
|
|
|
28
28
|
super();
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
async execute(context: AvmContext): Promise<void> {
|
|
31
|
+
public async execute(context: AvmContext): Promise<void> {
|
|
32
|
+
const memoryOperations = { reads: 2, writes: 1, indirect: this.indirect };
|
|
33
|
+
const memory = context.machineState.memory.track(this.type);
|
|
34
|
+
context.machineState.consumeGas(this.gasCost(memoryOperations));
|
|
35
|
+
|
|
32
36
|
// Note that this instruction accepts any type in memory, and converts to Field.
|
|
33
|
-
const noteHash =
|
|
34
|
-
const leafIndex =
|
|
37
|
+
const noteHash = memory.get(this.noteHashOffset).toFr();
|
|
38
|
+
const leafIndex = memory.get(this.leafIndexOffset).toFr();
|
|
35
39
|
|
|
36
40
|
const exists = await context.persistableState.checkNoteHashExists(
|
|
37
41
|
context.environment.storageAddress,
|
|
38
42
|
noteHash,
|
|
39
43
|
leafIndex,
|
|
40
44
|
);
|
|
41
|
-
|
|
45
|
+
memory.set(this.existsOffset, exists ? new Uint8(1) : new Uint8(0));
|
|
42
46
|
|
|
47
|
+
memory.assert(memoryOperations);
|
|
43
48
|
context.machineState.incrementPc();
|
|
44
49
|
}
|
|
45
50
|
}
|
|
@@ -54,14 +59,19 @@ export class EmitNoteHash extends Instruction {
|
|
|
54
59
|
super();
|
|
55
60
|
}
|
|
56
61
|
|
|
57
|
-
async execute(context: AvmContext): Promise<void> {
|
|
62
|
+
public async execute(context: AvmContext): Promise<void> {
|
|
63
|
+
const memoryOperations = { reads: 1, indirect: this.indirect };
|
|
64
|
+
const memory = context.machineState.memory.track(this.type);
|
|
65
|
+
context.machineState.consumeGas(this.gasCost(memoryOperations));
|
|
66
|
+
|
|
58
67
|
if (context.environment.isStaticCall) {
|
|
59
68
|
throw new StaticCallStorageAlterError();
|
|
60
69
|
}
|
|
61
70
|
|
|
62
|
-
const noteHash =
|
|
71
|
+
const noteHash = memory.get(this.noteHashOffset).toFr();
|
|
63
72
|
context.persistableState.writeNoteHash(noteHash);
|
|
64
73
|
|
|
74
|
+
memory.assert(memoryOperations);
|
|
65
75
|
context.machineState.incrementPc();
|
|
66
76
|
}
|
|
67
77
|
}
|
|
@@ -76,12 +86,17 @@ export class NullifierExists extends Instruction {
|
|
|
76
86
|
super();
|
|
77
87
|
}
|
|
78
88
|
|
|
79
|
-
async execute(context: AvmContext): Promise<void> {
|
|
80
|
-
const
|
|
89
|
+
public async execute(context: AvmContext): Promise<void> {
|
|
90
|
+
const memoryOperations = { reads: 1, writes: 1, indirect: this.indirect };
|
|
91
|
+
const memory = context.machineState.memory.track(this.type);
|
|
92
|
+
context.machineState.consumeGas(this.gasCost(memoryOperations));
|
|
93
|
+
|
|
94
|
+
const nullifier = memory.get(this.nullifierOffset).toFr();
|
|
81
95
|
const exists = await context.persistableState.checkNullifierExists(context.environment.storageAddress, nullifier);
|
|
82
96
|
|
|
83
|
-
|
|
97
|
+
memory.set(this.existsOffset, exists ? new Uint8(1) : new Uint8(0));
|
|
84
98
|
|
|
99
|
+
memory.assert(memoryOperations);
|
|
85
100
|
context.machineState.incrementPc();
|
|
86
101
|
}
|
|
87
102
|
}
|
|
@@ -96,12 +111,16 @@ export class EmitNullifier extends Instruction {
|
|
|
96
111
|
super();
|
|
97
112
|
}
|
|
98
113
|
|
|
99
|
-
async execute(context: AvmContext): Promise<void> {
|
|
114
|
+
public async execute(context: AvmContext): Promise<void> {
|
|
100
115
|
if (context.environment.isStaticCall) {
|
|
101
116
|
throw new StaticCallStorageAlterError();
|
|
102
117
|
}
|
|
103
118
|
|
|
104
|
-
const
|
|
119
|
+
const memoryOperations = { reads: 1, indirect: this.indirect };
|
|
120
|
+
const memory = context.machineState.memory.track(this.type);
|
|
121
|
+
context.machineState.consumeGas(this.gasCost(memoryOperations));
|
|
122
|
+
|
|
123
|
+
const nullifier = memory.get(this.nullifierOffset).toFr();
|
|
105
124
|
try {
|
|
106
125
|
await context.persistableState.writeNullifier(context.environment.storageAddress, nullifier);
|
|
107
126
|
} catch (e) {
|
|
@@ -115,6 +134,7 @@ export class EmitNullifier extends Instruction {
|
|
|
115
134
|
}
|
|
116
135
|
}
|
|
117
136
|
|
|
137
|
+
memory.assert(memoryOperations);
|
|
118
138
|
context.machineState.incrementPc();
|
|
119
139
|
}
|
|
120
140
|
}
|
|
@@ -140,12 +160,17 @@ export class L1ToL2MessageExists extends Instruction {
|
|
|
140
160
|
super();
|
|
141
161
|
}
|
|
142
162
|
|
|
143
|
-
async execute(context: AvmContext): Promise<void> {
|
|
144
|
-
const
|
|
145
|
-
const
|
|
163
|
+
public async execute(context: AvmContext): Promise<void> {
|
|
164
|
+
const memoryOperations = { reads: 2, writes: 1, indirect: this.indirect };
|
|
165
|
+
const memory = context.machineState.memory.track(this.type);
|
|
166
|
+
context.machineState.consumeGas(this.gasCost(memoryOperations));
|
|
167
|
+
|
|
168
|
+
const msgHash = memory.get(this.msgHashOffset).toFr();
|
|
169
|
+
const msgLeafIndex = memory.get(this.msgLeafIndexOffset).toFr();
|
|
146
170
|
const exists = await context.persistableState.checkL1ToL2MessageExists(msgHash, msgLeafIndex);
|
|
147
|
-
|
|
171
|
+
memory.set(this.existsOffset, exists ? new Uint8(1) : new Uint8(0));
|
|
148
172
|
|
|
173
|
+
memory.assert(memoryOperations);
|
|
149
174
|
context.machineState.incrementPc();
|
|
150
175
|
}
|
|
151
176
|
}
|
|
@@ -171,21 +196,26 @@ export class EmitUnencryptedLog extends Instruction {
|
|
|
171
196
|
super();
|
|
172
197
|
}
|
|
173
198
|
|
|
174
|
-
async execute(context: AvmContext): Promise<void> {
|
|
199
|
+
public async execute(context: AvmContext): Promise<void> {
|
|
175
200
|
if (context.environment.isStaticCall) {
|
|
176
201
|
throw new StaticCallStorageAlterError();
|
|
177
202
|
}
|
|
178
203
|
|
|
204
|
+
const memoryOperations = { reads: 1 + this.logSize, indirect: this.indirect };
|
|
205
|
+
const memory = context.machineState.memory.track(this.type);
|
|
206
|
+
context.machineState.consumeGas(this.gasCost(memoryOperations));
|
|
207
|
+
|
|
179
208
|
const [eventSelectorOffset, logOffset] = Addressing.fromWire(this.indirect).resolve(
|
|
180
209
|
[this.eventSelectorOffset, this.logOffset],
|
|
181
|
-
|
|
210
|
+
memory,
|
|
182
211
|
);
|
|
183
212
|
|
|
184
213
|
const contractAddress = context.environment.address;
|
|
185
|
-
const event =
|
|
186
|
-
const log =
|
|
214
|
+
const event = memory.get(eventSelectorOffset).toFr();
|
|
215
|
+
const log = memory.getSlice(logOffset, this.logSize).map(f => f.toFr());
|
|
187
216
|
context.persistableState.writeLog(contractAddress, event, log);
|
|
188
217
|
|
|
218
|
+
memory.assert(memoryOperations);
|
|
189
219
|
context.machineState.incrementPc();
|
|
190
220
|
}
|
|
191
221
|
}
|
|
@@ -200,15 +230,20 @@ export class SendL2ToL1Message extends Instruction {
|
|
|
200
230
|
super();
|
|
201
231
|
}
|
|
202
232
|
|
|
203
|
-
async execute(context: AvmContext): Promise<void> {
|
|
233
|
+
public async execute(context: AvmContext): Promise<void> {
|
|
204
234
|
if (context.environment.isStaticCall) {
|
|
205
235
|
throw new StaticCallStorageAlterError();
|
|
206
236
|
}
|
|
207
237
|
|
|
208
|
-
const
|
|
209
|
-
const
|
|
238
|
+
const memoryOperations = { reads: 2, indirect: this.indirect };
|
|
239
|
+
const memory = context.machineState.memory.track(this.type);
|
|
240
|
+
context.machineState.consumeGas(this.gasCost(memoryOperations));
|
|
241
|
+
|
|
242
|
+
const recipient = memory.get(this.recipientOffset).toFr();
|
|
243
|
+
const content = memory.get(this.contentOffset).toFr();
|
|
210
244
|
context.persistableState.writeL1Message(recipient, content);
|
|
211
245
|
|
|
246
|
+
memory.assert(memoryOperations);
|
|
212
247
|
context.machineState.incrementPc();
|
|
213
248
|
}
|
|
214
249
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { strict as assert } from 'assert';
|
|
2
2
|
|
|
3
|
-
import { type
|
|
3
|
+
import { type TaggedMemoryInterface } from '../avm_memory_types.js';
|
|
4
4
|
|
|
5
5
|
export enum AddressingMode {
|
|
6
6
|
DIRECT,
|
|
@@ -12,7 +12,7 @@ export enum AddressingMode {
|
|
|
12
12
|
export class Addressing {
|
|
13
13
|
public constructor(
|
|
14
14
|
/** The addressing mode for each operand. The length of this array is the number of operands of the instruction. */
|
|
15
|
-
|
|
15
|
+
private readonly modePerOperand: AddressingMode[],
|
|
16
16
|
) {
|
|
17
17
|
assert(modePerOperand.length <= 8, 'At most 8 operands are supported');
|
|
18
18
|
}
|
|
@@ -39,13 +39,18 @@ export class Addressing {
|
|
|
39
39
|
return wire;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/** Returns how many operands use the given addressing mode. */
|
|
43
|
+
public count(mode: AddressingMode): number {
|
|
44
|
+
return this.modePerOperand.filter(m => m === mode).length;
|
|
45
|
+
}
|
|
46
|
+
|
|
42
47
|
/**
|
|
43
48
|
* Resolves the offsets using the addressing mode.
|
|
44
49
|
* @param offsets The offsets to resolve.
|
|
45
50
|
* @param mem The memory to use for resolution.
|
|
46
51
|
* @returns The resolved offsets. The length of the returned array is the same as the length of the input array.
|
|
47
52
|
*/
|
|
48
|
-
public resolve(offsets: number[], mem:
|
|
53
|
+
public resolve(offsets: number[], mem: TaggedMemoryInterface): number[] {
|
|
49
54
|
assert(offsets.length <= this.modePerOperand.length);
|
|
50
55
|
const resolved = new Array(offsets.length);
|
|
51
56
|
for (const [i, offset] of offsets.entries()) {
|