Skip to content

test_contract_calling_many_addresses()

Documentation for tests/benchmark/compute/instruction/test_system.py::test_contract_calling_many_addresses@ffe48572.

Generate fixtures for these test cases for Bogota with:

fill -v tests/benchmark/compute/instruction/test_system.py::test_contract_calling_many_addresses --gas-benchmark-values 1

Benchmark a contract that calls many distinct addresses.

Source code in tests/benchmark/compute/instruction/test_system.py
@pytest.mark.parametrize("transfer_amount", [0, 1])
@pytest.mark.parametrize("opcode", [Op.CALL, Op.CALLCODE])
@pytest.mark.parametrize("access_warm", [True, False])
def test_contract_calling_many_addresses(
    benchmark_test: BenchmarkTestFiller,
    pre: Alloc,
    fork: Fork,
    transfer_amount: int,
    opcode: Op,
    access_warm: bool,
    gas_benchmark_value: int,
    fixed_opcode_count: float | None,
) -> None:
    """Benchmark a contract that calls many distinct addresses."""
    start_addr = 2**80 - 1

    value_transfer = transfer_amount > 0
    # Only CALL creates accounts on value transfer (CALLCODE doesn't).
    account_creation = value_transfer and opcode == Op.CALL

    setup = (
        Op.ADD(1, Op.CALLDATALOAD(32))  # [end+1 = limit]
        + Op.CALLDATALOAD(0)  # [start = index, limit]
    )

    iterating = While(
        body=Op.POP(
            opcode(
                address=Op.ADD(start_addr, Op.DUP6),
                value=transfer_amount,
                # gas accounting
                address_warm=access_warm,
                value_transfer=value_transfer,
                account_new=account_creation,
            )
        ),
        condition=Op.PUSH1(1)  # [1, index, limit]
        + Op.ADD  # [index+1, limit]
        + Op.DUP1  # [index+1, index+1, limit]
        + Op.DUP3  # [limit, index+1, index+1, limit]
        + Op.GT,  # [limit > index+1, index+1, limit]
    )
    code = IteratingBytecode(
        setup=setup,
        iterating=iterating,
        cleanup=Op.STOP,
    )

    contract_address = pre.deploy_contract(
        code=code,
        balance=10**9 if value_transfer else 0,
    )

    def calldata(iteration_count: int, start_iteration: int) -> bytes:
        index_end = start_iteration + iteration_count - 1
        return Hash(start_iteration) + Hash(index_end)

    def access_list(
        iteration_count: int, start_iteration: int
    ) -> list[AccessList]:
        return [
            AccessList(address=Address(start_addr + i), storage_keys=[])
            for i in range(start_iteration, start_iteration + iteration_count)
        ]

    tx_kwargs: dict = {
        "calldata": calldata,
        "access_list": access_list if access_warm else None,
    }

    stipend = fork.call_value_stipend() if value_transfer else 0

    def packing_budget() -> int:
        """
        Return the gas budget to size the transactions against.

        Every iteration is charged the call stipend but gets it back
        unused, so the block consumes less than it is charged; raising the
        budget by that ratio lands the block on the target. The stipend
        only comes back out of the execution dimension, so a block bounded
        by the state dimension needs no adjustment.
        """
        state_bound = code.state_gas_cost_by_iteration_count(
            fork=fork, iteration_count=1
        )
        if not stipend or state_bound:
            return gas_benchmark_value
        per_iteration = code.tx_execution_gas_cost_by_iteration_count(
            fork=fork, iteration_count=2, **tx_kwargs
        ) - code.tx_execution_gas_cost_by_iteration_count(
            fork=fork, iteration_count=1, **tx_kwargs
        )
        return gas_benchmark_value * per_iteration // (per_iteration - stipend)

    if fixed_opcode_count is not None:
        total_iterations = int(fixed_opcode_count * 1000)
    else:
        total_iterations = sum(
            code.tx_iterations_by_gas_limit(
                fork=fork, gas_limit=packing_budget(), **tx_kwargs
            )
        )

    def block_gas(iterations: int) -> int:
        """Return the block gas the iterations land, per gas dimension."""
        execution = 0
        state = 0
        start_iteration = 0
        for iteration_count in code.tx_iterations_by_total_iteration_count(
            fork=fork, total_iterations=iterations, **tx_kwargs
        ):
            execution += code.tx_execution_gas_cost_by_iteration_count(
                fork=fork,
                iteration_count=iteration_count,
                start_iteration=start_iteration,
                **tx_kwargs,
            )
            state += code.state_gas_cost_by_iteration_count(
                fork=fork, iteration_count=iteration_count
            )
            start_iteration += iteration_count
        # The block header carries the larger dimension only (EIP-8037),
        # and the unused stipend comes back out of the execution one.
        return max(execution - stipend * iterations, state)

    # The raised packing budget inflates the per-transaction intrinsic gas
    # too, which earns no stipend back, so the block can overshoot the
    # target by up to one iteration.
    while (
        total_iterations > 0
        and block_gas(total_iterations) > gas_benchmark_value
    ):
        total_iterations -= 1

    if total_iterations == 0:
        pytest.skip(
            "Benchmark gas value cannot cover a single call to the contract."
        )

    with TestPhaseManager.execution():
        sender = pre.fund_eoa()
        exec_txs = list(
            code.transactions_by_total_iteration_count(
                fork=fork,
                total_iterations=total_iterations,
                sender=sender,
                to=contract_address,
                **tx_kwargs,
            )
        )
        total_gas_cost = sum(tx.gas_cost for tx in exec_txs)
        total_gas_cost -= stipend * total_iterations

    post = {
        Address(start_addr + i): Account(balance=transfer_amount)
        for i in range(total_iterations)
        if account_creation
    }

    benchmark_test(
        post=post,
        blocks=[Block(txs=exec_txs)],
        expected_benchmark_gas_used=total_gas_cost,
    )

Parametrized Test Cases

This test generates 8 parametrized test cases across 4 forks.