@aztec/simulator 0.42.0 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/acvm/oracle/oracle.d.ts +5 -2
- package/dest/acvm/oracle/oracle.d.ts.map +1 -1
- package/dest/acvm/oracle/oracle.js +25 -8
- package/dest/acvm/oracle/typed_oracle.d.ts +7 -4
- package/dest/acvm/oracle/typed_oracle.d.ts.map +1 -1
- package/dest/acvm/oracle/typed_oracle.js +14 -5
- package/dest/avm/avm_execution_environment.d.ts +2 -0
- package/dest/avm/avm_execution_environment.d.ts.map +1 -1
- package/dest/avm/avm_execution_environment.js +9 -4
- package/dest/avm/avm_gas.d.ts.map +1 -1
- package/dest/avm/avm_gas.js +3 -1
- package/dest/avm/avm_memory_types.d.ts.map +1 -1
- package/dest/avm/avm_memory_types.js +2 -4
- package/dest/avm/avm_simulator.d.ts.map +1 -1
- package/dest/avm/avm_simulator.js +2 -3
- package/dest/avm/fixtures/index.d.ts +10 -3
- package/dest/avm/fixtures/index.d.ts.map +1 -1
- package/dest/avm/fixtures/index.js +9 -11
- package/dest/avm/journal/journal.d.ts +57 -66
- package/dest/avm/journal/journal.d.ts.map +1 -1
- package/dest/avm/journal/journal.js +97 -131
- package/dest/avm/journal/nullifiers.d.ts +21 -8
- package/dest/avm/journal/nullifiers.d.ts.map +1 -1
- package/dest/avm/journal/nullifiers.js +26 -8
- package/dest/avm/journal/public_storage.d.ts +4 -0
- package/dest/avm/journal/public_storage.d.ts.map +1 -1
- package/dest/avm/journal/public_storage.js +10 -1
- package/dest/avm/opcodes/accrued_substate.d.ts +2 -2
- package/dest/avm/opcodes/accrued_substate.d.ts.map +1 -1
- package/dest/avm/opcodes/accrued_substate.js +39 -24
- package/dest/avm/opcodes/arithmetic.d.ts.map +1 -1
- package/dest/avm/opcodes/arithmetic.js +12 -9
- package/dest/avm/opcodes/bitwise.d.ts.map +1 -1
- package/dest/avm/opcodes/bitwise.js +11 -8
- package/dest/avm/opcodes/comparators.d.ts.map +1 -1
- package/dest/avm/opcodes/comparators.js +7 -5
- package/dest/avm/opcodes/contract.d.ts.map +1 -1
- package/dest/avm/opcodes/contract.js +20 -24
- package/dest/avm/opcodes/control_flow.d.ts.map +1 -1
- package/dest/avm/opcodes/control_flow.js +4 -2
- package/dest/avm/opcodes/ec_add.d.ts +19 -0
- package/dest/avm/opcodes/ec_add.d.ts.map +1 -0
- package/dest/avm/opcodes/ec_add.js +78 -0
- package/dest/avm/opcodes/external_calls.d.ts.map +1 -1
- package/dest/avm/opcodes/external_calls.js +19 -29
- package/dest/avm/opcodes/hashing.d.ts.map +1 -1
- package/dest/avm/opcodes/hashing.js +10 -2
- package/dest/avm/opcodes/instruction_impl.d.ts.map +1 -1
- package/dest/avm/opcodes/instruction_impl.js +4 -2
- package/dest/avm/opcodes/memory.d.ts +1 -1
- package/dest/avm/opcodes/memory.d.ts.map +1 -1
- package/dest/avm/opcodes/memory.js +14 -12
- package/dest/avm/opcodes/multi_scalar_mul.d.ts +16 -0
- package/dest/avm/opcodes/multi_scalar_mul.d.ts.map +1 -0
- package/dest/avm/opcodes/multi_scalar_mul.js +95 -0
- package/dest/avm/opcodes/storage.d.ts +1 -1
- package/dest/avm/opcodes/storage.d.ts.map +1 -1
- package/dest/avm/opcodes/storage.js +11 -8
- package/dest/avm/serialization/bytecode_serialization.d.ts.map +1 -1
- package/dest/avm/serialization/bytecode_serialization.js +5 -1
- package/dest/avm/serialization/instruction_serialization.d.ts +3 -1
- package/dest/avm/serialization/instruction_serialization.d.ts.map +1 -1
- package/dest/avm/serialization/instruction_serialization.js +4 -2
- package/dest/avm/test_utils.d.ts +14 -0
- package/dest/avm/test_utils.d.ts.map +1 -0
- package/dest/avm/test_utils.js +36 -0
- package/dest/client/client_execution_context.d.ts +17 -5
- package/dest/client/client_execution_context.d.ts.map +1 -1
- package/dest/client/client_execution_context.js +32 -18
- package/dest/client/execution_note_cache.d.ts.map +1 -1
- package/dest/client/execution_note_cache.js +1 -1
- package/dest/client/execution_result.d.ts +2 -1
- package/dest/client/execution_result.d.ts.map +1 -1
- package/dest/client/execution_result.js +1 -1
- package/dest/client/index.d.ts +2 -0
- package/dest/client/index.d.ts.map +1 -1
- package/dest/client/index.js +3 -1
- package/dest/client/simulator.d.ts +4 -3
- package/dest/client/simulator.d.ts.map +1 -1
- package/dest/client/simulator.js +17 -9
- package/dest/client/view_data_oracle.d.ts +2 -0
- package/dest/client/view_data_oracle.d.ts.map +1 -1
- package/dest/client/view_data_oracle.js +7 -1
- package/dest/mocks/fixtures.d.ts.map +1 -1
- package/dest/mocks/fixtures.js +3 -2
- package/dest/public/abstract_phase_manager.d.ts +11 -11
- package/dest/public/abstract_phase_manager.d.ts.map +1 -1
- package/dest/public/abstract_phase_manager.js +84 -59
- package/dest/public/app_logic_phase_manager.d.ts +3 -3
- package/dest/public/app_logic_phase_manager.d.ts.map +1 -1
- package/dest/public/app_logic_phase_manager.js +4 -3
- package/dest/public/db_interfaces.d.ts +1 -0
- package/dest/public/db_interfaces.d.ts.map +1 -1
- package/dest/public/execution.d.ts +28 -40
- package/dest/public/execution.d.ts.map +1 -1
- package/dest/public/execution.js +1 -51
- package/dest/public/executor.d.ts +9 -4
- package/dest/public/executor.d.ts.map +1 -1
- package/dest/public/executor.js +38 -27
- package/dest/public/hints_builder.d.ts +1 -1
- package/dest/public/index.d.ts +6 -6
- package/dest/public/index.d.ts.map +1 -1
- package/dest/public/index.js +7 -7
- package/dest/public/phase_manager_factory.d.ts +3 -3
- package/dest/public/phase_manager_factory.d.ts.map +1 -1
- package/dest/public/phase_manager_factory.js +5 -5
- package/dest/public/public_db_sources.d.ts +3 -1
- package/dest/public/public_db_sources.d.ts.map +1 -1
- package/dest/public/public_db_sources.js +54 -8
- package/dest/public/public_processor.d.ts +5 -2
- package/dest/public/public_processor.d.ts.map +1 -1
- package/dest/public/public_processor.js +143 -125
- package/dest/public/setup_phase_manager.d.ts +3 -3
- package/dest/public/setup_phase_manager.d.ts.map +1 -1
- package/dest/public/setup_phase_manager.js +3 -3
- package/dest/public/side_effect_trace.d.ts +86 -0
- package/dest/public/side_effect_trace.d.ts.map +1 -0
- package/dest/public/side_effect_trace.js +222 -0
- package/dest/public/side_effect_trace_interface.d.ts +36 -0
- package/dest/public/side_effect_trace_interface.d.ts.map +1 -0
- package/dest/public/side_effect_trace_interface.js +2 -0
- package/dest/public/tail_phase_manager.d.ts +3 -3
- package/dest/public/tail_phase_manager.d.ts.map +1 -1
- package/dest/public/tail_phase_manager.js +3 -3
- package/dest/public/teardown_phase_manager.d.ts +3 -3
- package/dest/public/teardown_phase_manager.d.ts.map +1 -1
- package/dest/public/teardown_phase_manager.js +4 -3
- package/dest/public/transitional_adaptors.d.ts +2 -6
- package/dest/public/transitional_adaptors.d.ts.map +1 -1
- package/dest/public/transitional_adaptors.js +1 -43
- package/package.json +18 -9
- package/src/acvm/oracle/oracle.ts +46 -9
- package/src/acvm/oracle/typed_oracle.ts +37 -7
- package/src/avm/avm_execution_environment.ts +10 -3
- package/src/avm/avm_gas.ts +2 -0
- package/src/avm/avm_memory_types.ts +1 -3
- package/src/avm/avm_simulator.ts +2 -3
- package/src/avm/fixtures/index.ts +19 -14
- package/src/avm/journal/journal.ts +127 -231
- package/src/avm/journal/nullifiers.ts +30 -13
- package/src/avm/journal/public_storage.ts +10 -0
- package/src/avm/opcodes/accrued_substate.ts +60 -23
- package/src/avm/opcodes/arithmetic.ts +17 -8
- package/src/avm/opcodes/bitwise.ts +13 -8
- package/src/avm/opcodes/comparators.ts +9 -4
- package/src/avm/opcodes/contract.ts +22 -26
- package/src/avm/opcodes/control_flow.ts +3 -1
- package/src/avm/opcodes/ec_add.ts +92 -0
- package/src/avm/opcodes/external_calls.ts +20 -36
- package/src/avm/opcodes/hashing.ts +11 -1
- package/src/avm/opcodes/instruction_impl.ts +4 -1
- package/src/avm/opcodes/memory.ts +18 -11
- package/src/avm/opcodes/multi_scalar_mul.ts +114 -0
- package/src/avm/opcodes/storage.ts +10 -10
- package/src/avm/serialization/bytecode_serialization.ts +4 -0
- package/src/avm/serialization/instruction_serialization.ts +2 -0
- package/src/avm/test_utils.ts +53 -0
- package/src/client/client_execution_context.ts +55 -21
- package/src/client/execution_note_cache.ts +0 -1
- package/src/client/execution_result.ts +2 -1
- package/src/client/index.ts +2 -0
- package/src/client/simulator.ts +26 -10
- package/src/client/view_data_oracle.ts +8 -0
- package/src/mocks/fixtures.ts +2 -1
- package/src/public/abstract_phase_manager.ts +99 -70
- package/src/public/app_logic_phase_manager.ts +3 -2
- package/src/public/db_interfaces.ts +2 -0
- package/src/public/execution.ts +35 -94
- package/src/public/executor.ts +56 -40
- package/src/public/index.ts +6 -12
- package/src/public/phase_manager_factory.ts +6 -6
- package/src/public/public_db_sources.ts +62 -7
- package/src/public/public_processor.ts +15 -9
- package/src/public/setup_phase_manager.ts +2 -2
- package/src/public/side_effect_trace.ts +323 -0
- package/src/public/side_effect_trace_interface.ts +41 -0
- package/src/public/tail_phase_manager.ts +2 -2
- package/src/public/teardown_phase_manager.ts +3 -2
- package/src/public/transitional_adaptors.ts +2 -60
- package/dest/avm/journal/trace.d.ts +0 -33
- package/dest/avm/journal/trace.d.ts.map +0 -1
- package/dest/avm/journal/trace.js +0 -151
- package/dest/avm/journal/trace_types.d.ts +0 -51
- package/dest/avm/journal/trace_types.d.ts.map +0 -1
- package/dest/avm/journal/trace_types.js +0 -6
- package/dest/public/utils.d.ts +0 -8
- package/dest/public/utils.d.ts.map +0 -1
- package/dest/public/utils.js +0 -38
- package/src/avm/journal/trace.ts +0 -184
- package/src/avm/journal/trace_types.ts +0 -88
- package/src/public/utils.ts +0 -39
|
@@ -114,12 +114,17 @@ export class CMov extends Instruction {
|
|
|
114
114
|
const memory = context.machineState.memory.track(this.type);
|
|
115
115
|
context.machineState.consumeGas(this.gasCost(memoryOperations));
|
|
116
116
|
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
const [aOffset, bOffset, condOffset, dstOffset] = Addressing.fromWire(this.indirect).resolve(
|
|
118
|
+
[this.aOffset, this.bOffset, this.condOffset, this.dstOffset],
|
|
119
|
+
memory,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const a = memory.get(aOffset);
|
|
123
|
+
const b = memory.get(bOffset);
|
|
124
|
+
const cond = memory.get(condOffset);
|
|
120
125
|
|
|
121
126
|
// TODO: reconsider toBigInt() here
|
|
122
|
-
memory.set(
|
|
127
|
+
memory.set(dstOffset, cond.toBigInt() > 0 ? a : b);
|
|
123
128
|
|
|
124
129
|
memory.assert(memoryOperations);
|
|
125
130
|
context.machineState.incrementPc();
|
|
@@ -130,8 +135,8 @@ export class Cast extends TwoOperandInstruction {
|
|
|
130
135
|
static readonly type: string = 'CAST';
|
|
131
136
|
static readonly opcode = Opcode.CAST;
|
|
132
137
|
|
|
133
|
-
constructor(indirect: number, dstTag: number,
|
|
134
|
-
super(indirect, dstTag,
|
|
138
|
+
constructor(indirect: number, dstTag: number, srcOffset: number, dstOffset: number) {
|
|
139
|
+
super(indirect, dstTag, srcOffset, dstOffset);
|
|
135
140
|
}
|
|
136
141
|
|
|
137
142
|
public async execute(context: AvmContext): Promise<void> {
|
|
@@ -139,13 +144,14 @@ export class Cast extends TwoOperandInstruction {
|
|
|
139
144
|
const memory = context.machineState.memory.track(this.type);
|
|
140
145
|
context.machineState.consumeGas(this.gasCost(memoryOperations));
|
|
141
146
|
|
|
142
|
-
const
|
|
147
|
+
const [srcOffset, dstOffset] = Addressing.fromWire(this.indirect).resolve([this.aOffset, this.dstOffset], memory);
|
|
148
|
+
|
|
149
|
+
const a = memory.get(srcOffset);
|
|
143
150
|
|
|
144
|
-
// TODO: consider not using toBigInt()
|
|
145
151
|
const casted =
|
|
146
152
|
this.inTag == TypeTag.FIELD ? new Field(a.toBigInt()) : TaggedMemory.integralFromTag(a.toBigInt(), this.inTag);
|
|
147
153
|
|
|
148
|
-
memory.set(
|
|
154
|
+
memory.set(dstOffset, casted);
|
|
149
155
|
|
|
150
156
|
memory.assert(memoryOperations);
|
|
151
157
|
context.machineState.incrementPc();
|
|
@@ -204,10 +210,11 @@ export class CalldataCopy extends Instruction {
|
|
|
204
210
|
const memory = context.machineState.memory.track(this.type);
|
|
205
211
|
context.machineState.consumeGas(this.gasCost(memoryOperations));
|
|
206
212
|
|
|
207
|
-
|
|
213
|
+
// We don't need to check tags here because: (1) the calldata is NOT in memory, and (2) we are the ones writing to destination.
|
|
214
|
+
const [cdOffset, dstOffset] = Addressing.fromWire(this.indirect).resolve([this.cdOffset, this.dstOffset], memory);
|
|
208
215
|
|
|
209
216
|
const transformedData = context.environment.calldata
|
|
210
|
-
.slice(
|
|
217
|
+
.slice(cdOffset, cdOffset + this.copySize)
|
|
211
218
|
.map(f => new Field(f));
|
|
212
219
|
|
|
213
220
|
memory.setSlice(dstOffset, transformedData);
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Fq, Point } from '@aztec/circuits.js';
|
|
2
|
+
import { Grumpkin } from '@aztec/circuits.js/barretenberg';
|
|
3
|
+
|
|
4
|
+
import { strict as assert } from 'assert';
|
|
5
|
+
|
|
6
|
+
import { type AvmContext } from '../avm_context.js';
|
|
7
|
+
import { Field, TypeTag } from '../avm_memory_types.js';
|
|
8
|
+
import { InstructionExecutionError } from '../errors.js';
|
|
9
|
+
import { Opcode, OperandType } from '../serialization/instruction_serialization.js';
|
|
10
|
+
import { Addressing } from './addressing_mode.js';
|
|
11
|
+
import { Instruction } from './instruction.js';
|
|
12
|
+
|
|
13
|
+
export class MultiScalarMul extends Instruction {
|
|
14
|
+
static type: string = 'MultiScalarMul';
|
|
15
|
+
static readonly opcode: Opcode = Opcode.MSM;
|
|
16
|
+
|
|
17
|
+
// Informs (de)serialization. See Instruction.deserialize.
|
|
18
|
+
static readonly wireFormat: OperandType[] = [
|
|
19
|
+
OperandType.UINT8 /* opcode */,
|
|
20
|
+
OperandType.UINT8 /* indirect */,
|
|
21
|
+
OperandType.UINT32 /* points vector offset */,
|
|
22
|
+
OperandType.UINT32 /* scalars vector offset */,
|
|
23
|
+
OperandType.UINT32 /* output offset (fixed triplet) */,
|
|
24
|
+
OperandType.UINT32 /* points length offset */,
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
private indirect: number,
|
|
29
|
+
private pointsOffset: number,
|
|
30
|
+
private scalarsOffset: number,
|
|
31
|
+
private outputOffset: number,
|
|
32
|
+
private pointsLengthOffset: number,
|
|
33
|
+
) {
|
|
34
|
+
super();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public async execute(context: AvmContext): Promise<void> {
|
|
38
|
+
const memory = context.machineState.memory.track(this.type);
|
|
39
|
+
// Resolve indirects
|
|
40
|
+
const [pointsOffset, scalarsOffset, outputOffset] = Addressing.fromWire(this.indirect).resolve(
|
|
41
|
+
[this.pointsOffset, this.scalarsOffset, this.outputOffset],
|
|
42
|
+
memory,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// Length of the points vector should be U32
|
|
46
|
+
memory.checkTag(TypeTag.UINT32, this.pointsLengthOffset);
|
|
47
|
+
// Get the size of the unrolled (x, y , inf) points vector
|
|
48
|
+
const pointsReadLength = memory.get(this.pointsLengthOffset).toNumber();
|
|
49
|
+
assert(pointsReadLength % 3 === 0, 'Points vector offset should be a multiple of 3');
|
|
50
|
+
// Divide by 3 since each point is represented as a triplet to get the number of points
|
|
51
|
+
const numPoints = pointsReadLength / 3;
|
|
52
|
+
// The tag for each triplet will be (Field, Field, Uint8)
|
|
53
|
+
for (let i = 0; i < numPoints; i++) {
|
|
54
|
+
const offset = pointsOffset + i * 3;
|
|
55
|
+
// Check (Field, Field)
|
|
56
|
+
memory.checkTagsRange(TypeTag.FIELD, offset, 2);
|
|
57
|
+
// Check Uint8 (inf flag)
|
|
58
|
+
memory.checkTag(TypeTag.UINT8, offset + 2);
|
|
59
|
+
}
|
|
60
|
+
// Get the unrolled (x, y, inf) representing the points
|
|
61
|
+
const pointsVector = memory.getSlice(pointsOffset, pointsReadLength);
|
|
62
|
+
|
|
63
|
+
// The size of the scalars vector is twice the NUMBER of points because of the scalar limb decomposition
|
|
64
|
+
const scalarReadLength = numPoints * 2;
|
|
65
|
+
// Consume gas prior to performing work
|
|
66
|
+
const memoryOperations = {
|
|
67
|
+
reads: 1 + pointsReadLength + scalarReadLength /* points and scalars */,
|
|
68
|
+
writes: 3 /* output triplet */,
|
|
69
|
+
indirect: this.indirect,
|
|
70
|
+
};
|
|
71
|
+
context.machineState.consumeGas(this.gasCost(memoryOperations));
|
|
72
|
+
// Get the unrolled scalar (lo & hi) representing the scalars
|
|
73
|
+
const scalarsVector = memory.getSlice(scalarsOffset, scalarReadLength);
|
|
74
|
+
memory.checkTagsRange(TypeTag.FIELD, scalarsOffset, scalarReadLength);
|
|
75
|
+
|
|
76
|
+
// Now we need to reconstruct the points and scalars into something we can operate on.
|
|
77
|
+
const grumpkinPoints: Point[] = [];
|
|
78
|
+
for (let i = 0; i < numPoints; i++) {
|
|
79
|
+
const p: Point = new Point(pointsVector[3 * i].toFr(), pointsVector[3 * i + 1].toFr());
|
|
80
|
+
// Include this later when we have a standard for representing infinity
|
|
81
|
+
// const isInf = pointsVector[i + 2].toBoolean();
|
|
82
|
+
|
|
83
|
+
if (!p.isOnGrumpkin()) {
|
|
84
|
+
throw new InstructionExecutionError(`Point ${p.toString()} is not on the curve.`);
|
|
85
|
+
}
|
|
86
|
+
grumpkinPoints.push(p);
|
|
87
|
+
}
|
|
88
|
+
// The scalars are read from memory as Fr elements, which are limbs of Fq elements
|
|
89
|
+
// So we need to reconstruct them before performing the scalar multiplications
|
|
90
|
+
const scalarFqVector: Fq[] = [];
|
|
91
|
+
for (let i = 0; i < numPoints; i++) {
|
|
92
|
+
const scalarLo = scalarsVector[2 * i].toFr();
|
|
93
|
+
const scalarHi = scalarsVector[2 * i + 1].toFr();
|
|
94
|
+
const fqScalar = Fq.fromHighLow(scalarHi, scalarLo);
|
|
95
|
+
scalarFqVector.push(fqScalar);
|
|
96
|
+
}
|
|
97
|
+
// TODO: Is there an efficient MSM implementation in ts that we can replace this by?
|
|
98
|
+
const grumpkin = new Grumpkin();
|
|
99
|
+
// Zip the points and scalars into pairs
|
|
100
|
+
const [firstBaseScalarPair, ...rest]: Array<[Point, Fq]> = grumpkinPoints.map((p, idx) => [p, scalarFqVector[idx]]);
|
|
101
|
+
// Fold the points and scalars into a single point
|
|
102
|
+
// We have to ensure get the first point, since the identity element (point at infinity) isn't quite working in ts
|
|
103
|
+
const outputPoint = rest.reduce(
|
|
104
|
+
(acc, curr) => grumpkin.add(acc, grumpkin.mul(curr[0], curr[1])),
|
|
105
|
+
grumpkin.mul(firstBaseScalarPair[0], firstBaseScalarPair[1]),
|
|
106
|
+
);
|
|
107
|
+
const output = outputPoint.toFieldsWithInf().map(f => new Field(f));
|
|
108
|
+
|
|
109
|
+
memory.setSlice(outputOffset, output);
|
|
110
|
+
|
|
111
|
+
memory.assert(memoryOperations);
|
|
112
|
+
context.machineState.incrementPc();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Fr } from '@aztec/foundation/fields';
|
|
2
2
|
|
|
3
3
|
import type { AvmContext } from '../avm_context.js';
|
|
4
|
-
import { Field } from '../avm_memory_types.js';
|
|
4
|
+
import { Field, TypeTag } from '../avm_memory_types.js';
|
|
5
5
|
import { StaticCallAlterationError } from '../errors.js';
|
|
6
6
|
import { Opcode, OperandType } from '../serialization/instruction_serialization.js';
|
|
7
7
|
import { Addressing } from './addressing_mode.js';
|
|
@@ -31,8 +31,8 @@ export class SStore extends BaseStorageInstruction {
|
|
|
31
31
|
static readonly type: string = 'SSTORE';
|
|
32
32
|
static readonly opcode = Opcode.SSTORE;
|
|
33
33
|
|
|
34
|
-
constructor(indirect: number, srcOffset: number, /*temporary*/
|
|
35
|
-
super(indirect, srcOffset,
|
|
34
|
+
constructor(indirect: number, srcOffset: number, /*temporary*/ size: number, slotOffset: number) {
|
|
35
|
+
super(indirect, srcOffset, size, slotOffset);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
public async execute(context: AvmContext): Promise<void> {
|
|
@@ -45,6 +45,8 @@ export class SStore extends BaseStorageInstruction {
|
|
|
45
45
|
context.machineState.consumeGas(this.gasCost(memoryOperations));
|
|
46
46
|
|
|
47
47
|
const [srcOffset, slotOffset] = Addressing.fromWire(this.indirect).resolve([this.aOffset, this.bOffset], memory);
|
|
48
|
+
memory.checkTag(TypeTag.FIELD, slotOffset);
|
|
49
|
+
memory.checkTagsRange(TypeTag.FIELD, srcOffset, this.size);
|
|
48
50
|
|
|
49
51
|
const slot = memory.get(slotOffset).toFr();
|
|
50
52
|
const data = memory.getSlice(srcOffset, this.size).map(field => field.toFr());
|
|
@@ -72,21 +74,19 @@ export class SLoad extends BaseStorageInstruction {
|
|
|
72
74
|
const memory = context.machineState.memory.track(this.type);
|
|
73
75
|
context.machineState.consumeGas(this.gasCost(memoryOperations));
|
|
74
76
|
|
|
75
|
-
const [
|
|
76
|
-
|
|
77
|
-
memory,
|
|
78
|
-
);
|
|
77
|
+
const [slotOffset, dstOffset] = Addressing.fromWire(this.indirect).resolve([this.aOffset, this.bOffset], memory);
|
|
78
|
+
memory.checkTag(TypeTag.FIELD, slotOffset);
|
|
79
79
|
|
|
80
|
-
const slot = memory.get(
|
|
80
|
+
const slot = memory.get(slotOffset);
|
|
81
81
|
|
|
82
82
|
// Write each read value from storage into memory
|
|
83
|
-
for (let i = 0; i < size; i++) {
|
|
83
|
+
for (let i = 0; i < this.size; i++) {
|
|
84
84
|
const data: Fr = await context.persistableState.readStorage(
|
|
85
85
|
context.environment.storageAddress,
|
|
86
86
|
new Fr(slot.toBigInt() + BigInt(i)),
|
|
87
87
|
);
|
|
88
88
|
|
|
89
|
-
memory.set(
|
|
89
|
+
memory.set(dstOffset + i, new Field(data));
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
context.machineState.incrementPc();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DAGasLeft, L2GasLeft } from '../opcodes/context_getters.js';
|
|
2
|
+
import { EcAdd } from '../opcodes/ec_add.js';
|
|
2
3
|
import { Keccak, Pedersen, Poseidon2, Sha256 } from '../opcodes/hashing.js';
|
|
3
4
|
import type { Instruction } from '../opcodes/index.js';
|
|
4
5
|
import {
|
|
@@ -52,6 +53,7 @@ import {
|
|
|
52
53
|
Version,
|
|
53
54
|
Xor,
|
|
54
55
|
} from '../opcodes/index.js';
|
|
56
|
+
import { MultiScalarMul } from '../opcodes/multi_scalar_mul.js';
|
|
55
57
|
import { BufferCursor } from './buffer_cursor.js';
|
|
56
58
|
import { Opcode } from './instruction_serialization.js';
|
|
57
59
|
|
|
@@ -137,10 +139,12 @@ const INSTRUCTION_SET = () =>
|
|
|
137
139
|
[DebugLog.opcode, DebugLog],
|
|
138
140
|
|
|
139
141
|
// Gadgets
|
|
142
|
+
[EcAdd.opcode, EcAdd],
|
|
140
143
|
[Keccak.opcode, Keccak],
|
|
141
144
|
[Poseidon2.opcode, Poseidon2],
|
|
142
145
|
[Sha256.opcode, Sha256],
|
|
143
146
|
[Pedersen.opcode, Pedersen],
|
|
147
|
+
[MultiScalarMul.opcode, MultiScalarMul],
|
|
144
148
|
// Conversions
|
|
145
149
|
[ToRadixLE.opcode, ToRadixLE],
|
|
146
150
|
]);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Fr } from '@aztec/circuits.js';
|
|
2
|
+
import { type ContractInstanceWithAddress } from '@aztec/types/contracts';
|
|
3
|
+
|
|
4
|
+
import { type jest } from '@jest/globals';
|
|
5
|
+
import { mock } from 'jest-mock-extended';
|
|
6
|
+
|
|
7
|
+
import { type CommitmentsDB, type PublicContractsDB, type PublicStateDB } from '../public/db_interfaces.js';
|
|
8
|
+
import { type PublicSideEffectTraceInterface } from '../public/side_effect_trace_interface.js';
|
|
9
|
+
import { type HostStorage } from './journal/host_storage.js';
|
|
10
|
+
|
|
11
|
+
export function mockGetBytecode(hs: HostStorage, bytecode: Buffer) {
|
|
12
|
+
(hs as jest.Mocked<HostStorage>).contractsDb.getBytecode.mockResolvedValue(bytecode);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function mockTraceFork(trace: PublicSideEffectTraceInterface, nestedTrace?: PublicSideEffectTraceInterface) {
|
|
16
|
+
(trace as jest.Mocked<PublicSideEffectTraceInterface>).fork.mockReturnValue(
|
|
17
|
+
nestedTrace ?? mock<PublicSideEffectTraceInterface>(),
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function mockStorageRead(hs: HostStorage, value: Fr) {
|
|
22
|
+
(hs.publicStateDb as jest.Mocked<PublicStateDB>).storageRead.mockResolvedValue(value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function mockStorageReadWithMap(hs: HostStorage, mockedStorage: Map<bigint, Fr>) {
|
|
26
|
+
(hs.publicStateDb as jest.Mocked<PublicStateDB>).storageRead.mockImplementation((_address, slot) =>
|
|
27
|
+
Promise.resolve(mockedStorage.get(slot.toBigInt()) ?? Fr.ZERO),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function mockNoteHashExists(hs: HostStorage, leafIndex: Fr, _value?: Fr) {
|
|
32
|
+
(hs.commitmentsDb as jest.Mocked<CommitmentsDB>).getCommitmentIndex.mockResolvedValue(leafIndex.toBigInt());
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function mockNullifierExists(hs: HostStorage, leafIndex: Fr, _value?: Fr) {
|
|
36
|
+
(hs.commitmentsDb as jest.Mocked<CommitmentsDB>).getNullifierIndex.mockResolvedValue(leafIndex.toBigInt());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function mockL1ToL2MessageExists(hs: HostStorage, leafIndex: Fr, value: Fr, valueAtOtherIndices?: Fr) {
|
|
40
|
+
(hs.commitmentsDb as jest.Mocked<CommitmentsDB>).getL1ToL2LeafValue.mockImplementation((index: bigint) => {
|
|
41
|
+
if (index == leafIndex.toBigInt()) {
|
|
42
|
+
return Promise.resolve(value);
|
|
43
|
+
} else {
|
|
44
|
+
// any indices other than mockAtLeafIndex will return a different value
|
|
45
|
+
// (or undefined if no value is specified for other indices)
|
|
46
|
+
return Promise.resolve(valueAtOtherIndices!);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function mockGetContractInstance(hs: HostStorage, contractInstance: ContractInstanceWithAddress) {
|
|
52
|
+
(hs.contractsDb as jest.Mocked<PublicContractsDB>).getContractInstance.mockResolvedValue(contractInstance);
|
|
53
|
+
}
|
|
@@ -3,10 +3,12 @@ import {
|
|
|
3
3
|
type AztecNode,
|
|
4
4
|
EncryptedL2Log,
|
|
5
5
|
EncryptedL2NoteLog,
|
|
6
|
+
Event,
|
|
7
|
+
L1EventPayload,
|
|
6
8
|
L1NotePayload,
|
|
7
9
|
Note,
|
|
8
10
|
type NoteStatus,
|
|
9
|
-
|
|
11
|
+
TaggedLog,
|
|
10
12
|
type UnencryptedL2Log,
|
|
11
13
|
} from '@aztec/circuit-types';
|
|
12
14
|
import {
|
|
@@ -19,8 +21,14 @@ import {
|
|
|
19
21
|
type TxContext,
|
|
20
22
|
} from '@aztec/circuits.js';
|
|
21
23
|
import { Aes128 } from '@aztec/circuits.js/barretenberg';
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
+
import { computeUniqueNoteHash, siloNoteHash } from '@aztec/circuits.js/hash';
|
|
25
|
+
import {
|
|
26
|
+
EventSelector,
|
|
27
|
+
type FunctionAbi,
|
|
28
|
+
type FunctionArtifact,
|
|
29
|
+
type NoteSelector,
|
|
30
|
+
countArgumentsSize,
|
|
31
|
+
} from '@aztec/foundation/abi';
|
|
24
32
|
import { AztecAddress } from '@aztec/foundation/aztec-address';
|
|
25
33
|
import { pedersenHash } from '@aztec/foundation/crypto';
|
|
26
34
|
import { Fr, GrumpkinScalar, type Point } from '@aztec/foundation/fields';
|
|
@@ -286,7 +294,7 @@ export class ClientExecutionContext extends ViewDataOracle {
|
|
|
286
294
|
*/
|
|
287
295
|
public override notifyCreatedNote(
|
|
288
296
|
storageSlot: Fr,
|
|
289
|
-
noteTypeId:
|
|
297
|
+
noteTypeId: NoteSelector,
|
|
290
298
|
noteItems: Fr[],
|
|
291
299
|
innerNoteHash: Fr,
|
|
292
300
|
counter: number,
|
|
@@ -330,13 +338,15 @@ export class ClientExecutionContext extends ViewDataOracle {
|
|
|
330
338
|
|
|
331
339
|
/**
|
|
332
340
|
* Emit encrypted data
|
|
333
|
-
* @param
|
|
341
|
+
* @param contractAddress - The contract emitting the encrypted event.
|
|
342
|
+
* @param randomness - A value used to mask the contract address we are siloing with.
|
|
343
|
+
* @param encryptedEvent - The encrypted event data.
|
|
334
344
|
* @param counter - The effects counter.
|
|
335
345
|
*/
|
|
336
|
-
public override
|
|
346
|
+
public override emitEncryptedEventLog(
|
|
337
347
|
contractAddress: AztecAddress,
|
|
338
348
|
randomness: Fr,
|
|
339
|
-
|
|
349
|
+
encryptedEvent: Buffer,
|
|
340
350
|
counter: number,
|
|
341
351
|
) {
|
|
342
352
|
// In some cases, we actually want to reveal the contract address we are siloing with:
|
|
@@ -345,7 +355,7 @@ export class ClientExecutionContext extends ViewDataOracle {
|
|
|
345
355
|
const maskedContractAddress = randomness.isZero()
|
|
346
356
|
? contractAddress.toField()
|
|
347
357
|
: pedersenHash([contractAddress, randomness], 0);
|
|
348
|
-
const encryptedLog = new CountedLog(new EncryptedL2Log(
|
|
358
|
+
const encryptedLog = new CountedLog(new EncryptedL2Log(encryptedEvent, maskedContractAddress), counter);
|
|
349
359
|
this.encryptedLogs.push(encryptedLog);
|
|
350
360
|
}
|
|
351
361
|
|
|
@@ -360,6 +370,34 @@ export class ClientExecutionContext extends ViewDataOracle {
|
|
|
360
370
|
this.noteEncryptedLogs.push(encryptedLog);
|
|
361
371
|
}
|
|
362
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Encrypt an event
|
|
375
|
+
* @param contractAddress - The contract emitting the encrypted event.
|
|
376
|
+
* @param randomness - A value used to mask the contract address we are siloing with.
|
|
377
|
+
* @param eventTypeId - The type ID of the event (function selector).
|
|
378
|
+
* @param ovKeys - The outgoing viewing keys to use to encrypt.
|
|
379
|
+
* @param ivpkM - The master incoming viewing public key.
|
|
380
|
+
* @param preimage - The event preimage.
|
|
381
|
+
*/
|
|
382
|
+
public override computeEncryptedEventLog(
|
|
383
|
+
contractAddress: AztecAddress,
|
|
384
|
+
randomness: Fr,
|
|
385
|
+
eventTypeId: Fr,
|
|
386
|
+
ovKeys: KeyValidationRequest,
|
|
387
|
+
ivpkM: Point,
|
|
388
|
+
preimage: Fr[],
|
|
389
|
+
) {
|
|
390
|
+
const event = new Event(preimage);
|
|
391
|
+
const l1EventPayload = new L1EventPayload(event, contractAddress, randomness, EventSelector.fromField(eventTypeId));
|
|
392
|
+
const taggedEvent = new TaggedLog(l1EventPayload);
|
|
393
|
+
|
|
394
|
+
const ephSk = GrumpkinScalar.random();
|
|
395
|
+
|
|
396
|
+
const recipient = AztecAddress.random();
|
|
397
|
+
|
|
398
|
+
return taggedEvent.encrypt(ephSk, recipient, ivpkM, ovKeys);
|
|
399
|
+
}
|
|
400
|
+
|
|
363
401
|
/**
|
|
364
402
|
* Encrypt a note
|
|
365
403
|
* @param contractAddress - The contract address of the note.
|
|
@@ -369,20 +407,23 @@ export class ClientExecutionContext extends ViewDataOracle {
|
|
|
369
407
|
* @param ivpkM - The master incoming viewing public key.
|
|
370
408
|
* @param preimage - The note preimage.
|
|
371
409
|
*/
|
|
372
|
-
public override
|
|
410
|
+
public override computeEncryptedNoteLog(
|
|
373
411
|
contractAddress: AztecAddress,
|
|
374
412
|
storageSlot: Fr,
|
|
375
|
-
noteTypeId:
|
|
413
|
+
noteTypeId: NoteSelector,
|
|
376
414
|
ovKeys: KeyValidationRequest,
|
|
377
415
|
ivpkM: Point,
|
|
378
416
|
preimage: Fr[],
|
|
379
417
|
) {
|
|
380
418
|
const note = new Note(preimage);
|
|
381
419
|
const l1NotePayload = new L1NotePayload(note, contractAddress, storageSlot, noteTypeId);
|
|
382
|
-
const taggedNote = new
|
|
420
|
+
const taggedNote = new TaggedLog(l1NotePayload);
|
|
383
421
|
|
|
384
422
|
const ephSk = GrumpkinScalar.random();
|
|
385
423
|
|
|
424
|
+
// @todo This should be populated properly.
|
|
425
|
+
// Note that this encryption function SHOULD not be used, but is currently used
|
|
426
|
+
// as oracle for encrypted event logs.
|
|
386
427
|
const recipient = AztecAddress.random();
|
|
387
428
|
|
|
388
429
|
return taggedNote.encrypt(ephSk, recipient, ivpkM, ovKeys);
|
|
@@ -641,20 +682,13 @@ export class ClientExecutionContext extends ViewDataOracle {
|
|
|
641
682
|
* @param numberOfElements - Number of elements to read from the starting storage slot.
|
|
642
683
|
*/
|
|
643
684
|
public override async storageRead(startStorageSlot: Fr, numberOfElements: number): Promise<Fr[]> {
|
|
644
|
-
// TODO(#4320): This is a hack to work around not having directly access to the public data tree but
|
|
645
|
-
// still having access to the witnesses
|
|
646
|
-
const bn = await this.db.getBlockNumber();
|
|
647
|
-
|
|
648
685
|
const values = [];
|
|
649
686
|
for (let i = 0n; i < numberOfElements; i++) {
|
|
650
687
|
const storageSlot = new Fr(startStorageSlot.value + i);
|
|
651
|
-
|
|
652
|
-
const
|
|
653
|
-
if (!witness) {
|
|
654
|
-
throw new Error(`No witness for slot ${storageSlot.toString()}`);
|
|
655
|
-
}
|
|
656
|
-
const value = witness.leafPreimage.value;
|
|
688
|
+
|
|
689
|
+
const value = await this.aztecNode.getPublicStorageAt(this.callContext.storageContractAddress, storageSlot);
|
|
657
690
|
this.log.debug(`Oracle storage read: slot=${storageSlot.toString()} value=${value}`);
|
|
691
|
+
|
|
658
692
|
values.push(value);
|
|
659
693
|
}
|
|
660
694
|
return values;
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type UnencryptedL2Log,
|
|
9
9
|
} from '@aztec/circuit-types';
|
|
10
10
|
import { type IsEmpty, type PrivateCallStackItem, PublicCallRequest, sortByCounter } from '@aztec/circuits.js';
|
|
11
|
+
import { type NoteSelector } from '@aztec/foundation/abi';
|
|
11
12
|
import { type Fr } from '@aztec/foundation/fields';
|
|
12
13
|
|
|
13
14
|
import { type ACVMField } from '../acvm/index.js';
|
|
@@ -21,7 +22,7 @@ export interface NoteAndSlot {
|
|
|
21
22
|
/** The storage slot of the note. */
|
|
22
23
|
storageSlot: Fr;
|
|
23
24
|
/** The note type identifier. */
|
|
24
|
-
noteTypeId:
|
|
25
|
+
noteTypeId: NoteSelector;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export class CountedLog<TLog extends UnencryptedL2Log | EncryptedL2NoteLog | EncryptedL2Log> implements IsEmpty {
|
package/src/client/index.ts
CHANGED
package/src/client/simulator.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type FunctionArtifact,
|
|
6
6
|
FunctionSelector,
|
|
7
7
|
FunctionType,
|
|
8
|
+
type NoteSelector,
|
|
8
9
|
encodeArguments,
|
|
9
10
|
} from '@aztec/foundation/abi';
|
|
10
11
|
import { AztecAddress } from '@aztec/foundation/aztec-address';
|
|
@@ -132,29 +133,31 @@ export class AcirSimulator {
|
|
|
132
133
|
* @param nonce - The nonce of the note hash.
|
|
133
134
|
* @param storageSlot - The storage slot.
|
|
134
135
|
* @param noteTypeId - The note type identifier.
|
|
136
|
+
* @param computeNullifier - A flag indicating whether to compute the nullifier or just return 0.
|
|
135
137
|
* @param note - The note.
|
|
136
138
|
* @returns The nullifier.
|
|
137
139
|
*/
|
|
138
|
-
public async
|
|
140
|
+
public async computeNoteHashAndOptionallyANullifier(
|
|
139
141
|
contractAddress: AztecAddress,
|
|
140
142
|
nonce: Fr,
|
|
141
143
|
storageSlot: Fr,
|
|
142
|
-
noteTypeId:
|
|
144
|
+
noteTypeId: NoteSelector,
|
|
145
|
+
computeNullifier: boolean,
|
|
143
146
|
note: Note,
|
|
144
147
|
) {
|
|
145
148
|
const artifact: FunctionArtifact | undefined = await this.db.getFunctionArtifactByName(
|
|
146
149
|
contractAddress,
|
|
147
|
-
'
|
|
150
|
+
'compute_note_hash_and_optionally_a_nullifier',
|
|
148
151
|
);
|
|
149
152
|
if (!artifact) {
|
|
150
153
|
throw new Error(
|
|
151
|
-
`Mandatory implementation of "
|
|
154
|
+
`Mandatory implementation of "compute_note_hash_and_optionally_a_nullifier" missing in noir contract ${contractAddress.toString()}.`,
|
|
152
155
|
);
|
|
153
156
|
}
|
|
154
157
|
|
|
155
|
-
if (artifact.parameters.length !=
|
|
158
|
+
if (artifact.parameters.length != 6) {
|
|
156
159
|
throw new Error(
|
|
157
|
-
`Expected
|
|
160
|
+
`Expected 6 parameters in mandatory implementation of "compute_note_hash_and_optionally_a_nullifier", but found ${
|
|
158
161
|
artifact.parameters.length
|
|
159
162
|
} in noir contract ${contractAddress.toString()}.`,
|
|
160
163
|
);
|
|
@@ -163,7 +166,7 @@ export class AcirSimulator {
|
|
|
163
166
|
const maxNoteFields = (artifact.parameters[artifact.parameters.length - 1].type as ArrayType).length;
|
|
164
167
|
if (maxNoteFields < note.items.length) {
|
|
165
168
|
throw new Error(
|
|
166
|
-
`The note being processed has ${note.items.length} fields, while "
|
|
169
|
+
`The note being processed has ${note.items.length} fields, while "compute_note_hash_and_optionally_a_nullifier" can only handle a maximum of ${maxNoteFields} fields. Please reduce the number of fields in your note.`,
|
|
167
170
|
);
|
|
168
171
|
}
|
|
169
172
|
|
|
@@ -175,7 +178,14 @@ export class AcirSimulator {
|
|
|
175
178
|
selector: FunctionSelector.empty(),
|
|
176
179
|
type: FunctionType.UNCONSTRAINED,
|
|
177
180
|
isStatic: artifact.isStatic,
|
|
178
|
-
args: encodeArguments(artifact, [
|
|
181
|
+
args: encodeArguments(artifact, [
|
|
182
|
+
contractAddress,
|
|
183
|
+
nonce,
|
|
184
|
+
storageSlot,
|
|
185
|
+
noteTypeId,
|
|
186
|
+
computeNullifier,
|
|
187
|
+
extendedNoteItems,
|
|
188
|
+
]),
|
|
179
189
|
returnTypes: artifact.returnTypes,
|
|
180
190
|
};
|
|
181
191
|
|
|
@@ -201,12 +211,18 @@ export class AcirSimulator {
|
|
|
201
211
|
* @param note - The note.
|
|
202
212
|
* @returns The note hash.
|
|
203
213
|
*/
|
|
204
|
-
public async computeInnerNoteHash(
|
|
205
|
-
|
|
214
|
+
public async computeInnerNoteHash(
|
|
215
|
+
contractAddress: AztecAddress,
|
|
216
|
+
storageSlot: Fr,
|
|
217
|
+
noteTypeId: NoteSelector,
|
|
218
|
+
note: Note,
|
|
219
|
+
) {
|
|
220
|
+
const { innerNoteHash } = await this.computeNoteHashAndOptionallyANullifier(
|
|
206
221
|
contractAddress,
|
|
207
222
|
Fr.ZERO,
|
|
208
223
|
storageSlot,
|
|
209
224
|
noteTypeId,
|
|
225
|
+
false,
|
|
210
226
|
note,
|
|
211
227
|
);
|
|
212
228
|
return innerNoteHash;
|
|
@@ -42,6 +42,14 @@ export class ViewDataOracle extends TypedOracle {
|
|
|
42
42
|
return Promise.resolve(this.contractAddress);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
public override getChainId(): Promise<Fr> {
|
|
46
|
+
return Promise.resolve(this.aztecNode.getChainId().then(id => new Fr(id)));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public override getVersion(): Promise<Fr> {
|
|
50
|
+
return Promise.resolve(this.aztecNode.getVersion().then(v => new Fr(v)));
|
|
51
|
+
}
|
|
52
|
+
|
|
45
53
|
/**
|
|
46
54
|
* Retrieve keys associated with a specific master public key and app address.
|
|
47
55
|
* @param pkMHash - The master public key hash.
|
package/src/mocks/fixtures.ts
CHANGED
|
@@ -143,7 +143,8 @@ export class PublicExecutionResultBuilder {
|
|
|
143
143
|
endGasLeft: Gas.test(),
|
|
144
144
|
transactionFee: Fr.ZERO,
|
|
145
145
|
calldata: [],
|
|
146
|
-
|
|
146
|
+
avmCircuitHints: AvmExecutionHints.empty(),
|
|
147
|
+
functionName: 'unknown',
|
|
147
148
|
...overrides,
|
|
148
149
|
};
|
|
149
150
|
}
|