Skip to content

test_bls12_pairing_uncachable()

Documentation for tests/benchmark/compute/precompile/test_bls12_381.py::test_bls12_pairing_uncachable@ffe48572.

Generate fixtures for these test cases for Bogota with:

fill -v tests/benchmark/compute/precompile/test_bls12_381.py::test_bls12_pairing_uncachable --gas-benchmark-values 1

Benchmark BLS12 pairing precompile with unique inputs per call.

Source code in tests/benchmark/compute/precompile/test_bls12_381.py
@pytest.mark.repricing
@pytest.mark.parametrize("num_pairs", [1, 3, 6, 12, 24])
def test_bls12_pairing_uncachable(
    benchmark_test: BenchmarkTestFiller,
    pre: Alloc,
    fork: Fork,
    gas_benchmark_value: int,
    tx_gas_limit: int,
    num_pairs: int,
) -> None:
    """Benchmark BLS12 pairing precompile with unique inputs per call."""
    precompile_address = bls12381_spec.Spec.PAIRING
    if precompile_address not in fork.precompiles():
        pytest.skip("BLS12_PAIRING precompile not enabled")

    pair_size = num_pairs * 384
    gsc = fork.gas_costs()
    intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator()
    mem_exp = fork.memory_expansion_gas_calculator()

    # Each iteration: STATICCALL pairing at advancing offset,
    # then advance offset by pair_size at memory[CALLDATASIZE].
    attack_block = Op.POP(
        Op.STATICCALL(
            gas=Op.GAS,
            address=precompile_address,
            args_offset=Op.MLOAD(Op.CALLDATASIZE),
            args_size=pair_size,
            address_warm=True,
        ),
    ) + Op.MSTORE(
        Op.CALLDATASIZE,
        Op.ADD(Op.MLOAD(Op.CALLDATASIZE), pair_size),
    )

    setup = Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE)
    loop = While(
        body=attack_block,
        condition=Op.GT(Op.CALLDATASIZE, Op.MLOAD(Op.CALLDATASIZE)),
    )
    code = setup + loop
    attack_contract_address = pre.deploy_contract(code=code)

    precompile_cost = (
        gsc.PRECOMPILE_BLS_PAIRING_BASE
        + gsc.PRECOMPILE_BLS_PAIRING_PER_PAIR * num_pairs
    )

    iteration_cost = loop.gas_cost(fork) + precompile_cost
    setup_cost = setup.gas_cost(fork)

    # Conservative per-variant estimate: one loop iteration
    # + worst-case calldata floor intrinsic (all non-zero, EIP-7623)
    # + CALLDATACOPY copy and linear memory expansion.
    words_per_variant = math.ceil(pair_size / 32)
    tokens_per_variant = pair_size * 4  # worst case: all non-zero
    per_variant_gas = (
        iteration_cost
        + tokens_per_variant * gsc.TX_DATA_TOKEN_FLOOR
        + words_per_variant * (gsc.OPCODE_COPY_PER_WORD + gsc.MEMORY_PER_WORD)
    )
    empty_intrinsic = intrinsic_gas_calculator(
        calldata=[],
        return_cost_deducted_prior_execution=False,
    )
    fixed_overhead = empty_intrinsic + setup_cost + mem_exp(new_bytes=32)

    seed_offset = 0
    txs: list[Transaction] = []
    remaining_gas = gas_benchmark_value
    expected_opcode_count = 0
    while remaining_gas > 0:
        per_tx_gas = min(tx_gas_limit, remaining_gas)
        per_tx_variants = max(
            1,
            (per_tx_gas - fixed_overhead) // per_variant_gas,
        )
        calldata = Bytes(
            b"".join(
                _generate_bls12_pairs(num_pairs, seed=42 + seed_offset + i)
                for i in range(per_tx_variants)
            )
        )

        execution_intrinsic = intrinsic_gas_calculator(
            calldata=calldata,
            return_cost_deducted_prior_execution=False,
        )
        gas_for_loop = (
            per_tx_gas
            - execution_intrinsic
            - setup_cost
            - math.ceil(len(calldata) / 32) * gsc.OPCODE_COPY_PER_WORD
            - mem_exp(new_bytes=len(calldata) + 32)
        )

        if gas_for_loop < per_tx_variants * iteration_cost:
            break
        expected_opcode_count += per_tx_variants

        txs.append(
            Transaction(
                to=attack_contract_address,
                sender=pre.fund_eoa(),
                gas_limit=per_tx_gas,
                data=calldata,
            )
        )
        remaining_gas -= per_tx_gas
        seed_offset += per_tx_variants

    # TODO: 24 pairs exceeds the 1M block gas limit used in CI,
    # which breaks the test run
    if num_pairs == 24:
        pytest.skip(
            f"gas_benchmark_value={gas_benchmark_value} too small for "
            f"num_pairs={num_pairs} "
            f"(need ~{per_variant_gas + fixed_overhead} gas)"
        )

    assert len(txs) != 0, "No transactions were added to the test."

    benchmark_test(
        target_opcode=Precompile.BLS12_PAIRING,
        skip_gas_used_validation=True,
        verifications=[ReceiptStatusExpected()],
        blocks=[Block(txs=txs)],
        expected_opcode_count=expected_opcode_count,
    )

Parametrized Test Cases

This test generates 5 parametrized test cases across 4 forks.