Skip to content

test_sload_bloated_multi_contract()

Documentation for tests/benchmark/stateful/bloatnet/test_single_opcode.py::test_sload_bloated_multi_contract@892e6d1e.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/benchmark/stateful/bloatnet/test_single_opcode.py::test_sload_bloated_multi_contract --gas-benchmark-values 1

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.

Source code in tests/benchmark/stateful/bloatnet/test_single_opcode.py
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,
    )

Parametrized Test Cases

This test generates 4 parametrized test cases across 3 forks.