Skip to content

test_modexp_uncachable()

Documentation for tests/benchmark/compute/precompile/test_modexp.py::test_modexp_uncachable@ffe48572.

Generate fixtures for these test cases for Bogota with:

fill -v tests/benchmark/compute/precompile/test_modexp.py::test_modexp_uncachable --gas-benchmark-values 1

Benchmark MODEXP with unique input per call.

Source code in tests/benchmark/compute/precompile/test_modexp.py
@pytest.mark.repricing
@pytest.mark.parametrize(
    "mod_exp_input",
    [
        pytest.param(
            ModExpInput(
                base=32 * b"\xff",
                exponent=32 * b"\xff",
                modulus=31 * b"\xff" + b"\x01",
            ),
            id="mod_odd_32b_exp_256",
        ),
        pytest.param(
            ModExpInput(
                base=64 * b"\xff",
                exponent=64 * b"\xff",
                modulus=63 * b"\xff" + b"\x01",
            ),
            id="mod_odd_64b_exp_512",
        ),
        pytest.param(
            ModExpInput(
                base=128 * b"\xff",
                exponent=128 * b"\xff",
                modulus=127 * b"\xff" + b"\x01",
            ),
            id="mod_odd_128b_exp_1024",
        ),
    ],
)
def test_modexp_uncachable(
    benchmark_test: BenchmarkTestFiller,
    pre: Alloc,
    fork: Fork,
    gas_benchmark_value: int,
    tx_gas_limit: int,
    mod_exp_input: ModExpInput,
) -> None:
    """Benchmark MODEXP with unique input per call."""
    base_length = len(mod_exp_input.base)
    intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator()

    base_calldata = bytes(mod_exp_input).rstrip(b"\x00")
    calldata_len = len(base_calldata)
    if fork >= Osaka:
        precompile_cost = Spec7883.calculate_gas_cost(mod_exp_input)
    else:
        precompile_cost = Spec.calculate_gas_cost(mod_exp_input)

    # bsize: data[0:31] 32 bytes
    # esize: data[32:64] 32 bytes
    # msize: data[64:96] 32 bytes
    # B: data[96:96+blength] blength bytes
    # E: data[96+blength:96+blength+elength] elength bytes
    # M: data[96+blength+elength:96+blength+elength+msize] msize bytes

    attack_block = Op.POP(
        Op.STATICCALL(
            gas=Op.GAS,
            address=Spec.MODEXP_ADDRESS,
            args_size=Op.CALLDATASIZE,
            ret_offset=96,
            ret_size=base_length,
            # gas accounting
            address_warm=True,
            inner_call_cost=precompile_cost,
        ),
    )
    setup = Op.CALLDATACOPY(
        0,
        0,
        Op.CALLDATASIZE,
        # gas accounting
        data_size=calldata_len,
        old_memory_size=0,
        new_memory_size=calldata_len,
    )
    setup_cost = setup.gas_cost(fork)

    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
    rng = random.Random(42)
    expected_opcode_count = 0

    while remaining_gas > 0:
        per_tx_gas = min(tx_gas_limit, remaining_gas)
        new_base = rng.randbytes(base_length)
        calldata = Bytes(
            base_calldata[:96]  # bsize, esize, msize
            + new_base  # B
            + base_calldata[96 + base_length :]  # E, M
        )

        intrinsic = intrinsic_gas_calculator(
            calldata=calldata,
            return_cost_deducted_prior_execution=True,
        )
        gas_for_loop = per_tx_gas - intrinsic - setup_cost
        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=per_tx_gas,
                data=calldata,
            )
        )
        remaining_gas -= per_tx_gas

    benchmark_test(
        target_opcode=Precompile.MODEXP,
        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.