Skip to content

test_sload_bloated_multi_contract()

Documentation for tests/benchmark/stateful/bloatnet/test_sload.py::test_sload_bloated_multi_contract@ffe48572.

Generate fixtures for these test cases for Bogota with:

fill -v tests/benchmark/stateful/bloatnet/test_sload.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_sload.py
@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, LOOP_GAS_THRESHOLD),
        )
        + 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 + 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,
        verifications=[ReceiptStatusExpected()],
    )

Parametrized Test Cases

This test generates 4 parametrized test cases across 4 forks.