def test_bn128_pairings_amortized(
benchmark_test: BenchmarkTestFiller,
fork: Fork,
tx_gas_limit: int,
) -> None:
"""Test running a block with as many BN128 pairings as possible."""
size_per_pairing = 192
gsc = fork.gas_costs()
base_cost = gsc.PRECOMPILE_ECPAIRING_BASE
pairing_cost = gsc.PRECOMPILE_ECPAIRING_PER_POINT
intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator()
mem_exp_gas_calculator = fork.memory_expansion_gas_calculator()
warm_account_access_cost = Op.STATICCALL(
gas=Op.GAS,
address=Op.PUSH20(0),
args_offset=Op.PUSH0,
args_size=Op.PUSH0,
ret_offset=Op.PUSH0,
ret_size=Op.PUSH0,
# gas accounting
address_warm=True,
).gas_cost(fork)
# This is a theoretical maximum number of pairings that can be done in a
# block. It is only used for an upper bound for calculating the optimal
# number of pairings below.
maximum_number_of_pairings = (tx_gas_limit - base_cost) // pairing_cost
# Discover the optimal number of pairings balancing two dimensions:
# 1. Amortize the precompile base cost as much as possible.
# 2. The cost of the memory expansion.
max_pairings = 0
optimal_per_call_num_pairings = 0
for i in range(1, maximum_number_of_pairings + 1):
# We'll pass all pairing arguments via calldata.
available_gas_after_intrinsic = (
tx_gas_limit
- intrinsic_gas_calculator(
calldata=[0xFF]
* size_per_pairing
* i # 0xFF is to indicate non-
# zero bytes.
)
)
available_gas_after_expansion = max(
0,
available_gas_after_intrinsic
- mem_exp_gas_calculator(new_bytes=i * size_per_pairing),
)
approx_gas_cost_per_call = (
warm_account_access_cost + base_cost + i * pairing_cost
)
num_precompile_calls = (
available_gas_after_expansion // approx_gas_cost_per_call
)
num_pairings_done = num_precompile_calls * i # Each precompile call
# does i pairings.
if num_pairings_done > max_pairings:
max_pairings = num_pairings_done
optimal_per_call_num_pairings = i
setup = Op.CALLDATACOPY(size=Op.CALLDATASIZE)
attack_block = Op.POP(
Op.STATICCALL(Op.GAS, EIP197Spec.ECPAIRING, 0, Op.CALLDATASIZE, 0, 0)
)
benchmark_test(
target_opcode=Precompile.BN128_PAIRING,
code_generator=JumpLoopGenerator(
setup=setup,
attack_block=attack_block,
tx_kwargs={
"data": _generate_bn128_pairs(
optimal_per_call_num_pairings, 42
)
},
),
)