@aztec/simulator 3.0.0-nightly.20251217 → 3.0.0-nightly.20251219
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 +1 -3
- 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/fixtures/custom_bytecode_tests.d.ts +3 -1
- package/dest/public/fixtures/custom_bytecode_tests.d.ts.map +1 -1
- package/dest/public/fixtures/custom_bytecode_tests.js +45 -1
- package/dest/public/fixtures/opcode_spammer.d.ts +3 -7
- package/dest/public/fixtures/opcode_spammer.d.ts.map +1 -1
- package/dest/public/fixtures/opcode_spammer.js +443 -166
- package/dest/public/fuzzing/avm_simulator_bin.js +1 -0
- package/dest/public/public_tx_simulator/contract_provider_for_cpp.js +11 -11
- package/package.json +16 -16
- package/src/public/avm/avm_memory_types.ts +2 -2
- package/src/public/fixtures/custom_bytecode_tests.ts +61 -1
- package/src/public/fixtures/opcode_spammer.ts +434 -153
- package/src/public/fuzzing/avm_simulator_bin.ts +1 -0
- package/src/public/public_tx_simulator/contract_provider_for_cpp.ts +11 -11
|
@@ -159,23 +159,14 @@ import {
|
|
|
159
159
|
PUBLIC_LOG_HEADER_LENGTH,
|
|
160
160
|
} from '@aztec/constants';
|
|
161
161
|
import { Grumpkin } from '@aztec/foundation/crypto/grumpkin';
|
|
162
|
+
import { randomBigInt } from '@aztec/foundation/crypto/random';
|
|
162
163
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
163
164
|
import type { Bufferable } from '@aztec/foundation/serialize';
|
|
164
165
|
import type { CallStackMetadata, PublicTxResult } from '@aztec/stdlib/avm';
|
|
165
166
|
|
|
166
167
|
import assert from 'assert';
|
|
167
168
|
|
|
168
|
-
import {
|
|
169
|
-
Field,
|
|
170
|
-
INTEGRAL_TAGS,
|
|
171
|
-
type MemoryValue,
|
|
172
|
-
TaggedMemory,
|
|
173
|
-
TypeTag,
|
|
174
|
-
Uint1,
|
|
175
|
-
Uint32,
|
|
176
|
-
Uint64,
|
|
177
|
-
VALID_TAGS,
|
|
178
|
-
} from '../avm/avm_memory_types.js';
|
|
169
|
+
import { Field, type MemoryValue, TaggedMemory, TypeTag, Uint1, Uint32 } from '../avm/avm_memory_types.js';
|
|
179
170
|
import {
|
|
180
171
|
Add,
|
|
181
172
|
And,
|
|
@@ -192,6 +183,8 @@ import {
|
|
|
192
183
|
FieldDiv,
|
|
193
184
|
GetContractInstance,
|
|
194
185
|
GetEnvVar,
|
|
186
|
+
InternalCall,
|
|
187
|
+
InternalReturn,
|
|
195
188
|
Jump,
|
|
196
189
|
JumpI,
|
|
197
190
|
KeccakF1600,
|
|
@@ -205,6 +198,7 @@ import {
|
|
|
205
198
|
NullifierExists,
|
|
206
199
|
Or,
|
|
207
200
|
Poseidon2,
|
|
201
|
+
Return,
|
|
208
202
|
ReturndataCopy,
|
|
209
203
|
ReturndataSize,
|
|
210
204
|
Revert,
|
|
@@ -215,6 +209,7 @@ import {
|
|
|
215
209
|
Sha256Compression,
|
|
216
210
|
Shl,
|
|
217
211
|
Shr,
|
|
212
|
+
StaticCall,
|
|
218
213
|
Sub,
|
|
219
214
|
SuccessCopy,
|
|
220
215
|
ToRadixBE,
|
|
@@ -223,7 +218,6 @@ import {
|
|
|
223
218
|
import { encodeToBytecode } from '../avm/serialization/bytecode_serialization.js';
|
|
224
219
|
import { Opcode } from '../avm/serialization/instruction_serialization.js';
|
|
225
220
|
import { deployCustomBytecode, executeCustomBytecode } from './custom_bytecode_tester.js';
|
|
226
|
-
import { deployAndExecuteCustomBytecode } from './index.js';
|
|
227
221
|
import type { PublicTxSimulationTester } from './public_tx_simulation_tester.js';
|
|
228
222
|
|
|
229
223
|
// ============================================================================
|
|
@@ -269,6 +263,9 @@ export interface SpamConfig {
|
|
|
269
263
|
|
|
270
264
|
/** Optional label for this config variant (e.g., UINT8 or MAXSIZE) */
|
|
271
265
|
label?: string;
|
|
266
|
+
|
|
267
|
+
/** Whether to pass the contract address as calldata[0] */
|
|
268
|
+
addressAsCalldata?: boolean;
|
|
272
269
|
}
|
|
273
270
|
|
|
274
271
|
/**
|
|
@@ -302,19 +299,147 @@ const BYTES_PER_FIELD = Fr.SIZE_IN_BYTES - 1; // 31 bytes of data per field
|
|
|
302
299
|
const MAX_BYTECODE_BYTES = (MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS - 1) * BYTES_PER_FIELD;
|
|
303
300
|
|
|
304
301
|
const JUMP_SIZE = encodeToBytecode([new Jump(0)]).length; // JUMP_32
|
|
302
|
+
const INTERNALCALL_SIZE = encodeToBytecode([new InternalCall(0)]).length;
|
|
305
303
|
|
|
306
304
|
// ============================================================================
|
|
307
305
|
// Type Variant Helpers (for generating multiple configs per opcode)
|
|
308
306
|
// ============================================================================
|
|
309
307
|
|
|
310
|
-
|
|
311
|
-
const
|
|
308
|
+
// Not using these sets directly because we want to control order
|
|
309
|
+
//const ALL_TAGS = Array.from(VALID_TAGS);
|
|
310
|
+
//const INT_TAGS = Array.from(INTEGRAL_TAGS);
|
|
311
|
+
// Ordered so that limiting #configs per opcode still tests max size (Field)
|
|
312
|
+
const ALL_TAGS = [
|
|
313
|
+
TypeTag.FIELD,
|
|
314
|
+
TypeTag.UINT1,
|
|
315
|
+
TypeTag.UINT8,
|
|
316
|
+
TypeTag.UINT16,
|
|
317
|
+
TypeTag.UINT32,
|
|
318
|
+
TypeTag.UINT64,
|
|
319
|
+
TypeTag.UINT128,
|
|
320
|
+
];
|
|
321
|
+
|
|
322
|
+
// ordered so that limiting #configs per opcode still tests max size
|
|
323
|
+
const INT_TAGS = [TypeTag.UINT128, TypeTag.UINT1, TypeTag.UINT8, TypeTag.UINT16, TypeTag.UINT32, TypeTag.UINT64];
|
|
312
324
|
|
|
313
325
|
/** Build from tag truncating - shorter name */
|
|
314
326
|
function withTag(v: bigint, tag: TypeTag): MemoryValue {
|
|
315
327
|
return TaggedMemory.buildFromTagTruncating(v, tag);
|
|
316
328
|
}
|
|
317
329
|
|
|
330
|
+
// ============================================================================
|
|
331
|
+
// Random Value Helpers (seeded via SEED env var for reproducibility)
|
|
332
|
+
// ============================================================================
|
|
333
|
+
|
|
334
|
+
/** Modulus (really just max+1) for each integer type tag */
|
|
335
|
+
const TAG_MODULI: Partial<Record<TypeTag, bigint>> = {
|
|
336
|
+
[TypeTag.UINT1]: 2n,
|
|
337
|
+
[TypeTag.UINT8]: 256n,
|
|
338
|
+
[TypeTag.UINT16]: 65536n,
|
|
339
|
+
[TypeTag.UINT32]: 0x1_0000_0000n,
|
|
340
|
+
[TypeTag.UINT64]: 0x1_0000_0000_0000_0000n,
|
|
341
|
+
[TypeTag.UINT128]: 0x1_0000_0000_0000_0000_0000_0000_0000_0000n,
|
|
342
|
+
[TypeTag.FIELD]: Fr.MODULUS,
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
/** Generate a random value with the given type tag. Uses SEED env var if set. */
|
|
346
|
+
function randomWithTag(tag: TypeTag): MemoryValue {
|
|
347
|
+
const modulus = TAG_MODULI[tag];
|
|
348
|
+
if (modulus === undefined) {
|
|
349
|
+
throw new Error(`Unsupported tag for random generation: ${TypeTag[tag]}`);
|
|
350
|
+
}
|
|
351
|
+
const value = randomBigInt(modulus);
|
|
352
|
+
return TaggedMemory.buildFromTagTruncating(value, tag);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Generate a random non-zero value with the given type tag (for division). */
|
|
356
|
+
function randomNonZeroWithTag(tag: TypeTag): MemoryValue {
|
|
357
|
+
const modulus = TAG_MODULI[tag];
|
|
358
|
+
if (modulus === undefined) {
|
|
359
|
+
throw new Error(`Unsupported tag for random generation: ${TypeTag[tag]}`);
|
|
360
|
+
}
|
|
361
|
+
// Generate random in range [1, max) by generating [0, max-1) and adding 1
|
|
362
|
+
const value = randomBigInt(modulus - 1n) + 1n;
|
|
363
|
+
return TaggedMemory.buildFromTagTruncating(value, tag);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** Generate a random non-zero Field value (for field division). */
|
|
367
|
+
function randomNonZeroField(): Field {
|
|
368
|
+
return new Field(randomBigInt(Fr.MODULUS - 1n) + 1n);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** Reserved memory offsets for external call loop (used by CALL spam and side-effect opcodes) */
|
|
372
|
+
const CONST_0_OFFSET = 0; // Uint32(0)
|
|
373
|
+
const CONST_1_OFFSET = 1; // Uint32(1)
|
|
374
|
+
const CONST_MAX_U32_OFFSET = 2; // Uint32(MAX_U32)
|
|
375
|
+
const CALL_ADDR_OFFSET = 3; // copy addr from calldata to here, and then use this addr for CALL
|
|
376
|
+
const CALL_ARGS_OFFSET = CALL_ADDR_OFFSET; // address is the arg to send to CALL
|
|
377
|
+
const CALL_COPY_SIZE_OFFSET = CONST_1_OFFSET; // copy size = 1 (forward calldata[0])
|
|
378
|
+
const CALL_CALLDATA_INDEX_OFFSET = CONST_0_OFFSET; // calldata[0]
|
|
379
|
+
const CALL_L2_GAS_OFFSET = CONST_MAX_U32_OFFSET; // MAX_U32 gets capped to remaining gas by AVM
|
|
380
|
+
const CALL_DA_GAS_OFFSET = CONST_MAX_U32_OFFSET; // MAX_U32 gets capped to remaining gas by AVM
|
|
381
|
+
const CALL_ARGS_SIZE_OFFSET = CONST_1_OFFSET; // argsSize = 1 (forward calldata[0] - might contain contract address)
|
|
382
|
+
const MAX_U32 = 0xffffffffn;
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* A SpamConfig for to make external CALLs to an address specified in calldata[0].
|
|
386
|
+
*/
|
|
387
|
+
const EXTERNAL_CALL_CONFIG: SpamConfig = {
|
|
388
|
+
setup: [
|
|
389
|
+
// calldata will contain 1 item: the external call address
|
|
390
|
+
{ offset: CONST_0_OFFSET, value: new Uint32(0) }, // used for cdStartOffset
|
|
391
|
+
{ offset: CONST_1_OFFSET, value: new Uint32(1) }, // used for copySize and argsSize
|
|
392
|
+
{ offset: CONST_MAX_U32_OFFSET, value: new Uint32(MAX_U32) }, // l2Gas/daGas - MAX_U32 gets capped to remaining gas
|
|
393
|
+
() => [
|
|
394
|
+
new CalldataCopy(
|
|
395
|
+
/*indirect=*/ 0,
|
|
396
|
+
/*copySizeOffset=*/ CALL_COPY_SIZE_OFFSET,
|
|
397
|
+
/*cdStartOffset=*/ CALL_CALLDATA_INDEX_OFFSET,
|
|
398
|
+
/*dstOffset=*/ CALL_ADDR_OFFSET,
|
|
399
|
+
),
|
|
400
|
+
], // address = calldata[0] of parent call
|
|
401
|
+
],
|
|
402
|
+
targetInstructions: () => [
|
|
403
|
+
new Call(
|
|
404
|
+
/*indirect=*/ 0,
|
|
405
|
+
/*l2GasOffset=*/ CALL_L2_GAS_OFFSET,
|
|
406
|
+
/*daGasOffset=*/ CALL_DA_GAS_OFFSET,
|
|
407
|
+
/*addrOffset=*/ CALL_ADDR_OFFSET,
|
|
408
|
+
/*argsSizeOffset=*/ CALL_ARGS_SIZE_OFFSET,
|
|
409
|
+
/*argsOffset=*/ CALL_ARGS_OFFSET,
|
|
410
|
+
),
|
|
411
|
+
],
|
|
412
|
+
addressAsCalldata: true, // indicates that the contract address should be passed as calldata[0]
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
const STATIC_CALL_CONFIG: SpamConfig = {
|
|
416
|
+
setup: [
|
|
417
|
+
// calldata will contain 1 item: the external call address
|
|
418
|
+
{ offset: CONST_0_OFFSET, value: new Uint32(0) }, // used for cdStartOffset
|
|
419
|
+
{ offset: CONST_1_OFFSET, value: new Uint32(1) }, // used for copySize and argsSize
|
|
420
|
+
{ offset: CONST_MAX_U32_OFFSET, value: new Uint32(MAX_U32) }, // l2Gas/daGas - MAX_U32 gets capped to remaining gas
|
|
421
|
+
() => [
|
|
422
|
+
new CalldataCopy(
|
|
423
|
+
/*indirect=*/ 0,
|
|
424
|
+
/*copySizeOffset=*/ CALL_COPY_SIZE_OFFSET,
|
|
425
|
+
/*cdStartOffset=*/ CALL_CALLDATA_INDEX_OFFSET,
|
|
426
|
+
/*dstOffset=*/ CALL_ADDR_OFFSET,
|
|
427
|
+
),
|
|
428
|
+
], // address = calldata[0] of parent call
|
|
429
|
+
],
|
|
430
|
+
targetInstructions: () => [
|
|
431
|
+
new StaticCall(
|
|
432
|
+
/*indirect=*/ 0,
|
|
433
|
+
/*l2GasOffset=*/ CALL_L2_GAS_OFFSET,
|
|
434
|
+
/*daGasOffset=*/ CALL_DA_GAS_OFFSET,
|
|
435
|
+
/*addrOffset=*/ CALL_ADDR_OFFSET,
|
|
436
|
+
/*argsSizeOffset=*/ CALL_ARGS_SIZE_OFFSET,
|
|
437
|
+
/*argsOffset=*/ CALL_ARGS_OFFSET,
|
|
438
|
+
),
|
|
439
|
+
],
|
|
440
|
+
addressAsCalldata: true, // indicates that the contract address should be passed as calldata[0]
|
|
441
|
+
};
|
|
442
|
+
|
|
318
443
|
// ============================================================================
|
|
319
444
|
// Configuration Map
|
|
320
445
|
// ============================================================================
|
|
@@ -326,13 +451,13 @@ function withTag(v: bigint, tag: TypeTag): MemoryValue {
|
|
|
326
451
|
*/
|
|
327
452
|
export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
328
453
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
329
|
-
// ARITHMETIC - Test with all type variants
|
|
454
|
+
// ARITHMETIC - Test with all type variants (random values, seeded via SEED env var)
|
|
330
455
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
331
456
|
[Opcode.ADD_8]: ALL_TAGS.map(tag => ({
|
|
332
457
|
label: TypeTag[tag],
|
|
333
458
|
setup: [
|
|
334
|
-
{ offset: 0, value:
|
|
335
|
-
{ offset: 1, value:
|
|
459
|
+
{ offset: 0, value: randomWithTag(tag) }, // random accumulator
|
|
460
|
+
{ offset: 1, value: randomWithTag(tag) }, // random addend
|
|
336
461
|
],
|
|
337
462
|
targetInstructions: () => [
|
|
338
463
|
new Add(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.ADD_8, Add.wireFormat8),
|
|
@@ -342,8 +467,8 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
342
467
|
[Opcode.SUB_8]: ALL_TAGS.map(tag => ({
|
|
343
468
|
label: TypeTag[tag],
|
|
344
469
|
setup: [
|
|
345
|
-
{ offset: 0, value:
|
|
346
|
-
{ offset: 1, value:
|
|
470
|
+
{ offset: 0, value: randomWithTag(tag) }, // random minuend
|
|
471
|
+
{ offset: 1, value: randomWithTag(tag) }, // random subtrahend
|
|
347
472
|
],
|
|
348
473
|
targetInstructions: () => [
|
|
349
474
|
new Sub(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.SUB_8, Sub.wireFormat8),
|
|
@@ -353,8 +478,8 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
353
478
|
[Opcode.MUL_8]: ALL_TAGS.map(tag => ({
|
|
354
479
|
label: TypeTag[tag],
|
|
355
480
|
setup: [
|
|
356
|
-
{ offset: 0, value:
|
|
357
|
-
{ offset: 1, value:
|
|
481
|
+
{ offset: 0, value: randomWithTag(tag) }, // random multiplicand
|
|
482
|
+
{ offset: 1, value: randomWithTag(tag) }, // random multiplier
|
|
358
483
|
],
|
|
359
484
|
targetInstructions: () => [
|
|
360
485
|
new Mul(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.MUL_8, Mul.wireFormat8),
|
|
@@ -365,8 +490,8 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
365
490
|
[Opcode.DIV_8]: INT_TAGS.map(tag => ({
|
|
366
491
|
label: TypeTag[tag],
|
|
367
492
|
setup: [
|
|
368
|
-
{ offset: 0, value:
|
|
369
|
-
{ offset: 1, value:
|
|
493
|
+
{ offset: 0, value: randomWithTag(tag) }, // random dividend
|
|
494
|
+
{ offset: 1, value: randomNonZeroWithTag(tag) }, // random non-zero divisor
|
|
370
495
|
],
|
|
371
496
|
targetInstructions: () => [
|
|
372
497
|
new Div(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.DIV_8, Div.wireFormat8),
|
|
@@ -377,8 +502,8 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
377
502
|
[Opcode.FDIV_8]: [
|
|
378
503
|
{
|
|
379
504
|
setup: [
|
|
380
|
-
{ offset: 0, value: new Field(
|
|
381
|
-
{ offset: 1, value:
|
|
505
|
+
{ offset: 0, value: new Field(Fr.random()) }, // random dividend
|
|
506
|
+
{ offset: 1, value: randomNonZeroField() }, // random non-zero divisor
|
|
382
507
|
],
|
|
383
508
|
targetInstructions: () => [
|
|
384
509
|
new FieldDiv(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(
|
|
@@ -390,13 +515,13 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
390
515
|
],
|
|
391
516
|
|
|
392
517
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
393
|
-
// COMPARATORS - Test with all type variants
|
|
518
|
+
// COMPARATORS - Test with all type variants (random values)
|
|
394
519
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
395
520
|
[Opcode.EQ_8]: ALL_TAGS.map(tag => ({
|
|
396
521
|
label: TypeTag[tag],
|
|
397
522
|
setup: [
|
|
398
|
-
{ offset: 0, value:
|
|
399
|
-
{ offset: 1, value:
|
|
523
|
+
{ offset: 0, value: randomWithTag(tag) }, // random value a
|
|
524
|
+
{ offset: 1, value: randomWithTag(tag) }, // random value b
|
|
400
525
|
],
|
|
401
526
|
targetInstructions: () => [
|
|
402
527
|
new Eq(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2).as(Opcode.EQ_8, Eq.wireFormat8),
|
|
@@ -406,8 +531,8 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
406
531
|
[Opcode.LT_8]: ALL_TAGS.map(tag => ({
|
|
407
532
|
label: TypeTag[tag],
|
|
408
533
|
setup: [
|
|
409
|
-
{ offset: 0, value:
|
|
410
|
-
{ offset: 1, value:
|
|
534
|
+
{ offset: 0, value: randomWithTag(tag) }, // random value a
|
|
535
|
+
{ offset: 1, value: randomWithTag(tag) }, // random value b
|
|
411
536
|
],
|
|
412
537
|
targetInstructions: () => [
|
|
413
538
|
new Lt(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2).as(Opcode.LT_8, Lt.wireFormat8),
|
|
@@ -417,8 +542,8 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
417
542
|
[Opcode.LTE_8]: ALL_TAGS.map(tag => ({
|
|
418
543
|
label: TypeTag[tag],
|
|
419
544
|
setup: [
|
|
420
|
-
{ offset: 0, value:
|
|
421
|
-
{ offset: 1, value:
|
|
545
|
+
{ offset: 0, value: randomWithTag(tag) }, // random value a
|
|
546
|
+
{ offset: 1, value: randomWithTag(tag) }, // random value b
|
|
422
547
|
],
|
|
423
548
|
targetInstructions: () => [
|
|
424
549
|
new Lte(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2).as(Opcode.LTE_8, Lte.wireFormat8),
|
|
@@ -426,13 +551,13 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
426
551
|
})),
|
|
427
552
|
|
|
428
553
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
429
|
-
// BITWISE - Integer types only (no FIELD)
|
|
554
|
+
// BITWISE - Integer types only (no FIELD) (random values)
|
|
430
555
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
431
556
|
[Opcode.AND_8]: INT_TAGS.map(tag => ({
|
|
432
557
|
label: TypeTag[tag],
|
|
433
558
|
setup: [
|
|
434
|
-
{ offset: 0, value:
|
|
435
|
-
{ offset: 1, value:
|
|
559
|
+
{ offset: 0, value: randomWithTag(tag) }, // random value a
|
|
560
|
+
{ offset: 1, value: randomWithTag(tag) }, // random value b
|
|
436
561
|
],
|
|
437
562
|
targetInstructions: () => [
|
|
438
563
|
new And(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.AND_8, And.wireFormat8),
|
|
@@ -442,8 +567,8 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
442
567
|
[Opcode.OR_8]: INT_TAGS.map(tag => ({
|
|
443
568
|
label: TypeTag[tag],
|
|
444
569
|
setup: [
|
|
445
|
-
{ offset: 0, value:
|
|
446
|
-
{ offset: 1, value:
|
|
570
|
+
{ offset: 0, value: randomWithTag(tag) }, // random value a
|
|
571
|
+
{ offset: 1, value: randomWithTag(tag) }, // random value b
|
|
447
572
|
],
|
|
448
573
|
targetInstructions: () => [
|
|
449
574
|
new Or(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.OR_8, Or.wireFormat8),
|
|
@@ -453,8 +578,8 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
453
578
|
[Opcode.XOR_8]: INT_TAGS.map(tag => ({
|
|
454
579
|
label: TypeTag[tag],
|
|
455
580
|
setup: [
|
|
456
|
-
{ offset: 0, value:
|
|
457
|
-
{ offset: 1, value:
|
|
581
|
+
{ offset: 0, value: randomWithTag(tag) }, // random value a
|
|
582
|
+
{ offset: 1, value: randomWithTag(tag) }, // random value b
|
|
458
583
|
],
|
|
459
584
|
targetInstructions: () => [
|
|
460
585
|
new Xor(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.XOR_8, Xor.wireFormat8),
|
|
@@ -463,7 +588,7 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
463
588
|
|
|
464
589
|
[Opcode.NOT_8]: INT_TAGS.map(tag => ({
|
|
465
590
|
label: TypeTag[tag],
|
|
466
|
-
setup: [{ offset: 0, value:
|
|
591
|
+
setup: [{ offset: 0, value: randomWithTag(tag) }], // random value
|
|
467
592
|
targetInstructions: () => [
|
|
468
593
|
new Not(/*indirect=*/ 0, /*srcOffset=*/ 0, /*dstOffset=*/ 0).as(Opcode.NOT_8, Not.wireFormat8),
|
|
469
594
|
],
|
|
@@ -472,8 +597,8 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
472
597
|
[Opcode.SHL_8]: INT_TAGS.map(tag => ({
|
|
473
598
|
label: TypeTag[tag],
|
|
474
599
|
setup: [
|
|
475
|
-
{ offset: 0, value:
|
|
476
|
-
{ offset: 1, value: withTag(1n, tag) },
|
|
600
|
+
{ offset: 0, value: randomWithTag(tag) }, // random value to shift
|
|
601
|
+
{ offset: 1, value: withTag(1n, tag) }, // shift by 1 (small fixed amount to avoid overflow)
|
|
477
602
|
],
|
|
478
603
|
targetInstructions: () => [
|
|
479
604
|
new Shl(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.SHL_8, Shl.wireFormat8),
|
|
@@ -483,8 +608,8 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
483
608
|
[Opcode.SHR_8]: INT_TAGS.map(tag => ({
|
|
484
609
|
label: TypeTag[tag],
|
|
485
610
|
setup: [
|
|
486
|
-
{ offset: 0, value:
|
|
487
|
-
{ offset: 1, value: withTag(1n, tag) },
|
|
611
|
+
{ offset: 0, value: randomWithTag(tag) }, // random value to shift
|
|
612
|
+
{ offset: 1, value: withTag(1n, tag) }, // shift by 1 (small fixed amount)
|
|
488
613
|
],
|
|
489
614
|
targetInstructions: () => [
|
|
490
615
|
new Shr(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.SHR_8, Shr.wireFormat8),
|
|
@@ -492,11 +617,11 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
492
617
|
})),
|
|
493
618
|
|
|
494
619
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
495
|
-
// CAST / MOV - Test with all type variants
|
|
620
|
+
// CAST / MOV - Test with all type variants (random values)
|
|
496
621
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
497
622
|
[Opcode.CAST_8]: ALL_TAGS.map(tag => ({
|
|
498
623
|
label: TypeTag[tag],
|
|
499
|
-
setup: [{ offset: 0, value:
|
|
624
|
+
setup: [{ offset: 0, value: randomWithTag(tag) }], // random value to cast
|
|
500
625
|
targetInstructions: () => [
|
|
501
626
|
new Cast(/*indirect=*/ 0, /*srcOffset=*/ 0, /*dstOffset=*/ 1, /*dstTag=*/ TypeTag.UINT32).as(
|
|
502
627
|
Opcode.CAST_8,
|
|
@@ -507,7 +632,7 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
507
632
|
|
|
508
633
|
[Opcode.MOV_8]: ALL_TAGS.map(tag => ({
|
|
509
634
|
label: TypeTag[tag],
|
|
510
|
-
setup: [{ offset: 0, value:
|
|
635
|
+
setup: [{ offset: 0, value: randomWithTag(tag) }], // random value to move
|
|
511
636
|
targetInstructions: () => [
|
|
512
637
|
new Mov(/*indirect=*/ 0, /*srcOffset=*/ 0, /*dstOffset=*/ 1).as(Opcode.MOV_8, Mov.wireFormat8),
|
|
513
638
|
],
|
|
@@ -583,6 +708,65 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
583
708
|
},
|
|
584
709
|
],
|
|
585
710
|
|
|
711
|
+
// INTERNALCALL: calls itself infinitely by jumping to its own PC (PC 0, since no setup)
|
|
712
|
+
// Creates infinite recursion until OOG (internal call stack grows forever)
|
|
713
|
+
[Opcode.INTERNALCALL]: [
|
|
714
|
+
{
|
|
715
|
+
setup: [],
|
|
716
|
+
targetInstructions: () => [new InternalCall(/*loc=*/ 0)],
|
|
717
|
+
},
|
|
718
|
+
],
|
|
719
|
+
|
|
720
|
+
// INTERNALRETURN: needs INTERNALCALL to return without error
|
|
721
|
+
// Layout: INTERNALCALL(10) -> JUMP(0) -> INTERNALRETURN
|
|
722
|
+
// INTERNALCALL jumps to INTERNALRETURN, which returns to JUMP, which loops back
|
|
723
|
+
[Opcode.INTERNALRETURN]: [
|
|
724
|
+
{
|
|
725
|
+
setup: [],
|
|
726
|
+
targetInstructions: () => [
|
|
727
|
+
new InternalCall(/*loc=*/ INTERNALCALL_SIZE + JUMP_SIZE), // jump to INTERNALRETURN
|
|
728
|
+
new Jump(/*jumpOffset=*/ 0), // loop back to start
|
|
729
|
+
new InternalReturn(), // return back to jump
|
|
730
|
+
],
|
|
731
|
+
},
|
|
732
|
+
],
|
|
733
|
+
|
|
734
|
+
// CALL (EXTERNALCALL): calls the current contract address (self) in a loop
|
|
735
|
+
// Contract address is passed via calldata[0] and propagated to nested calls
|
|
736
|
+
[Opcode.CALL]: [EXTERNAL_CALL_CONFIG],
|
|
737
|
+
[Opcode.STATICCALL]: [STATIC_CALL_CONFIG],
|
|
738
|
+
|
|
739
|
+
// RETURN: terminates execution, so we need to use the two-contract pattern
|
|
740
|
+
// Outer contract CALLs inner contract in a loop, inner contract does RETURN
|
|
741
|
+
[Opcode.RETURN]: [
|
|
742
|
+
{
|
|
743
|
+
setup: [
|
|
744
|
+
{ offset: 0, value: new Uint32(0) }, // returnSize = 0
|
|
745
|
+
],
|
|
746
|
+
targetInstructions: () => [
|
|
747
|
+
new Return(/*indirect=*/ 0, /*returnSizeOffset=*/ 0, /*returnOffset=*/ 0), // return nothing (size=0)
|
|
748
|
+
],
|
|
749
|
+
// Use the side-effect-limit pattern (even though it's not a side-effect) as it fits
|
|
750
|
+
// this case (we want to CALL, RETURN, then CALL again back in parent). We omit "cleanup"
|
|
751
|
+
// because we don't need to REVERT as we do for real side-effects.
|
|
752
|
+
limit: 1, // RETURN can only execute once per call
|
|
753
|
+
},
|
|
754
|
+
],
|
|
755
|
+
|
|
756
|
+
// REVERT: terminates execution, so we need to use the two-contract pattern
|
|
757
|
+
// Outer contract CALLs inner contract in a loop, inner contract does REVERT
|
|
758
|
+
[Opcode.REVERT_8]: [
|
|
759
|
+
{
|
|
760
|
+
setup: [
|
|
761
|
+
{ offset: 0, value: new Uint32(0) }, // retSize = 0
|
|
762
|
+
],
|
|
763
|
+
targetInstructions: () => [
|
|
764
|
+
new Revert(/*indirect=*/ 0, /*retSizeOffset=*/ 0, /*returnOffset=*/ 1).as(Opcode.REVERT_8, Revert.wireFormat8),
|
|
765
|
+
],
|
|
766
|
+
limit: 1, // REVERT can only execute once per call
|
|
767
|
+
},
|
|
768
|
+
],
|
|
769
|
+
|
|
586
770
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
587
771
|
// ENVIRONMENT
|
|
588
772
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -598,14 +782,38 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
598
782
|
},
|
|
599
783
|
],
|
|
600
784
|
|
|
785
|
+
// CALLDATACOPY has dynamic gas scaling with copySize
|
|
601
786
|
[Opcode.CALLDATACOPY]: [
|
|
602
787
|
{
|
|
603
|
-
|
|
604
|
-
//
|
|
605
|
-
|
|
788
|
+
label: 'Min copy size',
|
|
789
|
+
// CalldataCopy with copySize=0 is a no-op but still executes the opcode
|
|
790
|
+
setup: [
|
|
791
|
+
{ offset: 0, value: new Uint32(0n) }, // copySize = 0 (minimum)
|
|
792
|
+
{ offset: 1, value: new Uint32(0n) }, // cdStart = 0
|
|
793
|
+
],
|
|
794
|
+
targetInstructions: () => [
|
|
795
|
+
new CalldataCopy(/*indirect=*/ 0, /*copySizeOffset=*/ 0, /*cdStartOffset=*/ 1, /*dstOffset=*/ 2),
|
|
796
|
+
],
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
label: 'Large copy size',
|
|
800
|
+
// Large copySize with large dynamic gas - will OOG quickly
|
|
801
|
+
// NOTE: we don't want it so large that it exceeds memory bounds (MAX_MEMORY_SIZE = 2^32)
|
|
802
|
+
// and we really want it small enough that we run at least 1 successful target opcode.
|
|
803
|
+
setup: [
|
|
804
|
+
{ offset: 0, value: new Uint32(1000n) }, // copySize = 1000 (large enough to show scaling)
|
|
805
|
+
{ offset: 1, value: new Uint32(0n) }, // cdStart = 0
|
|
806
|
+
],
|
|
807
|
+
targetInstructions: () => [
|
|
808
|
+
new CalldataCopy(/*indirect=*/ 0, /*copySizeOffset=*/ 0, /*cdStartOffset=*/ 1, /*dstOffset=*/ 2),
|
|
809
|
+
],
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
label: 'Near min copy size of 1',
|
|
813
|
+
// Near-min but actually copies data (more meaningful than size=0 no-op)
|
|
606
814
|
setup: [
|
|
607
815
|
{ offset: 0, value: new Uint32(1n) }, // copySize = 1
|
|
608
|
-
{ offset: 1, value: new Uint32(0n) }, // cdStart = 0
|
|
816
|
+
{ offset: 1, value: new Uint32(0n) }, // cdStart = 0
|
|
609
817
|
],
|
|
610
818
|
targetInstructions: () => [
|
|
611
819
|
new CalldataCopy(/*indirect=*/ 0, /*copySizeOffset=*/ 0, /*cdStartOffset=*/ 1, /*dstOffset=*/ 2),
|
|
@@ -627,10 +835,36 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
627
835
|
},
|
|
628
836
|
],
|
|
629
837
|
|
|
838
|
+
// RETURNDATACOPY has dynamic gas scaling with copySize
|
|
630
839
|
[Opcode.RETURNDATACOPY]: [
|
|
631
840
|
{
|
|
841
|
+
label: 'Min copy size',
|
|
632
842
|
setup: [
|
|
633
|
-
{ offset: 0, value: new Uint32(0n) }, // copySize = 0
|
|
843
|
+
{ offset: 0, value: new Uint32(0n) }, // copySize = 0 (minimum)
|
|
844
|
+
{ offset: 1, value: new Uint32(0n) }, // rdOffset
|
|
845
|
+
],
|
|
846
|
+
targetInstructions: () => [
|
|
847
|
+
new ReturndataCopy(/*indirect=*/ 0, /*copySizeOffset=*/ 0, /*rdStartOffset=*/ 1, /*dstOffset=*/ 2),
|
|
848
|
+
],
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
label: 'Large copy size',
|
|
852
|
+
// Large copySize to maximize dynamic gas - will OOG quickly
|
|
853
|
+
// NOTE: we don't want it so large that it exceeds memory bounds (MAX_MEMORY_SIZE = 2^32)
|
|
854
|
+
// and we really want it small enough that we run at least 1 successful target opcode.
|
|
855
|
+
setup: [
|
|
856
|
+
{ offset: 0, value: new Uint32(1000n) }, // copySize = 1000 (large enough to show scaling)
|
|
857
|
+
{ offset: 1, value: new Uint32(0n) }, // rdOffset
|
|
858
|
+
],
|
|
859
|
+
targetInstructions: () => [
|
|
860
|
+
new ReturndataCopy(/*indirect=*/ 0, /*copySizeOffset=*/ 0, /*rdStartOffset=*/ 1, /*dstOffset=*/ 2),
|
|
861
|
+
],
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
label: 'Near min copy size of 1',
|
|
865
|
+
// Near-min but actually copies data (more meaningful than size=0 no-op)
|
|
866
|
+
setup: [
|
|
867
|
+
{ offset: 0, value: new Uint32(1n) }, // copySize = 1
|
|
634
868
|
{ offset: 1, value: new Uint32(0n) }, // rdOffset
|
|
635
869
|
],
|
|
636
870
|
targetInstructions: () => [
|
|
@@ -644,16 +878,38 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
644
878
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
645
879
|
[Opcode.SLOAD]: [
|
|
646
880
|
{
|
|
647
|
-
|
|
881
|
+
label: 'Cold read (slot not written)',
|
|
882
|
+
setup: [{ offset: 0, value: new Field(Fr.random()) }], // random slot
|
|
648
883
|
targetInstructions: () => [new SLoad(/*indirect=*/ 0, /*slotOffset=*/ 0, /*dstOffset=*/ 1)],
|
|
649
884
|
},
|
|
885
|
+
{
|
|
886
|
+
label: 'Warm read (SSTORE first)',
|
|
887
|
+
// Memory layout: slot (incremented), value, constant 1, revertSize, loaded value
|
|
888
|
+
setup: [
|
|
889
|
+
{ offset: 0, value: new Field(Fr.random()) }, // slot (will be incremented)
|
|
890
|
+
{ offset: 1, value: new Field(Fr.random()) }, // value to store
|
|
891
|
+
{ offset: 2, value: new Field(1n) }, // constant 1 for ADD
|
|
892
|
+
{ offset: 3, value: new Uint32(0n) }, // revertSize
|
|
893
|
+
],
|
|
894
|
+
targetInstructions: () => [
|
|
895
|
+
new SStore(/*indirect=*/ 0, /*srcOffset=*/ 1, /*slotOffset=*/ 0),
|
|
896
|
+
new SLoad(/*indirect=*/ 0, /*slotOffset=*/ 0, /*dstOffset=*/ 4),
|
|
897
|
+
new Add(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 0).as(Opcode.ADD_8, Add.wireFormat8), // slot++
|
|
898
|
+
],
|
|
899
|
+
cleanupInstructions: () => [
|
|
900
|
+
new Revert(/*indirect=*/ 0, /*retSizeOffset=*/ 3, /*returnOffset=*/ 0).as(Opcode.REVERT_8, Revert.wireFormat8),
|
|
901
|
+
],
|
|
902
|
+
limit: MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
|
|
903
|
+
},
|
|
650
904
|
],
|
|
651
905
|
|
|
652
906
|
[Opcode.NOTEHASHEXISTS]: [
|
|
653
907
|
{
|
|
908
|
+
// Note: Can't easily do "write first" version - would need to know the leaf index
|
|
909
|
+
// that EMITNOTEHASH will produce, which depends on tree state
|
|
654
910
|
setup: [
|
|
655
|
-
{ offset: 0, value: new Field(
|
|
656
|
-
{ offset: 1, value:
|
|
911
|
+
{ offset: 0, value: new Field(Fr.random()) }, // random noteHash
|
|
912
|
+
{ offset: 1, value: randomWithTag(TypeTag.UINT64) }, // random leafIndex
|
|
657
913
|
],
|
|
658
914
|
targetInstructions: () => [
|
|
659
915
|
new NoteHashExists(/*indirect=*/ 0, /*noteHashOffset=*/ 0, /*leafIndexOffset=*/ 1, /*existsOffset=*/ 2),
|
|
@@ -663,21 +919,47 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
663
919
|
|
|
664
920
|
[Opcode.NULLIFIEREXISTS]: [
|
|
665
921
|
{
|
|
922
|
+
label: 'Non-existent nullifier',
|
|
666
923
|
setup: [
|
|
667
|
-
{ offset: 0, value: new Field(
|
|
668
|
-
{ offset: 1, value: new Field(
|
|
924
|
+
{ offset: 0, value: new Field(Fr.random()) }, // random nullifier
|
|
925
|
+
{ offset: 1, value: new Field(Fr.random()) }, // random address
|
|
669
926
|
],
|
|
670
927
|
targetInstructions: () => [
|
|
671
928
|
new NullifierExists(/*indirect=*/ 0, /*nullifierOffset=*/ 0, /*addressOffset=*/ 1, /*existsOffset=*/ 2),
|
|
672
929
|
],
|
|
673
930
|
},
|
|
931
|
+
{
|
|
932
|
+
label: 'Existing nullifier (EMITNULLIFIER first)',
|
|
933
|
+
// Memory layout: nullifier (incremented), constant 1, current address (from GETENVVAR), revertSize, exists result
|
|
934
|
+
setup: [
|
|
935
|
+
{ offset: 0, value: new Field(Fr.random()) }, // nullifier (will be incremented)
|
|
936
|
+
{ offset: 1, value: new Field(1n) }, // constant 1 for ADD
|
|
937
|
+
() => [
|
|
938
|
+
// Get current contract address into offset 2
|
|
939
|
+
new GetEnvVar(/*indirect=*/ 0, /*dstOffset=*/ 2, /*varEnum=*/ 0).as(
|
|
940
|
+
Opcode.GETENVVAR_16,
|
|
941
|
+
GetEnvVar.wireFormat16,
|
|
942
|
+
),
|
|
943
|
+
],
|
|
944
|
+
{ offset: 3, value: new Uint32(0n) }, // revertSize
|
|
945
|
+
],
|
|
946
|
+
targetInstructions: () => [
|
|
947
|
+
new EmitNullifier(/*indirect=*/ 0, /*nullifierOffset=*/ 0),
|
|
948
|
+
new NullifierExists(/*indirect=*/ 0, /*nullifierOffset=*/ 0, /*addressOffset=*/ 2, /*existsOffset=*/ 4),
|
|
949
|
+
new Add(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.ADD_8, Add.wireFormat8), // nullifier++
|
|
950
|
+
],
|
|
951
|
+
cleanupInstructions: () => [
|
|
952
|
+
new Revert(/*indirect=*/ 0, /*retSizeOffset=*/ 3, /*returnOffset=*/ 0).as(Opcode.REVERT_8, Revert.wireFormat8),
|
|
953
|
+
],
|
|
954
|
+
limit: MAX_NULLIFIERS_PER_TX - 1,
|
|
955
|
+
},
|
|
674
956
|
],
|
|
675
957
|
|
|
676
958
|
[Opcode.L1TOL2MSGEXISTS]: [
|
|
677
959
|
{
|
|
678
960
|
setup: [
|
|
679
|
-
{ offset: 0, value: new Field(
|
|
680
|
-
{ offset: 1, value:
|
|
961
|
+
{ offset: 0, value: new Field(Fr.random()) }, // random msgHash
|
|
962
|
+
{ offset: 1, value: randomWithTag(TypeTag.UINT64) }, // random msgLeafIndex
|
|
681
963
|
],
|
|
682
964
|
targetInstructions: () => [
|
|
683
965
|
new L1ToL2MessageExists(/*indirect=*/ 0, /*msgHashOffset=*/ 0, /*msgLeafIndexOffset=*/ 1, /*existsOffset=*/ 2),
|
|
@@ -687,7 +969,16 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
687
969
|
|
|
688
970
|
[Opcode.GETCONTRACTINSTANCE]: [
|
|
689
971
|
{
|
|
690
|
-
|
|
972
|
+
// Use GETENVVAR to get current contract address (varEnum 0 = ADDRESS)
|
|
973
|
+
// This ensures we're querying a valid deployed contract
|
|
974
|
+
setup: [
|
|
975
|
+
() => [
|
|
976
|
+
new GetEnvVar(/*indirect=*/ 0, /*dstOffset=*/ 0, /*varEnum=*/ 0).as(
|
|
977
|
+
Opcode.GETENVVAR_16,
|
|
978
|
+
GetEnvVar.wireFormat16,
|
|
979
|
+
),
|
|
980
|
+
],
|
|
981
|
+
],
|
|
691
982
|
// memberEnum 0 = DEPLOYER
|
|
692
983
|
targetInstructions: () => [
|
|
693
984
|
new GetContractInstance(/*indirect=*/ 0, /*addressOffset=*/ 0, /*dstOffset=*/ 1, /*memberEnum=*/ 0),
|
|
@@ -701,7 +992,7 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
701
992
|
[Opcode.EMITNOTEHASH]: [
|
|
702
993
|
{
|
|
703
994
|
setup: [
|
|
704
|
-
{ offset: 0, value: new Field(
|
|
995
|
+
{ offset: 0, value: new Field(Fr.random()) }, // random noteHash
|
|
705
996
|
{ offset: 1, value: new Uint32(0n) }, // revertSize
|
|
706
997
|
],
|
|
707
998
|
targetInstructions: () => [new EmitNoteHash(/*indirect=*/ 0, /*noteHashOffset=*/ 0)],
|
|
@@ -717,7 +1008,7 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
717
1008
|
// Nullifiers must be unique - increment value after each emit
|
|
718
1009
|
// Memory layout: offset 0 = nullifier value, offset 1 = constant 1 for incrementing
|
|
719
1010
|
setup: [
|
|
720
|
-
{ offset: 0, value: new Field(
|
|
1011
|
+
{ offset: 0, value: new Field(Fr.random()) }, // random nullifier (will be incremented)
|
|
721
1012
|
{ offset: 1, value: new Field(1n) }, // constant 1 for ADD
|
|
722
1013
|
{ offset: 2, value: new Uint32(0n) }, // revertSize
|
|
723
1014
|
],
|
|
@@ -735,8 +1026,8 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
735
1026
|
[Opcode.SENDL2TOL1MSG]: [
|
|
736
1027
|
{
|
|
737
1028
|
setup: [
|
|
738
|
-
{ offset: 0, value: new Field(
|
|
739
|
-
{ offset: 1, value: new Field(
|
|
1029
|
+
{ offset: 0, value: new Field(Fr.random()) }, // random recipient
|
|
1030
|
+
{ offset: 1, value: new Field(Fr.random()) }, // random content
|
|
740
1031
|
{ offset: 2, value: new Uint32(0n) }, // revertSize
|
|
741
1032
|
],
|
|
742
1033
|
targetInstructions: () => [new SendL2ToL1Message(/*indirect=*/ 0, /*recipientOffset=*/ 0, /*contentOffset=*/ 1)],
|
|
@@ -754,8 +1045,8 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
754
1045
|
{
|
|
755
1046
|
label: 'Same slot (no limit)',
|
|
756
1047
|
setup: [
|
|
757
|
-
{ offset: 0, value: new Field(
|
|
758
|
-
{ offset: 1, value: new Field(
|
|
1048
|
+
{ offset: 0, value: new Field(Fr.random()) }, // random value
|
|
1049
|
+
{ offset: 1, value: new Field(Fr.random()) }, // random slot (same slot each iteration)
|
|
759
1050
|
{ offset: 2, value: new Uint32(0n) }, // revertSize
|
|
760
1051
|
],
|
|
761
1052
|
targetInstructions: () => [new SStore(/*indirect=*/ 0, /*srcOffset=*/ 0, /*slotOffset=*/ 1)],
|
|
@@ -766,8 +1057,8 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
766
1057
|
{
|
|
767
1058
|
label: 'Unique slots (side-effect limited)',
|
|
768
1059
|
setup: [
|
|
769
|
-
{ offset: 0, value: new Field(
|
|
770
|
-
{ offset: 1, value: new Field(
|
|
1060
|
+
{ offset: 0, value: new Field(Fr.random()) }, // random value (constant)
|
|
1061
|
+
{ offset: 1, value: new Field(Fr.random()) }, // random slot (will be incremented)
|
|
771
1062
|
{ offset: 2, value: new Field(1n) }, // constant 1 for ADD
|
|
772
1063
|
{ offset: 3, value: new Uint32(0n) }, // revertSize
|
|
773
1064
|
],
|
|
@@ -820,14 +1111,14 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
820
1111
|
],
|
|
821
1112
|
|
|
822
1113
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
823
|
-
// GADGETS -
|
|
1114
|
+
// GADGETS - Random inputs (seeded via SEED env var)
|
|
824
1115
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
825
1116
|
[Opcode.POSEIDON2]: [
|
|
826
1117
|
{
|
|
827
1118
|
// Poseidon2 takes 4 field elements as input
|
|
828
1119
|
setup: Array.from({ length: 4 }, (_, i) => ({
|
|
829
1120
|
offset: i,
|
|
830
|
-
value: new Field(
|
|
1121
|
+
value: new Field(Fr.random()), // random field element
|
|
831
1122
|
})),
|
|
832
1123
|
// Poseidon hash data at M[0..3], write result to M[0:3] (reuse results as next inputs)
|
|
833
1124
|
targetInstructions: () => [new Poseidon2(/*indirect=*/ 0, /*inputStateOffset=*/ 0, /*outputStateOffset=*/ 0)],
|
|
@@ -837,19 +1128,15 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
837
1128
|
[Opcode.SHA256COMPRESSION]: [
|
|
838
1129
|
{
|
|
839
1130
|
setup: [
|
|
840
|
-
// State: 8 x UINT32 at offsets 0-7 (
|
|
841
|
-
{
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
{ offset: 5, value: new Uint32(0x9b05688cn) },
|
|
847
|
-
{ offset: 6, value: new Uint32(0x1f83d9abn) },
|
|
848
|
-
{ offset: 7, value: new Uint32(0x5be0cd19n) },
|
|
849
|
-
// Inputs: 16 x UINT32 at offsets 8-23 (non-trivial message block)
|
|
1131
|
+
// State: 8 x UINT32 at offsets 0-7 (random initial state)
|
|
1132
|
+
...Array.from({ length: 8 }, (_, i) => ({
|
|
1133
|
+
offset: i,
|
|
1134
|
+
value: randomWithTag(TypeTag.UINT32),
|
|
1135
|
+
})),
|
|
1136
|
+
// Inputs: 16 x UINT32 at offsets 8-23 (random message block)
|
|
850
1137
|
...Array.from({ length: 16 }, (_, i) => ({
|
|
851
1138
|
offset: 8 + i,
|
|
852
|
-
value:
|
|
1139
|
+
value: randomWithTag(TypeTag.UINT32),
|
|
853
1140
|
})),
|
|
854
1141
|
],
|
|
855
1142
|
targetInstructions: () => [
|
|
@@ -860,10 +1147,10 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
860
1147
|
|
|
861
1148
|
[Opcode.KECCAKF1600]: [
|
|
862
1149
|
{
|
|
863
|
-
// Keccak state: 25 x UINT64 (5x5 lane array)
|
|
1150
|
+
// Keccak state: 25 x UINT64 (5x5 lane array) with random values
|
|
864
1151
|
setup: Array.from({ length: 25 }, (_, i) => ({
|
|
865
1152
|
offset: i,
|
|
866
|
-
value:
|
|
1153
|
+
value: randomWithTag(TypeTag.UINT64),
|
|
867
1154
|
})),
|
|
868
1155
|
targetInstructions: () => [new KeccakF1600(/*indirect=*/ 0, /*dstOffset=*/ 0, /*inputOffset=*/ 0)],
|
|
869
1156
|
},
|
|
@@ -895,12 +1182,33 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
895
1182
|
},
|
|
896
1183
|
],
|
|
897
1184
|
|
|
1185
|
+
// TORADIXBE has dynamic gas scaling with numLimbs
|
|
898
1186
|
[Opcode.TORADIXBE]: [
|
|
899
1187
|
{
|
|
1188
|
+
label: 'Min limbs',
|
|
1189
|
+
setup: [
|
|
1190
|
+
{ offset: 0, value: new Field(1n) }, // small value that fits in 1 limb (can't randomize - would truncate)
|
|
1191
|
+
{ offset: 1, value: new Uint32(2n) }, // radix = 2 (binary)
|
|
1192
|
+
{ offset: 2, value: new Uint32(1n) }, // numLimbs = 1 (minimum)
|
|
1193
|
+
{ offset: 3, value: new Uint1(0n) }, // outputBits = false
|
|
1194
|
+
],
|
|
1195
|
+
targetInstructions: () => [
|
|
1196
|
+
new ToRadixBE(
|
|
1197
|
+
/*indirect=*/ 0,
|
|
1198
|
+
/*srcOffset=*/ 0,
|
|
1199
|
+
/*radixOffset=*/ 1,
|
|
1200
|
+
/*numLimbsOffset=*/ 2,
|
|
1201
|
+
/*outputBitsOffset=*/ 3,
|
|
1202
|
+
/*dstOffset=*/ 4,
|
|
1203
|
+
),
|
|
1204
|
+
],
|
|
1205
|
+
},
|
|
1206
|
+
{
|
|
1207
|
+
label: 'Max limbs',
|
|
900
1208
|
setup: [
|
|
901
|
-
{ offset: 0, value: new Field(
|
|
902
|
-
{ offset: 1, value: new Uint32(
|
|
903
|
-
{ offset: 2, value: new Uint32(
|
|
1209
|
+
{ offset: 0, value: new Field(Fr.random()) }, // random field value (fits in 256 bits)
|
|
1210
|
+
{ offset: 1, value: new Uint32(2n) }, // radix = 2 (binary)
|
|
1211
|
+
{ offset: 2, value: new Uint32(256n) }, // numLimbs = 256 (max bits in field)
|
|
904
1212
|
{ offset: 3, value: new Uint1(0n) }, // outputBits = false
|
|
905
1213
|
],
|
|
906
1214
|
targetInstructions: () => [
|
|
@@ -919,6 +1227,8 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
919
1227
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
920
1228
|
// MISC
|
|
921
1229
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1230
|
+
// DEBUGLOG only has base gas (no dynamic gas scaling) - memory reads only happen
|
|
1231
|
+
// when collectDebugLogs config is enabled
|
|
922
1232
|
[Opcode.DEBUGLOG]: [
|
|
923
1233
|
{
|
|
924
1234
|
setup: [
|
|
@@ -939,6 +1249,28 @@ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
|
|
|
939
1249
|
),
|
|
940
1250
|
],
|
|
941
1251
|
},
|
|
1252
|
+
// No real reason to test this by default since debug logs only meaningfully do scaling work
|
|
1253
|
+
// when collectDebugLogs is enabled.
|
|
1254
|
+
// {
|
|
1255
|
+
// label: 'Max sizes',
|
|
1256
|
+
// setup: [
|
|
1257
|
+
// { offset: 0, value: new Field(0n) }, // level (0 = trace)
|
|
1258
|
+
// { offset: 1, value: new Field(0n) }, // message start
|
|
1259
|
+
// { offset: 2, value: new Field(0n) }, // fields start
|
|
1260
|
+
// { offset: 3, value: new Uint32(1000n) }, // fieldsSize = 1000 (large enough to show scaling)
|
|
1261
|
+
// ],
|
|
1262
|
+
// // messageSize = 1000 (large enough to show scaling)
|
|
1263
|
+
// targetInstructions: () => [
|
|
1264
|
+
// new DebugLog(
|
|
1265
|
+
// /*indirect=*/ 0,
|
|
1266
|
+
// /*levelOffset=*/ 0,
|
|
1267
|
+
// /*messageOffset=*/ 1,
|
|
1268
|
+
// /*fieldsOffset=*/ 2,
|
|
1269
|
+
// /*fieldsSizeOffset=*/ 3,
|
|
1270
|
+
// /*messageSize=*/ 1000,
|
|
1271
|
+
// ),
|
|
1272
|
+
// ],
|
|
1273
|
+
// },
|
|
942
1274
|
],
|
|
943
1275
|
};
|
|
944
1276
|
|
|
@@ -1083,7 +1415,7 @@ export function createOpcodeSpamBytecode(config: SpamConfig): Buffer {
|
|
|
1083
1415
|
// 1. Setup memory
|
|
1084
1416
|
appendSetupInstructions(instructions, config.setup);
|
|
1085
1417
|
|
|
1086
|
-
// 2. Infinite loop - maximize
|
|
1418
|
+
// 2. Infinite loop - maximize calls to target until out-of-gas
|
|
1087
1419
|
appendInfiniteLoop(instructions, config);
|
|
1088
1420
|
|
|
1089
1421
|
return encodeToBytecode(instructions);
|
|
@@ -1117,78 +1449,23 @@ export function createSideEffectSpamBytecode(config: SpamConfig): Buffer {
|
|
|
1117
1449
|
return encodeToBytecode(instructions);
|
|
1118
1450
|
}
|
|
1119
1451
|
|
|
1120
|
-
/** Reserved memory offsets for outer call loop */
|
|
1121
|
-
const CONST_1_OFFSET = 0;
|
|
1122
|
-
const CALL_L2_GAS_OFFSET = 1;
|
|
1123
|
-
const CALL_DA_GAS_OFFSET = 2;
|
|
1124
|
-
const CALL_ADDR_OFFSET = 3;
|
|
1125
|
-
const CALL_ARGS_SIZE = 4;
|
|
1126
|
-
const CALL_ARGS_OFFSET = 5;
|
|
1127
|
-
const CALLDATA_INDEX_OFFSET = 6; // calldata index as in calldata[index]
|
|
1128
|
-
|
|
1129
|
-
/**
|
|
1130
|
-
* A SpamConfig for an external call loop.
|
|
1131
|
-
*/
|
|
1132
|
-
const EXTERNAL_CALL_LOOP_CONFIG: SpamConfig = {
|
|
1133
|
-
setup: [
|
|
1134
|
-
// calldata will contain 1 item: the external call address
|
|
1135
|
-
{ offset: CONST_1_OFFSET, value: new Uint32(1) }, // calldata size = 1
|
|
1136
|
-
{ offset: CALLDATA_INDEX_OFFSET, value: new Uint32(0) }, // we want calldata[0]
|
|
1137
|
-
{ offset: CALL_L2_GAS_OFFSET, value: new Uint32(0xffffffffn) }, // l2Gas = max uint32
|
|
1138
|
-
{ offset: CALL_DA_GAS_OFFSET, value: new Uint32(0xffffffffn) }, // daGas = max uint32
|
|
1139
|
-
() => [
|
|
1140
|
-
new CalldataCopy(
|
|
1141
|
-
/*indirect=*/ 0,
|
|
1142
|
-
/*copySizeOffset=*/ CONST_1_OFFSET,
|
|
1143
|
-
/*cdStartOffset=*/ CALLDATA_INDEX_OFFSET,
|
|
1144
|
-
/*dstOffset=*/ CALL_ADDR_OFFSET,
|
|
1145
|
-
),
|
|
1146
|
-
], // address = calldata[0] of parent call
|
|
1147
|
-
{ offset: CALL_ARGS_SIZE, value: new Uint32(0) }, // argsSize = max uint32
|
|
1148
|
-
{ offset: CALL_ARGS_SIZE, value: new Uint32(0) }, // argsSize = max uint32
|
|
1149
|
-
],
|
|
1150
|
-
targetInstructions: () => [
|
|
1151
|
-
new Call(
|
|
1152
|
-
/*indirect=*/ 0,
|
|
1153
|
-
/*l2GasOffset=*/ CALL_L2_GAS_OFFSET,
|
|
1154
|
-
/*daGasOffset=*/ CALL_DA_GAS_OFFSET,
|
|
1155
|
-
/*addrOffset=*/ CALL_ADDR_OFFSET,
|
|
1156
|
-
/*argsSizeOffset=*/ CALL_ARGS_SIZE,
|
|
1157
|
-
/*argsOffset=*/ CALL_ARGS_OFFSET,
|
|
1158
|
-
),
|
|
1159
|
-
],
|
|
1160
|
-
};
|
|
1161
|
-
|
|
1162
|
-
/**
|
|
1163
|
-
* Create bytecode that makes an external call in a loop.
|
|
1164
|
-
*
|
|
1165
|
-
* @returns the bytecode for the external call loop
|
|
1166
|
-
*/
|
|
1167
|
-
export function createExternalCallLoopBytecode(): Buffer {
|
|
1168
|
-
const config = EXTERNAL_CALL_LOOP_CONFIG;
|
|
1169
|
-
const instructions: Bufferable[] = [];
|
|
1170
|
-
|
|
1171
|
-
// 1. Setup memory
|
|
1172
|
-
appendSetupInstructions(instructions, config.setup);
|
|
1173
|
-
// 2. Infinite loop of external calls - maximize iterations until out-of-gas
|
|
1174
|
-
appendInfiniteLoop(instructions, config);
|
|
1175
|
-
return encodeToBytecode(instructions);
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
1452
|
async function testStandardOpcodeSpam(
|
|
1179
1453
|
tester: PublicTxSimulationTester,
|
|
1180
1454
|
config: SpamConfig,
|
|
1181
1455
|
expectToBeTrue: (x: boolean) => void,
|
|
1182
1456
|
): Promise<PublicTxResult> {
|
|
1183
1457
|
const bytecode = createOpcodeSpamBytecode(config);
|
|
1184
|
-
const
|
|
1458
|
+
const contract = await deployCustomBytecode(bytecode, tester, config.label);
|
|
1459
|
+
// Should we pass the contract address as calldata?
|
|
1460
|
+
const calldata = config.addressAsCalldata ? [contract.address.toField()] : [];
|
|
1461
|
+
const result = await executeCustomBytecode(contract, tester, config.label, calldata);
|
|
1185
1462
|
|
|
1186
1463
|
// should have halted with out of gas
|
|
1187
1464
|
expectToBeTrue(!result.revertCode.isOK());
|
|
1188
1465
|
const revertReason = result.findRevertReason()?.message.toLowerCase();
|
|
1189
1466
|
const allowedReasons = ['out of gas', 'not enough l2gas'];
|
|
1190
1467
|
// expect the reason to match ONE of the allowed reasons
|
|
1191
|
-
expectToBeTrue(allowedReasons.some(allowedReason => revertReason
|
|
1468
|
+
expectToBeTrue(allowedReasons.some(allowedReason => revertReason?.includes(allowedReason)));
|
|
1192
1469
|
return result;
|
|
1193
1470
|
}
|
|
1194
1471
|
|
|
@@ -1197,8 +1474,10 @@ async function testSideEffectOpcodeSpam(
|
|
|
1197
1474
|
config: SpamConfig,
|
|
1198
1475
|
expectToBeTrue: (x: boolean) => void,
|
|
1199
1476
|
): Promise<PublicTxResult> {
|
|
1477
|
+
// Inner contract will spam the side-effect limited opcode up to its limit, then REVERT
|
|
1200
1478
|
const innerBytecode = createSideEffectSpamBytecode(config);
|
|
1201
|
-
|
|
1479
|
+
// Outer contract will CALL to inner contract in a loop
|
|
1480
|
+
const outerBytecode = createOpcodeSpamBytecode(EXTERNAL_CALL_CONFIG);
|
|
1202
1481
|
const innerContract = await deployCustomBytecode(innerBytecode, tester, `${config.label}_Inner`);
|
|
1203
1482
|
const outerContract = await deployCustomBytecode(outerBytecode, tester, `${config.label}_Outer`);
|
|
1204
1483
|
// Outer contract reads calldata[0] as inner contract address to CALL to
|
|
@@ -1209,16 +1488,18 @@ async function testSideEffectOpcodeSpam(
|
|
|
1209
1488
|
const revertReason = result.findRevertReason()?.message.toLowerCase();
|
|
1210
1489
|
const allowedReasons = ['assertion failed', 'out of gas', 'not enough l2gas'];
|
|
1211
1490
|
// expect the reason to match ONE of the allowed reasons
|
|
1212
|
-
expectToBeTrue(allowedReasons.some(allowedReason => revertReason
|
|
1491
|
+
expectToBeTrue(allowedReasons.some(allowedReason => revertReason?.includes(allowedReason)));
|
|
1213
1492
|
|
|
1214
1493
|
// Top-level should _always_ run out of gas for these tests
|
|
1215
1494
|
// Check top-level halting message
|
|
1216
1495
|
// WARNING: only the C++ simulator (or TsVsCpp) will have haltingMessage
|
|
1217
1496
|
const allowedOuterReasons = ['out of gas', 'not enough l2gas'];
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1497
|
+
if (result.callStackMetadata && result.callStackMetadata.length > 0) {
|
|
1498
|
+
const outerCallMetadata = result.callStackMetadata[0] as CallStackMetadata;
|
|
1499
|
+
const outerReason = outerCallMetadata.haltingMessage?.toLowerCase();
|
|
1500
|
+
// expect the reason to match ONE of the allowed reasons
|
|
1501
|
+
expectToBeTrue(allowedOuterReasons.some(allowedReason => outerReason?.includes(allowedReason)));
|
|
1502
|
+
}
|
|
1222
1503
|
|
|
1223
1504
|
return result;
|
|
1224
1505
|
}
|