417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574 | @pytest.mark.parametrize("distinct_senders", [False, True])
@pytest.mark.parametrize("existing_slots", [False, True])
def test_sload_bloated_multi_contract(
benchmark_test: BenchmarkTestFiller,
pre: Alloc,
fork: Fork,
gas_benchmark_value: int,
tx_gas_limit: int,
existing_slots: bool,
distinct_senders: bool,
) -> None:
"""
Benchmark SLOAD across a distinct contract per transaction.
Each transaction calls a freshly-deployed contract whose slot 0
is pre-loaded with the starting offset; the contract then runs a
SLOAD loop over sequential slots until gas runs low. Unlike
test_sload_bloated_prefetch_miss which hammers one account's
storage trie via an EIP-7702 delegated EOA, every transaction
here opens a different storage trie, stressing cross-account
state access and state-trie breadth in a single block.
Every target contract first CALLs a shared offset_holder
contract whose slot 0 is read, incremented, and written back.
This mirrors the first test's "same-contract slot 0" dependency
pattern via cross-contract CALL: every transaction forms a
read-after-write edge on offset_holder's slot 0, preventing
parallel execution.
When ``distinct_senders`` is True every transaction uses a fresh
sender. This additionally exercises per-sender prewarm
serialization (e.g. Nethermind) differently than the shared-
sender case; we run both so clients can be measured in both
regimes.
"""
# Shared offset_holder: reads, increments, and writes its own
# slot 0. Every target CALLs this to create an inter-tx RAW
# dependency chain on a single shared storage slot.
offset_holder = pre.deploy_contract(
code=Op.SSTORE(0, Op.ADD(Op.SLOAD(0), 1)),
)
# Target runtime: CALL offset_holder (for the dependency), then
# run the same SLOAD loop as test_sload_bloated in its own
# storage. Final counter is written back to slot 0.
runtime_code = (
Op.POP(
Op.CALL(
address=offset_holder,
)
)
+ Op.SLOAD(Op.PUSH0)
+ While(
body=(Op.DUP1 + Op.SLOAD + Op.POP + Op.PUSH1(1) + Op.ADD),
condition=Op.GT(Op.GAS, 0xFFFF),
)
+ Op.PUSH0
+ Op.SSTORE
)
base_offset = 1 if existing_slots else START_SLOT
max_sloads_per_tx = _max_sloads_per_tx(tx_gas_limit, fork)
# Pre-load slot 0 with the starting offset. For existing_slots,
# also fill the slot range the loop will read so SLOADs land on
# populated entries rather than empty slots. A fresh Storage
# instance is built per deployment (below) so that every target
# gets an independent root dict, not an alias of the same one.
storage_data: Storage.StorageDictType = {0: base_offset}
if existing_slots:
for i in range(base_offset, base_offset + max_sloads_per_tx):
storage_data[i] = i
intrinsic_gas = fork.transaction_intrinsic_cost_calculator()()
# Minimum per-tx gas ensuring the SLOAD loop runs at least one
# iteration so every target satisfies storage_reads=[base_offset]:
# intrinsic + CALL + offset_holder + setup + 0xFFFF loop threshold
# + one iteration + final SSTORE, with buffer.
min_tx_gas = intrinsic_gas + 130_000
# senders_iter yields one sender per tx (fresh per call in
# distinct mode, a single shared sender otherwise). The senders
# list collects one entry per tx so the BAL builder below can
# group nonce changes by sender uniformly.
senders_iter = _sender_generator(pre, distinct_senders)
senders: list[EOA] = []
gas_available = gas_benchmark_value
targets: list[Address] = []
txs: list[Transaction] = []
# Each tx targets a freshly-deployed contract with identical code
# and storage layout.
while gas_available >= min_tx_gas:
tx_gas = min(gas_available, tx_gas_limit)
target = pre.deploy_contract(
code=runtime_code,
storage=Storage(storage_data),
)
targets.append(target)
sender = next(senders_iter)
senders.append(sender)
txs.append(
Transaction(
gas_limit=tx_gas,
to=target,
sender=sender,
)
)
gas_available -= tx_gas
expectations: dict[Address, BalAccountExpectation] = {
offset_holder: BalAccountExpectation(
storage_changes=[
BalStorageSlot(
slot=0,
validate_any_change=True,
),
],
),
}
for t in targets:
expectations[t] = BalAccountExpectation(
storage_reads=[base_offset],
storage_changes=[
BalStorageSlot(
slot=0,
validate_any_change=True,
),
],
)
sender_nonces: dict[Address, list[BalNonceChange]] = {}
for i, s in enumerate(senders):
changes = sender_nonces.setdefault(s, [])
changes.append(
BalNonceChange(
block_access_index=i + 1,
post_nonce=len(changes) + 1,
)
)
for addr, nonces in sender_nonces.items():
expectations[addr] = BalAccountExpectation(nonce_changes=nonces)
blocks = [
Block(
txs=txs,
expected_block_access_list=BlockAccessListExpectation(
account_expectations=expectations,
),
)
]
benchmark_test(
pre=pre,
blocks=blocks,
skip_gas_used_validation=True,
expected_receipt_status=True,
)
|