Skip to content

test_alt_bn128_uncachable()

Documentation for tests/benchmark/compute/precompile/test_alt_bn128.py::test_alt_bn128_uncachable@ffe48572.

Generate fixtures for these test cases for Bogota with:

fill -v tests/benchmark/compute/precompile/test_alt_bn128.py::test_alt_bn128_uncachable --gas-benchmark-values 1

Benchmark ecAdd/ecMul with unique input per call.

Write the precompile's G1 output (64 bytes) back over the input point so each loop iteration receives a distinct input, avoiding precompile result caching in clients.

Source code in tests/benchmark/compute/precompile/test_alt_bn128.py
@pytest.mark.repricing
@pytest.mark.parametrize(
    "precompile_address,scalar,target",
    [
        pytest.param(
            EIP196Spec.ECADD, None, Precompile.BN128_ADD, id="ec_add"
        ),
        pytest.param(
            EIP196Spec.ECMUL,
            2,
            Precompile.BN128_MUL,
            id="ec_mul_small_scalar",
        ),
        pytest.param(
            EIP196Spec.ECMUL,
            2**256 - 1,
            Precompile.BN128_MUL,
            id="ec_mul_max_scalar",
        ),
    ],
)
def test_alt_bn128_uncachable(
    benchmark_test: BenchmarkTestFiller,
    pre: Alloc,
    fork: Fork,
    gas_benchmark_value: int,
    tx_gas_limit: int,
    precompile_address: Address,
    scalar: int | None,
    target: OpcodeTarget,
) -> None:
    """
    Benchmark ecAdd/ecMul with unique input per call.

    Write the precompile's G1 output (64 bytes) back over the
    input point so each loop iteration receives a distinct
    input, avoiding precompile result caching in clients.
    """
    intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator()
    gsc = fork.gas_costs()
    precompile_cost = (
        gsc.PRECOMPILE_ECMUL
        if precompile_address == EIP196Spec.ECMUL
        else gsc.PRECOMPILE_ECADD
    )
    attack_block = Op.POP(
        Op.STATICCALL(
            gas=Op.GAS,
            address=precompile_address,
            args_size=Op.CALLDATASIZE,
            # One G1 point (2 * 32 bytes), overwrites the input point
            # so each iteration has unique precompile input.
            ret_size=64,
            # gas accounting
            address_warm=True,
            inner_call_cost=precompile_cost,
        ),
    )

    setup = Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE)
    loop = WhileGas(body=attack_block, fork=fork)
    code = setup + loop
    attack_contract_address = pre.deploy_contract(code=code)

    txs: list[Transaction] = []
    remaining_gas = gas_benchmark_value

    seed = 0
    expected_opcode_count = 0
    while remaining_gas > 0:
        gas_available = min(tx_gas_limit, remaining_gas)

        calldata = Bytes(
            _generate_g1_point(seed) + _generate_g1_point(seed + 1000)
            if scalar is None
            else _generate_g1_point(seed) + scalar.to_bytes(32, "big")
        )

        intrinsic = intrinsic_gas_calculator(
            calldata=calldata,
            return_cost_deducted_prior_execution=True,
        )
        gas_for_loop = gas_available - intrinsic - setup.gas_cost(fork)
        if gas_for_loop < loop.gas_cost(fork):
            break
        expected_opcode_count += gas_for_loop // loop.gas_cost(fork)
        txs.append(
            Transaction(
                to=attack_contract_address,
                sender=pre.fund_eoa(),
                gas_limit=gas_available,
                data=calldata,
            )
        )
        remaining_gas -= gas_available
        seed += 1

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

Parametrized Test Cases

This test generates 3 parametrized test cases across 4 forks.