@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,
)