@aztec/simulator 3.0.0-nightly.20251214 → 3.0.0-nightly.20251217
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/public/avm/avm_memory_types.d.ts +3 -1
- package/dest/public/avm/avm_memory_types.d.ts.map +1 -1
- package/dest/public/avm/avm_memory_types.js +2 -2
- package/dest/public/debug_fn_name.d.ts +1 -1
- package/dest/public/debug_fn_name.d.ts.map +1 -1
- package/dest/public/debug_fn_name.js +10 -3
- package/dest/public/fixtures/custom_bytecode_tester.d.ts +28 -6
- package/dest/public/fixtures/custom_bytecode_tester.d.ts.map +1 -1
- package/dest/public/fixtures/custom_bytecode_tester.js +36 -12
- package/dest/public/fixtures/custom_bytecode_tests.js +9 -9
- package/dest/public/fixtures/index.d.ts +3 -1
- package/dest/public/fixtures/index.d.ts.map +1 -1
- package/dest/public/fixtures/index.js +2 -0
- package/dest/public/fixtures/minimal_public_tx.js +2 -2
- package/dest/public/fixtures/opcode_spammer.d.ts +90 -0
- package/dest/public/fixtures/opcode_spammer.d.ts.map +1 -0
- package/dest/public/fixtures/opcode_spammer.js +1262 -0
- package/dest/public/fixtures/public_tx_simulation_tester.d.ts +2 -2
- package/dest/public/fixtures/public_tx_simulation_tester.d.ts.map +1 -1
- package/dest/public/fixtures/public_tx_simulation_tester.js +19 -7
- package/dest/public/public_tx_simulator/contract_provider_for_cpp.d.ts +1 -1
- package/dest/public/public_tx_simulator/contract_provider_for_cpp.d.ts.map +1 -1
- package/dest/public/public_tx_simulator/contract_provider_for_cpp.js +5 -1
- package/dest/public/public_tx_simulator/cpp_vs_ts_public_tx_simulator.d.ts +1 -1
- package/dest/public/public_tx_simulator/cpp_vs_ts_public_tx_simulator.d.ts.map +1 -1
- package/dest/public/public_tx_simulator/cpp_vs_ts_public_tx_simulator.js +2 -1
- package/package.json +16 -16
- package/src/public/avm/avm_memory_types.ts +2 -2
- package/src/public/debug_fn_name.ts +10 -3
- package/src/public/fixtures/custom_bytecode_tester.ts +53 -19
- package/src/public/fixtures/custom_bytecode_tests.ts +9 -9
- package/src/public/fixtures/index.ts +6 -0
- package/src/public/fixtures/minimal_public_tx.ts +2 -2
- package/src/public/fixtures/opcode_spammer.ts +1235 -0
- package/src/public/fixtures/public_tx_simulation_tester.ts +19 -5
- package/src/public/public_tx_simulator/contract_provider_for_cpp.ts +6 -1
- package/src/public/public_tx_simulator/cpp_vs_ts_public_tx_simulator.ts +2 -1
|
@@ -0,0 +1,1262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opcode Spammer - A minimal, data-driven opcode spammer for AVM gas benchmarking.
|
|
3
|
+
*
|
|
4
|
+
* Design principles:
|
|
5
|
+
* 1. Data over code: Opcode behavior is configuration, not control flow
|
|
6
|
+
* 2. Derive, don't declare: Categories and strategies follow from the data
|
|
7
|
+
* 3. Maximize coverage: Fill bytecode to the limit for accurate gas measurement
|
|
8
|
+
* 4. Smallest wire format: Use _8 variants over _16 to fit more instructions per loop
|
|
9
|
+
* 5. Single file: Everything in one module
|
|
10
|
+
*
|
|
11
|
+
* ## Architecture
|
|
12
|
+
*
|
|
13
|
+
* ```
|
|
14
|
+
* ┌─────────────────────────────────────────────────────────────────┐
|
|
15
|
+
* │ SPAM_CONFIGS │
|
|
16
|
+
* │ Record<Opcode, SpamConfig[]> │
|
|
17
|
+
* │ │
|
|
18
|
+
* │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
19
|
+
* │ │ ADD_8 │ │ POSEIDON2 │ │EMITNULLIFIER│ ... │
|
|
20
|
+
* │ │ [7 configs] │ │ [1 config] │ │ [1 config] │ │
|
|
21
|
+
* │ │ (per type) │ │ │ │ (limit=63) │ │
|
|
22
|
+
* │ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|
23
|
+
* └─────────────────────────────────────────────────────────────────┘
|
|
24
|
+
* │
|
|
25
|
+
* ▼
|
|
26
|
+
* ┌─────────────────────────────────────────────────────────────────┐
|
|
27
|
+
* │ getSpamConfigsPerOpcode() │
|
|
28
|
+
* │ Returns { opcodes, config[] } for test iteration │
|
|
29
|
+
* └─────────────────────────────────────────────────────────────────┘
|
|
30
|
+
* │
|
|
31
|
+
* ▼
|
|
32
|
+
* ┌─────────────────────────────────────────────────────────────────┐
|
|
33
|
+
* │ testOpcodeSpamCase() │
|
|
34
|
+
* │ Routes to appropriate bytecode generator & executes test │
|
|
35
|
+
* │ │
|
|
36
|
+
* │ config.limit === undefined? │
|
|
37
|
+
* │ YES → testStandardOpcodeSpam() │
|
|
38
|
+
* │ NO → testSideEffectOpcodeSpam() │
|
|
39
|
+
* └─────────────────────────────────────────────────────────────────┘
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* ## Two Execution Strategies
|
|
43
|
+
*
|
|
44
|
+
* ### Strategy 1: Standard Opcodes (Gas-Limited)
|
|
45
|
+
*
|
|
46
|
+
* For opcodes without per-TX limits (arithmetic, comparisons, memory ops, etc.), we create a single contract with an infinite loop:
|
|
47
|
+
*
|
|
48
|
+
* ```
|
|
49
|
+
* ┌────────────────────────────────────────────────────────────────┐
|
|
50
|
+
* │ SINGLE CONTRACT │
|
|
51
|
+
* │ │
|
|
52
|
+
* │ ┌──────────────────────────────────────────────────────────┐ │
|
|
53
|
+
* │ │ SETUP PHASE │ │
|
|
54
|
+
* │ │ SET mem[0] = initial_value │ │
|
|
55
|
+
* │ │ SET mem[1] = operand │ │
|
|
56
|
+
* │ │ ... │ │
|
|
57
|
+
* │ └──────────────────────────────────────────────────────────┘ │
|
|
58
|
+
* │ │ │
|
|
59
|
+
* │ ▼ │
|
|
60
|
+
* │ ┌──────────────────────────────────────────────────────────┐ │
|
|
61
|
+
* │ │ LOOP (fills remaining bytecode space) ◄─────┐ │ │
|
|
62
|
+
* │ │ TARGET_OPCODE ─┐ │ │ │
|
|
63
|
+
* │ │ TARGET_OPCODE │ unrolled N times │ │ │
|
|
64
|
+
* │ │ TARGET_OPCODE │ (N = available_bytes / instr_size)│ │ │
|
|
65
|
+
* │ │ ... ─┘ │ │ │
|
|
66
|
+
* │ │ JUMP back ──────────────────────────────────────────┘ │ │
|
|
67
|
+
* │ └──────────────────────────────────────────────────────────┘ │
|
|
68
|
+
* │ │
|
|
69
|
+
* │ Executes until: OUT OF GAS │
|
|
70
|
+
* └────────────────────────────────────────────────────────────────┘
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* **Bytecode Layout:**
|
|
74
|
+
* ```
|
|
75
|
+
* ┌─────────────────────────────────────────────────────────────────┐
|
|
76
|
+
* │ 0x00: SET instructions (setup) │
|
|
77
|
+
* │ ... │
|
|
78
|
+
* │ 0xNN: ┌─── LOOP START ◄──────────────────────────────────────┐ │
|
|
79
|
+
* │ │ TARGET_OPCODE │ │
|
|
80
|
+
* │ │ TARGET_OPCODE (unrolled to fill max bytecode size) │ │
|
|
81
|
+
* │ │ TARGET_OPCODE │ │
|
|
82
|
+
* │ │ ... │ │
|
|
83
|
+
* │ └─► JUMP 0xNN ─────────────────────────────────────────┘ │
|
|
84
|
+
* │ MAX_BYTECODE_BYTES │
|
|
85
|
+
* └─────────────────────────────────────────────────────────────────┘
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* ### Strategy 2: Side-Effect Limited Opcodes (Nested Call Pattern)
|
|
89
|
+
*
|
|
90
|
+
* For opcodes with per-TX limits (EMITNOTEHASH, EMITNULLIFIER, SENDL2TOL1MSG, etc.), we use a two-contract pattern where the inner contract executes side effects up to the limit, then REVERTs to discard them:
|
|
91
|
+
*
|
|
92
|
+
* ```
|
|
93
|
+
* ┌─────────────────────────────────────────────────────────────────┐
|
|
94
|
+
* │ OUTER CONTRACT │
|
|
95
|
+
* │ │
|
|
96
|
+
* │ ┌───────────────────────────────────────────────────────────┐ │
|
|
97
|
+
* │ │ SETUP │ │
|
|
98
|
+
* │ │ CALLDATACOPY inner_address from calldata[0] │ │
|
|
99
|
+
* │ │ SET l2Gas = MAX_UINT32 │ │
|
|
100
|
+
* │ │ SET daGas = MAX_UINT32 │ │
|
|
101
|
+
* │ └───────────────────────────────────────────────────────────┘ │
|
|
102
|
+
* │ │ │
|
|
103
|
+
* │ ▼ │
|
|
104
|
+
* │ ┌───────────────────────────────────────────────────────────┐ │
|
|
105
|
+
* │ │ LOOP ◄────┐ │ │
|
|
106
|
+
* │ │ CALL inner_contract ──────────────────────┐ │ │ │
|
|
107
|
+
* │ │ JUMP back ─────────────────────────────────────────────┘ │ │
|
|
108
|
+
* │ └───────────────────────────────────────────────────────────┘ │
|
|
109
|
+
* │ │ │
|
|
110
|
+
* │ Executes until: OUT OF GAS │ │
|
|
111
|
+
* └───────────────────────────────────────────────│─────────────────┘
|
|
112
|
+
* │
|
|
113
|
+
* ▼
|
|
114
|
+
* ┌─────────────────────────────────────────────────────────────────┐
|
|
115
|
+
* │ INNER CONTRACT │
|
|
116
|
+
* │ │
|
|
117
|
+
* │ ┌───────────────────────────────────────────────────────────┐ │
|
|
118
|
+
* │ │ SETUP │ │
|
|
119
|
+
* │ │ SET initial values for side-effect opcode │ │
|
|
120
|
+
* │ └───────────────────────────────────────────────────────────┘ │
|
|
121
|
+
* │ │ │
|
|
122
|
+
* │ ▼ │
|
|
123
|
+
* │ ┌───────────────────────────────────────────────────────────┐ │
|
|
124
|
+
* │ │ BODY (unrolled, NOT a loop) │ │
|
|
125
|
+
* │ │ SIDE_EFFECT_OPCODE ─┐ │ │
|
|
126
|
+
* │ │ SIDE_EFFECT_OPCODE │ repeated `limit` times │ │
|
|
127
|
+
* │ │ SIDE_EFFECT_OPCODE │ (e.g., 64 for EMITNOTEHASH) │ │
|
|
128
|
+
* │ │ ... ─┘ │ │
|
|
129
|
+
* │ └───────────────────────────────────────────────────────────┘ │
|
|
130
|
+
* │ │ │
|
|
131
|
+
* │ ▼ │
|
|
132
|
+
* │ ┌───────────────────────────────────────────────────────────┐ │
|
|
133
|
+
* │ │ CLEANUP │ │
|
|
134
|
+
* │ │ REVERT (discards all side effects from this call) │ │
|
|
135
|
+
* │ └───────────────────────────────────────────────────────────┘ │
|
|
136
|
+
* │ │
|
|
137
|
+
* └─────────────────────────────────────────────────────────────────┘
|
|
138
|
+
* ```
|
|
139
|
+
*
|
|
140
|
+
* **Why this pattern?**
|
|
141
|
+
*
|
|
142
|
+
* Side-effect opcodes have per-TX limits:
|
|
143
|
+
* - `EMITNOTEHASH`: max 64 per TX
|
|
144
|
+
* - `EMITNULLIFIER`: max 63 per TX (one reserved for TX nullifier)
|
|
145
|
+
* - `SENDL2TOL1MSG`: max 8 per TX
|
|
146
|
+
* - `EMITUNENCRYPTEDLOG`: limited by total log payload size
|
|
147
|
+
*
|
|
148
|
+
* By having the inner contract REVERT after emitting side effects, those effects are discarded, allowing the outer contract to call it again. This enables thousands of opcode executions per TX instead of just the limit.
|
|
149
|
+
*
|
|
150
|
+
*/ import { FLAT_PUBLIC_LOGS_PAYLOAD_LENGTH, MAX_L2_TO_L1_MSGS_PER_TX, MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_PUBLIC_LOG_SIZE_IN_FIELDS, PUBLIC_LOG_HEADER_LENGTH } from '@aztec/constants';
|
|
151
|
+
import { Grumpkin } from '@aztec/foundation/crypto/grumpkin';
|
|
152
|
+
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
153
|
+
import assert from 'assert';
|
|
154
|
+
import { Field, INTEGRAL_TAGS, TaggedMemory, TypeTag, Uint1, Uint32, Uint64, VALID_TAGS } from '../avm/avm_memory_types.js';
|
|
155
|
+
import { Add, And, Call, CalldataCopy, Cast, DebugLog, Div, EcAdd, EmitNoteHash, EmitNullifier, EmitUnencryptedLog, Eq, FieldDiv, GetContractInstance, GetEnvVar, Jump, JumpI, KeccakF1600, L1ToL2MessageExists, Lt, Lte, Mov, Mul, Not, NoteHashExists, NullifierExists, Or, Poseidon2, ReturndataCopy, ReturndataSize, Revert, SLoad, SStore, SendL2ToL1Message, Set, Sha256Compression, Shl, Shr, Sub, SuccessCopy, ToRadixBE, Xor } from '../avm/opcodes/index.js';
|
|
156
|
+
import { encodeToBytecode } from '../avm/serialization/bytecode_serialization.js';
|
|
157
|
+
import { Opcode } from '../avm/serialization/instruction_serialization.js';
|
|
158
|
+
import { deployCustomBytecode, executeCustomBytecode } from './custom_bytecode_tester.js';
|
|
159
|
+
import { deployAndExecuteCustomBytecode } from './index.js';
|
|
160
|
+
// ============================================================================
|
|
161
|
+
// Constants
|
|
162
|
+
// ============================================================================
|
|
163
|
+
/**
|
|
164
|
+
* Maximum bytecode size in bytes.
|
|
165
|
+
*
|
|
166
|
+
* Bytecode is encoded as fields using bufferAsFields():
|
|
167
|
+
* - 1 field for the byte length
|
|
168
|
+
* - ceil(byteLength / 31) fields for the data (31 bytes per field)
|
|
169
|
+
*
|
|
170
|
+
* So: 1 + ceil(byteLength / 31) <= MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS
|
|
171
|
+
* ceil(byteLength / 31) <= MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS - 1
|
|
172
|
+
* byteLength <= (MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS - 1) * 31
|
|
173
|
+
*/ const BYTES_PER_FIELD = Fr.SIZE_IN_BYTES - 1; // 31 bytes of data per field
|
|
174
|
+
const MAX_BYTECODE_BYTES = (MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS - 1) * BYTES_PER_FIELD;
|
|
175
|
+
const JUMP_SIZE = encodeToBytecode([
|
|
176
|
+
new Jump(0)
|
|
177
|
+
]).length; // JUMP_32
|
|
178
|
+
// ============================================================================
|
|
179
|
+
// Type Variant Helpers (for generating multiple configs per opcode)
|
|
180
|
+
// ============================================================================
|
|
181
|
+
const ALL_TAGS = Array.from(VALID_TAGS);
|
|
182
|
+
const INT_TAGS = Array.from(INTEGRAL_TAGS);
|
|
183
|
+
/** Build from tag truncating - shorter name */ function withTag(v, tag) {
|
|
184
|
+
return TaggedMemory.buildFromTagTruncating(v, tag);
|
|
185
|
+
}
|
|
186
|
+
// ============================================================================
|
|
187
|
+
// Configuration Map
|
|
188
|
+
// ============================================================================
|
|
189
|
+
/**
|
|
190
|
+
* Opcode spammer configs for ~all opcodes.
|
|
191
|
+
* Each opcode maps to an array of configs (usually one, but can be multiple for type variants, etc.)
|
|
192
|
+
* Uses smallest wire format (_8) for maximum instruction density.
|
|
193
|
+
*/ export const SPAM_CONFIGS = {
|
|
194
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
195
|
+
// ARITHMETIC - Test with all type variants
|
|
196
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
197
|
+
[Opcode.ADD_8]: ALL_TAGS.map((tag)=>({
|
|
198
|
+
label: TypeTag[tag],
|
|
199
|
+
setup: [
|
|
200
|
+
{
|
|
201
|
+
offset: 0,
|
|
202
|
+
value: withTag(1n, tag)
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
offset: 1,
|
|
206
|
+
value: withTag(1n, tag)
|
|
207
|
+
}
|
|
208
|
+
],
|
|
209
|
+
targetInstructions: ()=>[
|
|
210
|
+
new Add(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.ADD_8, Add.wireFormat8)
|
|
211
|
+
]
|
|
212
|
+
})),
|
|
213
|
+
[Opcode.SUB_8]: ALL_TAGS.map((tag)=>({
|
|
214
|
+
label: TypeTag[tag],
|
|
215
|
+
setup: [
|
|
216
|
+
{
|
|
217
|
+
offset: 0,
|
|
218
|
+
value: withTag(1000000n, tag)
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
offset: 1,
|
|
222
|
+
value: withTag(1n, tag)
|
|
223
|
+
}
|
|
224
|
+
],
|
|
225
|
+
targetInstructions: ()=>[
|
|
226
|
+
new Sub(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.SUB_8, Sub.wireFormat8)
|
|
227
|
+
]
|
|
228
|
+
})),
|
|
229
|
+
[Opcode.MUL_8]: ALL_TAGS.map((tag)=>({
|
|
230
|
+
label: TypeTag[tag],
|
|
231
|
+
setup: [
|
|
232
|
+
{
|
|
233
|
+
offset: 0,
|
|
234
|
+
value: withTag(2n, tag)
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
offset: 1,
|
|
238
|
+
value: withTag(2n, tag)
|
|
239
|
+
}
|
|
240
|
+
],
|
|
241
|
+
targetInstructions: ()=>[
|
|
242
|
+
new Mul(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.MUL_8, Mul.wireFormat8)
|
|
243
|
+
]
|
|
244
|
+
})),
|
|
245
|
+
// DIV doesn't support FIELD type
|
|
246
|
+
[Opcode.DIV_8]: INT_TAGS.map((tag)=>({
|
|
247
|
+
label: TypeTag[tag],
|
|
248
|
+
setup: [
|
|
249
|
+
{
|
|
250
|
+
offset: 0,
|
|
251
|
+
value: withTag(111111111111111111n, tag)
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
offset: 1,
|
|
255
|
+
value: withTag(1n, tag)
|
|
256
|
+
}
|
|
257
|
+
],
|
|
258
|
+
targetInstructions: ()=>[
|
|
259
|
+
new Div(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.DIV_8, Div.wireFormat8)
|
|
260
|
+
]
|
|
261
|
+
})),
|
|
262
|
+
// Field-only
|
|
263
|
+
[Opcode.FDIV_8]: [
|
|
264
|
+
{
|
|
265
|
+
setup: [
|
|
266
|
+
{
|
|
267
|
+
offset: 0,
|
|
268
|
+
value: new Field(1000000n)
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
offset: 1,
|
|
272
|
+
value: new Field(1n)
|
|
273
|
+
}
|
|
274
|
+
],
|
|
275
|
+
targetInstructions: ()=>[
|
|
276
|
+
new FieldDiv(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.FDIV_8, FieldDiv.wireFormat8)
|
|
277
|
+
]
|
|
278
|
+
}
|
|
279
|
+
],
|
|
280
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
281
|
+
// COMPARATORS - Test with all type variants
|
|
282
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
283
|
+
[Opcode.EQ_8]: ALL_TAGS.map((tag)=>({
|
|
284
|
+
label: TypeTag[tag],
|
|
285
|
+
setup: [
|
|
286
|
+
{
|
|
287
|
+
offset: 0,
|
|
288
|
+
value: withTag(42n, tag)
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
offset: 1,
|
|
292
|
+
value: withTag(42n, tag)
|
|
293
|
+
}
|
|
294
|
+
],
|
|
295
|
+
targetInstructions: ()=>[
|
|
296
|
+
new Eq(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2).as(Opcode.EQ_8, Eq.wireFormat8)
|
|
297
|
+
]
|
|
298
|
+
})),
|
|
299
|
+
[Opcode.LT_8]: ALL_TAGS.map((tag)=>({
|
|
300
|
+
label: TypeTag[tag],
|
|
301
|
+
setup: [
|
|
302
|
+
{
|
|
303
|
+
offset: 0,
|
|
304
|
+
value: withTag(1n, tag)
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
offset: 1,
|
|
308
|
+
value: withTag(1000000n, tag)
|
|
309
|
+
}
|
|
310
|
+
],
|
|
311
|
+
targetInstructions: ()=>[
|
|
312
|
+
new Lt(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2).as(Opcode.LT_8, Lt.wireFormat8)
|
|
313
|
+
]
|
|
314
|
+
})),
|
|
315
|
+
[Opcode.LTE_8]: ALL_TAGS.map((tag)=>({
|
|
316
|
+
label: TypeTag[tag],
|
|
317
|
+
setup: [
|
|
318
|
+
{
|
|
319
|
+
offset: 0,
|
|
320
|
+
value: withTag(1n, tag)
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
offset: 1,
|
|
324
|
+
value: withTag(1000000n, tag)
|
|
325
|
+
}
|
|
326
|
+
],
|
|
327
|
+
targetInstructions: ()=>[
|
|
328
|
+
new Lte(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2).as(Opcode.LTE_8, Lte.wireFormat8)
|
|
329
|
+
]
|
|
330
|
+
})),
|
|
331
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
332
|
+
// BITWISE - Integer types only (no FIELD)
|
|
333
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
334
|
+
[Opcode.AND_8]: INT_TAGS.map((tag)=>({
|
|
335
|
+
label: TypeTag[tag],
|
|
336
|
+
setup: [
|
|
337
|
+
{
|
|
338
|
+
offset: 0,
|
|
339
|
+
value: withTag(0xffffffffffffffffffffffffffffffffn, tag)
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
offset: 1,
|
|
343
|
+
value: withTag(0xffffffffffffffffffffffffffffffffn, tag)
|
|
344
|
+
}
|
|
345
|
+
],
|
|
346
|
+
targetInstructions: ()=>[
|
|
347
|
+
new And(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.AND_8, And.wireFormat8)
|
|
348
|
+
]
|
|
349
|
+
})),
|
|
350
|
+
[Opcode.OR_8]: INT_TAGS.map((tag)=>({
|
|
351
|
+
label: TypeTag[tag],
|
|
352
|
+
setup: [
|
|
353
|
+
{
|
|
354
|
+
offset: 0,
|
|
355
|
+
value: withTag(0xffffffffffffffffffffffffffffffffn, tag)
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
offset: 1,
|
|
359
|
+
value: withTag(0n, tag)
|
|
360
|
+
}
|
|
361
|
+
],
|
|
362
|
+
targetInstructions: ()=>[
|
|
363
|
+
new Or(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.OR_8, Or.wireFormat8)
|
|
364
|
+
]
|
|
365
|
+
})),
|
|
366
|
+
[Opcode.XOR_8]: INT_TAGS.map((tag)=>({
|
|
367
|
+
label: TypeTag[tag],
|
|
368
|
+
setup: [
|
|
369
|
+
{
|
|
370
|
+
offset: 0,
|
|
371
|
+
value: withTag(0xdeadbeefcafebaben, tag)
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
offset: 1,
|
|
375
|
+
value: withTag(0x1234567890abcdefn, tag)
|
|
376
|
+
}
|
|
377
|
+
],
|
|
378
|
+
targetInstructions: ()=>[
|
|
379
|
+
new Xor(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.XOR_8, Xor.wireFormat8)
|
|
380
|
+
]
|
|
381
|
+
})),
|
|
382
|
+
[Opcode.NOT_8]: INT_TAGS.map((tag)=>({
|
|
383
|
+
label: TypeTag[tag],
|
|
384
|
+
setup: [
|
|
385
|
+
{
|
|
386
|
+
offset: 0,
|
|
387
|
+
value: withTag(0xffffffffffffffffn, tag)
|
|
388
|
+
}
|
|
389
|
+
],
|
|
390
|
+
targetInstructions: ()=>[
|
|
391
|
+
new Not(/*indirect=*/ 0, /*srcOffset=*/ 0, /*dstOffset=*/ 0).as(Opcode.NOT_8, Not.wireFormat8)
|
|
392
|
+
]
|
|
393
|
+
})),
|
|
394
|
+
[Opcode.SHL_8]: INT_TAGS.map((tag)=>({
|
|
395
|
+
label: TypeTag[tag],
|
|
396
|
+
setup: [
|
|
397
|
+
{
|
|
398
|
+
offset: 0,
|
|
399
|
+
value: withTag(1n, tag)
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
offset: 1,
|
|
403
|
+
value: withTag(1n, tag)
|
|
404
|
+
}
|
|
405
|
+
],
|
|
406
|
+
targetInstructions: ()=>[
|
|
407
|
+
new Shl(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.SHL_8, Shl.wireFormat8)
|
|
408
|
+
]
|
|
409
|
+
})),
|
|
410
|
+
[Opcode.SHR_8]: INT_TAGS.map((tag)=>({
|
|
411
|
+
label: TypeTag[tag],
|
|
412
|
+
setup: [
|
|
413
|
+
{
|
|
414
|
+
offset: 0,
|
|
415
|
+
value: withTag(0xffffffffffffffffn, tag)
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
offset: 1,
|
|
419
|
+
value: withTag(1n, tag)
|
|
420
|
+
}
|
|
421
|
+
],
|
|
422
|
+
targetInstructions: ()=>[
|
|
423
|
+
new Shr(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.SHR_8, Shr.wireFormat8)
|
|
424
|
+
]
|
|
425
|
+
})),
|
|
426
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
427
|
+
// CAST / MOV - Test with all type variants
|
|
428
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
429
|
+
[Opcode.CAST_8]: ALL_TAGS.map((tag)=>({
|
|
430
|
+
label: TypeTag[tag],
|
|
431
|
+
setup: [
|
|
432
|
+
{
|
|
433
|
+
offset: 0,
|
|
434
|
+
value: withTag(42n, tag)
|
|
435
|
+
}
|
|
436
|
+
],
|
|
437
|
+
targetInstructions: ()=>[
|
|
438
|
+
new Cast(/*indirect=*/ 0, /*srcOffset=*/ 0, /*dstOffset=*/ 1, /*dstTag=*/ TypeTag.UINT32).as(Opcode.CAST_8, Cast.wireFormat8)
|
|
439
|
+
]
|
|
440
|
+
})),
|
|
441
|
+
[Opcode.MOV_8]: ALL_TAGS.map((tag)=>({
|
|
442
|
+
label: TypeTag[tag],
|
|
443
|
+
setup: [
|
|
444
|
+
{
|
|
445
|
+
offset: 0,
|
|
446
|
+
value: withTag(42n, tag)
|
|
447
|
+
}
|
|
448
|
+
],
|
|
449
|
+
targetInstructions: ()=>[
|
|
450
|
+
new Mov(/*indirect=*/ 0, /*srcOffset=*/ 0, /*dstOffset=*/ 1).as(Opcode.MOV_8, Mov.wireFormat8)
|
|
451
|
+
]
|
|
452
|
+
})),
|
|
453
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
454
|
+
// MEMORY - SET
|
|
455
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
456
|
+
// Not testing all wire formats as they should be roughly the same in terms of simulation
|
|
457
|
+
// and proving time
|
|
458
|
+
//[Opcode.SET_8]: [
|
|
459
|
+
// {
|
|
460
|
+
// setup: [],
|
|
461
|
+
// targetInstructions: () => [new Set(0, 0, TypeTag.UINT8, 42).as(Opcode.SET_8, Set.wireFormat8)],
|
|
462
|
+
// },
|
|
463
|
+
//],
|
|
464
|
+
//[Opcode.SET_16]: [
|
|
465
|
+
// {
|
|
466
|
+
// setup: [],
|
|
467
|
+
// targetInstructions: () => [new Set(0, 0, TypeTag.UINT16, 4242).as(Opcode.SET_16, Set.wireFormat16)],
|
|
468
|
+
// },
|
|
469
|
+
//],
|
|
470
|
+
//[Opcode.SET_32]: [
|
|
471
|
+
// {
|
|
472
|
+
// setup: [],
|
|
473
|
+
// targetInstructions: () => [new Set(0, 0, TypeTag.UINT32, 424242).as(Opcode.SET_32, Set.wireFormat32)],
|
|
474
|
+
// },
|
|
475
|
+
//],
|
|
476
|
+
//[Opcode.SET_64]: [
|
|
477
|
+
// {
|
|
478
|
+
// setup: [],
|
|
479
|
+
// targetInstructions: () => [new Set(0, 0, TypeTag.UINT64, 42424242n).as(Opcode.SET_64, Set.wireFormat64)],
|
|
480
|
+
// },
|
|
481
|
+
//],
|
|
482
|
+
[Opcode.SET_128]: [
|
|
483
|
+
{
|
|
484
|
+
setup: [],
|
|
485
|
+
targetInstructions: ()=>[
|
|
486
|
+
new Set(/*indirect=*/ 0, /*dstOffset=*/ 0, /*inTag=*/ TypeTag.UINT128, /*value=*/ 4242424242424242n).as(Opcode.SET_128, Set.wireFormat128)
|
|
487
|
+
]
|
|
488
|
+
}
|
|
489
|
+
],
|
|
490
|
+
//[Opcode.SET_FF]: [
|
|
491
|
+
// {
|
|
492
|
+
// setup: [],
|
|
493
|
+
// targetInstructions: () => [new Set(0, 0, TypeTag.FIELD, 42n).as(Opcode.SET_FF, Set.wireFormatFF)],
|
|
494
|
+
// },
|
|
495
|
+
//],
|
|
496
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
497
|
+
// CONTROL FLOW
|
|
498
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
499
|
+
[Opcode.JUMP_32]: [
|
|
500
|
+
{
|
|
501
|
+
setup: [],
|
|
502
|
+
// Target will be overwritten by loop builder
|
|
503
|
+
targetInstructions: ()=>[
|
|
504
|
+
new Jump(/*jumpOffset=*/ 0)
|
|
505
|
+
]
|
|
506
|
+
}
|
|
507
|
+
],
|
|
508
|
+
[Opcode.JUMPI_32]: [
|
|
509
|
+
{
|
|
510
|
+
setup: [
|
|
511
|
+
{
|
|
512
|
+
offset: 0,
|
|
513
|
+
value: new Uint1(0n)
|
|
514
|
+
}
|
|
515
|
+
],
|
|
516
|
+
targetInstructions: ()=>[
|
|
517
|
+
new JumpI(/*indirect=*/ 0, /*condOffset=*/ 0, /*loc=*/ 0)
|
|
518
|
+
]
|
|
519
|
+
}
|
|
520
|
+
],
|
|
521
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
522
|
+
// ENVIRONMENT
|
|
523
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
524
|
+
[Opcode.GETENVVAR_16]: [
|
|
525
|
+
{
|
|
526
|
+
setup: [],
|
|
527
|
+
targetInstructions: ()=>[
|
|
528
|
+
new GetEnvVar(/*indirect=*/ 0, /*dstOffset=*/ 0, /*varEnum=*/ 0).as(Opcode.GETENVVAR_16, GetEnvVar.wireFormat16)
|
|
529
|
+
]
|
|
530
|
+
}
|
|
531
|
+
],
|
|
532
|
+
[Opcode.CALLDATACOPY]: [
|
|
533
|
+
{
|
|
534
|
+
// CalldataCopy(indirect=0, copySizeOffset=0, cdStartOffset=1, dstOffset=2)
|
|
535
|
+
// Copies M[0]=1 elements starting from CD[M[1]]=CD[0] into M[2].
|
|
536
|
+
// In other words: M[2] = CD[0]
|
|
537
|
+
setup: [
|
|
538
|
+
{
|
|
539
|
+
offset: 0,
|
|
540
|
+
value: new Uint32(1n)
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
offset: 1,
|
|
544
|
+
value: new Uint32(0n)
|
|
545
|
+
}
|
|
546
|
+
],
|
|
547
|
+
targetInstructions: ()=>[
|
|
548
|
+
new CalldataCopy(/*indirect=*/ 0, /*copySizeOffset=*/ 0, /*cdStartOffset=*/ 1, /*dstOffset=*/ 2)
|
|
549
|
+
]
|
|
550
|
+
}
|
|
551
|
+
],
|
|
552
|
+
[Opcode.SUCCESSCOPY]: [
|
|
553
|
+
{
|
|
554
|
+
setup: [],
|
|
555
|
+
targetInstructions: ()=>[
|
|
556
|
+
new SuccessCopy(/*indirect=*/ 0, /*dstOffset=*/ 0)
|
|
557
|
+
]
|
|
558
|
+
}
|
|
559
|
+
],
|
|
560
|
+
[Opcode.RETURNDATASIZE]: [
|
|
561
|
+
{
|
|
562
|
+
setup: [],
|
|
563
|
+
targetInstructions: ()=>[
|
|
564
|
+
new ReturndataSize(/*indirect=*/ 0, /*dstOffset=*/ 0)
|
|
565
|
+
]
|
|
566
|
+
}
|
|
567
|
+
],
|
|
568
|
+
[Opcode.RETURNDATACOPY]: [
|
|
569
|
+
{
|
|
570
|
+
setup: [
|
|
571
|
+
{
|
|
572
|
+
offset: 0,
|
|
573
|
+
value: new Uint32(0n)
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
offset: 1,
|
|
577
|
+
value: new Uint32(0n)
|
|
578
|
+
}
|
|
579
|
+
],
|
|
580
|
+
targetInstructions: ()=>[
|
|
581
|
+
new ReturndataCopy(/*indirect=*/ 0, /*copySizeOffset=*/ 0, /*rdStartOffset=*/ 1, /*dstOffset=*/ 2)
|
|
582
|
+
]
|
|
583
|
+
}
|
|
584
|
+
],
|
|
585
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
586
|
+
// WORLD STATE READS
|
|
587
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
588
|
+
[Opcode.SLOAD]: [
|
|
589
|
+
{
|
|
590
|
+
setup: [
|
|
591
|
+
{
|
|
592
|
+
offset: 0,
|
|
593
|
+
value: new Field(0n)
|
|
594
|
+
}
|
|
595
|
+
],
|
|
596
|
+
targetInstructions: ()=>[
|
|
597
|
+
new SLoad(/*indirect=*/ 0, /*slotOffset=*/ 0, /*dstOffset=*/ 1)
|
|
598
|
+
]
|
|
599
|
+
}
|
|
600
|
+
],
|
|
601
|
+
[Opcode.NOTEHASHEXISTS]: [
|
|
602
|
+
{
|
|
603
|
+
setup: [
|
|
604
|
+
{
|
|
605
|
+
offset: 0,
|
|
606
|
+
value: new Field(0n)
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
offset: 1,
|
|
610
|
+
value: new Uint64(0n)
|
|
611
|
+
}
|
|
612
|
+
],
|
|
613
|
+
targetInstructions: ()=>[
|
|
614
|
+
new NoteHashExists(/*indirect=*/ 0, /*noteHashOffset=*/ 0, /*leafIndexOffset=*/ 1, /*existsOffset=*/ 2)
|
|
615
|
+
]
|
|
616
|
+
}
|
|
617
|
+
],
|
|
618
|
+
[Opcode.NULLIFIEREXISTS]: [
|
|
619
|
+
{
|
|
620
|
+
setup: [
|
|
621
|
+
{
|
|
622
|
+
offset: 0,
|
|
623
|
+
value: new Field(0n)
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
offset: 1,
|
|
627
|
+
value: new Field(0n)
|
|
628
|
+
}
|
|
629
|
+
],
|
|
630
|
+
targetInstructions: ()=>[
|
|
631
|
+
new NullifierExists(/*indirect=*/ 0, /*nullifierOffset=*/ 0, /*addressOffset=*/ 1, /*existsOffset=*/ 2)
|
|
632
|
+
]
|
|
633
|
+
}
|
|
634
|
+
],
|
|
635
|
+
[Opcode.L1TOL2MSGEXISTS]: [
|
|
636
|
+
{
|
|
637
|
+
setup: [
|
|
638
|
+
{
|
|
639
|
+
offset: 0,
|
|
640
|
+
value: new Field(0n)
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
offset: 1,
|
|
644
|
+
value: new Uint64(0n)
|
|
645
|
+
}
|
|
646
|
+
],
|
|
647
|
+
targetInstructions: ()=>[
|
|
648
|
+
new L1ToL2MessageExists(/*indirect=*/ 0, /*msgHashOffset=*/ 0, /*msgLeafIndexOffset=*/ 1, /*existsOffset=*/ 2)
|
|
649
|
+
]
|
|
650
|
+
}
|
|
651
|
+
],
|
|
652
|
+
[Opcode.GETCONTRACTINSTANCE]: [
|
|
653
|
+
{
|
|
654
|
+
setup: [
|
|
655
|
+
{
|
|
656
|
+
offset: 0,
|
|
657
|
+
value: new Field(0n)
|
|
658
|
+
}
|
|
659
|
+
],
|
|
660
|
+
// memberEnum 0 = DEPLOYER
|
|
661
|
+
targetInstructions: ()=>[
|
|
662
|
+
new GetContractInstance(/*indirect=*/ 0, /*addressOffset=*/ 0, /*dstOffset=*/ 1, /*memberEnum=*/ 0)
|
|
663
|
+
]
|
|
664
|
+
}
|
|
665
|
+
],
|
|
666
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
667
|
+
// SIDE-EFFECT LIMITED (have per-TX limit, use nested call pattern)
|
|
668
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
669
|
+
[Opcode.EMITNOTEHASH]: [
|
|
670
|
+
{
|
|
671
|
+
setup: [
|
|
672
|
+
{
|
|
673
|
+
offset: 0,
|
|
674
|
+
value: new Field(0x1000n)
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
offset: 1,
|
|
678
|
+
value: new Uint32(0n)
|
|
679
|
+
}
|
|
680
|
+
],
|
|
681
|
+
targetInstructions: ()=>[
|
|
682
|
+
new EmitNoteHash(/*indirect=*/ 0, /*noteHashOffset=*/ 0)
|
|
683
|
+
],
|
|
684
|
+
cleanupInstructions: ()=>[
|
|
685
|
+
new Revert(/*indirect=*/ 0, /*retSizeOffset=*/ 1, /*returnOffset=*/ 0).as(Opcode.REVERT_8, Revert.wireFormat8)
|
|
686
|
+
],
|
|
687
|
+
limit: MAX_NOTE_HASHES_PER_TX
|
|
688
|
+
}
|
|
689
|
+
],
|
|
690
|
+
[Opcode.EMITNULLIFIER]: [
|
|
691
|
+
{
|
|
692
|
+
// Nullifiers must be unique - increment value after each emit
|
|
693
|
+
// Memory layout: offset 0 = nullifier value, offset 1 = constant 1 for incrementing
|
|
694
|
+
setup: [
|
|
695
|
+
{
|
|
696
|
+
offset: 0,
|
|
697
|
+
value: new Field(0x2000n)
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
offset: 1,
|
|
701
|
+
value: new Field(1n)
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
offset: 2,
|
|
705
|
+
value: new Uint32(0n)
|
|
706
|
+
}
|
|
707
|
+
],
|
|
708
|
+
targetInstructions: ()=>[
|
|
709
|
+
new EmitNullifier(/*indirect=*/ 0, /*nullifierOffset=*/ 0),
|
|
710
|
+
new Add(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.ADD_8, Add.wireFormat8)
|
|
711
|
+
],
|
|
712
|
+
cleanupInstructions: ()=>[
|
|
713
|
+
new Revert(/*indirect=*/ 0, /*retSizeOffset=*/ 2, /*returnOffset=*/ 0).as(Opcode.REVERT_8, Revert.wireFormat8)
|
|
714
|
+
],
|
|
715
|
+
limit: MAX_NULLIFIERS_PER_TX - 1
|
|
716
|
+
}
|
|
717
|
+
],
|
|
718
|
+
[Opcode.SENDL2TOL1MSG]: [
|
|
719
|
+
{
|
|
720
|
+
setup: [
|
|
721
|
+
{
|
|
722
|
+
offset: 0,
|
|
723
|
+
value: new Field(1n)
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
offset: 1,
|
|
727
|
+
value: new Field(0x3000n)
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
offset: 2,
|
|
731
|
+
value: new Uint32(0n)
|
|
732
|
+
}
|
|
733
|
+
],
|
|
734
|
+
targetInstructions: ()=>[
|
|
735
|
+
new SendL2ToL1Message(/*indirect=*/ 0, /*recipientOffset=*/ 0, /*contentOffset=*/ 1)
|
|
736
|
+
],
|
|
737
|
+
cleanupInstructions: ()=>[
|
|
738
|
+
new Revert(/*indirect=*/ 0, /*retSizeOffset=*/ 2, /*returnOffset=*/ 0).as(Opcode.REVERT_8, Revert.wireFormat8)
|
|
739
|
+
],
|
|
740
|
+
limit: MAX_L2_TO_L1_MSGS_PER_TX
|
|
741
|
+
}
|
|
742
|
+
],
|
|
743
|
+
// SSTORE has two modes:
|
|
744
|
+
// 1. Same slot: Writing to the same slot repeatedly has no per-TX limit - it just overwrites.
|
|
745
|
+
// 2. Unique slots: Writing to unique slots is limited by MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX.
|
|
746
|
+
[Opcode.SSTORE]: [
|
|
747
|
+
{
|
|
748
|
+
label: 'Same slot (no limit)',
|
|
749
|
+
setup: [
|
|
750
|
+
{
|
|
751
|
+
offset: 0,
|
|
752
|
+
value: new Field(42n)
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
offset: 1,
|
|
756
|
+
value: new Field(0x100n)
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
offset: 2,
|
|
760
|
+
value: new Uint32(0n)
|
|
761
|
+
}
|
|
762
|
+
],
|
|
763
|
+
targetInstructions: ()=>[
|
|
764
|
+
new SStore(/*indirect=*/ 0, /*srcOffset=*/ 0, /*slotOffset=*/ 1)
|
|
765
|
+
],
|
|
766
|
+
cleanupInstructions: ()=>[
|
|
767
|
+
new Revert(/*indirect=*/ 0, /*retSizeOffset=*/ 2, /*returnOffset=*/ 0).as(Opcode.REVERT_8, Revert.wireFormat8)
|
|
768
|
+
]
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
label: 'Unique slots (side-effect limited)',
|
|
772
|
+
setup: [
|
|
773
|
+
{
|
|
774
|
+
offset: 0,
|
|
775
|
+
value: new Field(42n)
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
offset: 1,
|
|
779
|
+
value: new Field(0x100n)
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
offset: 2,
|
|
783
|
+
value: new Field(1n)
|
|
784
|
+
},
|
|
785
|
+
{
|
|
786
|
+
offset: 3,
|
|
787
|
+
value: new Uint32(0n)
|
|
788
|
+
}
|
|
789
|
+
],
|
|
790
|
+
targetInstructions: ()=>[
|
|
791
|
+
new SStore(/*indirect=*/ 0, /*srcOffset=*/ 0, /*slotOffset=*/ 1),
|
|
792
|
+
new Add(/*indirect=*/ 0, /*aOffset=*/ 1, /*bOffset=*/ 2, /*dstOffset=*/ 1).as(Opcode.ADD_8, Add.wireFormat8)
|
|
793
|
+
],
|
|
794
|
+
cleanupInstructions: ()=>[
|
|
795
|
+
new Revert(/*indirect=*/ 0, /*retSizeOffset=*/ 3, /*returnOffset=*/ 0).as(Opcode.REVERT_8, Revert.wireFormat8)
|
|
796
|
+
],
|
|
797
|
+
limit: MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX
|
|
798
|
+
}
|
|
799
|
+
],
|
|
800
|
+
// EMITUNENCRYPTEDLOG - two configs: minimal (many small logs) and max-size (one large log)
|
|
801
|
+
[Opcode.EMITUNENCRYPTEDLOG]: [
|
|
802
|
+
{
|
|
803
|
+
label: 'Many empty logs, revert, repeat',
|
|
804
|
+
setup: [
|
|
805
|
+
{
|
|
806
|
+
offset: 0,
|
|
807
|
+
value: new Uint32(0n)
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
offset: 1,
|
|
811
|
+
value: new Uint32(0n)
|
|
812
|
+
}
|
|
813
|
+
],
|
|
814
|
+
targetInstructions: ()=>[
|
|
815
|
+
new EmitUnencryptedLog(/*indirect=*/ 0, /*logSizeOffset=*/ 0, /*logOffset=*/ 1)
|
|
816
|
+
],
|
|
817
|
+
cleanupInstructions: ()=>[
|
|
818
|
+
new Revert(/*indirect=*/ 0, /*retSizeOffset=*/ 1, /*returnOffset=*/ 0).as(Opcode.REVERT_8, Revert.wireFormat8)
|
|
819
|
+
],
|
|
820
|
+
// Max logs with 0-field content: floor(4096 / 2) = 2048
|
|
821
|
+
limit: Math.floor(FLAT_PUBLIC_LOGS_PAYLOAD_LENGTH / PUBLIC_LOG_HEADER_LENGTH)
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
label: 'One max size log, revert, repeat',
|
|
825
|
+
setup: [
|
|
826
|
+
// logSize = MAX_PUBLIC_LOG_SIZE_IN_FIELDS
|
|
827
|
+
{
|
|
828
|
+
offset: 0,
|
|
829
|
+
value: new Uint32(BigInt(MAX_PUBLIC_LOG_SIZE_IN_FIELDS))
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
offset: 1,
|
|
833
|
+
value: new Uint32(0n)
|
|
834
|
+
}
|
|
835
|
+
],
|
|
836
|
+
targetInstructions: ()=>[
|
|
837
|
+
new EmitUnencryptedLog(/*indirect=*/ 0, /*logSizeOffset=*/ 0, /*logOffset=*/ 2)
|
|
838
|
+
],
|
|
839
|
+
cleanupInstructions: ()=>[
|
|
840
|
+
new Revert(/*indirect=*/ 0, /*retSizeOffset=*/ 1, /*returnOffset=*/ 0).as(Opcode.REVERT_8, Revert.wireFormat8)
|
|
841
|
+
],
|
|
842
|
+
limit: 1
|
|
843
|
+
}
|
|
844
|
+
],
|
|
845
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
846
|
+
// GADGETS - Use non-trivial inputs to avoid special-case optimizations
|
|
847
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
848
|
+
[Opcode.POSEIDON2]: [
|
|
849
|
+
{
|
|
850
|
+
// Poseidon2 takes 4 field elements as input
|
|
851
|
+
setup: Array.from({
|
|
852
|
+
length: 4
|
|
853
|
+
}, (_, i)=>({
|
|
854
|
+
offset: i,
|
|
855
|
+
value: new Field(BigInt(0xdeadbeef + i * 0x1111))
|
|
856
|
+
})),
|
|
857
|
+
// Poseidon hash data at M[0..3], write result to M[0:3] (reuse results as next inputs)
|
|
858
|
+
targetInstructions: ()=>[
|
|
859
|
+
new Poseidon2(/*indirect=*/ 0, /*inputStateOffset=*/ 0, /*outputStateOffset=*/ 0)
|
|
860
|
+
]
|
|
861
|
+
}
|
|
862
|
+
],
|
|
863
|
+
[Opcode.SHA256COMPRESSION]: [
|
|
864
|
+
{
|
|
865
|
+
setup: [
|
|
866
|
+
// State: 8 x UINT32 at offsets 0-7 (use SHA256 initial hash values)
|
|
867
|
+
{
|
|
868
|
+
offset: 0,
|
|
869
|
+
value: new Uint32(0x6a09e667n)
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
offset: 1,
|
|
873
|
+
value: new Uint32(0xbb67ae85n)
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
offset: 2,
|
|
877
|
+
value: new Uint32(0x3c6ef372n)
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
offset: 3,
|
|
881
|
+
value: new Uint32(0xa54ff53an)
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
offset: 4,
|
|
885
|
+
value: new Uint32(0x510e527fn)
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
offset: 5,
|
|
889
|
+
value: new Uint32(0x9b05688cn)
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
offset: 6,
|
|
893
|
+
value: new Uint32(0x1f83d9abn)
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
offset: 7,
|
|
897
|
+
value: new Uint32(0x5be0cd19n)
|
|
898
|
+
},
|
|
899
|
+
// Inputs: 16 x UINT32 at offsets 8-23 (non-trivial message block)
|
|
900
|
+
...Array.from({
|
|
901
|
+
length: 16
|
|
902
|
+
}, (_, i)=>({
|
|
903
|
+
offset: 8 + i,
|
|
904
|
+
value: new Uint32(0xcafebaben + BigInt(i) * 0x01010101n & 0xffffffffn)
|
|
905
|
+
}))
|
|
906
|
+
],
|
|
907
|
+
targetInstructions: ()=>[
|
|
908
|
+
new Sha256Compression(/*indirect=*/ 0, /*outputOffset=*/ 0, /*stateOffset=*/ 0, /*inputsOffset=*/ 8)
|
|
909
|
+
]
|
|
910
|
+
}
|
|
911
|
+
],
|
|
912
|
+
[Opcode.KECCAKF1600]: [
|
|
913
|
+
{
|
|
914
|
+
// Keccak state: 25 x UINT64 (5x5 lane array)
|
|
915
|
+
setup: Array.from({
|
|
916
|
+
length: 25
|
|
917
|
+
}, (_, i)=>({
|
|
918
|
+
offset: i,
|
|
919
|
+
value: new Uint64(0xdeadbeefcafebaben + BigInt(i) * 0x0101010101010101n & 0xffffffffffffffffn)
|
|
920
|
+
})),
|
|
921
|
+
targetInstructions: ()=>[
|
|
922
|
+
new KeccakF1600(/*indirect=*/ 0, /*dstOffset=*/ 0, /*inputOffset=*/ 0)
|
|
923
|
+
]
|
|
924
|
+
}
|
|
925
|
+
],
|
|
926
|
+
[Opcode.ECADD]: [
|
|
927
|
+
{
|
|
928
|
+
// Use the Grumpkin generator point G for both points (valid curve point)
|
|
929
|
+
setup: [
|
|
930
|
+
{
|
|
931
|
+
offset: 0,
|
|
932
|
+
value: new Field(Grumpkin.generator.x)
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
offset: 1,
|
|
936
|
+
value: new Field(Grumpkin.generator.y)
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
offset: 2,
|
|
940
|
+
value: new Uint1(0n)
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
offset: 3,
|
|
944
|
+
value: new Field(Grumpkin.generator.x)
|
|
945
|
+
},
|
|
946
|
+
{
|
|
947
|
+
offset: 4,
|
|
948
|
+
value: new Field(Grumpkin.generator.y)
|
|
949
|
+
},
|
|
950
|
+
{
|
|
951
|
+
offset: 5,
|
|
952
|
+
value: new Uint1(0n)
|
|
953
|
+
}
|
|
954
|
+
],
|
|
955
|
+
targetInstructions: ()=>[
|
|
956
|
+
new EcAdd(/*indirect=*/ 0, /*p1XOffset=*/ 0, /*p1YOffset=*/ 1, /*p1IsInfiniteOffset=*/ 2, /*p2XOffset=*/ 3, /*p2YOffset=*/ 4, /*p2IsInfiniteOffset=*/ 5, /*dstOffset=*/ 0)
|
|
957
|
+
]
|
|
958
|
+
}
|
|
959
|
+
],
|
|
960
|
+
[Opcode.TORADIXBE]: [
|
|
961
|
+
{
|
|
962
|
+
setup: [
|
|
963
|
+
{
|
|
964
|
+
offset: 0,
|
|
965
|
+
value: new Field(0xdeadbeefcafebaben)
|
|
966
|
+
},
|
|
967
|
+
{
|
|
968
|
+
offset: 1,
|
|
969
|
+
value: new Uint32(16n)
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
offset: 2,
|
|
973
|
+
value: new Uint32(16n)
|
|
974
|
+
},
|
|
975
|
+
{
|
|
976
|
+
offset: 3,
|
|
977
|
+
value: new Uint1(0n)
|
|
978
|
+
}
|
|
979
|
+
],
|
|
980
|
+
targetInstructions: ()=>[
|
|
981
|
+
new ToRadixBE(/*indirect=*/ 0, /*srcOffset=*/ 0, /*radixOffset=*/ 1, /*numLimbsOffset=*/ 2, /*outputBitsOffset=*/ 3, /*dstOffset=*/ 4)
|
|
982
|
+
]
|
|
983
|
+
}
|
|
984
|
+
],
|
|
985
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
986
|
+
// MISC
|
|
987
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
988
|
+
[Opcode.DEBUGLOG]: [
|
|
989
|
+
{
|
|
990
|
+
setup: [
|
|
991
|
+
{
|
|
992
|
+
offset: 0,
|
|
993
|
+
value: new Field(0n)
|
|
994
|
+
},
|
|
995
|
+
{
|
|
996
|
+
offset: 1,
|
|
997
|
+
value: new Field(0n)
|
|
998
|
+
},
|
|
999
|
+
{
|
|
1000
|
+
offset: 2,
|
|
1001
|
+
value: new Field(0n)
|
|
1002
|
+
},
|
|
1003
|
+
{
|
|
1004
|
+
offset: 3,
|
|
1005
|
+
value: new Uint32(0n)
|
|
1006
|
+
}
|
|
1007
|
+
],
|
|
1008
|
+
// messageSize = 0
|
|
1009
|
+
targetInstructions: ()=>[
|
|
1010
|
+
new DebugLog(/*indirect=*/ 0, /*levelOffset=*/ 0, /*messageOffset=*/ 1, /*fieldsOffset=*/ 2, /*fieldsSizeOffset=*/ 3, /*messageSize=*/ 0)
|
|
1011
|
+
]
|
|
1012
|
+
}
|
|
1013
|
+
]
|
|
1014
|
+
};
|
|
1015
|
+
/**
|
|
1016
|
+
* Get all spam test cases grouped by opcode.
|
|
1017
|
+
* This is the main entry point for tests - it handles all the complexity of
|
|
1018
|
+
* type variants, multiple configs, etc.
|
|
1019
|
+
*
|
|
1020
|
+
* Returns hierarchical structure for nested describe blocks in tests.
|
|
1021
|
+
*
|
|
1022
|
+
* @param maxConfigsPerOpcode - Maximum number of configs to include per opcode.
|
|
1023
|
+
* Defaults to Infinity (no limit). Useful for quick
|
|
1024
|
+
* smoke tests where testing all type variants is too slow,
|
|
1025
|
+
* or for proving tests that are inherently slower.
|
|
1026
|
+
*/ export function getSpamConfigsPerOpcode(maxConfigsPerOpcode = Infinity) {
|
|
1027
|
+
const groups = [];
|
|
1028
|
+
for (const [opcodeKey, configs] of Object.entries(SPAM_CONFIGS)){
|
|
1029
|
+
const opcode = Opcode[Number(opcodeKey)];
|
|
1030
|
+
if (!configs) {
|
|
1031
|
+
throw new Error(`Opcode ${opcode} listed in spam configs, but empty`);
|
|
1032
|
+
}
|
|
1033
|
+
// Apply the limit to the number of configs per opcode
|
|
1034
|
+
const limitedConfigs = configs.slice(0, maxConfigsPerOpcode);
|
|
1035
|
+
const cases = limitedConfigs.map((config)=>({
|
|
1036
|
+
...config,
|
|
1037
|
+
// unlabeled configs just get opcode name
|
|
1038
|
+
label: config.label ? `${opcode}/${config.label}` : opcode
|
|
1039
|
+
}));
|
|
1040
|
+
groups.push({
|
|
1041
|
+
opcode: opcode,
|
|
1042
|
+
configs: cases
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
return groups;
|
|
1046
|
+
}
|
|
1047
|
+
// ============================================================================
|
|
1048
|
+
// Helper Functions
|
|
1049
|
+
// ============================================================================
|
|
1050
|
+
/**
|
|
1051
|
+
* Create a SET instruction from a MemoryValue.
|
|
1052
|
+
* Chooses smallest SET variant based on offset and value magnitude for optimal bytecode density.
|
|
1053
|
+
*/ function createSetInstruction(offset, memValue) {
|
|
1054
|
+
const tag = memValue.getTag();
|
|
1055
|
+
const value = memValue.toBigInt();
|
|
1056
|
+
// SET_8 only supports offset <= 255 and value <= 255
|
|
1057
|
+
if (offset <= 0xff && value <= 0xffn) {
|
|
1058
|
+
return new Set(0, offset, tag, Number(value)).as(Opcode.SET_8, Set.wireFormat8);
|
|
1059
|
+
}
|
|
1060
|
+
// SET_16+ support offset <= 65535
|
|
1061
|
+
if (value <= 0xffffn) {
|
|
1062
|
+
return new Set(0, offset, tag, Number(value)).as(Opcode.SET_16, Set.wireFormat16);
|
|
1063
|
+
}
|
|
1064
|
+
if (value <= 0xffffffffn) {
|
|
1065
|
+
return new Set(0, offset, tag, Number(value)).as(Opcode.SET_32, Set.wireFormat32);
|
|
1066
|
+
}
|
|
1067
|
+
if (value <= 0xffffffffffffffffn) {
|
|
1068
|
+
return new Set(0, offset, tag, value).as(Opcode.SET_64, Set.wireFormat64);
|
|
1069
|
+
}
|
|
1070
|
+
if (value <= 0xffffffffffffffffffffffffffffffffn) {
|
|
1071
|
+
return new Set(0, offset, tag, value).as(Opcode.SET_128, Set.wireFormat128);
|
|
1072
|
+
}
|
|
1073
|
+
return new Set(0, offset, tag, value).as(Opcode.SET_FF, Set.wireFormatFF);
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Append (to the instructions array) the SET instructions for the setup.
|
|
1077
|
+
*
|
|
1078
|
+
* @param instructions - the instructions array to append the setup to
|
|
1079
|
+
* @param setup - the setup configuration specifying what SETs to do
|
|
1080
|
+
*/ function appendSetupInstructions(instructions, setup) {
|
|
1081
|
+
for (const item of setup){
|
|
1082
|
+
if (typeof item === 'function') {
|
|
1083
|
+
// item is a function that creates setup instructions (like)
|
|
1084
|
+
instructions.push(...item());
|
|
1085
|
+
} else {
|
|
1086
|
+
// MemSetup
|
|
1087
|
+
instructions.push(createSetInstruction(item.offset, item.value));
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Append (to the instructions array) the target instructions nTimes times.
|
|
1093
|
+
*
|
|
1094
|
+
* @param instructions - the instructions array to append the loop to
|
|
1095
|
+
* @param config - the spam config to use
|
|
1096
|
+
* @param nTimes - the number of times to append the target instructions
|
|
1097
|
+
* @returns the number of target instructions appended
|
|
1098
|
+
*/ function appendTargetNTimes(instructions, config, nTimes) {
|
|
1099
|
+
for(let i = 0; i < nTimes; i++){
|
|
1100
|
+
instructions.push(...config.targetInstructions());
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Append (to the instructions array) an infinite loop that maximizes target instruction density.
|
|
1105
|
+
* Fills remaining bytecode space with unrolled target instructions.
|
|
1106
|
+
*
|
|
1107
|
+
* @param instructions - the instructions array to append the loop to
|
|
1108
|
+
* @param config - the spam config to use
|
|
1109
|
+
* @returns the number of target instructions in the loop body
|
|
1110
|
+
*/ function appendInfiniteLoop(instructions, config) {
|
|
1111
|
+
const setupBytecode = encodeToBytecode(instructions);
|
|
1112
|
+
const setupSize = setupBytecode.length;
|
|
1113
|
+
// Compute the size of the target instruction(s)
|
|
1114
|
+
const targetSize = encodeToBytecode(config.targetInstructions()).length;
|
|
1115
|
+
// Fill remaining space (loop body) with target instructions
|
|
1116
|
+
const availableForLoopBody = MAX_BYTECODE_BYTES - setupSize - JUMP_SIZE;
|
|
1117
|
+
const numTargetsInLoopBody = Math.floor(availableForLoopBody / targetSize);
|
|
1118
|
+
const loopStartPc = setupSize;
|
|
1119
|
+
appendTargetNTimes(instructions, config, numTargetsInLoopBody);
|
|
1120
|
+
instructions.push(new Jump(loopStartPc)); // JUMP_SIZE (JUMP_32)
|
|
1121
|
+
return numTargetsInLoopBody;
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Generate basic opcode spam bytecode from a SpamConfig.
|
|
1125
|
+
* Spams the target instruction(s) in an infinite loop until out-of-gas.
|
|
1126
|
+
*/ export function createOpcodeSpamBytecode(config) {
|
|
1127
|
+
assert(config.limit === undefined, 'If config has `limit`, use createSideEffectLimitedSpamInRevertingNestedCall instead');
|
|
1128
|
+
const instructions = [];
|
|
1129
|
+
// 1. Setup memory
|
|
1130
|
+
appendSetupInstructions(instructions, config.setup);
|
|
1131
|
+
// 2. Infinite loop - maximize iterations until out-of-gas
|
|
1132
|
+
appendInfiniteLoop(instructions, config);
|
|
1133
|
+
return encodeToBytecode(instructions);
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Generate a bytecode that spams a side-effect limited opcode #limit times
|
|
1137
|
+
* NOT in a loop, but inline/unrolled. Then revert.
|
|
1138
|
+
*
|
|
1139
|
+
* @param config - the side-effect limited spam config to use
|
|
1140
|
+
* @returns the bytecode for the side-effect limited spam
|
|
1141
|
+
*/ export function createSideEffectSpamBytecode(config) {
|
|
1142
|
+
assert(config.limit !== undefined, 'If config has `limit`, use createSideEffectLimitedSpamInRevertingNestedCall instead');
|
|
1143
|
+
const instructions = [];
|
|
1144
|
+
// 1. Setup
|
|
1145
|
+
appendSetupInstructions(instructions, config.setup);
|
|
1146
|
+
// 2. Body - run target instruction(s) #limit times
|
|
1147
|
+
appendTargetNTimes(instructions, config, config.limit);
|
|
1148
|
+
// 3. Cleanup (revert)
|
|
1149
|
+
if (config.cleanupInstructions) {
|
|
1150
|
+
instructions.push(...config.cleanupInstructions());
|
|
1151
|
+
}
|
|
1152
|
+
return encodeToBytecode(instructions);
|
|
1153
|
+
}
|
|
1154
|
+
/** Reserved memory offsets for outer call loop */ const CONST_1_OFFSET = 0;
|
|
1155
|
+
const CALL_L2_GAS_OFFSET = 1;
|
|
1156
|
+
const CALL_DA_GAS_OFFSET = 2;
|
|
1157
|
+
const CALL_ADDR_OFFSET = 3;
|
|
1158
|
+
const CALL_ARGS_SIZE = 4;
|
|
1159
|
+
const CALL_ARGS_OFFSET = 5;
|
|
1160
|
+
const CALLDATA_INDEX_OFFSET = 6; // calldata index as in calldata[index]
|
|
1161
|
+
/**
|
|
1162
|
+
* A SpamConfig for an external call loop.
|
|
1163
|
+
*/ const EXTERNAL_CALL_LOOP_CONFIG = {
|
|
1164
|
+
setup: [
|
|
1165
|
+
// calldata will contain 1 item: the external call address
|
|
1166
|
+
{
|
|
1167
|
+
offset: CONST_1_OFFSET,
|
|
1168
|
+
value: new Uint32(1)
|
|
1169
|
+
},
|
|
1170
|
+
{
|
|
1171
|
+
offset: CALLDATA_INDEX_OFFSET,
|
|
1172
|
+
value: new Uint32(0)
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
offset: CALL_L2_GAS_OFFSET,
|
|
1176
|
+
value: new Uint32(0xffffffffn)
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
offset: CALL_DA_GAS_OFFSET,
|
|
1180
|
+
value: new Uint32(0xffffffffn)
|
|
1181
|
+
},
|
|
1182
|
+
()=>[
|
|
1183
|
+
new CalldataCopy(/*indirect=*/ 0, /*copySizeOffset=*/ CONST_1_OFFSET, /*cdStartOffset=*/ CALLDATA_INDEX_OFFSET, /*dstOffset=*/ CALL_ADDR_OFFSET)
|
|
1184
|
+
],
|
|
1185
|
+
{
|
|
1186
|
+
offset: CALL_ARGS_SIZE,
|
|
1187
|
+
value: new Uint32(0)
|
|
1188
|
+
},
|
|
1189
|
+
{
|
|
1190
|
+
offset: CALL_ARGS_SIZE,
|
|
1191
|
+
value: new Uint32(0)
|
|
1192
|
+
}
|
|
1193
|
+
],
|
|
1194
|
+
targetInstructions: ()=>[
|
|
1195
|
+
new Call(/*indirect=*/ 0, /*l2GasOffset=*/ CALL_L2_GAS_OFFSET, /*daGasOffset=*/ CALL_DA_GAS_OFFSET, /*addrOffset=*/ CALL_ADDR_OFFSET, /*argsSizeOffset=*/ CALL_ARGS_SIZE, /*argsOffset=*/ CALL_ARGS_OFFSET)
|
|
1196
|
+
]
|
|
1197
|
+
};
|
|
1198
|
+
/**
|
|
1199
|
+
* Create bytecode that makes an external call in a loop.
|
|
1200
|
+
*
|
|
1201
|
+
* @returns the bytecode for the external call loop
|
|
1202
|
+
*/ export function createExternalCallLoopBytecode() {
|
|
1203
|
+
const config = EXTERNAL_CALL_LOOP_CONFIG;
|
|
1204
|
+
const instructions = [];
|
|
1205
|
+
// 1. Setup memory
|
|
1206
|
+
appendSetupInstructions(instructions, config.setup);
|
|
1207
|
+
// 2. Infinite loop of external calls - maximize iterations until out-of-gas
|
|
1208
|
+
appendInfiniteLoop(instructions, config);
|
|
1209
|
+
return encodeToBytecode(instructions);
|
|
1210
|
+
}
|
|
1211
|
+
async function testStandardOpcodeSpam(tester, config, expectToBeTrue) {
|
|
1212
|
+
const bytecode = createOpcodeSpamBytecode(config);
|
|
1213
|
+
const result = await deployAndExecuteCustomBytecode(bytecode, tester, config.label);
|
|
1214
|
+
// should have halted with out of gas
|
|
1215
|
+
expectToBeTrue(!result.revertCode.isOK());
|
|
1216
|
+
const revertReason = result.findRevertReason()?.message.toLowerCase();
|
|
1217
|
+
const allowedReasons = [
|
|
1218
|
+
'out of gas',
|
|
1219
|
+
'not enough l2gas'
|
|
1220
|
+
];
|
|
1221
|
+
// expect the reason to match ONE of the allowed reasons
|
|
1222
|
+
expectToBeTrue(allowedReasons.some((allowedReason)=>revertReason.includes(allowedReason)));
|
|
1223
|
+
return result;
|
|
1224
|
+
}
|
|
1225
|
+
async function testSideEffectOpcodeSpam(tester, config, expectToBeTrue) {
|
|
1226
|
+
const innerBytecode = createSideEffectSpamBytecode(config);
|
|
1227
|
+
const outerBytecode = createExternalCallLoopBytecode();
|
|
1228
|
+
const innerContract = await deployCustomBytecode(innerBytecode, tester, `${config.label}_Inner`);
|
|
1229
|
+
const outerContract = await deployCustomBytecode(outerBytecode, tester, `${config.label}_Outer`);
|
|
1230
|
+
// Outer contract reads calldata[0] as inner contract address to CALL to
|
|
1231
|
+
const result = await executeCustomBytecode(outerContract, tester, config.label, [
|
|
1232
|
+
innerContract.address.toField()
|
|
1233
|
+
]);
|
|
1234
|
+
// should have halted with out of gas or explicit REVERT (assertion failed)
|
|
1235
|
+
expectToBeTrue(!result.revertCode.isOK());
|
|
1236
|
+
const revertReason = result.findRevertReason()?.message.toLowerCase();
|
|
1237
|
+
const allowedReasons = [
|
|
1238
|
+
'assertion failed',
|
|
1239
|
+
'out of gas',
|
|
1240
|
+
'not enough l2gas'
|
|
1241
|
+
];
|
|
1242
|
+
// expect the reason to match ONE of the allowed reasons
|
|
1243
|
+
expectToBeTrue(allowedReasons.some((allowedReason)=>revertReason.includes(allowedReason)));
|
|
1244
|
+
// Top-level should _always_ run out of gas for these tests
|
|
1245
|
+
// Check top-level halting message
|
|
1246
|
+
// WARNING: only the C++ simulator (or TsVsCpp) will have haltingMessage
|
|
1247
|
+
const allowedOuterReasons = [
|
|
1248
|
+
'out of gas',
|
|
1249
|
+
'not enough l2gas'
|
|
1250
|
+
];
|
|
1251
|
+
const outerCallMetadata = result.callStackMetadata[0];
|
|
1252
|
+
const outerReason = outerCallMetadata.haltingMessage?.toLowerCase();
|
|
1253
|
+
// expect the reason to match ONE of the allowed reasons
|
|
1254
|
+
expectToBeTrue(allowedOuterReasons.some((allowedReason)=>outerReason.includes(allowedReason)));
|
|
1255
|
+
return result;
|
|
1256
|
+
}
|
|
1257
|
+
export async function testOpcodeSpamCase(tester, config, expectToBeTrue = ()=>{}) {
|
|
1258
|
+
if (config.limit) {
|
|
1259
|
+
return await testSideEffectOpcodeSpam(tester, config, expectToBeTrue);
|
|
1260
|
+
}
|
|
1261
|
+
return await testStandardOpcodeSpam(tester, config, expectToBeTrue);
|
|
1262
|
+
}
|