diff --git a/tests/benchmark/compute/helpers.py b/tests/benchmark/compute/helpers.py new file mode 100644 index 0000000000..0343ff3cf6 --- /dev/null +++ b/tests/benchmark/compute/helpers.py @@ -0,0 +1,221 @@ +"""Helper functions for the EVM benchmark worst-case tests.""" + +import math +from enum import Enum, auto +from typing import Sequence, cast + +from execution_testing import Fork, Hash, Op + +from tests.osaka.eip7951_p256verify_precompiles.spec import ( + BytesConcatenation as P256BytesConcatenation, +) +from tests.osaka.eip7951_p256verify_precompiles.spec import ( + FieldElement, +) +from tests.prague.eip2537_bls_12_381_precompiles.spec import ( + BytesConcatenation as BLSBytesConcatenation, +) + +DEFAULT_BINOP_ARGS = ( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, + 0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001, +) + +XOR_TABLE_SIZE = 256 +XOR_TABLE = [Hash(i).sha256() for i in range(XOR_TABLE_SIZE)] + + +class StorageAction: + """Enum for storage actions.""" + + READ = auto() + WRITE_SAME_VALUE = auto() + WRITE_NEW_VALUE = auto() + + +class TransactionResult: + """Enum for the possible transaction outcomes.""" + + SUCCESS = auto() + OUT_OF_GAS = auto() + REVERT = auto() + + +class ReturnDataStyle(Enum): + """Helper enum to specify return data is returned to the caller.""" + + RETURN = auto() + REVERT = auto() + IDENTITY = auto() + + +class CallDataOrigin: + """Enum for calldata origins.""" + + TRANSACTION = auto() + CALL = auto() + + +def neg(x: int) -> int: + """Negate the given integer in the two's complement 256-bit range.""" + assert 0 <= x < 2**256 + return 2**256 - x + + +def make_dup(index: int) -> Op: + """ + Create a DUP instruction which duplicates the index-th (counting from 0) + element from the top of the stack. E.g. make_dup(0) → DUP1. + """ + assert 0 <= index < 16, f"DUP index {index} out of range [0, 15]" + return getattr(Op, f"DUP{index + 1}") + + +def to_signed(x: int) -> int: + """Convert an unsigned integer to a signed integer.""" + return x if x < 2**255 else x - 2**256 + + +def to_unsigned(x: int) -> int: + """Convert a signed integer to an unsigned integer.""" + return x if x >= 0 else x + 2**256 + + +def shr(x: int, s: int) -> int: + """Shift right.""" + return x >> s + + +def shl(x: int, s: int) -> int: + """Shift left.""" + return x << s + + +def sar(x: int, s: int) -> int: + """Arithmetic shift right.""" + return to_unsigned(to_signed(x) >> s) + + +def concatenate_parameters( + parameters: ( + Sequence[str] + | Sequence[P256BytesConcatenation] + | Sequence[BLSBytesConcatenation] + | Sequence[bytes] + ), +) -> bytes: + """ + Concatenate precompile parameters into bytes. + + Args: + parameters: List of parameters, either as hex strings or byte objects + (bytes, BytesConcatenation, or FieldElement). + + Returns: + Concatenated bytes from all parameters. + + """ + if all(isinstance(p, str) for p in parameters): + parameters_str = cast(Sequence[str], parameters) + concatenated_hex_string = "".join(parameters_str) + return bytes.fromhex(concatenated_hex_string) + elif all( + isinstance( + p, + ( + bytes, + P256BytesConcatenation, + BLSBytesConcatenation, + FieldElement, + ), + ) + for p in parameters + ): + parameters_bytes_list = [ + bytes(p) + for p in cast( + Sequence[ + P256BytesConcatenation + | BLSBytesConcatenation + | bytes + | FieldElement + ], + parameters, + ) + ] + return b"".join(parameters_bytes_list) + else: + raise TypeError( + "parameters must be a sequence of strings (hex) " + "or a sequence of byte-like objects (bytes, BytesConcatenation or " + "FieldElement)." + ) + + +def calculate_optimal_input_length( + available_gas: int, + fork: Fork, + static_cost: int, + per_word_dynamic_cost: int, + bytes_per_unit_of_work: int, +) -> int: + """ + Calculate the optimal input length to maximize precompile work. + + This function finds the input size that maximizes the total amount of + work (in terms of bytes processed) a precompile can perform given a + fixed gas budget. It balances the trade-off between making more calls + with smaller inputs versus fewer calls with larger inputs. + + Args: + available_gas: Total gas available for precompile calls. + fork: The fork to use for gas cost calculations. + static_cost: Static gas cost per precompile call. + per_word_dynamic_cost: Dynamic gas cost per 32-byte word of input. + bytes_per_unit_of_work: Number of bytes processed per unit of work. + + Returns: + The optimal input length in bytes that maximizes total work. + + """ + gsc = fork.gas_costs() + mem_exp_gas_calculator = fork.memory_expansion_gas_calculator() + + max_work = 0 + optimal_input_length = 0 + + for input_length in range(1, 1_000_000, 32): + parameters_gas = ( + gsc.G_BASE # PUSH0 = arg offset + + gsc.G_BASE # PUSH0 = arg size + + gsc.G_BASE # PUSH0 = arg size + + gsc.G_VERY_LOW # PUSH0 = arg offset + + gsc.G_VERY_LOW # PUSHN = address + + gsc.G_BASE # GAS + ) + iteration_gas_cost = ( + parameters_gas + + static_cost # Precompile static cost + + math.ceil(input_length / 32) * per_word_dynamic_cost + # Precompile dynamic cost + + gsc.G_BASE # POP + ) + + # From the available gas, subtract the memory expansion costs + # considering the current input size length. + available_gas_after_expansion = max( + 0, available_gas - mem_exp_gas_calculator(new_bytes=input_length) + ) + + # Calculate how many calls we can do. + num_calls = available_gas_after_expansion // iteration_gas_cost + total_work = num_calls * math.ceil( + input_length / bytes_per_unit_of_work + ) + + # If we found an input size with better total work, save it. + if total_work > max_work: + max_work = total_work + optimal_input_length = input_length + + return optimal_input_length diff --git a/tests/benchmark/compute/instruction/test_account_query.py b/tests/benchmark/compute/instruction/test_account_query.py new file mode 100644 index 0000000000..1190194f77 --- /dev/null +++ b/tests/benchmark/compute/instruction/test_account_query.py @@ -0,0 +1,493 @@ +""" +Benchmark operations that require querying the account state, either on the +current executing account or on a target account. +""" + +import math + +import pytest +from execution_testing import ( + Account, + Address, + Alloc, + BenchmarkTestFiller, + Block, + BlockchainTestFiller, + Bytecode, + Environment, + ExtCallGenerator, + Fork, + Hash, + JumpLoopGenerator, + Op, + Transaction, + While, + compute_create2_address, +) + +from tests.benchmark.compute.helpers import ( + XOR_TABLE, +) + + +@pytest.mark.parametrize("contract_balance", [0, 1]) +def test_selfbalance( + benchmark_test: BenchmarkTestFiller, + contract_balance: int, +) -> None: + """Benchmark SELFBALANCE instruction.""" + benchmark_test( + code_generator=ExtCallGenerator( + attack_block=Op.SELFBALANCE, + contract_balance=contract_balance, + ), + ) + + +def test_codesize( + benchmark_test: BenchmarkTestFiller, +) -> None: + """Benchmark CODESIZE instruction.""" + benchmark_test( + code_generator=ExtCallGenerator(attack_block=Op.CODESIZE), + ) + + +@pytest.mark.parametrize( + "max_code_size_ratio", + [ + pytest.param(0, id="0 bytes"), + pytest.param(0.25, id="0.25x max code size"), + pytest.param(0.50, id="0.50x max code size"), + pytest.param(0.75, id="0.75x max code size"), + pytest.param(1.00, id="max code size"), + ], +) +@pytest.mark.parametrize( + "fixed_src_dst", + [ + True, + False, + ], +) +def test_codecopy( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + max_code_size_ratio: float, + fixed_src_dst: bool, +) -> None: + """Benchmark CODECOPY instruction.""" + max_code_size = fork.max_code_size() + + size = int(max_code_size * max_code_size_ratio) + + setup = Op.PUSH32(size) + src_dst = 0 if fixed_src_dst else Op.MOD(Op.GAS, 7) + attack_block = Op.CODECOPY(src_dst, src_dst, Op.DUP1) # DUP1 copies size. + + code = JumpLoopGenerator( + setup=setup, attack_block=attack_block + ).generate_repeated_code( + repeated_code=attack_block, setup=setup, fork=fork + ) + + # Pad the generated code to ensure the contract size matches the maximum + # The content of the padding bytes is arbitrary. + code += Op.INVALID * (max_code_size - len(code)) + assert len(code) == max_code_size, ( + f"Code size {len(code)} is not equal to max code size {max_code_size}." + ) + + tx = Transaction( + to=pre.deploy_contract(code=code), + sender=pre.fund_eoa(), + ) + + benchmark_test(tx=tx) + + +@pytest.mark.parametrize( + "opcode", + [ + Op.EXTCODESIZE, + Op.EXTCODEHASH, + Op.EXTCODECOPY, + ], +) +def test_extcode_ops( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + opcode: Op, + env: Environment, + gas_benchmark_value: int, +) -> None: + """ + Benchmark a block execution where a single opcode execution. + """ + # The attack gas limit is the gas limit which the target tx will use The + # test will scale the block gas limit to setup the contracts accordingly to + # be able to pay for the contract deposit. This has to take into account + # the 200 gas per byte, but also the quadratic memory expansion costs which + # have to be paid each time the memory is being setup + attack_gas_limit = gas_benchmark_value + max_contract_size = fork.max_code_size() + + gas_costs = fork.gas_costs() + + # Calculate the absolute minimum gas costs to deploy the contract This does + # not take into account setting up the actual memory (using KECCAK256 and + # XOR) so the actual costs of deploying the contract is higher + memory_expansion_gas_calculator = fork.memory_expansion_gas_calculator() + memory_gas_minimum = memory_expansion_gas_calculator( + new_bytes=len(bytes(max_contract_size)) + ) + code_deposit_gas_minimum = ( + fork.gas_costs().G_CODE_DEPOSIT_BYTE * max_contract_size + + memory_gas_minimum + ) + + intrinsic_gas_cost_calc = fork.transaction_intrinsic_cost_calculator() + # Calculate the loop cost of the attacker to query one address + loop_cost = ( + gas_costs.G_KECCAK_256 # KECCAK static cost + + math.ceil(85 / 32) * gas_costs.G_KECCAK_256_WORD # KECCAK dynamic + # cost for CREATE2 + + gas_costs.G_VERY_LOW * 3 # ~MSTOREs+ADDs + + gas_costs.G_COLD_ACCOUNT_ACCESS # Opcode cost + + 30 # ~Gluing opcodes + ) + # Calculate the number of contracts to be targeted + num_contracts = ( + # Base available gas = GAS_LIMIT - intrinsic - (out of loop MSTOREs) + attack_gas_limit - intrinsic_gas_cost_calc() - gas_costs.G_VERY_LOW * 4 + ) // loop_cost + + # Set the block gas limit to a relative high value to ensure the code + # deposit tx fits in the block (there is enough gas available in the block + # to execute this) + minimum_gas_limit = code_deposit_gas_minimum * 2 * num_contracts + if env.gas_limit < minimum_gas_limit: + raise Exception( + f"`BENCHMARKING_MAX_GAS` ({env.gas_limit}) is no longer enough to" + f" support this test, which requires {minimum_gas_limit} gas for " + "its setup. Update the value or consider optimizing gas usage " + "during the setup phase of this test." + ) + + # The initcode will take its address as a starting point to the input to + # the keccak hash function. It will reuse the output of the hash function + # in a loop to create a large amount of seemingly random code, until it + # reaches the maximum contract size. + initcode = ( + Op.MSTORE(0, Op.ADDRESS) + + While( + body=( + Op.SHA3(Op.SUB(Op.MSIZE, 32), 32) + # Use a xor table to avoid having to call the "expensive" sha3 + # opcode as much + + sum( + ( + Op.PUSH32[xor_value] + + Op.XOR + + Op.DUP1 + + Op.MSIZE + + Op.MSTORE + ) + for xor_value in XOR_TABLE + ) + + Op.POP + ), + condition=Op.LT(Op.MSIZE, max_contract_size), + ) + # Despite the whole contract has random bytecode, we make the first + # opcode be a STOP so CALL-like attacks return as soon as possible, + # while EXTCODE(HASH|SIZE) work as intended. + + Op.MSTORE8(0, 0x00) + + Op.RETURN(0, max_contract_size) + ) + initcode_address = pre.deploy_contract(code=initcode) + + # The factory contract will simply use the initcode that is already + # deployed, and create a new contract and return its address if successful. + factory_code = ( + Op.EXTCODECOPY( + address=initcode_address, + dest_offset=0, + offset=0, + size=Op.EXTCODESIZE(initcode_address), + ) + + Op.MSTORE( + 0, + Op.CREATE2( + value=0, + offset=0, + size=Op.EXTCODESIZE(initcode_address), + salt=Op.SLOAD(0), + ), + ) + + Op.SSTORE(0, Op.ADD(Op.SLOAD(0), 1)) + + Op.RETURN(0, 32) + ) + factory_address = pre.deploy_contract(code=factory_code) + + # The factory caller will call the factory contract N times, creating N new + # contracts. Calldata should contain the N value. + factory_caller_code = Op.CALLDATALOAD(0) + While( + body=Op.POP(Op.CALL(address=factory_address)), + condition=Op.PUSH1(1) + + Op.SWAP1 + + Op.SUB + + Op.DUP1 + + Op.ISZERO + + Op.ISZERO, + ) + factory_caller_address = pre.deploy_contract(code=factory_caller_code) + + contracts_deployment_tx = Transaction( + to=factory_caller_address, + gas_limit=env.gas_limit, + gas_price=10**6, + data=Hash(num_contracts), + sender=pre.fund_eoa(), + ) + + post = {} + deployed_contract_addresses = [] + for i in range(num_contracts): + deployed_contract_address = compute_create2_address( + address=factory_address, + salt=i, + initcode=initcode, + ) + post[deployed_contract_address] = Account(nonce=1) + deployed_contract_addresses.append(deployed_contract_address) + + attack_call = Bytecode() + if opcode == Op.EXTCODECOPY: + attack_call = Op.EXTCODECOPY( + address=Op.SHA3(32 - 20 - 1, 85), dest_offset=96, size=1000 + ) + else: + # For the rest of the opcodes, we can use the same generic attack call + # since all only minimally need the `address` of the target. + attack_call = Op.POP(opcode(address=Op.SHA3(32 - 20 - 1, 85))) + attack_code = ( + # Setup memory for later CREATE2 address generation loop. + # 0xFF+[Address(20bytes)]+[seed(32bytes)]+[initcode keccak(32bytes)] + Op.MSTORE(0, factory_address) + + Op.MSTORE8(32 - 20 - 1, 0xFF) + + Op.MSTORE(32, 0) + + Op.MSTORE(64, initcode.keccak256()) + # Main loop + + While( + body=attack_call + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), + ) + ) + + if len(attack_code) > max_contract_size: + # TODO: A workaround could be to split the opcode code into multiple + # contracts and call them in sequence. + raise ValueError( + f"Code size {len(attack_code)} exceeds maximum " + f"code size {max_contract_size}" + ) + opcode_address = pre.deploy_contract(code=attack_code) + opcode_tx = Transaction( + to=opcode_address, + gas_limit=attack_gas_limit, + gas_price=10**9, + sender=pre.fund_eoa(), + ) + + blockchain_test( + pre=pre, + post=post, + blocks=[ + Block(txs=[contracts_deployment_tx]), + Block(txs=[opcode_tx]), + ], + exclude_full_post_state_in_output=True, + ) + + +@pytest.mark.parametrize( + "copied_size", + [ + pytest.param(512, id="512"), + pytest.param(1024, id="1KiB"), + pytest.param(5 * 1024, id="5KiB"), + ], +) +def test_extcodecopy_warm( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + copied_size: int, + gas_benchmark_value: int, +) -> None: + """Benchmark EXTCODECOPY instruction.""" + copied_contract_address = pre.deploy_contract( + code=Op.JUMPDEST * copied_size, + ) + + execution_code = ( + Op.PUSH10(copied_size) + + Op.PUSH20(copied_contract_address) + + While( + body=Op.EXTCODECOPY(Op.DUP4, 0, 0, Op.DUP2), + ) + ) + execution_code_address = pre.deploy_contract(code=execution_code) + tx = Transaction( + to=execution_code_address, + gas_limit=gas_benchmark_value, + sender=pre.fund_eoa(), + ) + + benchmark_test(tx=tx) + + +@pytest.mark.parametrize( + "opcode", + [ + Op.BALANCE, + Op.EXTCODESIZE, + Op.EXTCODEHASH, + Op.CALL, + Op.CALLCODE, + Op.DELEGATECALL, + Op.STATICCALL, + ], +) +@pytest.mark.parametrize( + "absent_target", + [ + True, + False, + ], +) +def test_ext_account_query_warm( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + opcode: Op, + absent_target: bool, +) -> None: + """ + Test running a block with as many stateful opcodes doing warm access + for an account. + """ + # Setup + target_addr = pre.empty_account() + post = {} + if not absent_target: + code = Op.STOP + Op.JUMPDEST * 100 + target_addr = pre.deploy_contract(balance=100, code=code) + post[target_addr] = Account(balance=100, code=code) + + # Execution + setup = Op.MSTORE(0, target_addr) + attack_block = Op.POP(opcode(address=Op.MLOAD(0))) + benchmark_test( + post=post, + code_generator=JumpLoopGenerator( + setup=setup, attack_block=attack_block + ), + ) + + +@pytest.mark.parametrize( + "opcode", + [ + Op.BALANCE, + ], +) +@pytest.mark.parametrize( + "absent_accounts", + [ + True, + False, + ], +) +def test_ext_account_query_cold( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + opcode: Op, + absent_accounts: bool, + env: Environment, + gas_benchmark_value: int, +) -> None: + """ + Benchmark stateful opcodes accessing cold accounts. + """ + attack_gas_limit = gas_benchmark_value + + gas_costs = fork.gas_costs() + intrinsic_gas_cost_calc = fork.transaction_intrinsic_cost_calculator() + # For calculation robustness, the calculation below ignores "glue" opcodes + # like PUSH and POP. It should be considered a worst-case number of + # accounts, and a few of them might not be targeted before the attacking + # transaction runs out of gas. + num_target_accounts = ( + attack_gas_limit - intrinsic_gas_cost_calc() + ) // gas_costs.G_COLD_ACCOUNT_ACCESS + + blocks = [] + post = {} + + # Setup The target addresses are going to be constructed (in the case of + # absent=False) and called as addr_offset + i, where i is the index of the + # account. This is to avoid collisions with the addresses indirectly + # created by the testing framework. + addr_offset = int.from_bytes(pre.fund_eoa(amount=0)) + + if not absent_accounts: + factory_code = Op.PUSH4(num_target_accounts) + While( + body=Op.POP( + Op.CALL(address=Op.ADD(addr_offset, Op.DUP6), value=10) + ), + condition=Op.PUSH1(1) + + Op.SWAP1 + + Op.SUB + + Op.DUP1 + + Op.ISZERO + + Op.ISZERO, + ) + factory_address = pre.deploy_contract( + code=factory_code, balance=10**18 + ) + + setup_tx = Transaction( + to=factory_address, + gas_limit=env.gas_limit, + sender=pre.fund_eoa(), + ) + blocks.append(Block(txs=[setup_tx])) + + for i in range(num_target_accounts): + addr = Address(i + addr_offset + 1) + post[addr] = Account(balance=10) + + # Execution + op_code = Op.PUSH4(num_target_accounts) + While( + body=Op.POP(opcode(Op.ADD(addr_offset, Op.DUP1))), + condition=Op.PUSH1(1) + + Op.SWAP1 + + Op.SUB + + Op.DUP1 + + Op.ISZERO + + Op.ISZERO, + ) + op_address = pre.deploy_contract(code=op_code) + op_tx = Transaction( + to=op_address, + gas_limit=attack_gas_limit, + sender=pre.fund_eoa(), + ) + blocks.append(Block(txs=[op_tx])) + + benchmark_test( + post=post, + blocks=blocks, + ) diff --git a/tests/benchmark/compute/instruction/test_arithmetic.py b/tests/benchmark/compute/instruction/test_arithmetic.py new file mode 100644 index 0000000000..14c76bba3c --- /dev/null +++ b/tests/benchmark/compute/instruction/test_arithmetic.py @@ -0,0 +1,373 @@ +"""Benchmark arithmetic instructions.""" + +import operator +import random + +import pytest +from execution_testing import ( + Alloc, + BenchmarkTestFiller, + Bytecode, + Fork, + JumpLoopGenerator, + Op, + Transaction, +) + +from tests.benchmark.compute.helpers import DEFAULT_BINOP_ARGS, make_dup, neg + +# Arithmetic instructions: +# ADD, ADDMOD +# SUB, SUBMOD +# MUL, MULMOD +# DIV, SDIV +# MOD, SMOD +# EXP +# SIGNEXTEND + + +@pytest.mark.parametrize( + "opcode,opcode_args", + [ + ( + Op.ADD, + DEFAULT_BINOP_ARGS, + ), + ( + Op.MUL, + DEFAULT_BINOP_ARGS, + ), + ( + # After every 2 SUB operations, values return to initial. + Op.SUB, + DEFAULT_BINOP_ARGS, + ), + ( + # This has the cycle of 2: + # v[0] = a // b + # v[1] = a // v[0] = a // (a // b) = b + # v[2] = a // b + Op.DIV, + ( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, + # We want the first divisor to be slightly bigger than 2**128: + # this is the worst case for the division algorithm with + # optimized paths for division by 1 and 2 words. + 0x100000000000000000000000000000033, + ), + ), + ( + # This has the cycle of 2, see above. + Op.DIV, + ( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, + # We want the first divisor to be slightly bigger than 2**64: + # this is the worst case for the division algorithm with an + # optimized path for division by 1 word. + 0x10000000000000033, + ), + ), + ( + # Same as DIV-0 + # But the numerator made positive, and the divisor made negative. + Op.SDIV, + ( + 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCD, + ), + ), + ( + # Same as DIV-1 + # But the numerator made positive, and the divisor made negative. + Op.SDIV, + ( + 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFCD, + ), + ), + ( + # Not suitable for MOD, as values quickly become zero. + Op.MOD, + DEFAULT_BINOP_ARGS, + ), + ( + # Not suitable for SMOD, as values quickly become zero. + Op.SMOD, + DEFAULT_BINOP_ARGS, + ), + ( + # This keeps the values unchanged + # pow(2**256-1, 2**256-1, 2**256) == 2**256-1. + Op.EXP, + ( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + ), + ), + ( + # Not great, as we always sign-extend the 4 bytes. + Op.SIGNEXTEND, + ( + 3, + 0xFFDADADA, # Negative to have more work. + ), + ), + ], + ids=lambda param: "" if isinstance(param, tuple) else param, +) +def test_arithmetic( + benchmark_test: BenchmarkTestFiller, + opcode: Op, + opcode_args: tuple[int, int], +) -> None: + """ + Benchmark binary instructions (takes two args, pushes one value). + The execution starts with two initial values on the stack + The stack is balanced by the DUP2 instruction. + """ + tx_data = b"".join( + arg.to_bytes(32, byteorder="big") for arg in opcode_args + ) + + setup = Op.CALLDATALOAD(0) + Op.CALLDATALOAD(32) + Op.DUP2 + Op.DUP2 + attack_block = Op.DUP2 + opcode + cleanup = Op.POP + Op.POP + Op.DUP2 + Op.DUP2 + benchmark_test( + code_generator=JumpLoopGenerator( + setup=setup, + attack_block=attack_block, + cleanup=cleanup, + tx_kwargs={"data": tx_data}, + ), + ) + + +@pytest.mark.parametrize("mod_bits", [255, 191, 127, 63]) +@pytest.mark.parametrize("op", [Op.MOD, Op.SMOD]) +def test_mod( + benchmark_test: BenchmarkTestFiller, + mod_bits: int, + op: Op, +) -> None: + """ + Benchmark MOD instructions. + + The program consists of code segments evaluating the "MOD chain": + mod[0] = calldataload(0) + mod[1] = numerators[indexes[0]] % mod[0] + mod[2] = numerators[indexes[1]] % mod[1] ... + + The "numerators" is a pool of 15 constants pushed to the EVM stack at the + program start. + + The order of accessing the numerators is selected in a way the mod value + remains in the range as long as possible. + """ + # For SMOD we negate both numerator and modulus. The underlying + # computation is the same, + # just the SMOD implementation will have to additionally handle the + # sign bits. + # The result stays negative. + should_negate = op == Op.SMOD + + num_numerators = 15 + numerator_bits = 256 if not should_negate else 255 + numerator_max = 2**numerator_bits - 1 + numerator_min = 2 ** (numerator_bits - 1) + + # Pick the modulus min value so that it is _unlikely_ to drop to the lower + # word count. + assert mod_bits >= 63 + mod_min = 2 ** (mod_bits - 63) + + # Select the random seed giving the longest found MOD chain. You can look + # for a longer one by increasing the numerators_min_len. This will activate + # the while loop below. + match op, mod_bits: + case Op.MOD, 255: + seed = 20393 + numerators_min_len = 750 + case Op.MOD, 191: + seed = 25979 + numerators_min_len = 770 + case Op.MOD, 127: + seed = 17671 + numerators_min_len = 750 + case Op.MOD, 63: + seed = 29181 + numerators_min_len = 730 + case Op.SMOD, 255: + seed = 4015 + numerators_min_len = 750 + case Op.SMOD, 191: + seed = 17355 + numerators_min_len = 750 + case Op.SMOD, 127: + seed = 897 + numerators_min_len = 750 + case Op.SMOD, 63: + seed = 7562 + numerators_min_len = 720 + case _: + raise ValueError(f"{mod_bits}-bit {op} not supported.") + + while True: + rng = random.Random(seed) + + # Create the list of random numerators. + numerators = [ + rng.randint(numerator_min, numerator_max) + for _ in range(num_numerators) + ] + + # Create the random initial modulus. + initial_mod = rng.randint(2 ** (mod_bits - 1), 2**mod_bits - 1) + + # Evaluate the MOD chain and collect the order of accessing numerators. + mod = initial_mod + indexes = [] + while mod >= mod_min: + # Compute results for each numerator. + results = [n % mod for n in numerators] + # And pick the best one. + i = max(range(len(results)), key=results.__getitem__) + mod = results[i] + indexes.append(i) + + # Disable if you want to find longer MOD chains. + assert len(indexes) > numerators_min_len + if len(indexes) > numerators_min_len: + break + seed += 1 + print(f"{seed=}") + + # TODO: Don't use fixed PUSH32. Let Bytecode helpers to select optimal + # push opcode. + setup = sum((Op.PUSH32[n] for n in numerators), Bytecode()) + attack_block = ( + Op.CALLDATALOAD(0) + + sum(make_dup(len(numerators) - i) + op for i in indexes) + + Op.POP + ) + + input_value = initial_mod if not should_negate else neg(initial_mod) + benchmark_test( + code_generator=JumpLoopGenerator( + setup=setup, + attack_block=attack_block, + tx_kwargs={"data": input_value.to_bytes(32, byteorder="big")}, + ), + ) + + +@pytest.mark.parametrize("mod_bits", [255, 191, 127, 63]) +@pytest.mark.parametrize("op", [Op.ADDMOD, Op.MULMOD]) +def test_mod_arithmetic( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + mod_bits: int, + op: Op, + gas_benchmark_value: int, +) -> None: + """ + Benchmark ADDMOD and MULMOD instructions. + + The program consists of code segments evaluating the "op chain": + mod[0] = calldataload(0) + mod[1] = (fixed_arg op args[indexes[0]]) % mod[0] + mod[2] = (fixed_arg op args[indexes[1]]) % mod[1] + The "args" is a pool of 15 constants pushed to the EVM stack at the program + start. + The "fixed_arg" is the 0xFF...FF constant added to the EVM stack by PUSH32 + just before executing the "op". + The order of accessing the numerators is selected in a way the mod value + remains in the range as long as possible. + """ + fixed_arg = 2**256 - 1 + num_args = 15 + + max_code_size = fork.max_code_size() + + # Pick the modulus min value so that it is _unlikely_ to drop to the lower + # word count. + assert mod_bits >= 63 + mod_min = 2 ** (mod_bits - 63) + + # Select the random seed giving the longest found op chain. You can look + # for a longer one by increasing the op_chain_len. This will activate the + # while loop below. + op_chain_len = 666 + match op, mod_bits: + case Op.ADDMOD, 255: + seed = 4 + case Op.ADDMOD, 191: + seed = 2 + case Op.ADDMOD, 127: + seed = 2 + case Op.ADDMOD, 63: + seed = 64 + case Op.MULMOD, 255: + seed = 5 + case Op.MULMOD, 191: + seed = 389 + case Op.MULMOD, 127: + seed = 5 + case Op.MULMOD, 63: + # For this setup we were not able to find an op-chain longer than + # 600. + seed = 4193 + op_chain_len = 600 + case _: + raise ValueError(f"{mod_bits}-bit {op} not supported.") + + while True: + rng = random.Random(seed) + args = [rng.randint(2**255, 2**256 - 1) for _ in range(num_args)] + initial_mod = rng.randint(2 ** (mod_bits - 1), 2**mod_bits - 1) + + # Evaluate the op chain and collect the order of accessing numerators. + op_fn = operator.add if op == Op.ADDMOD else operator.mul + mod = initial_mod + indexes: list[int] = [] + while mod >= mod_min and len(indexes) < op_chain_len: + results = [op_fn(a, fixed_arg) % mod for a in args] + # And pick the best one. + i = max(range(len(results)), key=results.__getitem__) + mod = results[i] + indexes.append(i) + + # Disable if you want to find longer op chains. + assert len(indexes) == op_chain_len + if len(indexes) == op_chain_len: + break + seed += 1 + print(f"{seed=}") + + code_constant_pool = sum((Op.PUSH32[n] for n in args), Bytecode()) + code_segment = ( + Op.CALLDATALOAD(0) + + sum( + make_dup(len(args) - i) + Op.PUSH32[fixed_arg] + op + for i in indexes + ) + + Op.POP + ) + # Construct the final code. Because of the usage of PUSH32 the code segment + # is very long, so don't try to include multiple of these. + code = ( + code_constant_pool + + Op.JUMPDEST + + code_segment + + Op.JUMP(len(code_constant_pool)) + ) + assert (max_code_size - len(code_segment)) < len(code) <= max_code_size + + tx = Transaction( + to=pre.deploy_contract(code=code), + data=initial_mod.to_bytes(32, byteorder="big"), + gas_limit=gas_benchmark_value, + sender=pre.fund_eoa(), + ) + + benchmark_test(tx=tx) diff --git a/tests/benchmark/compute/instruction/test_bitwise.py b/tests/benchmark/compute/instruction/test_bitwise.py new file mode 100644 index 0000000000..18d59082d8 --- /dev/null +++ b/tests/benchmark/compute/instruction/test_bitwise.py @@ -0,0 +1,228 @@ +"""Benchmark bitwise instructions.""" + +import random +from typing import Callable + +import pytest +from execution_testing import ( + Alloc, + BenchmarkTestFiller, + Bytecode, + Fork, + JumpLoopGenerator, + Op, + Transaction, +) + +from tests.benchmark.compute.helpers import ( + DEFAULT_BINOP_ARGS, + make_dup, + sar, + shl, + shr, +) + +# Bitwise instructions: +# AND, OR, XOR, NOT, BYTE, SHL, SHR, SAR, CLZ + + +@pytest.mark.parametrize( + "opcode,opcode_args", + [ + ( + Op.AND, + DEFAULT_BINOP_ARGS, + ), + ( + Op.OR, + DEFAULT_BINOP_ARGS, + ), + ( + Op.XOR, + DEFAULT_BINOP_ARGS, + ), + ( + Op.BYTE, # Keep extracting the last byte: 0x2F. + ( + 31, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, + ), + ), + ( + Op.SHL, # Shift by 1 until getting 0. + ( + 1, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, + ), + ), + ( + Op.SHR, # Shift by 1 until getting 0. + ( + 1, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, + ), + ), + ( + Op.SAR, # Shift by 1 until getting -1. + ( + 1, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, + ), + ), + ], + ids=lambda param: "" if isinstance(param, tuple) else param, +) +def test_bitwise( + benchmark_test: BenchmarkTestFiller, + opcode: Op, + opcode_args: tuple[int, int], +) -> None: + """ + Benchmark binary instructions (takes two args, pushes one value). + The execution starts with two initial values on the stack + The stack is balanced by the DUP2 instruction. + """ + tx_data = b"".join( + arg.to_bytes(32, byteorder="big") for arg in opcode_args + ) + + setup = Op.CALLDATALOAD(0) + Op.CALLDATALOAD(32) + Op.DUP2 + Op.DUP2 + attack_block = Op.DUP2 + opcode + cleanup = Op.POP + Op.POP + Op.DUP2 + Op.DUP2 + benchmark_test( + code_generator=JumpLoopGenerator( + setup=setup, + attack_block=attack_block, + cleanup=cleanup, + tx_kwargs={"data": tx_data}, + ), + ) + + +def test_not_op( + benchmark_test: BenchmarkTestFiller, +) -> None: + """ + Benchmark NOT instruction (takes one arg, pushes one value). + """ + benchmark_test( + code_generator=JumpLoopGenerator(setup=Op.PUSH0, attack_block=Op.NOT), + ) + + +@pytest.mark.parametrize("shift_right", [Op.SHR, Op.SAR]) +def test_shifts( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + shift_right: Op, + gas_benchmark_value: int, +) -> None: + """ + Benchmark shift instructions with non-trivial arguments. + + This generates left-right pairs of shifts to avoid zeroing the argument. + Shift amounts are randomly selected from constant pool of 15 stack values. + """ + max_code_size = fork.max_code_size() + + match shift_right: + case Op.SHR: + shift_right_fn = shr + case Op.SAR: + shift_right_fn = sar + case _: + raise ValueError(f"Unexpected shift op: {shift_right}") + + rng = random.Random(1) # Use random with a fixed seed. + initial_value = 2**256 - 1 # The initial value to be shifted; should be + # negative for SAR. + + # Create the list of shift amounts with 15 elements (max reachable by DUPs + # instructions). For the worst case keep the values small and omit values + # divisible by 8. + shift_amounts = [x + (x >= 8) + (x >= 15) for x in range(1, 16)] + + code_prefix = ( + sum(Op.PUSH1[sh] for sh in shift_amounts) + + Op.JUMPDEST + + Op.CALLDATALOAD(0) + ) + code_suffix = Op.POP + Op.JUMP(len(shift_amounts) * 2) + code_body_len = max_code_size - len(code_prefix) - len(code_suffix) + + def select_shift_amount( + shift_fn: Callable[[int, int], int], v: int + ) -> tuple[int, int]: + """Select a shift amount that will produce a non-zero result.""" + while True: + index = rng.randint(0, len(shift_amounts) - 1) + sh = shift_amounts[index] + new_v = shift_fn(v, sh) % 2**256 + if new_v != 0: + return new_v, index + + code_body = Bytecode() + v = initial_value + while len(code_body) <= code_body_len - 4: + v, i = select_shift_amount(shl, v) + code_body += make_dup(len(shift_amounts) - i) + Op.SHL + v, i = select_shift_amount(shift_right_fn, v) + code_body += make_dup(len(shift_amounts) - i) + shift_right + + code = code_prefix + code_body + code_suffix + assert len(code) == max_code_size - 2 + + tx = Transaction( + to=pre.deploy_contract(code=code), + data=initial_value.to_bytes(32, byteorder="big"), + gas_limit=gas_benchmark_value, + sender=pre.fund_eoa(), + ) + + benchmark_test(tx=tx) + + +@pytest.mark.valid_from("Osaka") +def test_clz_same(benchmark_test: BenchmarkTestFiller) -> None: + """Benchmark CLZ instruction with same input.""" + magic_value = 248 # CLZ(248) = 248 + benchmark_test( + code_generator=JumpLoopGenerator( + setup=Op.PUSH1(magic_value), attack_block=Op.CLZ + ), + ) + + +@pytest.mark.valid_from("Osaka") +def test_clz_diff( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """Benchmark CLZ instruction with different input.""" + max_code_size = fork.max_code_size() + + code_prefix = Op.JUMPDEST + code_suffix = Op.PUSH0 + Op.JUMP + + available_code_size = max_code_size - len(code_prefix) - len(code_suffix) + + code_seq = Bytecode() + + for i in range(available_code_size): + value = (2**256 - 1) >> (i % 256) + clz_op = Op.CLZ(value) + Op.POP + if len(code_seq) + len(clz_op) > available_code_size: + break + code_seq += clz_op + + attack_code = code_prefix + code_seq + code_suffix + assert len(attack_code) <= max_code_size + + tx = Transaction( + to=pre.deploy_contract(code=attack_code), + sender=pre.fund_eoa(), + ) + + benchmark_test(tx=tx) diff --git a/tests/benchmark/compute/instruction/test_block_context.py b/tests/benchmark/compute/instruction/test_block_context.py new file mode 100644 index 0000000000..204f87d170 --- /dev/null +++ b/tests/benchmark/compute/instruction/test_block_context.py @@ -0,0 +1,49 @@ +"""Benchmark block context instructions.""" + +import pytest +from execution_testing import ( + BenchmarkTestFiller, + Block, + ExtCallGenerator, + Op, +) + +# Block context instructions: +# BLOCKHASH, COINBASE, TIMESTAMP, NUMBER, PREVRANDAO, GASLIMIT, CHAINID, +# BASEFEE, BLOBBASEFEE + + +@pytest.mark.parametrize( + "opcode", + [ + Op.COINBASE, + Op.TIMESTAMP, + Op.NUMBER, + Op.PREVRANDAO, + Op.GASLIMIT, + Op.CHAINID, + Op.BASEFEE, + Op.BLOBBASEFEE, + ], +) +def test_block_context_ops( + benchmark_test: BenchmarkTestFiller, + opcode: Op, +) -> None: + """Benchmark zero-parameter block context instructions.""" + benchmark_test( + code_generator=ExtCallGenerator(attack_block=opcode), + ) + + +def test_blockhash( + benchmark_test: BenchmarkTestFiller, +) -> None: + """Benchmark BLOCKHASH instruction accessing oldest allowed block.""" + # Create 256 dummy blocks to fill the blockhash window. + blocks = [Block()] * 256 + + benchmark_test( + setup_blocks=blocks, + code_generator=ExtCallGenerator(attack_block=Op.BLOCKHASH(1)), + ) diff --git a/tests/benchmark/test_worst_memory.py b/tests/benchmark/compute/instruction/test_call_context.py old mode 100755 new mode 100644 similarity index 58% rename from tests/benchmark/test_worst_memory.py rename to tests/benchmark/compute/instruction/test_call_context.py index b71eac031d..1405ccb724 --- a/tests/benchmark/test_worst_memory.py +++ b/tests/benchmark/compute/instruction/test_call_context.py @@ -1,8 +1,4 @@ -""" -Tests that benchmark EVMs in the worst-case memory opcodes. -""" - -from enum import auto +"""Benchmark call frame context instructions.""" import pytest from execution_testing import ( @@ -10,21 +6,109 @@ BenchmarkTestFiller, Bytecode, Bytes, + ExtCallGenerator, Fork, JumpLoopGenerator, Op, Transaction, ) -REFERENCE_SPEC_GIT_PATH = "TODO" -REFERENCE_SPEC_VERSION = "TODO" +from tests.benchmark.compute.helpers import ( + CallDataOrigin, + ReturnDataStyle, +) -class CallDataOrigin: - """Enum for calldata origins.""" +@pytest.mark.parametrize( + "opcode", + [ + Op.ADDRESS, + Op.CALLER, + ], +) +def test_call_frame_context_ops( + benchmark_test: BenchmarkTestFiller, + opcode: Op, +) -> None: + """Benchmark call zero-parameter instructions.""" + benchmark_test( + code_generator=ExtCallGenerator(attack_block=opcode), + ) - TRANSACTION = auto() - CALL = auto() + +@pytest.mark.parametrize("calldata_length", [0, 1_000, 10_000]) +def test_calldatasize( + benchmark_test: BenchmarkTestFiller, + calldata_length: int, +) -> None: + """Benchmark CALLDATASIZE instruction.""" + benchmark_test( + code_generator=JumpLoopGenerator( + attack_block=Op.POP(Op.CALLDATASIZE), + tx_kwargs={"data": b"\x00" * calldata_length}, + ), + ) + + +@pytest.mark.parametrize("non_zero_value", [True, False]) +@pytest.mark.parametrize("from_origin", [True, False]) +def test_callvalue( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + non_zero_value: bool, + from_origin: bool, +) -> None: + """ + Benchmark CALLVALUE instruction. + + - non_zero_value: whether opcode must return non-zero value. + - from_origin: whether the call frame is the immediate one + from the transaction or a previous CALL. + """ + code_address = JumpLoopGenerator( + attack_block=Op.POP(Op.CALLVALUE) + ).deploy_contracts(pre=pre, fork=fork) + + if from_origin: + tx_to = code_address + else: + entry_code = ( + Op.JUMPDEST + + Op.CALL(address=code_address, value=1 if non_zero_value else 0) + + Op.JUMP(Op.PUSH0) + ) + tx_to = pre.deploy_contract(code=entry_code, balance=1_000_000) + + tx = Transaction( + to=tx_to, + value=1 if non_zero_value and from_origin else 0, + sender=pre.fund_eoa(), + ) + + benchmark_test(tx=tx) + + +@pytest.mark.parametrize( + "calldata", + [ + pytest.param(b"", id="empty"), + pytest.param(b"\x00", id="zero-loop"), + pytest.param(b"\x00" * 31 + b"\x20", id="one-loop"), + ], +) +def test_calldataload( + benchmark_test: BenchmarkTestFiller, + calldata: bytes, +) -> None: + """Benchmark CALLDATALOAD instruction.""" + benchmark_test( + code_generator=JumpLoopGenerator( + setup=Op.PUSH0, + attack_block=Op.CALLDATALOAD, + tx_kwargs={"data": calldata}, + ), + ) @pytest.mark.parametrize( @@ -57,7 +141,7 @@ class CallDataOrigin: False, ], ) -def test_worst_calldatacopy( +def test_calldatacopy( benchmark_test: BenchmarkTestFiller, pre: Alloc, fork: Fork, @@ -67,7 +151,7 @@ def test_worst_calldatacopy( non_zero_data: bool, gas_benchmark_value: int, ) -> None: - """Test running a block filled with CALLDATACOPY executions.""" + """Benchmark CALLDATACOPY instruction.""" if size == 0 and non_zero_data: pytest.skip("Non-zero data with size 0 is not applicable.") @@ -135,61 +219,56 @@ def test_worst_calldatacopy( @pytest.mark.parametrize( - "max_code_size_ratio", + "return_data_style", [ - pytest.param(0, id="0 bytes"), - pytest.param(0.25, id="0.25x max code size"), - pytest.param(0.50, id="0.50x max code size"), - pytest.param(0.75, id="0.75x max code size"), - pytest.param(1.00, id="max code size"), - ], -) -@pytest.mark.parametrize( - "fixed_src_dst", - [ - True, - False, + ReturnDataStyle.RETURN, + ReturnDataStyle.REVERT, + ReturnDataStyle.IDENTITY, ], ) -def test_worst_codecopy( +@pytest.mark.parametrize("returned_size", [1, 0]) +def test_returndatasize_nonzero( benchmark_test: BenchmarkTestFiller, pre: Alloc, - fork: Fork, - max_code_size_ratio: float, - fixed_src_dst: bool, + returned_size: int, + return_data_style: ReturnDataStyle, ) -> None: - """Test running a block filled with CODECOPY executions.""" - max_code_size = fork.max_code_size() - - size = int(max_code_size * max_code_size_ratio) - - setup = Op.PUSH32(size) - src_dst = 0 if fixed_src_dst else Op.MOD(Op.GAS, 7) - attack_block = Op.CODECOPY(src_dst, src_dst, Op.DUP1) # DUP1 copies size. + """ + Benchmark RETURNDATASIZE instruction with non-zero buffer. + + - returned_size: the size of the returned data buffer. + - return_data_style: how returned data is produced for the opcode caller. + """ + setup = Bytecode() + if return_data_style != ReturnDataStyle.IDENTITY: + setup += Op.STATICCALL( + address=pre.deploy_contract( + code=Op.REVERT(0, returned_size) + if return_data_style == ReturnDataStyle.REVERT + else Op.RETURN(0, returned_size) + ) + ) + else: + setup += Op.MSTORE8(0, 1) + Op.STATICCALL( + address=0x04, # Identity precompile + args_size=returned_size, + ) - code = JumpLoopGenerator( - setup=setup, attack_block=attack_block - ).generate_repeated_code( - repeated_code=attack_block, setup=setup, fork=fork + benchmark_test( + code_generator=JumpLoopGenerator( + setup=setup, attack_block=Op.POP(Op.RETURNDATASIZE) + ), ) - # The code generated above is not guaranteed to be of max_code_size, so - # we pad it since - # a test parameter targets CODECOPYing a contract with max code size. - # Padded bytecode values - # are not relevant. - code += Op.INVALID * (max_code_size - len(code)) - assert len(code) == max_code_size, ( - f"Code size {len(code)} is not equal to max code size {max_code_size}." - ) - tx = Transaction( - to=pre.deploy_contract(code=code), - sender=pre.fund_eoa(), +def test_returndatasize_zero( + benchmark_test: BenchmarkTestFiller, +) -> None: + """Benchmark RETURNDATASIZE instruction with zero buffer.""" + benchmark_test( + code_generator=ExtCallGenerator(attack_block=Op.RETURNDATASIZE), ) - benchmark_test(tx=tx) - @pytest.mark.parametrize( "size", @@ -207,13 +286,13 @@ def test_worst_codecopy( False, ], ) -def test_worst_returndatacopy( +def test_returndatacopy( benchmark_test: BenchmarkTestFiller, pre: Alloc, size: int, fixed_dst: bool, ) -> None: - """Test running a block filled with RETURNDATACOPY executions.""" + """Benchmark RETURNDATACOPY instruction.""" # Create the contract that will RETURN the data that will be used for # RETURNDATACOPY. # Random-ish data is injected at different points in memory to avoid @@ -239,19 +318,6 @@ def test_worst_returndatacopy( ) attack_block = Op.RETURNDATACOPY(dst, Op.PUSH0, Op.RETURNDATASIZE) - # The attack loop is constructed as: - # ``` - # JUMPDEST(#) - # RETURNDATACOPY(...) - # RETURNDATACOPY(...) - # ... - # STATICCALL(address=helper_contract) - # JUMP(#) - # ``` - # The goal is that once per (big) loop iteration, the helper contract is - # called to - # generate fresh returndata to continue calling RETURNDATACOPY. - benchmark_test( code_generator=JumpLoopGenerator( setup=returndata_gen, @@ -259,42 +325,3 @@ def test_worst_returndatacopy( cleanup=returndata_gen, ), ) - - -@pytest.mark.parametrize( - "size", - [ - pytest.param(0, id="0 bytes"), - pytest.param(100, id="100 bytes"), - pytest.param(10 * 1024, id="10KiB"), - pytest.param(1024 * 1024, id="1MiB"), - ], -) -@pytest.mark.parametrize( - "fixed_src_dst", - [ - True, - False, - ], -) -def test_worst_mcopy( - benchmark_test: BenchmarkTestFiller, - size: int, - fixed_src_dst: bool, -) -> None: - """Test running a block filled with MCOPY executions.""" - src_dst = 0 if fixed_src_dst else Op.MOD(Op.GAS, 7) - attack_block = Op.MCOPY(src_dst, src_dst, size) - - mem_touch = ( - Op.MSTORE8(0, Op.GAS) - + Op.MSTORE8(size // 2, Op.GAS) - + Op.MSTORE8(size - 1, Op.GAS) - if size > 0 - else Bytecode() - ) - benchmark_test( - code_generator=JumpLoopGenerator( - setup=mem_touch, attack_block=attack_block, cleanup=mem_touch - ), - ) diff --git a/tests/benchmark/compute/instruction/test_comparison.py b/tests/benchmark/compute/instruction/test_comparison.py new file mode 100644 index 0000000000..aeb97dfbcc --- /dev/null +++ b/tests/benchmark/compute/instruction/test_comparison.py @@ -0,0 +1,86 @@ +"""Benchmark comparison instructions.""" + +import pytest +from execution_testing import ( + BenchmarkTestFiller, + JumpLoopGenerator, + Op, +) + +# Comparison instructions: +# LT, SLT, GT, SGT, EQ, ISZERO + + +@pytest.mark.parametrize( + "opcode,opcode_args", + [ + ( + Op.LT, # Keeps getting result 1. + (0, 1), + ), + ( + Op.GT, # Keeps getting result 0. + (0, 1), + ), + ( + Op.SLT, # Keeps getting result 1. + ( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + 1, + ), + ), + ( + Op.SGT, # Keeps getting result 0. + ( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + 1, + ), + ), + ( + # The worst case is if the arguments are equal (no early return), + # so let's keep it comparing ones. + Op.EQ, + (1, 1), + ), + ], + ids=lambda param: "" if isinstance(param, tuple) else param, +) +def test_comparison( + benchmark_test: BenchmarkTestFiller, + opcode: Op, + opcode_args: tuple[int, int], +) -> None: + """ + Benchmark binary instructions (takes two args, pushes one value). + The execution starts with two initial values on the stack + The stack is balanced by the DUP2 instruction. + """ + tx_data = b"".join( + arg.to_bytes(32, byteorder="big") for arg in opcode_args + ) + + setup = Op.CALLDATALOAD(0) + Op.CALLDATALOAD(32) + Op.DUP2 + Op.DUP2 + attack_block = Op.DUP2 + opcode + cleanup = Op.POP + Op.POP + Op.DUP2 + Op.DUP2 + benchmark_test( + code_generator=JumpLoopGenerator( + setup=setup, + attack_block=attack_block, + cleanup=cleanup, + tx_kwargs={"data": tx_data}, + ), + ) + + +def test_iszero( + benchmark_test: BenchmarkTestFiller, +) -> None: + """ + Benchmark ISZERO instruction (takes one arg, pushes one value). + """ + benchmark_test( + code_generator=JumpLoopGenerator( + setup=Op.PUSH0, + attack_block=Op.ISZERO, + ), + ) diff --git a/tests/benchmark/compute/instruction/test_control_flow.py b/tests/benchmark/compute/instruction/test_control_flow.py new file mode 100644 index 0000000000..f3a2d696cd --- /dev/null +++ b/tests/benchmark/compute/instruction/test_control_flow.py @@ -0,0 +1,68 @@ +"""Benchmark control flow instructions.""" + +from execution_testing import ( + Alloc, + BenchmarkTestFiller, + ExtCallGenerator, + JumpLoopGenerator, + Op, + Transaction, +) + +# Control flow instructions: +# STOP, JUMP, JUMPI, PC, GAS, JUMPDEST + + +def test_gas_op( + benchmark_test: BenchmarkTestFiller, +) -> None: + """Benchmark GAS instruction.""" + benchmark_test( + code_generator=ExtCallGenerator(attack_block=Op.GAS), + ) + + +def test_jumps( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, +) -> None: + """Benchmark JUMP instruction.""" + tx = Transaction( + to=pre.deploy_contract(code=(Op.JUMPDEST + Op.JUMP(Op.PUSH0))), + sender=pre.fund_eoa(), + ) + + benchmark_test(tx=tx) + + +def test_jumpi_fallthrough( + benchmark_test: BenchmarkTestFiller, +) -> None: + """Benchmark JUMPI instruction with fallthrough.""" + benchmark_test( + code_generator=JumpLoopGenerator( + attack_block=Op.JUMPI(Op.PUSH0, Op.PUSH0) + ), + ) + + +def test_jumpis( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, +) -> None: + """Benchmark JUMPI instruction.""" + tx = Transaction( + to=pre.deploy_contract( + code=(Op.JUMPDEST + Op.JUMPI(Op.PUSH0, Op.NUMBER)) + ), + sender=pre.fund_eoa(), + ) + + benchmark_test(tx=tx) + + +def test_jumpdests( + benchmark_test: BenchmarkTestFiller, +) -> None: + """Benchmark JUMPDEST instruction.""" + benchmark_test(code_generator=JumpLoopGenerator(attack_block=Op.JUMPDEST)) diff --git a/tests/benchmark/compute/instruction/test_keccak.py b/tests/benchmark/compute/instruction/test_keccak.py new file mode 100644 index 0000000000..28edd306bd --- /dev/null +++ b/tests/benchmark/compute/instruction/test_keccak.py @@ -0,0 +1,66 @@ +"""Benchmark keccak instructions.""" + +import math + +from execution_testing import ( + BenchmarkTestFiller, + Fork, + JumpLoopGenerator, + Op, +) + +# Keccak instructions: +# KECCAK256 + +KECCAK_RATE = 136 + + +def test_keccak( + benchmark_test: BenchmarkTestFiller, + fork: Fork, + gas_benchmark_value: int, +) -> None: + """Benchmark KECCAK256 instruction.""" + # Intrinsic gas cost is paid once. + intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() + available_gas = gas_benchmark_value - intrinsic_gas_calculator() + + gsc = fork.gas_costs() + mem_exp_gas_calculator = fork.memory_expansion_gas_calculator() + + # Discover the optimal input size to maximize keccak-permutations, + # not to maximize keccak calls. + # The complication of the discovery arises from + # the non-linear gas cost of memory expansion. + max_keccak_perm_per_block = 0 + optimal_input_length = 0 + for i in range(1, 1_000_000, 32): + iteration_gas_cost = ( + 2 * gsc.G_VERY_LOW # PUSHN + PUSH1 + + gsc.G_KECCAK_256 # KECCAK256 static cost + + math.ceil(i / 32) * gsc.G_KECCAK_256_WORD # KECCAK256 dynamic + # cost + + gsc.G_BASE # POP + ) + # From the available gas, we subtract the mem expansion costs + # considering we know the current input size length i. + available_gas_after_expansion = max( + 0, available_gas - mem_exp_gas_calculator(new_bytes=i) + ) + # Calculate how many calls we can do. + num_keccak_calls = available_gas_after_expansion // iteration_gas_cost + # KECCAK does 1 permutation every 136 bytes. + num_keccak_permutations = num_keccak_calls * math.ceil(i / KECCAK_RATE) + + # If we found an input size that is better (reg permutations/gas), then + # save it. + if num_keccak_permutations > max_keccak_perm_per_block: + max_keccak_perm_per_block = num_keccak_permutations + optimal_input_length = i + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=Op.PUSH20[optimal_input_length], + attack_block=Op.POP(Op.SHA3(Op.PUSH0, Op.DUP1)), + ), + ) diff --git a/tests/benchmark/test_worst_opcode.py b/tests/benchmark/compute/instruction/test_log.py old mode 100755 new mode 100644 similarity index 91% rename from tests/benchmark/test_worst_opcode.py rename to tests/benchmark/compute/instruction/test_log.py index 18f488edd3..fed038f2f3 --- a/tests/benchmark/test_worst_opcode.py +++ b/tests/benchmark/compute/instruction/test_log.py @@ -1,6 +1,4 @@ -""" -Tests benchmark worst-case opcode scenarios. -""" +"""Benchmark log instructions.""" import pytest from execution_testing import ( @@ -8,9 +6,11 @@ Bytecode, JumpLoopGenerator, Op, - Opcode, ) +# Log instructions: +# LOG0, LOG1, LOG2, LOG3, LOG4 + @pytest.mark.parametrize( "opcode", @@ -38,15 +38,15 @@ ], ) @pytest.mark.parametrize("fixed_offset", [True, False]) -def test_worst_log_opcodes( +def test_log( benchmark_test: BenchmarkTestFiller, - opcode: Opcode, + opcode: Op, zeros_topic: bool, size: int, fixed_offset: bool, non_zero_data: bool, ) -> None: - """Test running a block with as many LOG opcodes as possible.""" + """Benchmark LOG instructions.""" setup = Bytecode() # For non-zero data, load into memory. diff --git a/tests/benchmark/compute/instruction/test_memory.py b/tests/benchmark/compute/instruction/test_memory.py new file mode 100644 index 0000000000..d939b1bce7 --- /dev/null +++ b/tests/benchmark/compute/instruction/test_memory.py @@ -0,0 +1,104 @@ +"""Benchmark memory instructions.""" + +import pytest +from execution_testing import ( + BenchmarkTestFiller, + Bytecode, + ExtCallGenerator, + JumpLoopGenerator, + Op, +) + +# Memory instructions: +# MSTORE, MSTORE8, MLOAD, MSIZE, MCOPY + + +@pytest.mark.parametrize("mem_size", [0, 1, 1_000, 100_000, 1_000_000]) +def test_msize( + benchmark_test: BenchmarkTestFiller, + mem_size: int, +) -> None: + """ + Benchmark MSIZE instruction. + + - mem_size: by how much the memory is expanded. + """ + benchmark_test( + code_generator=ExtCallGenerator( + setup=Op.MLOAD(Op.SELFBALANCE) + Op.POP, + attack_block=Op.MSIZE, + contract_balance=mem_size, + ), + ) + + +@pytest.mark.parametrize("opcode", [Op.MLOAD, Op.MSTORE, Op.MSTORE8]) +@pytest.mark.parametrize("offset", [0, 1, 31]) +@pytest.mark.parametrize("offset_initialized", [True, False]) +@pytest.mark.parametrize("big_memory_expansion", [True, False]) +def test_memory_access( + benchmark_test: BenchmarkTestFiller, + opcode: Op, + offset: int, + offset_initialized: bool, + big_memory_expansion: bool, +) -> None: + """Benchmark memory access instructions.""" + mem_exp_code = ( + Op.MSTORE8(10 * 1024, 1) if big_memory_expansion else Bytecode() + ) + offset_set_code = ( + Op.MSTORE(offset, 43) if offset_initialized else Bytecode() + ) + setup = mem_exp_code + offset_set_code + Op.PUSH1(42) + Op.PUSH1(offset) + + attack_block = ( + Op.POP(Op.MLOAD(Op.DUP1)) + if opcode == Op.MLOAD + else opcode(Op.DUP2, Op.DUP2) + ) + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=setup, attack_block=attack_block + ), + ) + + +@pytest.mark.parametrize( + "size", + [ + pytest.param(0, id="0 bytes"), + pytest.param(100, id="100 bytes"), + pytest.param(10 * 1024, id="10KiB"), + pytest.param(1024 * 1024, id="1MiB"), + ], +) +@pytest.mark.parametrize( + "fixed_src_dst", + [ + True, + False, + ], +) +def test_mcopy( + benchmark_test: BenchmarkTestFiller, + size: int, + fixed_src_dst: bool, +) -> None: + """Benchmark MCOPY instruction.""" + src_dst = 0 if fixed_src_dst else Op.MOD(Op.GAS, 7) + attack_block = Op.MCOPY(src_dst, src_dst, size) + + mem_touch = ( + Op.MSTORE8(0, Op.GAS) + + Op.MSTORE8(size // 2, Op.GAS) + + Op.MSTORE8(size - 1, Op.GAS) + if size > 0 + else Bytecode() + ) + benchmark_test( + code_generator=JumpLoopGenerator( + setup=mem_touch, attack_block=attack_block, cleanup=mem_touch + ), + ) diff --git a/tests/benchmark/compute/instruction/test_stack.py b/tests/benchmark/compute/instruction/test_stack.py new file mode 100644 index 0000000000..204852dde3 --- /dev/null +++ b/tests/benchmark/compute/instruction/test_stack.py @@ -0,0 +1,142 @@ +"""Benchmark stack instructions.""" + +import pytest +from execution_testing import ( + Alloc, + BenchmarkTestFiller, + ExtCallGenerator, + Fork, + JumpLoopGenerator, + Op, +) + +# Stack instructions: +# POP, PUSHx, DUPx, SWAPx + + +@pytest.mark.parametrize( + "opcode", + [ + Op.SWAP1, + Op.SWAP2, + Op.SWAP3, + Op.SWAP4, + Op.SWAP5, + Op.SWAP6, + Op.SWAP7, + Op.SWAP8, + Op.SWAP9, + Op.SWAP10, + Op.SWAP11, + Op.SWAP12, + Op.SWAP13, + Op.SWAP14, + Op.SWAP15, + Op.SWAP16, + ], +) +def test_swap( + benchmark_test: BenchmarkTestFiller, + opcode: Op, +) -> None: + """Benchmark SWAP instruction.""" + benchmark_test( + code_generator=JumpLoopGenerator( + attack_block=opcode, setup=Op.PUSH0 * opcode.min_stack_height + ), + ) + + +@pytest.mark.parametrize( + "opcode", + [ + pytest.param(Op.DUP1), + pytest.param(Op.DUP2), + pytest.param(Op.DUP3), + pytest.param(Op.DUP4), + pytest.param(Op.DUP5), + pytest.param(Op.DUP6), + pytest.param(Op.DUP7), + pytest.param(Op.DUP8), + pytest.param(Op.DUP9), + pytest.param(Op.DUP10), + pytest.param(Op.DUP11), + pytest.param(Op.DUP12), + pytest.param(Op.DUP13), + pytest.param(Op.DUP14), + pytest.param(Op.DUP15), + pytest.param(Op.DUP16), + ], +) +def test_dup( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + opcode: Op, +) -> None: + """Benchmark DUP instruction.""" + max_stack_height = fork.max_stack_height() + + min_stack_height = opcode.min_stack_height + code = Op.PUSH0 * min_stack_height + opcode * ( + max_stack_height - min_stack_height + ) + target_contract_address = pre.deploy_contract(code=code) + + attack_block = Op.POP( + Op.STATICCALL(Op.GAS, target_contract_address, 0, 0, 0, 0) + ) + + benchmark_test( + code_generator=JumpLoopGenerator(attack_block=attack_block), + ) + + +@pytest.mark.parametrize( + "opcode", + [ + pytest.param(Op.PUSH0), + pytest.param(Op.PUSH1), + pytest.param(Op.PUSH2), + pytest.param(Op.PUSH3), + pytest.param(Op.PUSH4), + pytest.param(Op.PUSH5), + pytest.param(Op.PUSH6), + pytest.param(Op.PUSH7), + pytest.param(Op.PUSH8), + pytest.param(Op.PUSH9), + pytest.param(Op.PUSH10), + pytest.param(Op.PUSH11), + pytest.param(Op.PUSH12), + pytest.param(Op.PUSH13), + pytest.param(Op.PUSH14), + pytest.param(Op.PUSH15), + pytest.param(Op.PUSH16), + pytest.param(Op.PUSH17), + pytest.param(Op.PUSH18), + pytest.param(Op.PUSH19), + pytest.param(Op.PUSH20), + pytest.param(Op.PUSH21), + pytest.param(Op.PUSH22), + pytest.param(Op.PUSH23), + pytest.param(Op.PUSH24), + pytest.param(Op.PUSH25), + pytest.param(Op.PUSH26), + pytest.param(Op.PUSH27), + pytest.param(Op.PUSH28), + pytest.param(Op.PUSH29), + pytest.param(Op.PUSH30), + pytest.param(Op.PUSH31), + pytest.param(Op.PUSH32), + ], +) +def test_push( + benchmark_test: BenchmarkTestFiller, + opcode: Op, +) -> None: + """Benchmark PUSH instruction.""" + benchmark_test( + code_generator=ExtCallGenerator( + attack_block=opcode[1] if opcode.has_data_portion() else opcode + ), + ) diff --git a/tests/benchmark/compute/instruction/test_storage.py b/tests/benchmark/compute/instruction/test_storage.py new file mode 100644 index 0000000000..a07673c35f --- /dev/null +++ b/tests/benchmark/compute/instruction/test_storage.py @@ -0,0 +1,361 @@ +"""Benchmark storage instructions.""" + +import pytest +from execution_testing import ( + Alloc, + BenchmarkTestFiller, + Block, + Bytecode, + Environment, + Fork, + JumpLoopGenerator, + Op, + TestPhaseManager, + Transaction, + While, + compute_create_address, +) + +from tests.benchmark.compute.helpers import StorageAction, TransactionResult + +# Storage instructions: +# SLOAD, SSTORE, TLOAD, TSTORE + + +# `key_mut` indicates the key isn't fixed. +@pytest.mark.parametrize("key_mut", [True, False]) +# `val_mut` indicates that at the end of each big-loop, the value of the target +# key changes. +@pytest.mark.parametrize("val_mut", [True, False]) +def test_tload( + benchmark_test: BenchmarkTestFiller, + key_mut: bool, + val_mut: bool, +) -> None: + """Benchmark TLOAD instruction.""" + start_key = 41 + code_key_mut = Bytecode() + code_val_mut = Bytecode() + setup = Bytecode() + if key_mut and val_mut: + setup = Op.PUSH1(start_key) + attack_block = Op.POP(Op.TLOAD(Op.DUP1)) + code_key_mut = Op.POP + Op.GAS + code_val_mut = Op.TSTORE(Op.DUP2, Op.GAS) + if key_mut and not val_mut: + attack_block = Op.POP(Op.TLOAD(Op.GAS)) + if not key_mut and val_mut: + attack_block = Op.POP(Op.TLOAD(Op.CALLVALUE)) + code_val_mut = Op.TSTORE( + Op.CALLVALUE, Op.GAS + ) # CALLVALUE configured in the tx + if not key_mut and not val_mut: + attack_block = Op.POP(Op.TLOAD(Op.CALLVALUE)) + + cleanup = code_key_mut + code_val_mut + tx_value = start_key if not key_mut and val_mut else 0 + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=setup, + attack_block=attack_block, + cleanup=cleanup, + tx_kwargs={ + "value": tx_value, + }, + ), + ) + + +@pytest.mark.parametrize("key_mut", [True, False]) +@pytest.mark.parametrize("dense_val_mut", [True, False]) +def test_tstore( + benchmark_test: BenchmarkTestFiller, + key_mut: bool, + dense_val_mut: bool, +) -> None: + """Benchmark TSTORE instruction.""" + init_key = 42 + setup = Op.PUSH1(init_key) + + # If `dense_val_mut` is set, we use GAS as a cheap way of always + # storing a different value than + # the previous one. + attack_block = Op.TSTORE(Op.DUP2, Op.GAS if dense_val_mut else Op.DUP1) + + # If `key_mut` is True, we mutate the key on every iteration of the + # big loop. + cleanup = Op.POP + Op.GAS if key_mut else Bytecode() + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=setup, attack_block=attack_block, cleanup=cleanup + ), + ) + + +@pytest.mark.parametrize( + "storage_action,tx_result", + [ + pytest.param( + StorageAction.READ, + TransactionResult.SUCCESS, + id="SSLOAD", + ), + pytest.param( + StorageAction.WRITE_SAME_VALUE, + TransactionResult.SUCCESS, + id="SSTORE same value", + ), + pytest.param( + StorageAction.WRITE_SAME_VALUE, + TransactionResult.REVERT, + id="SSTORE same value, revert", + ), + pytest.param( + StorageAction.WRITE_SAME_VALUE, + TransactionResult.OUT_OF_GAS, + id="SSTORE same value, out of gas", + ), + pytest.param( + StorageAction.WRITE_NEW_VALUE, + TransactionResult.SUCCESS, + id="SSTORE new value", + ), + pytest.param( + StorageAction.WRITE_NEW_VALUE, + TransactionResult.REVERT, + id="SSTORE new value, revert", + ), + pytest.param( + StorageAction.WRITE_NEW_VALUE, + TransactionResult.OUT_OF_GAS, + id="SSTORE new value, out of gas", + ), + ], +) +@pytest.mark.parametrize( + "absent_slots", + [ + True, + False, + ], +) +def test_storage_access_cold( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + storage_action: StorageAction, + absent_slots: bool, + env: Environment, + gas_benchmark_value: int, + tx_result: TransactionResult, +) -> None: + """ + Benchmark cold storage slot accesses. + """ + gas_costs = fork.gas_costs() + intrinsic_gas_cost_calc = fork.transaction_intrinsic_cost_calculator() + + loop_cost = gas_costs.G_COLD_SLOAD # All accesses are always cold + if storage_action == StorageAction.WRITE_NEW_VALUE: + if not absent_slots: + loop_cost += gas_costs.G_STORAGE_RESET + else: + loop_cost += gas_costs.G_STORAGE_SET + elif storage_action == StorageAction.WRITE_SAME_VALUE: + if absent_slots: + loop_cost += gas_costs.G_STORAGE_SET + else: + loop_cost += gas_costs.G_WARM_SLOAD + elif storage_action == StorageAction.READ: + loop_cost += 0 # Only G_COLD_SLOAD is charged + + # Contract code + execution_code_body = Bytecode() + if storage_action == StorageAction.WRITE_SAME_VALUE: + # All the storage slots in the contract are initialized to their index. + # That is, storage slot `i` is initialized to `i`. + execution_code_body = Op.SSTORE(Op.DUP1, Op.DUP1) + loop_cost += gas_costs.G_VERY_LOW * 2 + elif storage_action == StorageAction.WRITE_NEW_VALUE: + # The new value 2^256-1 is guaranteed to be different from the initial + # value. + execution_code_body = Op.SSTORE(Op.DUP2, Op.NOT(0)) + loop_cost += gas_costs.G_VERY_LOW * 3 + elif storage_action == StorageAction.READ: + execution_code_body = Op.POP(Op.SLOAD(Op.DUP1)) + loop_cost += gas_costs.G_VERY_LOW + gas_costs.G_BASE + + # Add costs jump-logic costs + loop_cost += ( + gas_costs.G_JUMPDEST # Prefix Jumpdest + + gas_costs.G_VERY_LOW * 7 # ISZEROs, PUSHs, SWAPs, SUB, DUP + + gas_costs.G_HIGH # JUMPI + ) + + prefix_cost = ( + gas_costs.G_VERY_LOW # Target slots push + ) + + suffix_cost = 0 + if tx_result == TransactionResult.REVERT: + suffix_cost = ( + gas_costs.G_VERY_LOW * 2 # Revert PUSHs + ) + + num_target_slots = ( + gas_benchmark_value + - intrinsic_gas_cost_calc() + - prefix_cost + - suffix_cost + ) // loop_cost + if tx_result == TransactionResult.OUT_OF_GAS: + # Add an extra slot to make it run out-of-gas + num_target_slots += 1 + + code_prefix = Op.PUSH4(num_target_slots) + Op.JUMPDEST + code_loop = execution_code_body + Op.JUMPI( + len(code_prefix) - 1, + Op.PUSH1(1) + Op.SWAP1 + Op.SUB + Op.DUP1 + Op.ISZERO + Op.ISZERO, + ) + execution_code = code_prefix + code_loop + + if tx_result == TransactionResult.REVERT: + execution_code += Op.REVERT(0, 0) + else: + execution_code += Op.STOP + + execution_code_address = pre.deploy_contract(code=execution_code) + + total_gas_used = ( + num_target_slots * loop_cost + + intrinsic_gas_cost_calc() + + prefix_cost + + suffix_cost + ) + + # Contract creation + slots_init = Bytecode() + if not absent_slots: + slots_init = Op.PUSH4(num_target_slots) + While( + body=Op.SSTORE(Op.DUP1, Op.DUP1), + condition=Op.PUSH1(1) + + Op.SWAP1 + + Op.SUB + + Op.DUP1 + + Op.ISZERO + + Op.ISZERO, + ) + + # To create the contract, we apply the slots_init code to initialize the + # storage slots (int the case of absent_slots=False) and then copy the + # execution code to the contract. + creation_code = ( + slots_init + + Op.EXTCODECOPY( + address=execution_code_address, + dest_offset=0, + offset=0, + size=Op.EXTCODESIZE(execution_code_address), + ) + + Op.RETURN(0, Op.MSIZE) + ) + sender_addr = pre.fund_eoa() + setup_tx = Transaction( + to=None, + gas_limit=env.gas_limit, + data=creation_code, + sender=sender_addr, + ) + + blocks = [Block(txs=[setup_tx])] + + contract_address = compute_create_address(address=sender_addr, nonce=0) + + op_tx = Transaction( + to=contract_address, + gas_limit=gas_benchmark_value, + sender=pre.fund_eoa(), + ) + blocks.append(Block(txs=[op_tx])) + + benchmark_test( + blocks=blocks, + expected_benchmark_gas_used=( + total_gas_used + if tx_result != TransactionResult.OUT_OF_GAS + else gas_benchmark_value + ), + ) + + +@pytest.mark.parametrize( + "storage_action", + [ + pytest.param(StorageAction.READ, id="SLOAD"), + pytest.param(StorageAction.WRITE_SAME_VALUE, id="SSTORE same value"), + pytest.param(StorageAction.WRITE_NEW_VALUE, id="SSTORE new value"), + ], +) +def test_storage_access_warm( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + storage_action: StorageAction, + gas_benchmark_value: int, + env: Environment, +) -> None: + """ + Benchmark warm storage slot accesses. + """ + blocks = [] + + # The target storage slot for the warm access is storage slot 0. + storage_slot_initial_value = 10 + + # Contract code + execution_code_body = Bytecode() + if storage_action == StorageAction.WRITE_SAME_VALUE: + execution_code_body = Op.SSTORE(0, Op.DUP1) + elif storage_action == StorageAction.WRITE_NEW_VALUE: + execution_code_body = Op.PUSH1(1) + Op.ADD + Op.SSTORE(0, Op.DUP1) + elif storage_action == StorageAction.READ: + execution_code_body = Op.POP(Op.SLOAD(0)) + + execution_code = Op.PUSH1(storage_slot_initial_value) + While( + body=execution_code_body, + ) + execution_code_address = pre.deploy_contract(code=execution_code) + + creation_code = ( + Op.SSTORE(0, storage_slot_initial_value) + + Op.EXTCODECOPY( + address=execution_code_address, + dest_offset=0, + offset=0, + size=Op.EXTCODESIZE(execution_code_address), + ) + + Op.RETURN(0, Op.MSIZE) + ) + + with TestPhaseManager.setup(): + sender_addr = pre.fund_eoa() + setup_tx = Transaction( + to=None, + gas_limit=env.gas_limit, + data=creation_code, + sender=sender_addr, + ) + blocks.append(Block(txs=[setup_tx])) + + contract_address = compute_create_address(address=sender_addr, nonce=0) + + with TestPhaseManager.execution(): + op_tx = Transaction( + to=contract_address, + gas_limit=gas_benchmark_value, + sender=pre.fund_eoa(), + ) + blocks.append(Block(txs=[op_tx])) + + benchmark_test(blocks=blocks) diff --git a/tests/benchmark/test_worst_bytecode.py b/tests/benchmark/compute/instruction/test_system.py old mode 100755 new mode 100644 similarity index 52% rename from tests/benchmark/test_worst_bytecode.py rename to tests/benchmark/compute/instruction/test_system.py index ce2a3ce84f..80933bc979 --- a/tests/benchmark/test_worst_bytecode.py +++ b/tests/benchmark/compute/instruction/test_system.py @@ -1,6 +1,4 @@ -""" -Tests that benchmark EVMs in worst-case opcode scenarios. -""" +"""Benchmark system instructions.""" import math @@ -17,32 +15,31 @@ Hash, JumpLoopGenerator, Op, + StateTestFiller, Transaction, While, compute_create2_address, compute_create_address, ) -REFERENCE_SPEC_GIT_PATH = "TODO" -REFERENCE_SPEC_VERSION = "TODO" +from tests.benchmark.compute.helpers import XOR_TABLE -XOR_TABLE_SIZE = 256 -XOR_TABLE = [Hash(i).sha256() for i in range(XOR_TABLE_SIZE)] +# System instructions: +# CREATE, CREATE2 +# RETURN, REVERT +# CALL, CALLCODE, SELFDESTRUCT, DELEGATECALL, STATICCALL @pytest.mark.parametrize( "opcode", [ - Op.EXTCODESIZE, - Op.EXTCODEHASH, Op.CALL, Op.CALLCODE, Op.DELEGATECALL, Op.STATICCALL, - Op.EXTCODECOPY, ], ) -def test_worst_bytecode_single_opcode( +def test_xcall( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, @@ -50,18 +47,7 @@ def test_worst_bytecode_single_opcode( env: Environment, gas_benchmark_value: int, ) -> None: - """ - Test a block execution where a single opcode execution maxes out the gas - limit, and the opcodes access a huge amount of contract code. - - We first use a single block to deploy a factory contract that will be used - to deploy a large number of contracts. - - This is done to avoid having a big pre-allocation size for the test. - - The test is performed in the last block of the test, and the entire block - gas limit is consumed by repeated opcode executions. - """ + """Benchmark a system execution where a single opcode execution.""" # The attack gas limit is the gas limit which the target tx will use The # test will scale the block gas limit to setup the contracts accordingly to # be able to pay for the contract deposit. This has to take into account @@ -248,83 +234,6 @@ def test_worst_bytecode_single_opcode( ) -@pytest.mark.parametrize( - "pattern", - [ - Op.STOP, - Op.JUMPDEST, - Op.PUSH1[bytes(Op.JUMPDEST)], - Op.PUSH2[bytes(Op.JUMPDEST + Op.JUMPDEST)], - Op.PUSH1[bytes(Op.JUMPDEST)] + Op.JUMPDEST, - Op.PUSH2[bytes(Op.JUMPDEST + Op.JUMPDEST)] + Op.JUMPDEST, - ], - ids=lambda x: x.hex(), -) -def test_worst_initcode_jumpdest_analysis( - benchmark_test: BenchmarkTestFiller, - fork: Fork, - pattern: Bytecode, -) -> None: - """ - Test the jumpdest analysis performance of the initcode. - - This benchmark places a very long initcode in the memory and then invoke - CREATE instructions with this initcode up to the block gas limit. The - initcode itself has minimal execution time but forces the EVM to perform - the full jumpdest analysis on the parametrized byte pattern. The initicode - is modified by mixing-in the returned create address between CREATE - invocations to prevent caching. - """ - initcode_size = fork.max_initcode_size() - - # Expand the initcode pattern to the transaction data so it can be used in - # CALLDATACOPY in the main contract. TODO: tune the tx_data_len param. - tx_data_len = 1024 - tx_data = pattern * (tx_data_len // len(pattern)) - tx_data += (tx_data_len - len(tx_data)) * bytes(Op.JUMPDEST) - assert len(tx_data) == tx_data_len - assert initcode_size % len(tx_data) == 0 - - # Prepare the initcode in memory. - code_prepare_initcode = sum( - ( - Op.CALLDATACOPY( - dest_offset=i * len(tx_data), offset=0, size=Op.CALLDATASIZE - ) - for i in range(initcode_size // len(tx_data)) - ), - Bytecode(), - ) - - # At the start of the initcode execution, jump to the last opcode. - # This forces EVM to do the full jumpdest analysis. - initcode_prefix = Op.JUMP(initcode_size - 1) - code_prepare_initcode += Op.MSTORE( - 0, Op.PUSH32[bytes(initcode_prefix).ljust(32, bytes(Op.JUMPDEST))] - ) - - # Make sure the last opcode in the initcode is JUMPDEST. - code_prepare_initcode += Op.MSTORE( - initcode_size - 32, Op.PUSH32[bytes(Op.JUMPDEST) * 32] - ) - - attack_block = ( - Op.PUSH1[len(initcode_prefix)] - + Op.MSTORE - + Op.CREATE(value=Op.PUSH0, offset=Op.PUSH0, size=Op.MSIZE) - ) - - setup = code_prepare_initcode + Op.PUSH0 - - benchmark_test( - code_generator=JumpLoopGenerator( - setup=setup, - attack_block=attack_block, - tx_kwargs={"data": tx_data}, - ), - ) - - @pytest.mark.parametrize( "opcode", [ @@ -356,7 +265,7 @@ def test_worst_initcode_jumpdest_analysis( pytest.param(1.00, False, 0, id="max code size with zero data"), ], ) -def test_worst_create( +def test_create( benchmark_test: BenchmarkTestFiller, pre: Alloc, fork: Fork, @@ -365,9 +274,7 @@ def test_worst_create( non_zero_data: bool, value: int, ) -> None: - """ - Test the CREATE and CREATE2 performance with different configurations. - """ + """Benchmark CREATE and CREATE2 instructions.""" max_code_size = fork.max_code_size() code_size = int(max_code_size * max_code_size_ratio) @@ -452,14 +359,14 @@ def test_worst_create( Op.CREATE2, ], ) -def test_worst_creates_collisions( +def test_creates_collisions( benchmark_test: BenchmarkTestFiller, pre: Alloc, fork: Fork, opcode: Op, gas_benchmark_value: int, ) -> None: - """Test the CREATE and CREATE2 collisions performance.""" + """Benchmark CREATE and CREATE2 instructions with collisions.""" # We deploy a "proxy contract" which is the contract that will be called in # a loop using all the gas in the block. This "proxy contract" is the one # executing CREATE2 failing with a collision. The reason why we need a @@ -514,3 +421,409 @@ def test_worst_creates_collisions( setup=setup, attack_block=attack_block ), ) + + +@pytest.mark.parametrize( + "opcode", + [Op.RETURN, Op.REVERT], +) +@pytest.mark.parametrize( + "return_size, return_non_zero_data", + [ + pytest.param(0, False, id="empty"), + pytest.param(1024, True, id="1KiB of non-zero data"), + pytest.param(1024, False, id="1KiB of zero data"), + pytest.param(1024 * 1024, True, id="1MiB of non-zero data"), + pytest.param(1024 * 1024, False, id="1MiB of zero data"), + ], +) +def test_return_revert( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + opcode: Op, + return_size: int, + return_non_zero_data: bool, +) -> None: + """Benchmark RETURN and REVERT instructions.""" + max_code_size = fork.max_code_size() + + # Create the contract that will be called repeatedly. + # The bytecode of the contract is: + # ``` + # [CODECOPY(returned_size) -- Conditional if return_non_zero_data] + # opcode(returned_size) + # + # ``` + # Filling the contract up to the max size is a cheap way of leveraging + # CODECOPY to return non-zero bytes if requested. Note that since this + # is a pre-deploy this cost isn't + # relevant for the benchmark. + mem_preparation = ( + Op.CODECOPY(size=return_size) if return_non_zero_data else Bytecode() + ) + executable_code = mem_preparation + opcode(size=return_size) + code = executable_code + if return_non_zero_data: + code += Op.INVALID * (max_code_size - len(executable_code)) + target_contract_address = pre.deploy_contract(code=code) + + attack_block = Op.POP(Op.STATICCALL(address=target_contract_address)) + + benchmark_test( + code_generator=JumpLoopGenerator(attack_block=attack_block), + ) + + +@pytest.mark.parametrize("value_bearing", [True, False]) +def test_selfdestruct_existing( + benchmark_test: BenchmarkTestFiller, + fork: Fork, + pre: Alloc, + value_bearing: bool, + env: Environment, + gas_benchmark_value: int, +) -> None: + """ + Benchmark SELFDESTRUCT instruction for existing contracts. + contracts. + """ + attack_gas_limit = gas_benchmark_value + fee_recipient = pre.fund_eoa(amount=1) + + # Template code that will be used to deploy a large number of contracts. + selfdestructable_contract_addr = pre.deploy_contract( + code=Op.SELFDESTRUCT(Op.COINBASE) + ) + initcode = Op.EXTCODECOPY( + address=selfdestructable_contract_addr, + dest_offset=0, + offset=0, + size=Op.EXTCODESIZE(selfdestructable_contract_addr), + ) + Op.RETURN(0, Op.EXTCODESIZE(selfdestructable_contract_addr)) + initcode_address = pre.deploy_contract(code=initcode) + + # Calculate the number of contracts that can be deployed with the available + # gas. + gas_costs = fork.gas_costs() + intrinsic_gas_cost_calc = fork.transaction_intrinsic_cost_calculator() + loop_cost = ( + gas_costs.G_KECCAK_256 # KECCAK static cost + + math.ceil(85 / 32) * gas_costs.G_KECCAK_256_WORD # KECCAK dynamic + # cost for CREATE2 + + gas_costs.G_VERY_LOW * 3 # ~MSTOREs+ADDs + + gas_costs.G_COLD_ACCOUNT_ACCESS # CALL to self-destructing contract + + gas_costs.G_SELF_DESTRUCT + + 63 # ~Gluing opcodes + ) + final_storage_gas = ( + gas_costs.G_STORAGE_RESET + + gas_costs.G_COLD_SLOAD + + (gas_costs.G_VERY_LOW * 2) + ) + memory_expansion_cost = fork().memory_expansion_gas_calculator()( + new_bytes=96 + ) + base_costs = ( + intrinsic_gas_cost_calc() + + (gas_costs.G_VERY_LOW * 12) # 8 PUSHs + 4 MSTOREs + + final_storage_gas + + memory_expansion_cost + ) + num_contracts = (attack_gas_limit - base_costs) // loop_cost + expected_benchmark_gas_used = num_contracts * loop_cost + base_costs + + # Create a factory that deployes a new SELFDESTRUCT contract instance pre- + # funded depending on the value_bearing parameter. We use CREATE2 so the + # caller contract can easily reproduce the addresses in a loop for CALLs. + factory_code = ( + Op.EXTCODECOPY( + address=initcode_address, + dest_offset=0, + offset=0, + size=Op.EXTCODESIZE(initcode_address), + ) + + Op.MSTORE( + 0, + Op.CREATE2( + value=1 if value_bearing else 0, + offset=0, + size=Op.EXTCODESIZE(initcode_address), + salt=Op.SLOAD(0), + ), + ) + + Op.SSTORE(0, Op.ADD(Op.SLOAD(0), 1)) + + Op.RETURN(0, 32) + ) + + required_balance = num_contracts if value_bearing else 0 # 1 wei per + # contract + factory_address = pre.deploy_contract( + code=factory_code, balance=required_balance + ) + + factory_caller_code = Op.CALLDATALOAD(0) + While( + body=Op.POP(Op.CALL(address=factory_address)), + condition=Op.PUSH1(1) + + Op.SWAP1 + + Op.SUB + + Op.DUP1 + + Op.ISZERO + + Op.ISZERO, + ) + factory_caller_address = pre.deploy_contract(code=factory_caller_code) + + contracts_deployment_tx = Transaction( + to=factory_caller_address, + gas_limit=env.gas_limit, + data=Hash(num_contracts), + sender=pre.fund_eoa(), + ) + + code = ( + # Setup memory for later CREATE2 address generation loop. + # 0xFF+[Address(20bytes)]+[seed(32bytes)]+[initcode keccak(32bytes)] + Op.MSTORE(0, factory_address) + + Op.MSTORE8(32 - 20 - 1, 0xFF) + + Op.MSTORE(32, 0) + + Op.MSTORE(64, initcode.keccak256()) + # Main loop + + While( + body=Op.POP(Op.CALL(address=Op.SHA3(32 - 20 - 1, 85))) + + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), + # Only loop if we have enough gas to cover another iteration plus + # the final storage gas. + condition=Op.GT(Op.GAS, final_storage_gas + loop_cost), + ) + + Op.SSTORE(0, 42) # Done for successful tx execution assertion below. + ) + assert len(code) <= fork.max_code_size() + + # The 0 storage slot is initialize to avoid creation costs in SSTORE above. + code_addr = pre.deploy_contract(code=code, storage={0: 1}) + opcode_tx = Transaction( + to=code_addr, + gas_limit=attack_gas_limit, + sender=pre.fund_eoa(), + ) + + post = { + factory_address: Account(storage={0: num_contracts}), + code_addr: Account(storage={0: 42}), # Check for successful execution. + } + deployed_contract_addresses = [] + for i in range(num_contracts): + deployed_contract_address = compute_create2_address( + address=factory_address, + salt=i, + initcode=initcode, + ) + post[deployed_contract_address] = Account(nonce=1) + deployed_contract_addresses.append(deployed_contract_address) + + benchmark_test( + post=post, + blocks=[ + Block(txs=[contracts_deployment_tx]), + Block(txs=[opcode_tx], fee_recipient=fee_recipient), + ], + expected_benchmark_gas_used=expected_benchmark_gas_used, + ) + + +@pytest.mark.parametrize("value_bearing", [True, False]) +def test_selfdestruct_created( + state_test: StateTestFiller, + pre: Alloc, + value_bearing: bool, + fork: Fork, + env: Environment, + gas_benchmark_value: int, +) -> None: + """ + Benchmark SELFDESTRUCT instruction for deployed contracts within same tx. + """ + fee_recipient = pre.fund_eoa(amount=1) + env.fee_recipient = fee_recipient + + # SELFDESTRUCT(COINBASE) contract deployment + initcode = ( + Op.MSTORE8(0, Op.COINBASE.int()) + + Op.MSTORE8(1, Op.SELFDESTRUCT.int()) + + Op.RETURN(0, 2) + ) + gas_costs = fork.gas_costs() + memory_expansion_calc = fork().memory_expansion_gas_calculator() + intrinsic_gas_cost_calc = fork.transaction_intrinsic_cost_calculator() + + initcode_costs = ( + gas_costs.G_VERY_LOW * 8 # MSTOREs, PUSHs + + memory_expansion_calc(new_bytes=2) # return into memory + ) + create_costs = ( + initcode_costs + + gas_costs.G_CREATE + + gas_costs.G_VERY_LOW * 3 # Create Parameter PUSHs + + gas_costs.G_CODE_DEPOSIT_BYTE * 2 + + gas_costs.G_INITCODE_WORD + ) + call_costs = ( + gas_costs.G_WARM_ACCOUNT_ACCESS + + gas_costs.G_BASE # COINBASE + + gas_costs.G_SELF_DESTRUCT + + gas_costs.G_VERY_LOW * 5 # CALL Parameter PUSHs + + gas_costs.G_BASE # Parameter GAS + ) + extra_costs = ( + gas_costs.G_BASE # POP + + gas_costs.G_VERY_LOW * 6 # PUSHs, ADD, DUP, GT + + gas_costs.G_HIGH # JUMPI + + gas_costs.G_JUMPDEST + ) + loop_cost = create_costs + call_costs + extra_costs + + prefix_cost = ( + gas_costs.G_VERY_LOW * 3 + + gas_costs.G_BASE + + memory_expansion_calc(new_bytes=32) + ) + suffix_cost = ( + gas_costs.G_COLD_SLOAD + + gas_costs.G_STORAGE_RESET + + (gas_costs.G_VERY_LOW * 2) + ) + + base_costs = prefix_cost + suffix_cost + intrinsic_gas_cost_calc() + + iterations = (gas_benchmark_value - base_costs) // loop_cost + + code_prefix = Op.MSTORE(0, initcode.hex()) + Op.PUSH0 + Op.JUMPDEST + code_suffix = ( + Op.SSTORE(0, 42) # Done for successful tx execution assertion below. + + Op.STOP + ) + loop_body = ( + Op.POP( + Op.CALL( + address=Op.CREATE( + value=1 if value_bearing else 0, + offset=32 - len(initcode), + size=len(initcode), + ) + ) + ) + + Op.PUSH1[1] + + Op.ADD + + Op.JUMPI(len(code_prefix) - 1, Op.GT(iterations, Op.DUP1)) + ) + code = code_prefix + loop_body + code_suffix + # The 0 storage slot is initialize to avoid creation costs in SSTORE above. + code_addr = pre.deploy_contract( + code=code, + balance=iterations if value_bearing else 0, + storage={0: 1}, + ) + code_tx = Transaction( + to=code_addr, + gas_limit=gas_benchmark_value, + sender=pre.fund_eoa(), + ) + + post = {code_addr: Account(storage={0: 42})} # Check for successful + # execution. + state_test( + pre=pre, + post=post, + tx=code_tx, + expected_benchmark_gas_used=iterations * loop_cost + base_costs, + ) + + +@pytest.mark.parametrize("value_bearing", [True, False]) +def test_selfdestruct_initcode( + state_test: StateTestFiller, + pre: Alloc, + value_bearing: bool, + fork: Fork, + env: Environment, + gas_benchmark_value: int, +) -> None: + """Benchmark SELFDESTRUCT instruction executed in initcode.""" + fee_recipient = pre.fund_eoa(amount=1) + env.fee_recipient = fee_recipient + + gas_costs = fork.gas_costs() + memory_expansion_calc = fork().memory_expansion_gas_calculator() + intrinsic_gas_cost_calc = fork.transaction_intrinsic_cost_calculator() + + initcode_costs = ( + gas_costs.G_BASE # COINBASE + + gas_costs.G_SELF_DESTRUCT + ) + create_costs = ( + initcode_costs + + gas_costs.G_CREATE + + gas_costs.G_VERY_LOW * 3 # Create Parameter PUSHs + + gas_costs.G_INITCODE_WORD + ) + extra_costs = ( + gas_costs.G_BASE # POP + + gas_costs.G_VERY_LOW * 6 # PUSHs, ADD, DUP, GT + + gas_costs.G_HIGH # JUMPI + + gas_costs.G_JUMPDEST + ) + loop_cost = create_costs + extra_costs + + prefix_cost = ( + gas_costs.G_VERY_LOW * 3 + + gas_costs.G_BASE + + memory_expansion_calc(new_bytes=32) + ) + suffix_cost = ( + gas_costs.G_COLD_SLOAD + + gas_costs.G_STORAGE_RESET + + (gas_costs.G_VERY_LOW * 2) + ) + + base_costs = prefix_cost + suffix_cost + intrinsic_gas_cost_calc() + + iterations = (gas_benchmark_value - base_costs) // loop_cost + + initcode = Op.SELFDESTRUCT(Op.COINBASE) + code_prefix = Op.MSTORE(0, initcode.hex()) + Op.PUSH0 + Op.JUMPDEST + code_suffix = ( + Op.SSTORE(0, 42) # Done for successful tx execution assertion below. + + Op.STOP + ) + + loop_body = ( + Op.POP( + Op.CREATE( + value=1 if value_bearing else 0, + offset=32 - len(initcode), + size=len(initcode), + ) + ) + + Op.PUSH1[1] + + Op.ADD + + Op.JUMPI(len(code_prefix) - 1, Op.GT(iterations, Op.DUP1)) + ) + code = code_prefix + loop_body + code_suffix + + # The 0 storage slot is initialize to avoid creation costs in SSTORE above. + code_addr = pre.deploy_contract(code=code, balance=100_000, storage={0: 1}) + code_tx = Transaction( + to=code_addr, + gas_limit=gas_benchmark_value, + gas_price=10, + sender=pre.fund_eoa(), + ) + + post = {code_addr: Account(storage={0: 42})} # Check for successful + # execution. + state_test( + pre=pre, + post=post, + tx=code_tx, + expected_benchmark_gas_used=iterations * loop_cost + base_costs, + ) diff --git a/tests/benchmark/compute/instruction/test_tx_context.py b/tests/benchmark/compute/instruction/test_tx_context.py new file mode 100644 index 0000000000..44d128f27a --- /dev/null +++ b/tests/benchmark/compute/instruction/test_tx_context.py @@ -0,0 +1,65 @@ +"""Benchmark transaction context instructions.""" + +from typing import Any, Dict + +import pytest +from execution_testing import ( + BenchmarkTestFiller, + ExtCallGenerator, + Fork, + Op, + TransactionType, + add_kzg_version, +) + +from tests.cancun.eip4844_blobs.spec import Spec as BlobsSpec + + +@pytest.mark.parametrize( + "opcode", + [ + Op.ORIGIN, + Op.GASPRICE, + ], +) +def test_call_frame_context_ops( + benchmark_test: BenchmarkTestFiller, + opcode: Op, +) -> None: + """Benchmark call zero-parameter instructions.""" + benchmark_test( + code_generator=ExtCallGenerator(attack_block=opcode), + ) + + +@pytest.mark.parametrize( + "blob_index, blobs_present", + [ + pytest.param(0, 0, id="no blobs"), + pytest.param(0, 1, id="one blob and accessed"), + pytest.param(1, 1, id="one blob but access non-existent index"), + pytest.param(5, 6, id="six blobs, access latest"), + ], +) +def test_blobhash( + fork: Fork, + benchmark_test: BenchmarkTestFiller, + blob_index: int, + blobs_present: bool, +) -> None: + """Benchmark BLOBHASH instruction.""" + tx_kwargs: Dict[str, Any] = {} + if blobs_present > 0: + tx_kwargs["ty"] = TransactionType.BLOB_TRANSACTION + tx_kwargs["max_fee_per_blob_gas"] = fork.min_base_fee_per_blob_gas() + tx_kwargs["blob_versioned_hashes"] = add_kzg_version( + [i.to_bytes() * 32 for i in range(blobs_present)], + BlobsSpec.BLOB_COMMITMENT_VERSION_KZG, + ) + + benchmark_test( + code_generator=ExtCallGenerator( + attack_block=Op.BLOBHASH(blob_index), + tx_kwargs=tx_kwargs, + ), + ) diff --git a/tests/benchmark/compute/precompile/test_alt_bn128.py b/tests/benchmark/compute/precompile/test_alt_bn128.py new file mode 100644 index 0000000000..55026a44eb --- /dev/null +++ b/tests/benchmark/compute/precompile/test_alt_bn128.py @@ -0,0 +1,492 @@ +"""Benchmark ALT_BN128 precompile.""" + +import random + +import pytest +from execution_testing import ( + Address, + BenchmarkTestFiller, + Bytes, + Fork, + JumpLoopGenerator, + Op, +) +from py_ecc.bn128 import G1, G2, multiply + +from tests.benchmark.compute.helpers import concatenate_parameters + + +@pytest.mark.parametrize( + "precompile_address,calldata", + [ + pytest.param( + 0x06, + concatenate_parameters( + [ + "18B18ACFB4C2C30276DB5411368E7185B311DD124691610C5D3B74034E093DC9", + "063C909C4720840CB5134CB9F59FA749755796819658D32EFC0D288198F37266", + "07C2B7F58A84BD6145F00C9C2BC0BB1A187F20FF2C92963A88019E7C6A014EED", + "06614E20C147E940F2D70DA3F74C9A17DF361706A4485C742BD6788478FA17D7", + ] + ), + id="bn128_add", + ), + # Ported from + # https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCase.cs#L326 + pytest.param( + 0x06, + concatenate_parameters( + [ + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + ] + ), + id="bn128_add_infinities", + ), + # Ported from + # https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCase.cs#L329 + pytest.param( + 0x06, + concatenate_parameters( + [ + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000002", + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000002", + ] + ), + id="bn128_add_1_2", + ), + pytest.param( + 0x07, + concatenate_parameters( + [ + "1A87B0584CE92F4593D161480614F2989035225609F08058CCFA3D0F940FEBE3", + "1A2F3C951F6DADCC7EE9007DFF81504B0FCD6D7CF59996EFDC33D92BF7F9F8F6", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + ] + ), + id="bn128_mul", + ), + # Ported from + # https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCase.cs#L335 + pytest.param( + 0x07, + concatenate_parameters( + [ + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000002", + ] + ), + id="bn128_mul_infinities_2_scalar", + ), + # Ported from + # https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCase.cs#L338 + pytest.param( + 0x07, + concatenate_parameters( + [ + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "25f8c89ea3437f44f8fc8b6bfbb6312074dc6f983809a5e809ff4e1d076dd585", + ] + ), + id="bn128_mul_infinities_32_byte_scalar", + ), + # Ported from + # https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCase.cs#L341 + pytest.param( + 0x07, + concatenate_parameters( + [ + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000002", + "0000000000000000000000000000000000000000000000000000000000000002", + ] + ), + id="bn128_mul_1_2_2_scalar", + ), + # Ported from + # https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCase.cs#L344 + pytest.param( + 0x07, + concatenate_parameters( + [ + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000002", + "25f8c89ea3437f44f8fc8b6bfbb6312074dc6f983809a5e809ff4e1d076dd585", + ] + ), + id="bn128_mul_1_2_32_byte_scalar", + ), + # Ported from + # https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCase.cs#L347 + pytest.param( + 0x07, + concatenate_parameters( + [ + "089142debb13c461f61523586a60732d8b69c5b38a3380a74da7b2961d867dbf", + "2d5fc7bbc013c16d7945f190b232eacc25da675c0eb093fe6b9f1b4b4e107b36", + "0000000000000000000000000000000000000000000000000000000000000002", + ] + ), + id="bn128_mul_32_byte_coord_and_2_scalar", + ), + # Ported from + # https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCase.cs#L350 + pytest.param( + 0x07, + concatenate_parameters( + [ + "089142debb13c461f61523586a60732d8b69c5b38a3380a74da7b2961d867dbf", + "2d5fc7bbc013c16d7945f190b232eacc25da675c0eb093fe6b9f1b4b4e107b36", + "25f8c89ea3437f44f8fc8b6bfbb6312074dc6f983809a5e809ff4e1d076dd585", + ] + ), + id="bn128_mul_32_byte_coord_and_scalar", + ), + pytest.param( + 0x08, + concatenate_parameters( + [ + # First pairing + "1C76476F4DEF4BB94541D57EBBA1193381FFA7AA76ADA664DD31C16024C43F59", + "3034DD2920F673E204FEE2811C678745FC819B55D3E9D294E45C9B03A76AEF41", + "209DD15EBFF5D46C4BD888E51A93CF99A7329636C63514396B4A452003A35BF7", + "04BF11CA01483BFA8B34B43561848D28905960114C8AC04049AF4B6315A41678", + "2BB8324AF6CFC93537A2AD1A445CFD0CA2A71ACD7AC41FADBF933C2A51BE344D", + "120A2A4CF30C1BF9845F20C6FE39E07EA2CCE61F0C9BB048165FE5E4DE877550", + # Second pairing + "111E129F1CF1097710D41C4AC70FCDFA5BA2023C6FF1CBEAC322DE49D1B6DF7C", + "103188585E2364128FE25C70558F1560F4F9350BAF3959E603CC91486E110936", + "198E9393920D483A7260BFB731FB5D25F1AA493335A9E71297E485B7AEF312C2", + "1800DEEF121F1E76426A00665E5C4479674322D4F75EDADD46DEBD5CD992F6ED", + "090689D0585FF075EC9E99AD690C3395BC4B313370B38EF355ACDADCD122975B", + "12C85EA5DB8C6DEB4AAB71808DCB408FE3D1E7690C43D37B4CE6CC0166FA7DAA", + ] + ), + id="bn128_two_pairings", + ), + pytest.param( + 0x08, + concatenate_parameters( + [ + # First pairing + "1C76476F4DEF4BB94541D57EBBA1193381FFA7AA76ADA664DD31C16024C43F59", + "3034DD2920F673E204FEE2811C678745FC819B55D3E9D294E45C9B03A76AEF41", + "209DD15EBFF5D46C4BD888E51A93CF99A7329636C63514396B4A452003A35BF7", + "04BF11CA01483BFA8B34B43561848D28905960114C8AC04049AF4B6315A41678", + "2BB8324AF6CFC93537A2AD1A445CFD0CA2A71ACD7AC41FADBF933C2A51BE344D", + "120A2A4CF30C1BF9845F20C6FE39E07EA2CCE61F0C9BB048165FE5E4DE877550", + ] + ), + id="bn128_one_pairing", + ), + # Ported from + # https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCase.cs#L353 + pytest.param(0x08, [], id="ec_pairing_zero_input"), + pytest.param( + 0x08, + concatenate_parameters( + [ + # First pairing + "2cf44499d5d27bb186308b7af7af02ac5bc9eeb6a3d147c186b21fb1b76e18da", + "2c0f001f52110ccfe69108924926e45f0b0c868df0e7bde1fe16d3242dc715f6", + "1fb19bb476f6b9e44e2a32234da8212f61cd63919354bc06aef31e3cfaff3ebc", + "22606845ff186793914e03e21df544c34ffe2f2f3504de8a79d9159eca2d98d9", + "2bd368e28381e8eccb5fa81fc26cf3f048eea9abfdd85d7ed3ab3698d63e4f90", + "2fe02e47887507adf0ff1743cbac6ba291e66f59be6bd763950bb16041a0a85e", + # Second pairing + "0000000000000000000000000000000000000000000000000000000000000013", + "0644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd451", + "971ff0471b09fa93caaf13cbf443c1aede09cc4328f5a62aad45f40ec133eb40", + "91058a3141822985733cbdddfed0fd8d6c104e9e9eff40bf5abfef9ab163bc72", + "a23af9a5ce2ba2796c1f4e453a370eb0af8c212d9dc9acd8fc02c2e907baea22", + "3a8eb0b0996252cb548a4487da97b02422ebc0e834613f954de6c7e0afdc1fc0", + ] + ), + id="ec_pairing_2_sets", + ), + pytest.param( + 0x08, + concatenate_parameters([""]), + id="ec_pairing_1_pair", + ), + pytest.param( + 0x08, + concatenate_parameters( + [ + # First pairing + "2371e7d92e9fc444d0e11526f0752b520318c80be68bf0131704b36b7976572e", + "2dca8f05ed5d58e0f2e13c49ae40480c0f99dfcd9268521eea6c81c6387b66c4", + "051a93d697db02afd3dcf8414ecb906a114a2bfdb6b06c95d41798d1801b3cbd", + "2e275fef7a0bdb0a2aea77d8ec5817e66e199b3d55bc0fa308dcdda74e85060b", + "1c7e33c2a72d6e12a31eababad3dbc388525135628102bb64742d9e325f43410", + "115dc41fa10b2dbf99036f252ad6f00e8876b22f02cb4738dc4413b22ea9b2df", + # Second pairing + "09a760ea8f9bd87dc258a949395a03f7d2500c6e72c61f570986328a096b610a", + "148027063c072345298117eb2cb980ad79601db31cc69bba6bcbe4937ada6720", + "198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2", + "1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed", + "090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b", + "12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", + ] + ), + id="ec_pairing_2_pair", + ), + pytest.param( + 0x08, + concatenate_parameters( + [ + # First pairing + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0ef4aac9b7954d5fc6eafae7f4f4c2a732ab05b45f8d50d102cee4973f36eb2c", + "23db7d30c99e0a2a7f3bb5cd1f04635aaea58732b58887df93d9239c28230d28", + "2bd99d31a5054f2556d226f2e5ef0e075423d8604178b2e2c08006311caee54f", + "0f11afb0c6073d12d21b13f4f78210e8ca9a66729206d3fcc2c1b04824c425f2", + # Second pairing + "0000000000000000000000000000000000000000000000000000000000000000", + "198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2", + "1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed", + "090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b", + "12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", + # Third pairing + "0000000000000000000000000000000000000000000000000000000000000000", + "198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2", + "1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed", + "090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b", + "12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", + ] + ), + id="ec_pairing_3_pair", + ), + pytest.param( + 0x08, + concatenate_parameters( + [ + # First pairing + "24ab69f46f3e3333027d67d51af71571141bd5652b9829157a3c5d1268461984", + "0f0e1495665bccf97d627b714e8a49e9c77c21e8d5b383ad7dde7e50040d0f62", + "2cab595b9d579f8b82e433249b83ae1d7b62d7073a4f67cb3aeb9b316988907f", + "1326d1905ffde0c77e8ebd98257aa239b05ae76c8ec7723ec19bbc8282b0debe", + "130502106676b537e01cc356765e91c005d6c4bd1a75f5f6d41d2556c73e56ac", + "2dc4cb08068b4aa5f14b7f1096ab35d5c13d78319ec7e66e9f67a1ff20cbbf03", + # Second pairing + "1459f4140b271cbc8746de9dfcb477d5b72d50ef95bec5fef4a68dd69ddfdb2e", + "2c589584551d16a9723b5d356d1ee2066d10381555cdc739e39efca2612fc544", + "229ab0abdb0a7d1a5f0d93fb36ce41e12a31ba52fd9e3c27bebce524ab6c4e9b", + "00f8756832b244377d06e2d00eeb95ec8096dcfd81f4e4931b50fea23c04a2fe", + "29605352ce973ec48d1ab2c8355643c999b70ff771946078b519c556058c3d56", + "059a65ae6e0189d4e04a966140aa40f781a1345824a90a91bb035e12ad29af1d", + # Third pairing + "1459f4140b271cbc8746de9dfcb477d5b72d50ef95bec5fef4a68dd69ddfdb2e", + "2c589584551d16a9723b5d356d1ee2066d10381555cdc739e39efca2612fc544", + "229ab0abdb0a7d1a5f0d93fb36ce41e12a31ba52fd9e3c27bebce524ab6c4e9b", + "00f8756832b244377d06e2d00eeb95ec8096dcfd81f4e4931b50fea23c04a2fe", + "29605352ce973ec48d1ab2c8355643c999b70ff771946078b519c556058c3d56", + "059a65ae6e0189d4e04a966140aa40f781a1345824a90a91bb035e12ad29af1d", + # Fourth pairing + "24ab69f46f3e3333027d67d51af71571141bd5652b9829157a3c5d1268461984", + "0f0e1495665bccf97d627b714e8a49e9c77c21e8d5b383ad7dde7e50040d0f62", + "2cab595b9d579f8b82e433249b83ae1d7b62d7073a4f67cb3aeb9b316988907f", + "1326d1905ffde0c77e8ebd98257aa239b05ae76c8ec7723ec19bbc8282b0debe", + "130502106676b537e01cc356765e91c005d6c4bd1a75f5f6d41d2556c73e56ac", + "2dc4cb08068b4aa5f14b7f1096ab35d5c13d78319ec7e66e9f67a1ff20cbbf03", + ] + ), + id="ec_pairing_4_pair", + ), + pytest.param( + 0x08, + concatenate_parameters( + [ + # First pairing + "1147057b17237df94a3186435acf66924e1d382b8c935fdd493ceb38c38def73", + "03cd046286139915160357ce5b29b9ea28bfb781b71734455d20ef1a64be76ca", + "0daa7cc4983cf74c94607519df747f61e317307c449bafb6923f6d6a65299a7e", + "1d48db8f275830859fd61370addbc5d5ef3f0ce7491d16918e065f7e3727439d", + "1ca8ac2f4a0f540e5505edbe1d15d13899a2a0dfccb012d068134ac66edec625", + "2162c315417d1d12c9d7028c5619015391003a9006d4d8979784c7af2c4537a3", + # Second pairing + "0d221a19ca86dafa8cb804daff78fd3d1bed30aa32e7d4029b1aa69afda2d750", + "018628c766a98de1d0cca887a6d90303e68a7729490f25f937b76b57624ba0be", + "14550ccf7139312da6fa9eb1259c6365b0bd688a27473ccb42bc5cd6f14c8abd", + "165f8721ee9f614382c8c7edb103c941d3a55c1849c9787f34317777d5d9365b", + "0d19da7439edb573a1b3e357faade63d5d68b6031771fd911459b7ab0bda9d3f", + "25a50a44d10c99c5f107e3b3874f717873cb2d4674699a468204df27c0c50a9a", + # Third pairing + "0d7136c59b907615e1b45cf730fbfd6cf38b7e126e85e52be804620a23ace4fb", + "03e80c29d24ed5cc407329ae093bb1be00f9e3c9332f532bc3658937110d7607", + "2129813bd7247065ac58eac42c81e874044e199f48c12aa749a9fe6bb6e4bddc", + "1b72b9ab4579283e62445555d5b2921424213d09a776152361c46988b82be8a7", + "111bc8198f932e379b8f9825f01af0f5e5cacbf8bfe274bf674f6eaa6e338e04", + "259f58d438fd6391e158c991e155966218e6a432703a84068a32543965749857", + # Fourth pairing + "1ba47a91d487cce77aa78390a295df54d9351637d67810c400415fb374278e3f", + "24318bbc05a4e4d779b9498075841c360c6973c1c51dea254281829bbc9aef33", + "198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2", + "1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed", + "090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b", + "12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", + # Fifth pairing + "1e219772c16eee72450bbf43e9cadae7bf6b2e6ae6637cfeb1d1e8965287acfb", + "0347e7bf4245debd3d00b6f51d2d50fd718e6769352f4fe1db0efe492fed2fc3", + "24fdcc7d4ed0953e3dad500c7ef9836fc61ded44ba454ec76f0a6d0687f4c1b4", + "282b18f7e59c1db4852e622919b2ce9aa5980ca883eac312049c19a3deb79f6d", + "0c9d6ce303b7811dd7ea506c8fa124837405bd209b8731bda79a66eb7206277b", + "1ac5dac62d2332faa8069faca3b0d27fcdf95d8c8bafc9074ee72b5c1f33aa70", + ] + ), + id="ec_pairing_5_pair", + ), + pytest.param( + 0x08, + concatenate_parameters( + [ + "0000000000000000000000000000000000000000000000000000000000000000", + ] + ), + id="ec_pairing_1_pair_empty", + ), + ], +) +def test_alt_bn128( + benchmark_test: BenchmarkTestFiller, + fork: Fork, + precompile_address: Address, + calldata: bytes, +) -> None: + """Benchmark ALT_BN128 precompile.""" + if precompile_address not in fork.precompiles(): + pytest.skip("Precompile not enabled") + + attack_block = Op.POP( + Op.STATICCALL( + gas=Op.GAS, address=precompile_address, args_size=Op.CALLDATASIZE + ), + ) + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE), + attack_block=attack_block, + tx_kwargs={"data": calldata}, + ), + ) + + +def _generate_bn128_pairs(n: int, seed: int = 0) -> Bytes: + rng = random.Random(seed) + calldata = Bytes() + + for _ in range(n): + priv_key_g1 = rng.randint(1, 2**32 - 1) + priv_key_g2 = rng.randint(1, 2**32 - 1) + + point_x_affine = multiply(G1, priv_key_g1) + point_y_affine = multiply(G2, priv_key_g2) + + assert point_x_affine is not None, ( + "G1 multiplication resulted in point at infinity" + ) + assert point_y_affine is not None, ( + "G2 multiplication resulted in point at infinity" + ) + + g1_x_bytes = point_x_affine[0].n.to_bytes(32, "big") + g1_y_bytes = point_x_affine[1].n.to_bytes(32, "big") + g1_serialized = g1_x_bytes + g1_y_bytes + + g2_x_c1_bytes = point_y_affine[0].coeffs[1].n.to_bytes(32, "big") # type: ignore + g2_x_c0_bytes = point_y_affine[0].coeffs[0].n.to_bytes(32, "big") # type: ignore + g2_y_c1_bytes = point_y_affine[1].coeffs[1].n.to_bytes(32, "big") # type: ignore + g2_y_c0_bytes = point_y_affine[1].coeffs[0].n.to_bytes(32, "big") # type: ignore + g2_serialized = ( + g2_x_c1_bytes + g2_x_c0_bytes + g2_y_c1_bytes + g2_y_c0_bytes + ) + + pair_calldata = g1_serialized + g2_serialized + calldata = Bytes(calldata + pair_calldata) + + return calldata + + +def test_bn128_pairings_amortized( + benchmark_test: BenchmarkTestFiller, + fork: Fork, + gas_benchmark_value: int, +) -> None: + """Test running a block with as many BN128 pairings as possible.""" + base_cost = 45_000 + pairing_cost = 34_000 + size_per_pairing = 192 + + gsc = fork.gas_costs() + intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() + mem_exp_gas_calculator = fork.memory_expansion_gas_calculator() + + # 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 = ( + gas_benchmark_value - 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 = ( + gas_benchmark_value + - 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), + ) + + # This is ignoring "glue" opcodes, but helps to have a rough idea of + # the right cutting point. + approx_gas_cost_per_call = ( + gsc.G_WARM_ACCOUNT_ACCESS + 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, 0x08, 0, Op.CALLDATASIZE, 0, 0) + ) + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=setup, + attack_block=attack_block, + tx_kwargs={ + "data": _generate_bn128_pairs( + optimal_per_call_num_pairings, 42 + ) + }, + ), + ) diff --git a/tests/benchmark/compute/precompile/test_blake2f.py b/tests/benchmark/compute/precompile/test_blake2f.py new file mode 100644 index 0000000000..20cfe71716 --- /dev/null +++ b/tests/benchmark/compute/precompile/test_blake2f.py @@ -0,0 +1,55 @@ +"""Benchmark BLAKE2F precompile.""" + +import pytest +from execution_testing import ( + Address, + BenchmarkTestFiller, + Fork, + JumpLoopGenerator, + Op, +) + +from tests.benchmark.compute.helpers import concatenate_parameters +from tests.istanbul.eip152_blake2.common import Blake2bInput +from tests.istanbul.eip152_blake2.spec import Spec as Blake2bSpec + + +@pytest.mark.parametrize( + "precompile_address,calldata", + [ + pytest.param( + Blake2bSpec.BLAKE2_PRECOMPILE_ADDRESS, + concatenate_parameters( + [ + Blake2bInput( + rounds=0xFFFF, f=True + ).create_blake2b_tx_data(), + ] + ), + id="blake2f", + ), + ], +) +def test_blake2f( + benchmark_test: BenchmarkTestFiller, + fork: Fork, + precompile_address: Address, + calldata: bytes, +) -> None: + """Benchmark BLAKE2F precompile.""" + if precompile_address not in fork.precompiles(): + pytest.skip("Precompile not enabled") + + attack_block = Op.POP( + Op.STATICCALL( + gas=Op.GAS, address=precompile_address, args_size=Op.CALLDATASIZE + ), + ) + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE), + attack_block=attack_block, + tx_kwargs={"data": calldata}, + ), + ) diff --git a/tests/benchmark/compute/precompile/test_bls12_381.py b/tests/benchmark/compute/precompile/test_bls12_381.py new file mode 100644 index 0000000000..faa86c07cc --- /dev/null +++ b/tests/benchmark/compute/precompile/test_bls12_381.py @@ -0,0 +1,123 @@ +"""Benchmark BLS12_381 precompile.""" + +import pytest +from execution_testing import ( + Address, + BenchmarkTestFiller, + Fork, + JumpLoopGenerator, + Op, +) + +from tests.benchmark.compute.helpers import concatenate_parameters +from tests.prague.eip2537_bls_12_381_precompiles import spec as bls12381_spec + + +@pytest.mark.parametrize( + "precompile_address,calldata", + [ + pytest.param( + bls12381_spec.Spec.G1ADD, + concatenate_parameters( + [ + bls12381_spec.Spec.G1, + bls12381_spec.Spec.P1, + ] + ), + id="bls12_g1add", + ), + pytest.param( + bls12381_spec.Spec.G1MSM, + concatenate_parameters( + [ + ( + bls12381_spec.Spec.P1 + + bls12381_spec.Scalar(bls12381_spec.Spec.Q) + ) + * (len(bls12381_spec.Spec.G1MSM_DISCOUNT_TABLE) - 1), + ] + ), + id="bls12_g1msm", + ), + pytest.param( + bls12381_spec.Spec.G2ADD, + concatenate_parameters( + [ + bls12381_spec.Spec.G2, + bls12381_spec.Spec.P2, + ] + ), + id="bls12_g2add", + ), + pytest.param( + bls12381_spec.Spec.G2MSM, + concatenate_parameters( + [ + # TODO: the //2 is required due to a limitation of the max + # contract size limit. In a further iteration we can insert + # inputs as calldata or storage and avoid doing PUSHes + # which has this limitation. This also applies to G1MSM. + ( + bls12381_spec.Spec.P2 + + bls12381_spec.Scalar(bls12381_spec.Spec.Q) + ) + * (len(bls12381_spec.Spec.G2MSM_DISCOUNT_TABLE) // 2), + ] + ), + id="bls12_g2msm", + ), + pytest.param( + bls12381_spec.Spec.PAIRING, + concatenate_parameters( + [ + bls12381_spec.Spec.G1, + bls12381_spec.Spec.G2, + ] + ), + id="bls12_pairing_check", + ), + pytest.param( + bls12381_spec.Spec.MAP_FP_TO_G1, + concatenate_parameters( + [ + bls12381_spec.FP(bls12381_spec.Spec.P - 1), + ] + ), + id="bls12_fp_to_g1", + ), + pytest.param( + bls12381_spec.Spec.MAP_FP2_TO_G2, + concatenate_parameters( + [ + bls12381_spec.FP2( + (bls12381_spec.Spec.P - 1, bls12381_spec.Spec.P - 1) + ), + ] + ), + id="bls12_fp_to_g2", + ), + ], +) +def test_bls12_381( + benchmark_test: BenchmarkTestFiller, + fork: Fork, + precompile_address: Address, + calldata: bytes, +) -> None: + """Benchmark BLS12_381 precompile.""" + if precompile_address not in fork.precompiles(): + pytest.skip("Precompile not enabled") + + attack_block = Op.POP( + Op.STATICCALL( + gas=Op.GAS, address=precompile_address, args_size=Op.CALLDATASIZE + ), + ) + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE), + attack_block=attack_block, + tx_kwargs={"data": calldata}, + ), + ) diff --git a/tests/benchmark/compute/precompile/test_ecrecover.py b/tests/benchmark/compute/precompile/test_ecrecover.py new file mode 100644 index 0000000000..0871c3281f --- /dev/null +++ b/tests/benchmark/compute/precompile/test_ecrecover.py @@ -0,0 +1,56 @@ +"""Benchmark ECRECOVER precompile.""" + +import pytest +from execution_testing import ( + Address, + BenchmarkTestFiller, + Fork, + JumpLoopGenerator, + Op, +) + +from tests.benchmark.compute.helpers import concatenate_parameters + + +@pytest.mark.parametrize( + "precompile_address,calldata", + [ + pytest.param( + 0x01, + concatenate_parameters( + [ + # Inputs below are a valid signature, thus ECRECOVER call + # will perform full computation, not blocked by validation. + "38D18ACB67D25C8BB9942764B62F18E17054F66A817BD4295423ADF9ED98873E", + "000000000000000000000000000000000000000000000000000000000000001B", + "38D18ACB67D25C8BB9942764B62F18E17054F66A817BD4295423ADF9ED98873E", + "789D1DD423D25F0772D2748D60F7E4B81BB14D086EBA8E8E8EFB6DCFF8A4AE02", + ] + ), + id="ecrecover", + ) + ], +) +def test_ecrecover( + benchmark_test: BenchmarkTestFiller, + fork: Fork, + precompile_address: Address, + calldata: bytes, +) -> None: + """Benchmark ECRECOVER precompile.""" + if precompile_address not in fork.precompiles(): + pytest.skip("Precompile not enabled") + + attack_block = Op.POP( + Op.STATICCALL( + gas=Op.GAS, address=precompile_address, args_size=Op.CALLDATASIZE + ), + ) + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE), + attack_block=attack_block, + tx_kwargs={"data": calldata}, + ), + ) diff --git a/tests/benchmark/compute/precompile/test_identity.py b/tests/benchmark/compute/precompile/test_identity.py new file mode 100644 index 0000000000..64e81a865c --- /dev/null +++ b/tests/benchmark/compute/precompile/test_identity.py @@ -0,0 +1,41 @@ +"""Benchmark IDENTITY precompile.""" + +from execution_testing import ( + BenchmarkTestFiller, + Fork, + JumpLoopGenerator, + Op, +) + +from tests.benchmark.compute.helpers import calculate_optimal_input_length + + +def test_identity( + benchmark_test: BenchmarkTestFiller, + fork: Fork, + gas_benchmark_value: int, +) -> None: + """Benchmark IDENTITY precompile.""" + intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() + gas_available = gas_benchmark_value - intrinsic_gas_calculator() + + optimal_input_length = calculate_optimal_input_length( + available_gas=gas_available, + fork=fork, + static_cost=15, + per_word_dynamic_cost=3, + bytes_per_unit_of_work=1, + ) + + attack_block = Op.POP( + Op.STATICCALL( + Op.GAS, 0x04, Op.PUSH0, optimal_input_length, Op.PUSH0, Op.PUSH0 + ) + ) + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=Op.CODECOPY(0, 0, optimal_input_length), + attack_block=attack_block, + ), + ) diff --git a/tests/benchmark/compute/precompile/test_modexp.py b/tests/benchmark/compute/precompile/test_modexp.py new file mode 100644 index 0000000000..ef196735d4 --- /dev/null +++ b/tests/benchmark/compute/precompile/test_modexp.py @@ -0,0 +1,416 @@ +"""Benchmark MODEXP precompile.""" + +import pytest +from _pytest.mark import ParameterSet +from execution_testing import ( + BenchmarkTestFiller, + JumpLoopGenerator, + Op, +) + +from tests.byzantium.eip198_modexp_precompile.helpers import ModExpInput + + +def create_modexp_test_cases() -> list[ParameterSet]: + """Create test cases for the MODEXP precompile.""" + test_cases = [ + # (base, exponent, modulus, test_id) + (8 * "ff", 112 * "ff", 7 * "ff" + "00", "mod_even_8b_exp_896"), + (16 * "ff", 40 * "ff", 15 * "ff" + "00", "mod_even_16b_exp_320"), + (24 * "ff", 21 * "ff", 23 * "ff" + "00", "mod_even_24b_exp_168"), + (32 * "ff", 5 * "ff", 31 * "ff" + "00", "mod_even_32b_exp_40"), + (32 * "ff", 12 * "ff", 31 * "ff" + "00", "mod_even_32b_exp_96"), + (32 * "ff", 32 * "ff", 31 * "ff" + "00", "mod_even_32b_exp_256"), + (64 * "ff", 64 * "ff", 63 * "ff" + "00", "mod_even_64b_exp_512"), + (128 * "ff", 128 * "ff", 127 * "ff" + "00", "mod_even_128b_exp_1024"), + (256 * "ff", 128 * "ff", 255 * "ff" + "00", "mod_even_256b_exp_1024"), + (512 * "ff", 128 * "ff", 511 * "ff" + "00", "mod_even_512b_exp_1024"), + ( + 1024 * "ff", + 128 * "ff", + 1023 * "ff" + "00", + "mod_even_1024b_exp_1024", + ), + (32 * "ff", 12 * "ff", 31 * "ff" + "01", "mod_odd_32b_exp_96"), + (32 * "ff", 32 * "ff", 31 * "ff" + "01", "mod_odd_32b_exp_256"), + (64 * "ff", 64 * "ff", 63 * "ff" + "01", "mod_odd_64b_exp_512"), + (128 * "ff", 128 * "ff", 127 * "ff" + "01", "mod_odd_128b_exp_1024"), + (256 * "ff", 128 * "ff", 255 * "ff" + "01", "mod_odd_256b_exp_1024"), + (512 * "ff", 128 * "ff", 511 * "ff" + "01", "mod_odd_512b_exp_1024"), + ( + 1024 * "ff", + 128 * "ff", + 1023 * "ff" + "01", + "mod_odd_1024b_exp_1024", + ), + ( + 32 * "ff", + 8 * "12345670", + 31 * "ff" + "01", + "mod_odd_32b_exp_cover_windows", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L38 + (192 * "FF", "03", 6 * ("00" + 31 * "FF"), "mod_min_gas_base_heavy"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L40 + (8 * "FF", "07" + 75 * "FF", 7 * "FF", "mod_min_gas_exp_heavy"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L42 + (40 * "FF", "01" + 3 * "FF", "00" + 38 * "FF", "mod_min_gas_balanced"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L44 + (32 * "FF", 5 * "FF", ("00" + 31 * "FF"), "mod_exp_208_gas_balanced"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L46 + (8 * "FF", 81 * "FF", 7 * "FF", "mod_exp_215_gas_exp_heavy"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L48 + (8 * "FF", 112 * "FF", 7 * "FF", "mod_exp_298_gas_exp_heavy"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L50 + (16 * "FF", 40 * "FF", 15 * "FF", "mod_pawel_2"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L52 + (24 * "FF", 21 * "FF", 23 * "FF", "mod_pawel_3"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L54 + (32 * "FF", 12 * "FF", "00" + 31 * "FF", "mod_pawel_4"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L56 + ( + 280 * "FF", + "03", + 8 * ("00" + 31 * "FF") + 23 * "FF", + "mod_408_gas_base_heavy", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L58 + (16 * "FF", "15" + 37 * "FF", 15 * "FF", "mod_400_gas_exp_heavy"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L60 + (48 * "FF", "07" + 4 * "FF", "00" + 46 * "FF", "mod_408_gas_balanced"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L62 + ( + 344 * "FF", + "03", + 10 * ("00" + 31 * "FF") + 23 * "FF", + "mod_616_gas_base_heavy", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L64 + (16 * "FF", "07" + 56 * "FF", 15 * "FF", "mod_600_gas_exp_heavy"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L66 + (48 * "FF", "07" + 6 * "FF", "00" + 46 * "FF", "mod_600_gas_balanced"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L68 + ( + 392 * "FF", + "03", + 12 * ("00" + 31 * "FF") + 7 * "FF", + "mod_800_gas_base_heavy", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L70 + (16 * "FF", "01" + 75 * "FF", 15 * "FF", "mod_800_gas_exp_heavy"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L72 + (56 * "FF", 6 * "FF", "00" + 54 * "FF", "mod_767_gas_balanced"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L74 + (16 * "FF", 80 * "FF", 15 * "FF", "mod_852_gas_exp_heavy"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L76 + ( + 408 * "FF", + "03", + 12 * ("00" + 31 * "FF") + 23 * "FF", + "mod_867_gas_base_heavy", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L78 + (56 * "FF", "2b" + 7 * "FF", "00" + 54 * "FF", "mod_996_gas_balanced"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L80 + (448 * "FF", "03", 14 * ("00" + 31 * "FF"), "mod_1045_gas_base_heavy"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L82 + (32 * "FF", 16 * "FF", "00" + 31 * "FF", "mod_677_gas_base_heavy"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L84 + (24 * "FF", 32 * "FF", 23 * "FF", "mod_765_gas_exp_heavy"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L86 + (32 * "FF", 32 * "FF", "00" + 31 * "FF", "mod_1360_gas_balanced"), + (8 * "FF", 81 * "FF", 7 * "FF", "mod_8_exp_648"), + (8 * "FF", "FF" + 111 * "FF", 7 * "FF", "mod_8_exp_896"), + (32 * "FF", 4 * "FF", "00" + 31 * "FF", "mod_32_exp_32"), + (32 * "FF", "0D" + 4 * "FF", "00" + 31 * "FF", "mod_32_exp_36"), + (32 * "FF", 5 * "FF", "00" + 31 * "FF", "mod_32_exp_40"), + (32 * "FF", 8 * "FF", "00" + 31 * "FF", "mod_32_exp_64"), + (32 * "FF", "01" + 8 * "FF", "00" + 31 * "FF", "mod_32_exp_65"), + (32 * "FF", 16 * "FF", "00" + 31 * "FF", "mod_32_exp_128"), + (256 * "FF", "03" + 0 * "FF", 8 * ("00" + 31 * "FF"), "mod_256_exp_2"), + ( + 264 * "FF", + "03" + 0 * "FF", + 8 * ("00" + 31 * "FF") + 7 * "FF", + "mod_264_exp_2", + ), + (1024 * "FF", "03", 32 * ("00" + 31 * "FF"), "mod_1024_exp_2"), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L122 + ( + "03", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "mod_vul_example_1", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L124 + ( + "", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "mod_vul_example_2", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L126 + ( + "e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb5", + "02", + "fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", + "mod_vul_nagydani_1_square", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L128 + ( + "e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb5", + "03", + "fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", + "mod_vul_nagydani_1_qube", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L130 + ( + "e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb5", + "010001", + "fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", + "mod_vul_nagydani_1_pow_0x10001", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L132 + ( + "cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf51", + "02", + "e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", + "mod_vul_nagydani_2_square", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L134 + ( + "cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf51", + "03", + "e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", + "mod_vul_nagydani_2_qube", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L136 + ( + "cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf51", + "010001", + "e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", + "mod_vul_nagydani_2_pow_0x10001", + ), + ( + "c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb", + "02", + "d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", + "mod_vul_nagydani_3_square", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L140 + ( + "c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb", + "03", + "d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", + "mod_vul_nagydani_3_qube", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L142 + ( + "c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb", + "010001", + "d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", + "mod_vul_nagydani_3_pow_0x10001", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L144 + ( + "db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b81", + "02", + "df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", + "mod_vul_nagydani_4_square", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L146 + ( + "db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b81", + "03", + "df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", + "mod_vul_nagydani_4_qube", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L148 + ( + "db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b81", + "010001", + "df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", + "mod_vul_nagydani_4_pow_0x10001", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L150 + ( + "c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf", + "02", + "e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", + "mod_vul_nagydani_5_square", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L152 + ( + "c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf", + "03", + "e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", + "mod_vul_nagydani_5_qube", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L154 + ( + "c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf", + "010001", + "e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", + "mod_vul_nagydani_5_pow_0x10001", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L156 + ( + "ffffff", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffe000007d7d7d83828282348286877d7d827d407d797d7d7d7d7d7d7d7d7d7d7d5b00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000021000000000000000000000000000000000000000000000000000000000000000cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4000007d7d", + "7d83828282348286877d7d82", + "mod_vul_marius_1_even", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L158 + ( + "ffffffffffffffff76ffffffffffffff", + "1cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c76ec7c7c7c7ffffffffffffffc7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7ffffffffffffc7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c76ec7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7ffff", + "ffffff3f000000000000000000000000", + "mod_vul_guido_1_even", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L160 + ( + "e0060000a921212121212121ff000021", + "2b212121ffff1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f00feffff212121212121ffffffff1fe1e0e0e01e1f1f169f1f1f1f490afcefffffffffffffffff82828282828282828282828282828282828282828200ffff28ff2b212121ffff1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1fffffffffff0afceffffff7ffffffffff7c8282828282a1828282828282828282828282828200ffff28ff2b212121ffff1f1f1f1f1f1fd11f1f1f1f1f1f1f1f1f1f1fffffffffffffffff21212121212121fb2121212121ffff1f1f1f1f1f1f1f1fffaf", + "82828282828200ffff28ff2b21828200", + "mod_vul_guido_2_even", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L162 + ( + "0193585a48e18aad777e9c1b54221a0f58140392e4f091cd5f42b2e8644a9384fbd58ae1edec2477ebf7edbf7c0a3f8bd21d1890ee87646feab3c47be716f842cc3da9b940af312dc54450a960e3fc0b86e56abddd154068e10571a96fff6259431632bc15695c6c8679057e66c2c25c127e97e64ee5de6ea1fc0a4a0e431343fed1daafa072c238a45841da86a9806680bc9f298411173210790359209cd454b5af7b4d5688b4403924e5f863d97e2c5349e1a04b54fcf385b1e9d7714bab8fbf5835f6ff9ed575e77dff7af5cbb641db5d537933bae1fa6555d6c12d6fb31ca27b57771f4aebfbe0bf95e8990c0108ffe7cbdaf370be52cf3ade594543af75ad9329d2d11a402270b5b9a6bf4b83307506e118fca4862749d04e916fc7a039f0d13f2a02e0eedb800199ec95df15b4ccd8669b52586879624d51219e72102fad810b5909b1e372ddf33888fb9beb09b416e4164966edbabd89e4a286be36277fc576ed519a15643dac602e92b63d0b9121f0491da5b16ef793a967f096d80b6c81ecaaffad7e3f06a4a5ac2796f1ed9f68e6a0fd5cf191f0c5c2eec338952ff8d31abc68bf760febeb57e088995ba1d7726a2fdd6d8ca28a181378b8b4ab699bfd4b696739bbf17a9eb2df6251143046137fdbbfacac312ebf67a67da9741b59600000000000", + "04", + "19a2917c61722b0713d3b00a2f0e1dd5aebbbe09615de424700eea3c3020fe6e9ea5de9fa1ace781df28b21f746d2ab61d0da496e08473c90ff7dfe25b43bcde76f4bafb82e0975bea75f5a0591dba80ba2fff80a07d8853bea5be13ab326ba70c57b153acc646151948d1cf061ca31b02d4719fac710e7c723ca44f5b1737824b7ccc74ba5bff980aabdbf267621cafc3d6dcc29d0ca9c16839a92ed34de136da7900aa3ee43d21aa57498981124357cf0ca9b86f9a8d3f9c604ca00c726e48f7a9945021ea6dfff92d6b2d6514693169ca133e993541bfa4c4c191de806aa80c48109bcfc9901eccfdeb2395ab75fe63c67de900829d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "mod_vul_guido_3_even", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L166 + ( + "ffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "ffffffffffffffff", + "mod_vul_pawel_1_exp_heavy", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L168 + ( + "ffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffff", + "mod_vul_pawel_2_exp_heavy", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L170 + ( + "ffffffffffffffffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffffffffffffff", + "mod_vul_pawel_3_exp_heavy", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L172 + ( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "mod_vul_pawel_4_exp_heavy", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L174 + ( + "29356abadad68ad986c416de6f620bda0e1818b589e84f853a97391694d35496", + "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254f", + "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + "mod_vul_common_1360n1", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L176 + ( + "d41afaeaea32f7409827761b68c41b6e535da4ede1f0800bfb4a6aed18394f6b", + "ffffffff00000001000000000000000000000000fffffffffffffffffffffffd", + "ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + "mod_vul_common_1360n2", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L178 + ( + "1a5be8fae3b3fda9ea329494ae8689c04fae4978ecccfa6a6bfb9f04b25846c0", + "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffffff", + "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001", + "mod_vul_common_1349n1", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L182 + ( + "0000000000000000000000000000000000000000000000000000000000000003", + "0000000001000000000000022000000000000000000000000000000000000000", + "0800000000000011000000000000000000000000000000000000000000000001", + "mod_vul_common_1152n1", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L184 + ( + "1fb473dd1171cf88116aa77ab3612c2c7d2cf466cc2386cc456130e2727c70b4", + "0000000000000000000000000000000000000000000000000000000001000000", + "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001", + "mod_vul_common_200n1", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L186 + ( + "1951441010b2b95a6e47a6075066a50a036f5ba978c050f2821df86636c0facb", + "0000000000000000000000000000000000000000000000000000000000ffffff", + "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001", + "mod_vul_common_200n2", + ), + # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L188 + ( + "288254ba43e713afbe36c9f03b54c00fae4c0a82df1cf165eb46a21c20a48ca2", + "0000000000000000000000000000000000000000000000000000000000ffffff", + "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001", + "mod_vul_common_200n3", + ), + ( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", + "mod_vul_zkevm_worst_case", + ), + ] + + special_cases = [ + pytest.param( + ModExpInput.from_bytes( + "000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000017bffffffffffffffffffffffffffffffffffffffffffffbffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffe" + ), + id="mod_vul_pawel_3_exp_8", + ), + ] + + regular_cases = [ + pytest.param( + ModExpInput( + base=base, + exponent=exponent, + modulus=modulus, + ), + id=test_id, + ) + for base, exponent, modulus, test_id in test_cases + ] + + return regular_cases + special_cases + + +@pytest.mark.parametrize( + ["mod_exp_input"], + create_modexp_test_cases(), +) +def test_modexp( + benchmark_test: BenchmarkTestFiller, + mod_exp_input: ModExpInput, +) -> None: + """ + Test running a block with as many calls to the MODEXP (5) precompile as + possible. All the calls have the same parametrized input. + """ + attack_block = Op.POP( + Op.STATICCALL( + Op.GAS, 0x5, Op.PUSH0, Op.CALLDATASIZE, Op.PUSH0, Op.PUSH0 + ) + ) + benchmark_test( + code_generator=JumpLoopGenerator( + setup=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE), + attack_block=attack_block, + tx_kwargs={"data": bytes(mod_exp_input).rstrip(b"\x00")}, + ), + ) diff --git a/tests/benchmark/compute/precompile/test_p256verify.py b/tests/benchmark/compute/precompile/test_p256verify.py new file mode 100644 index 0000000000..6218dd65fa --- /dev/null +++ b/tests/benchmark/compute/precompile/test_p256verify.py @@ -0,0 +1,87 @@ +"""Benchmark P256VERIFY precompile.""" + +import pytest +from execution_testing import ( + Address, + BenchmarkTestFiller, + Fork, + JumpLoopGenerator, + Op, +) + +from tests.benchmark.compute.helpers import concatenate_parameters +from tests.osaka.eip7951_p256verify_precompiles import spec as p256verify_spec + + +@pytest.mark.parametrize( + "precompile_address,calldata", + [ + pytest.param( + p256verify_spec.Spec.P256VERIFY, + concatenate_parameters( + [ + p256verify_spec.Spec.H0, + p256verify_spec.Spec.R0, + p256verify_spec.Spec.S0, + p256verify_spec.Spec.X0, + p256verify_spec.Spec.Y0, + ] + ), + id="p256verify", + marks=[ + pytest.mark.eip_checklist( + "precompile/test/excessive_gas_usage", eip=[7951] + ) + ], + ), + pytest.param( + p256verify_spec.Spec.P256VERIFY, + concatenate_parameters( + [ + "235060CAFE19A407880C272BC3E73600E3A12294F56143ED61929C2FF4525ABB", + "182E5CBDF96ACCB859E8EEA1850DE5FF6E430A19D1D9A680ECD5946BBEA8A32B", + "76DDFAE6797FA6777CAAB9FA10E75F52E70A4E6CEB117B3C5B2F445D850BD64C", + "3828736CDFC4C8696008F71999260329AD8B12287846FEDCEDE3BA1205B12729", + "3E5141734E971A8D55015068D9B3666760F4608A49B11F92E500ACEA647978C7", + ] + ), + id="p256verify_wrong_endianness", + ), + pytest.param( + p256verify_spec.Spec.P256VERIFY, + concatenate_parameters( + [ + "BB5A52F42F9C9261ED4361F59422A1E30036E7C32B270C8807A419FECA605023", + "000000000000000000000000000000004319055358E8617B0C46353D039CDAAB", + "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63254E", + "0AD99500288D466940031D72A9F5445A4D43784640855BF0A69874D2DE5FE103", + "C5011E6EF2C42DCD50D5D3D29F99AE6EBA2C80C9244F4C5422F0979FF0C3BA5E", + ] + ), + id="p256verify_modular_comp_x_coordinate_exceeds_n", + ), + ], +) +def test_p256verify( + benchmark_test: BenchmarkTestFiller, + fork: Fork, + precompile_address: Address, + calldata: bytes, +) -> None: + """Benchmark P256VERIFY precompile.""" + if precompile_address not in fork.precompiles(): + pytest.skip("Precompile not enabled") + + attack_block = Op.POP( + Op.STATICCALL( + gas=Op.GAS, address=precompile_address, args_size=Op.CALLDATASIZE + ), + ) + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE), + attack_block=attack_block, + tx_kwargs={"data": calldata}, + ), + ) diff --git a/tests/benchmark/compute/precompile/test_point_evaluation.py b/tests/benchmark/compute/precompile/test_point_evaluation.py new file mode 100644 index 0000000000..4ffcbb0470 --- /dev/null +++ b/tests/benchmark/compute/precompile/test_point_evaluation.py @@ -0,0 +1,56 @@ +"""Benchmark POINT EVALUATION precompile.""" + +import pytest +from execution_testing import ( + Address, + BenchmarkTestFiller, + Fork, + JumpLoopGenerator, + Op, +) + +from tests.benchmark.compute.helpers import concatenate_parameters +from tests.cancun.eip4844_blobs.spec import Spec as BlobsSpec + + +@pytest.mark.parametrize( + "precompile_address,calldata", + [ + pytest.param( + BlobsSpec.POINT_EVALUATION_PRECOMPILE_ADDRESS, + concatenate_parameters( + [ + "01E798154708FE7789429634053CBF9F99B619F9F084048927333FCE637F549B", + "564C0A11A0F704F4FC3E8ACFE0F8245F0AD1347B378FBF96E206DA11A5D36306", + "24D25032E67A7E6A4910DF5834B8FE70E6BCFEEAC0352434196BDF4B2485D5A1", + "8F59A8D2A1A625A17F3FEA0FE5EB8C896DB3764F3185481BC22F91B4AAFFCCA25F26936857BC3A7C2539EA8EC3A952B7", + "873033E038326E87ED3E1276FD140253FA08E9FC25FB2D9A98527FC22A2C9612FBEAFDAD446CBC7BCDBDCD780AF2C16A", + ] + ), + id="point_evaluation", + ), + ], +) +def test_point_evaluation( + benchmark_test: BenchmarkTestFiller, + fork: Fork, + precompile_address: Address, + calldata: bytes, +) -> None: + """Benchmark POINT EVALUATION precompile.""" + if precompile_address not in fork.precompiles(): + pytest.skip("Precompile not enabled") + + attack_block = Op.POP( + Op.STATICCALL( + gas=Op.GAS, address=precompile_address, args_size=Op.CALLDATASIZE + ), + ) + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE), + attack_block=attack_block, + tx_kwargs={"data": calldata}, + ), + ) diff --git a/tests/benchmark/compute/precompile/test_ripemd160.py b/tests/benchmark/compute/precompile/test_ripemd160.py new file mode 100644 index 0000000000..24821320d6 --- /dev/null +++ b/tests/benchmark/compute/precompile/test_ripemd160.py @@ -0,0 +1,41 @@ +"""Benchmark RIPEMD-160 precompile.""" + +from execution_testing import ( + BenchmarkTestFiller, + Fork, + JumpLoopGenerator, + Op, +) + +from tests.benchmark.compute.helpers import calculate_optimal_input_length + + +def test_ripemd160( + benchmark_test: BenchmarkTestFiller, + fork: Fork, + tx_gas_limit: int, +) -> None: + """Benchmark RIPEMD-160 precompile.""" + intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() + gas_available = tx_gas_limit - intrinsic_gas_calculator() + + optimal_input_length = calculate_optimal_input_length( + available_gas=gas_available, + fork=fork, + static_cost=600, + per_word_dynamic_cost=120, + bytes_per_unit_of_work=64, + ) + + attack_block = Op.POP( + Op.STATICCALL( + Op.GAS, 0x03, Op.PUSH0, optimal_input_length, Op.PUSH0, Op.PUSH0 + ) + ) + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=Op.CODECOPY(0, 0, optimal_input_length), + attack_block=attack_block, + ), + ) diff --git a/tests/benchmark/compute/precompile/test_sha256.py b/tests/benchmark/compute/precompile/test_sha256.py new file mode 100644 index 0000000000..8096d5dfde --- /dev/null +++ b/tests/benchmark/compute/precompile/test_sha256.py @@ -0,0 +1,41 @@ +"""Benchmark SHA256 precompile.""" + +from execution_testing import ( + BenchmarkTestFiller, + Fork, + JumpLoopGenerator, + Op, +) + +from tests.benchmark.compute.helpers import calculate_optimal_input_length + + +def test_sha256( + benchmark_test: BenchmarkTestFiller, + fork: Fork, + tx_gas_limit: int, +) -> None: + """Benchmark SHA256 precompile.""" + intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() + gas_available = tx_gas_limit - intrinsic_gas_calculator() + + optimal_input_length = calculate_optimal_input_length( + available_gas=gas_available, + fork=fork, + static_cost=60, + per_word_dynamic_cost=12, + bytes_per_unit_of_work=64, + ) + + attack_block = Op.POP( + Op.STATICCALL( + Op.GAS, 0x02, Op.PUSH0, optimal_input_length, Op.PUSH0, Op.PUSH0 + ) + ) + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=Op.CODECOPY(0, 0, optimal_input_length), + attack_block=attack_block, + ), + ) diff --git a/tests/benchmark/compute/scenario/test_mix_operations.py b/tests/benchmark/compute/scenario/test_mix_operations.py new file mode 100644 index 0000000000..bd534a0d35 --- /dev/null +++ b/tests/benchmark/compute/scenario/test_mix_operations.py @@ -0,0 +1,87 @@ +"""Benchmark mixed operations.""" + +import pytest +from execution_testing import ( + BenchmarkTestFiller, + Bytecode, + Fork, + JumpLoopGenerator, + Op, +) + + +@pytest.mark.parametrize( + "pattern", + [ + Op.STOP, + Op.JUMPDEST, + Op.PUSH1[bytes(Op.JUMPDEST)], + Op.PUSH2[bytes(Op.JUMPDEST + Op.JUMPDEST)], + Op.PUSH1[bytes(Op.JUMPDEST)] + Op.JUMPDEST, + Op.PUSH2[bytes(Op.JUMPDEST + Op.JUMPDEST)] + Op.JUMPDEST, + ], + ids=lambda x: x.hex(), +) +def test_jumpdest_analysis( + benchmark_test: BenchmarkTestFiller, + fork: Fork, + pattern: Bytecode, +) -> None: + """ + Test the jumpdest analysis performance of the initcode. + + This benchmark places a very long initcode in the memory and then invoke + CREATE instructions with this initcode up to the block gas limit. The + initcode itself has minimal execution time but forces the EVM to perform + the full jumpdest analysis on the parametrized byte pattern. The initicode + is modified by mixing-in the returned create address between CREATE + invocations to prevent caching. + """ + initcode_size = fork.max_initcode_size() + + # Expand the initcode pattern to the transaction data so it can be used in + # CALLDATACOPY in the main contract. TODO: tune the tx_data_len param. + tx_data_len = 1024 + tx_data = pattern * (tx_data_len // len(pattern)) + tx_data += (tx_data_len - len(tx_data)) * bytes(Op.JUMPDEST) + assert len(tx_data) == tx_data_len + assert initcode_size % len(tx_data) == 0 + + # Prepare the initcode in memory. + code_prepare_initcode = sum( + ( + Op.CALLDATACOPY( + dest_offset=i * len(tx_data), offset=0, size=Op.CALLDATASIZE + ) + for i in range(initcode_size // len(tx_data)) + ), + Bytecode(), + ) + + # At the start of the initcode execution, jump to the last opcode. + # This forces EVM to do the full jumpdest analysis. + initcode_prefix = Op.JUMP(initcode_size - 1) + code_prepare_initcode += Op.MSTORE( + 0, Op.PUSH32[bytes(initcode_prefix).ljust(32, bytes(Op.JUMPDEST))] + ) + + # Make sure the last opcode in the initcode is JUMPDEST. + code_prepare_initcode += Op.MSTORE( + initcode_size - 32, Op.PUSH32[bytes(Op.JUMPDEST) * 32] + ) + + attack_block = ( + Op.PUSH1[len(initcode_prefix)] + + Op.MSTORE + + Op.CREATE(value=Op.PUSH0, offset=Op.PUSH0, size=Op.MSIZE) + ) + + setup = code_prepare_initcode + Op.PUSH0 + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=setup, + attack_block=attack_block, + tx_kwargs={"data": tx_data}, + ), + ) diff --git a/tests/benchmark/test_worst_blocks.py b/tests/benchmark/compute/scenario/test_transaction_types.py old mode 100755 new mode 100644 similarity index 96% rename from tests/benchmark/test_worst_blocks.py rename to tests/benchmark/compute/scenario/test_transaction_types.py index f661efe4aa..cb0a4f4b64 --- a/tests/benchmark/test_worst_blocks.py +++ b/tests/benchmark/compute/scenario/test_transaction_types.py @@ -1,6 +1,4 @@ -""" -Tests that benchmark EVMs in worst-case block scenarios. -""" +"""Benchmark different transaction types.""" import math import random @@ -23,6 +21,16 @@ ) +def test_empty_block( + benchmark_test: BenchmarkTestFiller, +) -> None: + """Test running an empty block as a baseline for fixed proving costs.""" + benchmark_test( + blocks=[Block(txs=[])], + expected_benchmark_gas_used=0, + ) + + @pytest.fixture def iteration_count(intrinsic_cost: int, gas_benchmark_value: int) -> int: """ @@ -233,18 +241,17 @@ def test_block_full_data( intrinsic_cost: int, total_cost_floor_per_token: int, gas_benchmark_value: int, - tx_gas_limit_cap: int, - total_cost_standard_per_token: int, + tx_gas_limit: int, fork: Fork, ) -> None: """Test a block with empty payload.""" - iteration_count = math.ceil(gas_benchmark_value / tx_gas_limit_cap) + iteration_count = math.ceil(gas_benchmark_value / tx_gas_limit) gas_remaining = gas_benchmark_value total_gas_used = 0 txs = [] for _ in range(iteration_count): - gas_available = min(tx_gas_limit_cap, gas_remaining) - intrinsic_cost + gas_available = min(tx_gas_limit, gas_remaining) - intrinsic_cost data = calldata_generator( gas_available, zero_byte, @@ -278,20 +285,20 @@ def test_block_full_access_list_and_data( total_cost_standard_per_token: int, fork: Fork, gas_benchmark_value: int, - tx_gas_limit_cap: int, + tx_gas_limit: int, ) -> None: """ Test a block with access lists (60% gas) and calldata (40% gas) using random mixed bytes. """ - iteration_count = math.ceil(gas_benchmark_value / tx_gas_limit_cap) + iteration_count = math.ceil(gas_benchmark_value / tx_gas_limit) gas_remaining = gas_benchmark_value total_gas_used = 0 txs = [] for _ in range(iteration_count): - gas_available = min(tx_gas_limit_cap, gas_remaining) - intrinsic_cost + gas_available = min(tx_gas_limit, gas_remaining) - intrinsic_cost # Split available gas: 60% for access lists, 40% for calldata gas_for_access_list = int(gas_available * 0.6) diff --git a/tests/benchmark/conftest.py b/tests/benchmark/conftest.py index 6ec31dfd6f..c4f711c362 100755 --- a/tests/benchmark/conftest.py +++ b/tests/benchmark/conftest.py @@ -89,6 +89,6 @@ def pytest_collection_modifyitems(config: Any, items: Any) -> None: @pytest.fixture -def tx_gas_limit_cap(fork: Fork, gas_benchmark_value: int) -> int: +def tx_gas_limit(fork: Fork, gas_benchmark_value: int) -> int: """Return the transaction gas limit cap.""" return fork.transaction_gas_limit_cap() or gas_benchmark_value diff --git a/tests/benchmark/helpers.py b/tests/benchmark/helpers.py deleted file mode 100755 index 830bc33455..0000000000 --- a/tests/benchmark/helpers.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Helper functions for the EVM benchmark worst-case tests.""" - -from execution_testing import Bytecode, Fork, Op - - -def code_loop_precompile_call( - calldata: Bytecode, attack_block: Bytecode, fork: Fork -) -> Bytecode: - """Create a code loop that calls a precompile with the given calldata.""" - max_code_size = fork.max_code_size() - - # The attack contract is: CALLDATA_PREP + #JUMPDEST + [attack_block]* + - # JUMP(#) - jumpdest = Op.JUMPDEST - jump_back = Op.JUMP(len(calldata)) - max_iters_loop = ( - max_code_size - len(calldata) - len(jumpdest) - len(jump_back) - ) // len(attack_block) - code = ( - calldata + jumpdest + sum([attack_block] * max_iters_loop) + jump_back - ) - if len(code) > max_code_size: - # Must never happen, but keep it as a sanity check. - raise ValueError( - f"Code size {len(code)} exceeds maximum code size {max_code_size}" - ) - - return code diff --git a/tests/benchmark/test_worst_compute.py b/tests/benchmark/test_worst_compute.py deleted file mode 100755 index bb839eb3e2..0000000000 --- a/tests/benchmark/test_worst_compute.py +++ /dev/null @@ -1,2351 +0,0 @@ -""" -Tests that benchmark EVMs in worst-case compute scenarios. -""" - -import math -import operator -import random -from enum import Enum, auto -from typing import Any, Dict, cast - -import pytest -from _pytest.mark import ParameterSet -from execution_testing import ( - Address, - Alloc, - BenchmarkTestFiller, - Block, - Bytecode, - Bytes, - ExtCallGenerator, - Fork, - JumpLoopGenerator, - Op, - Opcode, - Transaction, - TransactionType, - add_kzg_version, -) -from py_ecc.bn128 import G1, G2, multiply - -from ..byzantium.eip198_modexp_precompile.test_modexp import ModExpInput -from ..cancun.eip4844_blobs.spec import Spec as BlobsSpec -from ..istanbul.eip152_blake2.common import Blake2bInput -from ..istanbul.eip152_blake2.spec import Spec as Blake2bSpec -from ..osaka.eip7951_p256verify_precompiles import spec as p256verify_spec -from ..osaka.eip7951_p256verify_precompiles.spec import FieldElement -from ..prague.eip2537_bls_12_381_precompiles import spec as bls12381_spec -from ..prague.eip2537_bls_12_381_precompiles.spec import BytesConcatenation - -REFERENCE_SPEC_GIT_PATH = "TODO" -REFERENCE_SPEC_VERSION = "TODO" - -KECCAK_RATE = 136 - - -def neg(x: int) -> int: - """Negate the given integer in the two's complement 256-bit range.""" - assert 0 <= x < 2**256 - return 2**256 - x - - -def make_dup(index: int) -> Opcode: - """ - Create a DUP instruction which duplicates the index-th (counting from 0) - element from the top of the stack. E.g. make_dup(0) → DUP1. - """ - assert 0 <= index < 16 - return Opcode( - 0x80 + index, pushed_stack_items=1, min_stack_height=index + 1 - ) - - -@pytest.mark.parametrize( - "opcode", - [ - Op.ADDRESS, - Op.ORIGIN, - Op.CALLER, - Op.CODESIZE, - Op.GASPRICE, - Op.COINBASE, - Op.TIMESTAMP, - Op.NUMBER, - Op.PREVRANDAO, - Op.GASLIMIT, - Op.CHAINID, - Op.BASEFEE, - Op.BLOBBASEFEE, - Op.GAS, - # Note that other 0-param opcodes are covered in separate tests. - ], -) -def test_worst_zero_param( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - opcode: Op, -) -> None: - """Test running a block with as many zero-parameter opcodes as possible.""" - benchmark_test( - pre=pre, - post={}, - code_generator=ExtCallGenerator(attack_block=opcode), - ) - - -@pytest.mark.parametrize("calldata_length", [0, 1_000, 10_000]) -def test_worst_calldatasize( - benchmark_test: BenchmarkTestFiller, - calldata_length: int, -) -> None: - """Test running a block with as many CALLDATASIZE as possible.""" - benchmark_test( - code_generator=JumpLoopGenerator( - attack_block=Op.POP(Op.CALLDATASIZE), - tx_kwargs={"data": b"\x00" * calldata_length}, - ), - ) - - -@pytest.mark.parametrize("non_zero_value", [True, False]) -@pytest.mark.parametrize("from_origin", [True, False]) -def test_worst_callvalue( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - non_zero_value: bool, - from_origin: bool, -) -> None: - """ - Test running a block with as many CALLVALUE opcodes as possible. - - The `non_zero_value` parameter controls whether opcode must return non-zero - value. The `from_origin` parameter controls whether the call frame is the - immediate from the transaction or a previous CALL. - """ - code_address = JumpLoopGenerator( - attack_block=Op.POP(Op.CALLVALUE) - ).deploy_contracts(pre=pre, fork=fork) - - if from_origin: - tx_to = code_address - else: - entry_code = ( - Op.JUMPDEST - + Op.CALL(address=code_address, value=1 if non_zero_value else 0) - + Op.JUMP(Op.PUSH0) - ) - tx_to = pre.deploy_contract(code=entry_code, balance=1_000_000) - - tx = Transaction( - to=tx_to, - value=1 if non_zero_value and from_origin else 0, - sender=pre.fund_eoa(), - ) - - benchmark_test(tx=tx) - - -class ReturnDataStyle(Enum): - """Helper enum to specify return data is returned to the caller.""" - - RETURN = auto() - REVERT = auto() - IDENTITY = auto() - - -@pytest.mark.parametrize( - "return_data_style", - [ - ReturnDataStyle.RETURN, - ReturnDataStyle.REVERT, - ReturnDataStyle.IDENTITY, - ], -) -@pytest.mark.parametrize("returned_size", [1, 0]) -def test_worst_returndatasize_nonzero( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - returned_size: int, - return_data_style: ReturnDataStyle, -) -> None: - """ - Test running a block which execute as many RETURNDATASIZE opcodes which - return a non-zero buffer as possible. - - The `returned_size` parameter indicates the size of the returned data - buffer. The `return_data_style` indicates how returned data is produced for - the opcode caller. - """ - setup = Bytecode() - if return_data_style != ReturnDataStyle.IDENTITY: - setup += Op.STATICCALL( - address=pre.deploy_contract( - code=Op.REVERT(0, returned_size) - if return_data_style == ReturnDataStyle.REVERT - else Op.RETURN(0, returned_size) - ) - ) - else: - setup += Op.MSTORE8(0, 1) + Op.STATICCALL( - address=0x04, # Identity precompile - args_size=returned_size, - ) - - benchmark_test( - code_generator=JumpLoopGenerator( - setup=setup, attack_block=Op.POP(Op.RETURNDATASIZE) - ), - ) - - -def test_worst_returndatasize_zero( - benchmark_test: BenchmarkTestFiller, -) -> None: - """ - Test running a block with as many RETURNDATASIZE opcodes as possible with - a zero buffer. - """ - benchmark_test( - code_generator=ExtCallGenerator(attack_block=Op.RETURNDATASIZE), - ) - - -@pytest.mark.parametrize("mem_size", [0, 1, 1_000, 100_000, 1_000_000]) -def test_worst_msize( - benchmark_test: BenchmarkTestFiller, - mem_size: int, -) -> None: - """ - Test running a block with as many MSIZE opcodes as possible. - - The `mem_size` parameter indicates by how much the memory is expanded. - """ - benchmark_test( - code_generator=ExtCallGenerator( - setup=Op.MLOAD(Op.SELFBALANCE) + Op.POP, - attack_block=Op.MSIZE, - contract_balance=mem_size, - ), - ) - - -def test_worst_keccak( - benchmark_test: BenchmarkTestFiller, - fork: Fork, - gas_benchmark_value: int, -) -> None: - """Test running a block with as many KECCAK256 permutations as possible.""" - # Intrinsic gas cost is paid once. - intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - available_gas = gas_benchmark_value - intrinsic_gas_calculator() - - gsc = fork.gas_costs() - mem_exp_gas_calculator = fork.memory_expansion_gas_calculator() - - # Discover the optimal input size to maximize keccak-permutations, - # not to maximize keccak calls. - # The complication of the discovery arises from - # the non-linear gas cost of memory expansion. - max_keccak_perm_per_block = 0 - optimal_input_length = 0 - for i in range(1, 1_000_000, 32): - iteration_gas_cost = ( - 2 * gsc.G_VERY_LOW # PUSHN + PUSH1 - + gsc.G_KECCAK_256 # KECCAK256 static cost - + math.ceil(i / 32) * gsc.G_KECCAK_256_WORD # KECCAK256 dynamic - # cost - + gsc.G_BASE # POP - ) - # From the available gas, we subtract the mem expansion costs - # considering we know the current input size length i. - available_gas_after_expansion = max( - 0, available_gas - mem_exp_gas_calculator(new_bytes=i) - ) - # Calculate how many calls we can do. - num_keccak_calls = available_gas_after_expansion // iteration_gas_cost - # KECCAK does 1 permutation every 136 bytes. - num_keccak_permutations = num_keccak_calls * math.ceil(i / KECCAK_RATE) - - # If we found an input size that is better (reg permutations/gas), then - # save it. - if num_keccak_permutations > max_keccak_perm_per_block: - max_keccak_perm_per_block = num_keccak_permutations - optimal_input_length = i - - # max_iters_loop contains how many keccak calls can be done per loop. The - # loop is as big as possible bounded by the maximum code size. - # - # The loop structure is: JUMPDEST + [attack iteration] + PUSH0 + JUMP - # - # Now calculate available gas for [attack iteration]: - # Numerator = max_code_size-3. (JUMPDEST, PUSH0 and JUMP) - # Denominator = (PUSHN + PUSH1 + KECCAK256 + POP) + PUSH1_DATA + - # PUSHN_DATA - # TODO: the testing framework uses PUSH1(0) instead of PUSH0 which is - # suboptimal for the - # attack, whenever this is fixed adjust accordingly. - benchmark_test( - code_generator=JumpLoopGenerator( - setup=Op.PUSH20[optimal_input_length], - attack_block=Op.POP(Op.SHA3(Op.PUSH0, Op.DUP1)), - ), - ) - - -@pytest.mark.parametrize( - "address,static_cost,per_word_dynamic_cost,bytes_per_unit_of_work", - [ - pytest.param(0x02, 60, 12, 64, id="SHA2-256"), - pytest.param(0x03, 600, 120, 64, id="RIPEMD-160"), - pytest.param(0x04, 15, 3, 1, id="IDENTITY"), - ], -) -def test_worst_precompile_only_data_input( - benchmark_test: BenchmarkTestFiller, - fork: Fork, - address: Address, - static_cost: int, - per_word_dynamic_cost: int, - bytes_per_unit_of_work: int, - gas_benchmark_value: int, -) -> None: - """ - Test running a block with as many precompile calls which have a single - `data` input. - """ - # Intrinsic gas cost is paid once. - intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - available_gas = gas_benchmark_value - intrinsic_gas_calculator() - - gsc = fork.gas_costs() - mem_exp_gas_calculator = fork.memory_expansion_gas_calculator() - - # Discover the optimal input size to maximize precompile work, not - # precompile calls. - max_work = 0 - optimal_input_length = 0 - for input_length in range(1, 1_000_000, 32): - parameters_gas = ( - gsc.G_BASE # PUSH0 = arg offset - + gsc.G_BASE # PUSH0 = arg size - + gsc.G_BASE # PUSH0 = arg size - + gsc.G_VERY_LOW # PUSH0 = arg offset - + gsc.G_VERY_LOW # PUSHN = address - + gsc.G_BASE # GAS - ) - iteration_gas_cost = ( - parameters_gas - + +static_cost # Precompile static cost - + math.ceil(input_length / 32) * per_word_dynamic_cost - # Precompile dynamic cost - + gsc.G_BASE # POP - ) - # From the available gas, we subtract the mem expansion costs - # considering we know the current input size length. - available_gas_after_expansion = max( - 0, available_gas - mem_exp_gas_calculator(new_bytes=input_length) - ) - # Calculate how many calls we can do. - num_calls = available_gas_after_expansion // iteration_gas_cost - total_work = num_calls * math.ceil( - input_length / bytes_per_unit_of_work - ) - - # If we found an input size that is better (reg permutations/gas), then - # save it. - if total_work > max_work: - max_work = total_work - optimal_input_length = input_length - - attack_block = Op.POP( - Op.STATICCALL(Op.GAS, address, 0, optimal_input_length, 0, 0) - ) - - benchmark_test( - code_generator=JumpLoopGenerator( - setup=Op.CODECOPY(0, 0, optimal_input_length), - attack_block=attack_block, - ), - ) - - -def create_modexp_test_cases() -> list[ParameterSet]: - """Create test cases for the MODEXP precompile.""" - test_cases = [ - # (base, exponent, modulus, test_id) - (8 * "ff", 112 * "ff", 7 * "ff" + "00", "mod_even_8b_exp_896"), - (16 * "ff", 40 * "ff", 15 * "ff" + "00", "mod_even_16b_exp_320"), - (24 * "ff", 21 * "ff", 23 * "ff" + "00", "mod_even_24b_exp_168"), - (32 * "ff", 5 * "ff", 31 * "ff" + "00", "mod_even_32b_exp_40"), - (32 * "ff", 12 * "ff", 31 * "ff" + "00", "mod_even_32b_exp_96"), - (32 * "ff", 32 * "ff", 31 * "ff" + "00", "mod_even_32b_exp_256"), - (64 * "ff", 64 * "ff", 63 * "ff" + "00", "mod_even_64b_exp_512"), - (128 * "ff", 128 * "ff", 127 * "ff" + "00", "mod_even_128b_exp_1024"), - (256 * "ff", 128 * "ff", 255 * "ff" + "00", "mod_even_256b_exp_1024"), - (512 * "ff", 128 * "ff", 511 * "ff" + "00", "mod_even_512b_exp_1024"), - ( - 1024 * "ff", - 128 * "ff", - 1023 * "ff" + "00", - "mod_even_1024b_exp_1024", - ), - (32 * "ff", 12 * "ff", 31 * "ff" + "01", "mod_odd_32b_exp_96"), - (32 * "ff", 32 * "ff", 31 * "ff" + "01", "mod_odd_32b_exp_256"), - (64 * "ff", 64 * "ff", 63 * "ff" + "01", "mod_odd_64b_exp_512"), - (128 * "ff", 128 * "ff", 127 * "ff" + "01", "mod_odd_128b_exp_1024"), - (256 * "ff", 128 * "ff", 255 * "ff" + "01", "mod_odd_256b_exp_1024"), - (512 * "ff", 128 * "ff", 511 * "ff" + "01", "mod_odd_512b_exp_1024"), - ( - 1024 * "ff", - 128 * "ff", - 1023 * "ff" + "01", - "mod_odd_1024b_exp_1024", - ), - ( - 32 * "ff", - 8 * "12345670", - 31 * "ff" + "01", - "mod_odd_32b_exp_cover_windows", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L38 - (192 * "FF", "03", 6 * ("00" + 31 * "FF"), "mod_min_gas_base_heavy"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L40 - (8 * "FF", "07" + 75 * "FF", 7 * "FF", "mod_min_gas_exp_heavy"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L42 - (40 * "FF", "01" + 3 * "FF", "00" + 38 * "FF", "mod_min_gas_balanced"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L44 - (32 * "FF", 5 * "FF", ("00" + 31 * "FF"), "mod_exp_208_gas_balanced"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L46 - (8 * "FF", 81 * "FF", 7 * "FF", "mod_exp_215_gas_exp_heavy"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L48 - (8 * "FF", 112 * "FF", 7 * "FF", "mod_exp_298_gas_exp_heavy"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L50 - (16 * "FF", 40 * "FF", 15 * "FF", "mod_pawel_2"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L52 - (24 * "FF", 21 * "FF", 23 * "FF", "mod_pawel_3"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L54 - (32 * "FF", 12 * "FF", "00" + 31 * "FF", "mod_pawel_4"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L56 - ( - 280 * "FF", - "03", - 8 * ("00" + 31 * "FF") + 23 * "FF", - "mod_408_gas_base_heavy", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L58 - (16 * "FF", "15" + 37 * "FF", 15 * "FF", "mod_400_gas_exp_heavy"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L60 - (48 * "FF", "07" + 4 * "FF", "00" + 46 * "FF", "mod_408_gas_balanced"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L62 - ( - 344 * "FF", - "03", - 10 * ("00" + 31 * "FF") + 23 * "FF", - "mod_616_gas_base_heavy", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L64 - (16 * "FF", "07" + 56 * "FF", 15 * "FF", "mod_600_gas_exp_heavy"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L66 - (48 * "FF", "07" + 6 * "FF", "00" + 46 * "FF", "mod_600_gas_balanced"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L68 - ( - 392 * "FF", - "03", - 12 * ("00" + 31 * "FF") + 7 * "FF", - "mod_800_gas_base_heavy", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L70 - (16 * "FF", "01" + 75 * "FF", 15 * "FF", "mod_800_gas_exp_heavy"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L72 - (56 * "FF", 6 * "FF", "00" + 54 * "FF", "mod_767_gas_balanced"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L74 - (16 * "FF", 80 * "FF", 15 * "FF", "mod_852_gas_exp_heavy"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L76 - ( - 408 * "FF", - "03", - 12 * ("00" + 31 * "FF") + 23 * "FF", - "mod_867_gas_base_heavy", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L78 - (56 * "FF", "2b" + 7 * "FF", "00" + 54 * "FF", "mod_996_gas_balanced"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L80 - (448 * "FF", "03", 14 * ("00" + 31 * "FF"), "mod_1045_gas_base_heavy"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L82 - (32 * "FF", 16 * "FF", "00" + 31 * "FF", "mod_677_gas_base_heavy"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L84 - (24 * "FF", 32 * "FF", 23 * "FF", "mod_765_gas_exp_heavy"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/Modexp.cs#L86 - (32 * "FF", 32 * "FF", "00" + 31 * "FF", "mod_1360_gas_balanced"), - (8 * "FF", 81 * "FF", 7 * "FF", "mod_8_exp_648"), - (8 * "FF", "FF" + 111 * "FF", 7 * "FF", "mod_8_exp_896"), - (32 * "FF", 4 * "FF", "00" + 31 * "FF", "mod_32_exp_32"), - (32 * "FF", "0D" + 4 * "FF", "00" + 31 * "FF", "mod_32_exp_36"), - (32 * "FF", 5 * "FF", "00" + 31 * "FF", "mod_32_exp_40"), - (32 * "FF", 8 * "FF", "00" + 31 * "FF", "mod_32_exp_64"), - (32 * "FF", "01" + 8 * "FF", "00" + 31 * "FF", "mod_32_exp_65"), - (32 * "FF", 16 * "FF", "00" + 31 * "FF", "mod_32_exp_128"), - (256 * "FF", "03" + 0 * "FF", 8 * ("00" + 31 * "FF"), "mod_256_exp_2"), - ( - 264 * "FF", - "03" + 0 * "FF", - 8 * ("00" + 31 * "FF") + 7 * "FF", - "mod_264_exp_2", - ), - (1024 * "FF", "03", 32 * ("00" + 31 * "FF"), "mod_1024_exp_2"), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L122 - ( - "03", - "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", - "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", - "mod_vul_example_1", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L124 - ( - "", - "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", - "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", - "mod_vul_example_2", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L126 - ( - "e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb5", - "02", - "fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", - "mod_vul_nagydani_1_square", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L128 - ( - "e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb5", - "03", - "fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", - "mod_vul_nagydani_1_qube", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L130 - ( - "e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb5", - "010001", - "fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", - "mod_vul_nagydani_1_pow_0x10001", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L132 - ( - "cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf51", - "02", - "e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", - "mod_vul_nagydani_2_square", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L134 - ( - "cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf51", - "03", - "e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", - "mod_vul_nagydani_2_qube", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L136 - ( - "cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf51", - "010001", - "e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", - "mod_vul_nagydani_2_pow_0x10001", - ), - ( - "c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb", - "02", - "d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", - "mod_vul_nagydani_3_square", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L140 - ( - "c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb", - "03", - "d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", - "mod_vul_nagydani_3_qube", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L142 - ( - "c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb", - "010001", - "d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", - "mod_vul_nagydani_3_pow_0x10001", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L144 - ( - "db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b81", - "02", - "df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", - "mod_vul_nagydani_4_square", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L146 - ( - "db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b81", - "03", - "df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", - "mod_vul_nagydani_4_qube", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L148 - ( - "db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b81", - "010001", - "df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", - "mod_vul_nagydani_4_pow_0x10001", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L150 - ( - "c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf", - "02", - "e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", - "mod_vul_nagydani_5_square", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L152 - ( - "c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf", - "03", - "e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", - "mod_vul_nagydani_5_qube", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L154 - ( - "c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf", - "010001", - "e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", - "mod_vul_nagydani_5_pow_0x10001", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L156 - ( - "ffffff", - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffe000007d7d7d83828282348286877d7d827d407d797d7d7d7d7d7d7d7d7d7d7d5b00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000021000000000000000000000000000000000000000000000000000000000000000cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4000007d7d", - "7d83828282348286877d7d82", - "mod_vul_marius_1_even", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L158 - ( - "ffffffffffffffff76ffffffffffffff", - "1cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c76ec7c7c7c7ffffffffffffffc7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7ffffffffffffc7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c76ec7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7ffff", - "ffffff3f000000000000000000000000", - "mod_vul_guido_1_even", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L160 - ( - "e0060000a921212121212121ff000021", - "2b212121ffff1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f00feffff212121212121ffffffff1fe1e0e0e01e1f1f169f1f1f1f490afcefffffffffffffffff82828282828282828282828282828282828282828200ffff28ff2b212121ffff1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1fffffffffff0afceffffff7ffffffffff7c8282828282a1828282828282828282828282828200ffff28ff2b212121ffff1f1f1f1f1f1fd11f1f1f1f1f1f1f1f1f1f1fffffffffffffffff21212121212121fb2121212121ffff1f1f1f1f1f1f1f1fffaf", - "82828282828200ffff28ff2b21828200", - "mod_vul_guido_2_even", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L162 - ( - "0193585a48e18aad777e9c1b54221a0f58140392e4f091cd5f42b2e8644a9384fbd58ae1edec2477ebf7edbf7c0a3f8bd21d1890ee87646feab3c47be716f842cc3da9b940af312dc54450a960e3fc0b86e56abddd154068e10571a96fff6259431632bc15695c6c8679057e66c2c25c127e97e64ee5de6ea1fc0a4a0e431343fed1daafa072c238a45841da86a9806680bc9f298411173210790359209cd454b5af7b4d5688b4403924e5f863d97e2c5349e1a04b54fcf385b1e9d7714bab8fbf5835f6ff9ed575e77dff7af5cbb641db5d537933bae1fa6555d6c12d6fb31ca27b57771f4aebfbe0bf95e8990c0108ffe7cbdaf370be52cf3ade594543af75ad9329d2d11a402270b5b9a6bf4b83307506e118fca4862749d04e916fc7a039f0d13f2a02e0eedb800199ec95df15b4ccd8669b52586879624d51219e72102fad810b5909b1e372ddf33888fb9beb09b416e4164966edbabd89e4a286be36277fc576ed519a15643dac602e92b63d0b9121f0491da5b16ef793a967f096d80b6c81ecaaffad7e3f06a4a5ac2796f1ed9f68e6a0fd5cf191f0c5c2eec338952ff8d31abc68bf760febeb57e088995ba1d7726a2fdd6d8ca28a181378b8b4ab699bfd4b696739bbf17a9eb2df6251143046137fdbbfacac312ebf67a67da9741b59600000000000", - "04", - "19a2917c61722b0713d3b00a2f0e1dd5aebbbe09615de424700eea3c3020fe6e9ea5de9fa1ace781df28b21f746d2ab61d0da496e08473c90ff7dfe25b43bcde76f4bafb82e0975bea75f5a0591dba80ba2fff80a07d8853bea5be13ab326ba70c57b153acc646151948d1cf061ca31b02d4719fac710e7c723ca44f5b1737824b7ccc74ba5bff980aabdbf267621cafc3d6dcc29d0ca9c16839a92ed34de136da7900aa3ee43d21aa57498981124357cf0ca9b86f9a8d3f9c604ca00c726e48f7a9945021ea6dfff92d6b2d6514693169ca133e993541bfa4c4c191de806aa80c48109bcfc9901eccfdeb2395ab75fe63c67de900829d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "mod_vul_guido_3_even", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L166 - ( - "ffffffffffffffff", - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "ffffffffffffffff", - "mod_vul_pawel_1_exp_heavy", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L168 - ( - "ffffffffffffffffffffffffffffffff", - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "ffffffffffffffffffffffffffffffff", - "mod_vul_pawel_2_exp_heavy", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L170 - ( - "ffffffffffffffffffffffffffffffffffffffffffffffff", - "ffffffffffffffffffffffffffffffffffffffffff", - "ffffffffffffffffffffffffffffffffffffffffffffffff", - "mod_vul_pawel_3_exp_heavy", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L172 - ( - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "ffffffffffffffffffffffff", - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "mod_vul_pawel_4_exp_heavy", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L174 - ( - "29356abadad68ad986c416de6f620bda0e1818b589e84f853a97391694d35496", - "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254f", - "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", - "mod_vul_common_1360n1", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L176 - ( - "d41afaeaea32f7409827761b68c41b6e535da4ede1f0800bfb4a6aed18394f6b", - "ffffffff00000001000000000000000000000000fffffffffffffffffffffffd", - "ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", - "mod_vul_common_1360n2", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L178 - ( - "1a5be8fae3b3fda9ea329494ae8689c04fae4978ecccfa6a6bfb9f04b25846c0", - "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffffff", - "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001", - "mod_vul_common_1349n1", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L182 - ( - "0000000000000000000000000000000000000000000000000000000000000003", - "0000000001000000000000022000000000000000000000000000000000000000", - "0800000000000011000000000000000000000000000000000000000000000001", - "mod_vul_common_1152n1", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L184 - ( - "1fb473dd1171cf88116aa77ab3612c2c7d2cf466cc2386cc456130e2727c70b4", - "0000000000000000000000000000000000000000000000000000000001000000", - "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001", - "mod_vul_common_200n1", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L186 - ( - "1951441010b2b95a6e47a6075066a50a036f5ba978c050f2821df86636c0facb", - "0000000000000000000000000000000000000000000000000000000000ffffff", - "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001", - "mod_vul_common_200n2", - ), - # Ported from https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCases/ModexpVulnerability.cs#L188 - ( - "288254ba43e713afbe36c9f03b54c00fae4c0a82df1cf165eb46a21c20a48ca2", - "0000000000000000000000000000000000000000000000000000000000ffffff", - "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001", - "mod_vul_common_200n3", - ), - ( - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", - "mod_vul_zkevm_worst_case", - ), - ] - - special_cases = [ - pytest.param( - ModExpInput.from_bytes( - "000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000017bffffffffffffffffffffffffffffffffffffffffffffbffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffe" - ), - id="mod_vul_pawel_3_exp_8", - ), - ] - - regular_cases = [ - pytest.param( - ModExpInput( - base=base, - exponent=exponent, - modulus=modulus, - ), - id=test_id, - ) - for base, exponent, modulus, test_id in test_cases - ] - - return regular_cases + special_cases - - -@pytest.mark.parametrize( - ["mod_exp_input"], - create_modexp_test_cases(), -) -def test_worst_modexp( - benchmark_test: BenchmarkTestFiller, - mod_exp_input: ModExpInput, -) -> None: - """ - Test running a block with as many calls to the MODEXP (5) precompile as - possible. All the calls have the same parametrized input. - """ - attack_block = Op.POP( - Op.STATICCALL( - Op.GAS, 0x5, Op.PUSH0, Op.CALLDATASIZE, Op.PUSH0, Op.PUSH0 - ) - ) - benchmark_test( - code_generator=JumpLoopGenerator( - setup=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE), - attack_block=attack_block, - tx_kwargs={"data": bytes(mod_exp_input).rstrip(b"\x00")}, - ), - ) - - -@pytest.mark.parametrize( - "precompile_address,parameters", - [ - pytest.param( - 0x01, - [ - # The inputs below are a valid signature, thus ECRECOVER call - # won't be short-circuited by validations and do actual work. - "38D18ACB67D25C8BB9942764B62F18E17054F66A817BD4295423ADF9ED98873E", - "000000000000000000000000000000000000000000000000000000000000001B", - "38D18ACB67D25C8BB9942764B62F18E17054F66A817BD4295423ADF9ED98873E", - "789D1DD423D25F0772D2748D60F7E4B81BB14D086EBA8E8E8EFB6DCFF8A4AE02", - ], - id="ecrecover", - ), - pytest.param( - 0x06, - [ - "18B18ACFB4C2C30276DB5411368E7185B311DD124691610C5D3B74034E093DC9", - "063C909C4720840CB5134CB9F59FA749755796819658D32EFC0D288198F37266", - "07C2B7F58A84BD6145F00C9C2BC0BB1A187F20FF2C92963A88019E7C6A014EED", - "06614E20C147E940F2D70DA3F74C9A17DF361706A4485C742BD6788478FA17D7", - ], - id="bn128_add", - ), - # Ported from - # https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCase.cs#L326 - pytest.param( - 0x06, - [ - "0000000000000000000000000000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000000", - ], - id="bn128_add_infinities", - ), - # Ported from - # https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCase.cs#L329 - pytest.param( - 0x06, - [ - "0000000000000000000000000000000000000000000000000000000000000001", - "0000000000000000000000000000000000000000000000000000000000000002", - "0000000000000000000000000000000000000000000000000000000000000001", - "0000000000000000000000000000000000000000000000000000000000000002", - ], - id="bn128_add_1_2", - ), - pytest.param( - 0x07, - [ - "1A87B0584CE92F4593D161480614F2989035225609F08058CCFA3D0F940FEBE3", - "1A2F3C951F6DADCC7EE9007DFF81504B0FCD6D7CF59996EFDC33D92BF7F9F8F6", - "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", - ], - id="bn128_mul", - ), - # Ported from - # https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCase.cs#L335 - pytest.param( - 0x07, - [ - "0000000000000000000000000000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000002", - ], - id="bn128_mul_infinities_2_scalar", - ), - # Ported from - # https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCase.cs#L338 - pytest.param( - 0x07, - [ - "0000000000000000000000000000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000000", - "25f8c89ea3437f44f8fc8b6bfbb6312074dc6f983809a5e809ff4e1d076dd585", - ], - id="bn128_mul_infinities_32_byte_scalar", - ), - # Ported from - # https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCase.cs#L341 - pytest.param( - 0x07, - [ - "0000000000000000000000000000000000000000000000000000000000000001", - "0000000000000000000000000000000000000000000000000000000000000002", - "0000000000000000000000000000000000000000000000000000000000000002", - ], - id="bn128_mul_1_2_2_scalar", - ), - # Ported from - # https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCase.cs#L344 - pytest.param( - 0x07, - [ - "0000000000000000000000000000000000000000000000000000000000000001", - "0000000000000000000000000000000000000000000000000000000000000002", - "25f8c89ea3437f44f8fc8b6bfbb6312074dc6f983809a5e809ff4e1d076dd585", - ], - id="bn128_mul_1_2_32_byte_scalar", - ), - # Ported from - # https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCase.cs#L347 - pytest.param( - 0x07, - [ - "089142debb13c461f61523586a60732d8b69c5b38a3380a74da7b2961d867dbf", - "2d5fc7bbc013c16d7945f190b232eacc25da675c0eb093fe6b9f1b4b4e107b36", - "0000000000000000000000000000000000000000000000000000000000000002", - ], - id="bn128_mul_32_byte_coord_and_2_scalar", - ), - # Ported from - # https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCase.cs#L350 - pytest.param( - 0x07, - [ - "089142debb13c461f61523586a60732d8b69c5b38a3380a74da7b2961d867dbf", - "2d5fc7bbc013c16d7945f190b232eacc25da675c0eb093fe6b9f1b4b4e107b36", - "25f8c89ea3437f44f8fc8b6bfbb6312074dc6f983809a5e809ff4e1d076dd585", - ], - id="bn128_mul_32_byte_coord_and_scalar", - ), - pytest.param( - 0x08, - [ - # First pairing - "1C76476F4DEF4BB94541D57EBBA1193381FFA7AA76ADA664DD31C16024C43F59", - "3034DD2920F673E204FEE2811C678745FC819B55D3E9D294E45C9B03A76AEF41", - "209DD15EBFF5D46C4BD888E51A93CF99A7329636C63514396B4A452003A35BF7", - "04BF11CA01483BFA8B34B43561848D28905960114C8AC04049AF4B6315A41678", - "2BB8324AF6CFC93537A2AD1A445CFD0CA2A71ACD7AC41FADBF933C2A51BE344D", - "120A2A4CF30C1BF9845F20C6FE39E07EA2CCE61F0C9BB048165FE5E4DE877550", - # Second pairing - "111E129F1CF1097710D41C4AC70FCDFA5BA2023C6FF1CBEAC322DE49D1B6DF7C", - "103188585E2364128FE25C70558F1560F4F9350BAF3959E603CC91486E110936", - "198E9393920D483A7260BFB731FB5D25F1AA493335A9E71297E485B7AEF312C2", - "1800DEEF121F1E76426A00665E5C4479674322D4F75EDADD46DEBD5CD992F6ED", - "090689D0585FF075EC9E99AD690C3395BC4B313370B38EF355ACDADCD122975B", - "12C85EA5DB8C6DEB4AAB71808DCB408FE3D1E7690C43D37B4CE6CC0166FA7DAA", - ], - id="bn128_two_pairings", - ), - pytest.param( - 0x08, - [ - # First pairing - "1C76476F4DEF4BB94541D57EBBA1193381FFA7AA76ADA664DD31C16024C43F59", - "3034DD2920F673E204FEE2811C678745FC819B55D3E9D294E45C9B03A76AEF41", - "209DD15EBFF5D46C4BD888E51A93CF99A7329636C63514396B4A452003A35BF7", - "04BF11CA01483BFA8B34B43561848D28905960114C8AC04049AF4B6315A41678", - "2BB8324AF6CFC93537A2AD1A445CFD0CA2A71ACD7AC41FADBF933C2A51BE344D", - "120A2A4CF30C1BF9845F20C6FE39E07EA2CCE61F0C9BB048165FE5E4DE877550", - ], - id="bn128_one_pairing", - ), - # Ported from - # https://github.com/NethermindEth/nethermind/blob/ceb8d57b8530ce8181d7427c115ca593386909d6/tools/EngineRequestsGenerator/TestCase.cs#L353 - pytest.param(0x08, [], id="ec_pairing_zero_input"), - pytest.param( - 0x08, - [ - # First pairing - "2cf44499d5d27bb186308b7af7af02ac5bc9eeb6a3d147c186b21fb1b76e18da", - "2c0f001f52110ccfe69108924926e45f0b0c868df0e7bde1fe16d3242dc715f6", - "1fb19bb476f6b9e44e2a32234da8212f61cd63919354bc06aef31e3cfaff3ebc", - "22606845ff186793914e03e21df544c34ffe2f2f3504de8a79d9159eca2d98d9", - "2bd368e28381e8eccb5fa81fc26cf3f048eea9abfdd85d7ed3ab3698d63e4f90", - "2fe02e47887507adf0ff1743cbac6ba291e66f59be6bd763950bb16041a0a85e", - # Second pairing - "0000000000000000000000000000000000000000000000000000000000000013", - "0644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd451", - "971ff0471b09fa93caaf13cbf443c1aede09cc4328f5a62aad45f40ec133eb40", - "91058a3141822985733cbdddfed0fd8d6c104e9e9eff40bf5abfef9ab163bc72", - "a23af9a5ce2ba2796c1f4e453a370eb0af8c212d9dc9acd8fc02c2e907baea22", - "3a8eb0b0996252cb548a4487da97b02422ebc0e834613f954de6c7e0afdc1fc0", - ], - id="ec_pairing_2_sets", - ), - pytest.param( - 0x08, - [""], - id="ec_pairing_1_pair", - ), - pytest.param( - 0x08, - [ - # First pairing - "2371e7d92e9fc444d0e11526f0752b520318c80be68bf0131704b36b7976572e", - "2dca8f05ed5d58e0f2e13c49ae40480c0f99dfcd9268521eea6c81c6387b66c4", - "051a93d697db02afd3dcf8414ecb906a114a2bfdb6b06c95d41798d1801b3cbd", - "2e275fef7a0bdb0a2aea77d8ec5817e66e199b3d55bc0fa308dcdda74e85060b", - "1c7e33c2a72d6e12a31eababad3dbc388525135628102bb64742d9e325f43410", - "115dc41fa10b2dbf99036f252ad6f00e8876b22f02cb4738dc4413b22ea9b2df", - # Second pairing - "09a760ea8f9bd87dc258a949395a03f7d2500c6e72c61f570986328a096b610a", - "148027063c072345298117eb2cb980ad79601db31cc69bba6bcbe4937ada6720", - "198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2", - "1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed", - "090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b", - "12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", - ], - id="ec_pairing_2_pair", - ), - pytest.param( - 0x08, - [ - # First pairing - "0000000000000000000000000000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000000", - "0ef4aac9b7954d5fc6eafae7f4f4c2a732ab05b45f8d50d102cee4973f36eb2c", - "23db7d30c99e0a2a7f3bb5cd1f04635aaea58732b58887df93d9239c28230d28", - "2bd99d31a5054f2556d226f2e5ef0e075423d8604178b2e2c08006311caee54f", - "0f11afb0c6073d12d21b13f4f78210e8ca9a66729206d3fcc2c1b04824c425f2", - # Second pairing - "0000000000000000000000000000000000000000000000000000000000000000", - "198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2", - "1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed", - "090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b", - "12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", - # Third pairing - "0000000000000000000000000000000000000000000000000000000000000000", - "198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2", - "1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed", - "090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b", - "12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", - ], - id="ec_pairing_3_pair", - ), - pytest.param( - 0x08, - [ - # First pairing - "24ab69f46f3e3333027d67d51af71571141bd5652b9829157a3c5d1268461984", - "0f0e1495665bccf97d627b714e8a49e9c77c21e8d5b383ad7dde7e50040d0f62", - "2cab595b9d579f8b82e433249b83ae1d7b62d7073a4f67cb3aeb9b316988907f", - "1326d1905ffde0c77e8ebd98257aa239b05ae76c8ec7723ec19bbc8282b0debe", - "130502106676b537e01cc356765e91c005d6c4bd1a75f5f6d41d2556c73e56ac", - "2dc4cb08068b4aa5f14b7f1096ab35d5c13d78319ec7e66e9f67a1ff20cbbf03", - # Second pairing - "1459f4140b271cbc8746de9dfcb477d5b72d50ef95bec5fef4a68dd69ddfdb2e", - "2c589584551d16a9723b5d356d1ee2066d10381555cdc739e39efca2612fc544", - "229ab0abdb0a7d1a5f0d93fb36ce41e12a31ba52fd9e3c27bebce524ab6c4e9b", - "00f8756832b244377d06e2d00eeb95ec8096dcfd81f4e4931b50fea23c04a2fe", - "29605352ce973ec48d1ab2c8355643c999b70ff771946078b519c556058c3d56", - "059a65ae6e0189d4e04a966140aa40f781a1345824a90a91bb035e12ad29af1d", - # Third pairing - "1459f4140b271cbc8746de9dfcb477d5b72d50ef95bec5fef4a68dd69ddfdb2e", - "2c589584551d16a9723b5d356d1ee2066d10381555cdc739e39efca2612fc544", - "229ab0abdb0a7d1a5f0d93fb36ce41e12a31ba52fd9e3c27bebce524ab6c4e9b", - "00f8756832b244377d06e2d00eeb95ec8096dcfd81f4e4931b50fea23c04a2fe", - "29605352ce973ec48d1ab2c8355643c999b70ff771946078b519c556058c3d56", - "059a65ae6e0189d4e04a966140aa40f781a1345824a90a91bb035e12ad29af1d", - # Fourth pairing - "24ab69f46f3e3333027d67d51af71571141bd5652b9829157a3c5d1268461984", - "0f0e1495665bccf97d627b714e8a49e9c77c21e8d5b383ad7dde7e50040d0f62", - "2cab595b9d579f8b82e433249b83ae1d7b62d7073a4f67cb3aeb9b316988907f", - "1326d1905ffde0c77e8ebd98257aa239b05ae76c8ec7723ec19bbc8282b0debe", - "130502106676b537e01cc356765e91c005d6c4bd1a75f5f6d41d2556c73e56ac", - "2dc4cb08068b4aa5f14b7f1096ab35d5c13d78319ec7e66e9f67a1ff20cbbf03", - ], - id="ec_pairing_4_pair", - ), - pytest.param( - 0x08, - [ - # First pairing - "1147057b17237df94a3186435acf66924e1d382b8c935fdd493ceb38c38def73", - "03cd046286139915160357ce5b29b9ea28bfb781b71734455d20ef1a64be76ca", - "0daa7cc4983cf74c94607519df747f61e317307c449bafb6923f6d6a65299a7e", - "1d48db8f275830859fd61370addbc5d5ef3f0ce7491d16918e065f7e3727439d", - "1ca8ac2f4a0f540e5505edbe1d15d13899a2a0dfccb012d068134ac66edec625", - "2162c315417d1d12c9d7028c5619015391003a9006d4d8979784c7af2c4537a3", - # Second pairing - "0d221a19ca86dafa8cb804daff78fd3d1bed30aa32e7d4029b1aa69afda2d750", - "018628c766a98de1d0cca887a6d90303e68a7729490f25f937b76b57624ba0be", - "14550ccf7139312da6fa9eb1259c6365b0bd688a27473ccb42bc5cd6f14c8abd", - "165f8721ee9f614382c8c7edb103c941d3a55c1849c9787f34317777d5d9365b", - "0d19da7439edb573a1b3e357faade63d5d68b6031771fd911459b7ab0bda9d3f", - "25a50a44d10c99c5f107e3b3874f717873cb2d4674699a468204df27c0c50a9a", - # Third pairing - "0d7136c59b907615e1b45cf730fbfd6cf38b7e126e85e52be804620a23ace4fb", - "03e80c29d24ed5cc407329ae093bb1be00f9e3c9332f532bc3658937110d7607", - "2129813bd7247065ac58eac42c81e874044e199f48c12aa749a9fe6bb6e4bddc", - "1b72b9ab4579283e62445555d5b2921424213d09a776152361c46988b82be8a7", - "111bc8198f932e379b8f9825f01af0f5e5cacbf8bfe274bf674f6eaa6e338e04", - "259f58d438fd6391e158c991e155966218e6a432703a84068a32543965749857", - # Fourth pairing - "1ba47a91d487cce77aa78390a295df54d9351637d67810c400415fb374278e3f", - "24318bbc05a4e4d779b9498075841c360c6973c1c51dea254281829bbc9aef33", - "198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2", - "1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed", - "090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b", - "12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", - # Fifth pairing - "1e219772c16eee72450bbf43e9cadae7bf6b2e6ae6637cfeb1d1e8965287acfb", - "0347e7bf4245debd3d00b6f51d2d50fd718e6769352f4fe1db0efe492fed2fc3", - "24fdcc7d4ed0953e3dad500c7ef9836fc61ded44ba454ec76f0a6d0687f4c1b4", - "282b18f7e59c1db4852e622919b2ce9aa5980ca883eac312049c19a3deb79f6d", - "0c9d6ce303b7811dd7ea506c8fa124837405bd209b8731bda79a66eb7206277b", - "1ac5dac62d2332faa8069faca3b0d27fcdf95d8c8bafc9074ee72b5c1f33aa70", - ], - id="ec_pairing_5_pair", - ), - pytest.param( - 0x08, - [ - "0000000000000000000000000000000000000000000000000000000000000000", - ], - id="ec_pairing_1_pair_empty", - ), - pytest.param( - Blake2bSpec.BLAKE2_PRECOMPILE_ADDRESS, - [ - Blake2bInput(rounds=0xFFFF, f=True).create_blake2b_tx_data(), - ], - id="blake2f", - ), - pytest.param( - BlobsSpec.POINT_EVALUATION_PRECOMPILE_ADDRESS, - [ - "01E798154708FE7789429634053CBF9F99B619F9F084048927333FCE637F549B", - "564C0A11A0F704F4FC3E8ACFE0F8245F0AD1347B378FBF96E206DA11A5D36306", - "24D25032E67A7E6A4910DF5834B8FE70E6BCFEEAC0352434196BDF4B2485D5A1", - "8F59A8D2A1A625A17F3FEA0FE5EB8C896DB3764F3185481BC22F91B4AAFFCCA25F26936857BC3A7C2539EA8EC3A952B7", - "873033E038326E87ED3E1276FD140253FA08E9FC25FB2D9A98527FC22A2C9612FBEAFDAD446CBC7BCDBDCD780AF2C16A", - ], - id="point_evaluation", - ), - pytest.param( - bls12381_spec.Spec.G1ADD, - [ - bls12381_spec.Spec.G1, - bls12381_spec.Spec.P1, - ], - id="bls12_g1add", - ), - pytest.param( - bls12381_spec.Spec.G1MSM, - [ - ( - bls12381_spec.Spec.P1 - + bls12381_spec.Scalar(bls12381_spec.Spec.Q) - ) - * (len(bls12381_spec.Spec.G1MSM_DISCOUNT_TABLE) - 1), - ], - id="bls12_g1msm", - ), - pytest.param( - bls12381_spec.Spec.G2ADD, - [ - bls12381_spec.Spec.G2, - bls12381_spec.Spec.P2, - ], - id="bls12_g2add", - ), - pytest.param( - bls12381_spec.Spec.G2MSM, - [ - # TODO: the //2 is required due to a limitation of the max - # contract size limit. In a further iteration we can insert the - # inputs as calldata or storage and avoid having to do PUSHes - # which has this limitation. This also applies to G1MSM. - ( - bls12381_spec.Spec.P2 - + bls12381_spec.Scalar(bls12381_spec.Spec.Q) - ) - * (len(bls12381_spec.Spec.G2MSM_DISCOUNT_TABLE) // 2), - ], - id="bls12_g2msm", - ), - pytest.param( - bls12381_spec.Spec.PAIRING, - [ - bls12381_spec.Spec.G1, - bls12381_spec.Spec.G2, - ], - id="bls12_pairing_check", - ), - pytest.param( - bls12381_spec.Spec.MAP_FP_TO_G1, - [ - bls12381_spec.FP(bls12381_spec.Spec.P - 1), - ], - id="bls12_fp_to_g1", - ), - pytest.param( - bls12381_spec.Spec.MAP_FP2_TO_G2, - [ - bls12381_spec.FP2( - (bls12381_spec.Spec.P - 1, bls12381_spec.Spec.P - 1) - ), - ], - id="bls12_fp_to_g2", - ), - pytest.param( - p256verify_spec.Spec.P256VERIFY, - [ - p256verify_spec.Spec.H0, - p256verify_spec.Spec.R0, - p256verify_spec.Spec.S0, - p256verify_spec.Spec.X0, - p256verify_spec.Spec.Y0, - ], - id="p256verify", - marks=[ - pytest.mark.eip_checklist( - "precompile/test/excessive_gas_usage", eip=[7951] - ) - ], - ), - pytest.param( - p256verify_spec.Spec.P256VERIFY, - [ - "235060CAFE19A407880C272BC3E73600E3A12294F56143ED61929C2FF4525ABB", - "182E5CBDF96ACCB859E8EEA1850DE5FF6E430A19D1D9A680ECD5946BBEA8A32B", - "76DDFAE6797FA6777CAAB9FA10E75F52E70A4E6CEB117B3C5B2F445D850BD64C", - "3828736CDFC4C8696008F71999260329AD8B12287846FEDCEDE3BA1205B12729", - "3E5141734E971A8D55015068D9B3666760F4608A49B11F92E500ACEA647978C7", - ], - id="p256verify_wrong_endianness", - ), - pytest.param( - p256verify_spec.Spec.P256VERIFY, - [ - "BB5A52F42F9C9261ED4361F59422A1E30036E7C32B270C8807A419FECA605023", - "000000000000000000000000000000004319055358E8617B0C46353D039CDAAB", - "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63254E", - "0AD99500288D466940031D72A9F5445A4D43784640855BF0A69874D2DE5FE103", - "C5011E6EF2C42DCD50D5D3D29F99AE6EBA2C80C9244F4C5422F0979FF0C3BA5E", - ], - id="p256verify_modular_comp_x_coordinate_exceeds_n", - ), - ], -) -def test_worst_precompile_fixed_cost( - benchmark_test: BenchmarkTestFiller, - fork: Fork, - precompile_address: Address, - parameters: list[str] | list[BytesConcatenation] | list[bytes], -) -> None: - """Test running a block filled with a precompile with fixed cost.""" - if precompile_address not in fork.precompiles(): - pytest.skip("Precompile not enabled") - - concatenated_bytes: bytes - if all(isinstance(p, str) for p in parameters): - parameters_str = cast(list[str], parameters) - concatenated_hex_string = "".join(parameters_str) - concatenated_bytes = bytes.fromhex(concatenated_hex_string) - elif all( - isinstance(p, (bytes, BytesConcatenation, FieldElement)) - for p in parameters - ): - parameters_bytes_list = [ - bytes(p) - for p in cast( - list[BytesConcatenation | bytes | FieldElement], parameters - ) - ] - concatenated_bytes = b"".join(parameters_bytes_list) - else: - raise TypeError( - "parameters must be a list of strings (hex) " - "or a list of byte-like objects (bytes, BytesConcatenation or " - "FieldElement)." - ) - - padding_length = (32 - (len(concatenated_bytes) % 32)) % 32 - input_bytes = concatenated_bytes + b"\x00" * padding_length - - setup = Bytecode() - for i in range(0, len(input_bytes), 32): - chunk = input_bytes[i : i + 32] - value_to_store = int.from_bytes(chunk, "big") - setup += Op.MSTORE(i, value_to_store) - - attack_block = Op.POP( - Op.STATICCALL( - Op.GAS, precompile_address, 0, len(concatenated_bytes), 0, 0 - ) - ) - - benchmark_test( - code_generator=JumpLoopGenerator( - setup=setup, attack_block=attack_block - ), - ) - - -def test_worst_jumps( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, -) -> None: - """Test running a JUMP-intensive contract.""" - tx = Transaction( - to=pre.deploy_contract(code=(Op.JUMPDEST + Op.JUMP(Op.PUSH0))), - sender=pre.fund_eoa(), - ) - - benchmark_test(tx=tx) - - -def test_worst_jumpi_fallthrough( - benchmark_test: BenchmarkTestFiller, -) -> None: - """Test running a JUMPI-intensive contract with fallthrough.""" - benchmark_test( - code_generator=JumpLoopGenerator( - attack_block=Op.JUMPI(Op.PUSH0, Op.PUSH0) - ), - ) - - -def test_worst_jumpis( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, -) -> None: - """Test running a JUMPI-intensive contract.""" - tx = Transaction( - to=pre.deploy_contract( - code=(Op.JUMPDEST + Op.JUMPI(Op.PUSH0, Op.NUMBER)) - ), - sender=pre.fund_eoa(), - ) - - benchmark_test(tx=tx) - - -def test_worst_jumpdests( - benchmark_test: BenchmarkTestFiller, -) -> None: - """Test running a JUMPDEST-intensive contract.""" - benchmark_test(code_generator=JumpLoopGenerator(attack_block=Op.JUMPDEST)) - - -DEFAULT_BINOP_ARGS = ( - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, - 0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001, -) - - -@pytest.mark.parametrize( - "opcode,opcode_args", - [ - ( - Op.ADD, - DEFAULT_BINOP_ARGS, - ), - ( - Op.MUL, - DEFAULT_BINOP_ARGS, - ), - ( - # This has the cycle of 2, after two SUBs values are back to - # initials. - Op.SUB, - DEFAULT_BINOP_ARGS, - ), - ( - # This has the cycle of 2: - # v[0] = a // b - # v[1] = a // v[0] = a // (a // b) = b - # v[2] = a // b - Op.DIV, - ( - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, - # We want the first divisor to be slightly bigger than 2**128: - # this is the worst case for the division algorithm with - # optimized paths for division by 1 and 2 words. - 0x100000000000000000000000000000033, - ), - ), - ( - # This has the cycle of 2, see above. - Op.DIV, - ( - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, - # We want the first divisor to be slightly bigger than 2**64: - # this is the worst case for the division algorithm with an - # optimized path for division by 1 word. - 0x10000000000000033, - ), - ), - ( - # Same as DIV-0, but the numerator made positive, and the divisor - # made negative. - Op.SDIV, - ( - 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCD, - ), - ), - ( - # Same as DIV-1, but the numerator made positive, and the divisor - # made negative. - Op.SDIV, - ( - 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFCD, - ), - ), - ( - # This scenario is not suitable for MOD because the values quickly - # become 0. - Op.MOD, - DEFAULT_BINOP_ARGS, - ), - ( - # This scenario is not suitable for SMOD because the values quickly - # become 0. - Op.SMOD, - DEFAULT_BINOP_ARGS, - ), - ( - # This keeps the values unchanged, pow(2**256-1, 2**256-1, 2**256) - # == 2**256-1. - Op.EXP, - ( - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, - ), - ), - ( - # Not great because we always sign-extend the 4 bytes. - Op.SIGNEXTEND, - ( - 3, - 0xFFDADADA, # Negative to have more work. - ), - ), - ( - Op.LT, # Keeps getting result 1. - (0, 1), - ), - ( - Op.GT, # Keeps getting result 0. - (0, 1), - ), - ( - Op.SLT, # Keeps getting result 1. - ( - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, - 1, - ), - ), - ( - Op.SGT, # Keeps getting result 0. - ( - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, - 1, - ), - ), - ( - # The worst case is if the arguments are equal (no early return), - # so let's keep it comparing ones. - Op.EQ, - (1, 1), - ), - ( - Op.AND, - DEFAULT_BINOP_ARGS, - ), - ( - Op.OR, - DEFAULT_BINOP_ARGS, - ), - ( - Op.XOR, - DEFAULT_BINOP_ARGS, - ), - ( - Op.BYTE, # Keep extracting the last byte: 0x2F. - ( - 31, - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, - ), - ), - ( - Op.SHL, # Shift by 1 until getting 0. - ( - 1, - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, - ), - ), - ( - Op.SHR, # Shift by 1 until getting 0. - ( - 1, - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, - ), - ), - ( - Op.SAR, # Shift by 1 until getting -1. - ( - 1, - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, - ), - ), - ], - ids=lambda param: "" if isinstance(param, tuple) else param, -) -def test_worst_binop_simple( - benchmark_test: BenchmarkTestFiller, - opcode: Op, - opcode_args: tuple[int, int], -) -> None: - """ - Test running a block with as many binary instructions (takes two args, - produces one value) as possible. The execution starts with two initial - values on the stack, and the stack is balanced by the DUP2 instruction. - """ - tx_data = b"".join( - arg.to_bytes(32, byteorder="big") for arg in opcode_args - ) - - setup = Op.CALLDATALOAD(0) + Op.CALLDATALOAD(32) + Op.DUP2 + Op.DUP2 - attack_block = Op.DUP2 + opcode - cleanup = Op.POP + Op.POP + Op.DUP2 + Op.DUP2 - benchmark_test( - code_generator=JumpLoopGenerator( - setup=setup, - attack_block=attack_block, - cleanup=cleanup, - tx_kwargs={"data": tx_data}, - ), - ) - - -@pytest.mark.parametrize("opcode", [Op.ISZERO, Op.NOT]) -def test_worst_unop( - benchmark_test: BenchmarkTestFiller, - opcode: Op, -) -> None: - """ - Test running a block with as many unary instructions (takes one arg, - produces one value) as possible. - """ - benchmark_test( - code_generator=JumpLoopGenerator(setup=Op.PUSH0, attack_block=opcode), - ) - - -# `key_mut` indicates the key isn't fixed. -@pytest.mark.parametrize("key_mut", [True, False]) -# `val_mut` indicates that at the end of each big-loop, the value of the target -# key changes. -@pytest.mark.parametrize("val_mut", [True, False]) -def test_worst_tload( - benchmark_test: BenchmarkTestFiller, - key_mut: bool, - val_mut: bool, -) -> None: - """Test running a block with as many TLOAD calls as possible.""" - start_key = 41 - code_key_mut = Bytecode() - code_val_mut = Bytecode() - setup = Bytecode() - if key_mut and val_mut: - setup = Op.PUSH1(start_key) - attack_block = Op.POP(Op.TLOAD(Op.DUP1)) - code_key_mut = Op.POP + Op.GAS - code_val_mut = Op.TSTORE(Op.DUP2, Op.GAS) - if key_mut and not val_mut: - attack_block = Op.POP(Op.TLOAD(Op.GAS)) - if not key_mut and val_mut: - attack_block = Op.POP(Op.TLOAD(Op.CALLVALUE)) - code_val_mut = Op.TSTORE( - Op.CALLVALUE, Op.GAS - ) # CALLVALUE configured in the tx - if not key_mut and not val_mut: - attack_block = Op.POP(Op.TLOAD(Op.CALLVALUE)) - - cleanup = code_key_mut + code_val_mut - tx_value = start_key if not key_mut and val_mut else 0 - - benchmark_test( - code_generator=JumpLoopGenerator( - setup=setup, - attack_block=attack_block, - cleanup=cleanup, - tx_kwargs={ - "value": tx_value, - }, - ), - ) - - -@pytest.mark.parametrize("key_mut", [True, False]) -@pytest.mark.parametrize("dense_val_mut", [True, False]) -def test_worst_tstore( - benchmark_test: BenchmarkTestFiller, - key_mut: bool, - dense_val_mut: bool, -) -> None: - """Test running a block with as many TSTORE calls as possible.""" - init_key = 42 - setup = Op.PUSH1(init_key) - - # If `dense_val_mut` is set, we use GAS as a cheap way of always - # storing a different value than - # the previous one. - attack_block = Op.TSTORE(Op.DUP2, Op.GAS if dense_val_mut else Op.DUP1) - - # If `key_mut` is True, we mutate the key on every iteration of the - # big loop. - cleanup = Op.POP + Op.GAS if key_mut else Bytecode() - - benchmark_test( - code_generator=JumpLoopGenerator( - setup=setup, attack_block=attack_block, cleanup=cleanup - ), - ) - - -@pytest.mark.parametrize("shift_right", [Op.SHR, Op.SAR]) -def test_worst_shifts( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - shift_right: Op, - gas_benchmark_value: int, -) -> None: - """ - Test running a block with as many shift instructions with non-trivial - arguments. This test generates left-right pairs of shifts to avoid zeroing - the argument. The shift amounts are randomly pre-selected from the constant - pool of 15 values on the stack. - """ - max_code_size = fork.max_code_size() - - def to_signed(x: int) -> int: - return x if x < 2**255 else x - 2**256 - - def to_unsigned(x: int) -> int: - return x if x >= 0 else x + 2**256 - - def shr(x: int, s: int) -> int: - return x >> s - - def shl(x: int, s: int) -> int: - return x << s - - def sar(x: int, s: int) -> int: - return to_unsigned(to_signed(x) >> s) - - match shift_right: - case Op.SHR: - shift_right_fn = shr - case Op.SAR: - shift_right_fn = sar - case _: - raise ValueError(f"Unexpected shift op: {shift_right}") - - rng = random.Random(1) # Use random with a fixed seed. - initial_value = 2**256 - 1 # The initial value to be shifted; should be - # negative for SAR. - - # Create the list of shift amounts with 15 elements (max reachable by DUPs - # instructions). For the worst case keep the values small and omit values - # divisible by 8. - shift_amounts = [x + (x >= 8) + (x >= 15) for x in range(1, 16)] - - code_prefix = ( - sum(Op.PUSH1[sh] for sh in shift_amounts) - + Op.JUMPDEST - + Op.CALLDATALOAD(0) - ) - code_suffix = Op.POP + Op.JUMP(len(shift_amounts) * 2) - code_body_len = max_code_size - len(code_prefix) - len(code_suffix) - - def select_shift_amount(shift_fn: Any, v: Any) -> Any: - """Select a shift amount that will produce a non-zero result.""" - while True: - index = rng.randint(0, len(shift_amounts) - 1) - sh = shift_amounts[index] - new_v = shift_fn(v, sh) % 2**256 - if new_v != 0: - return new_v, index - - code_body = Bytecode() - v = initial_value - while len(code_body) <= code_body_len - 4: - v, i = select_shift_amount(shl, v) - code_body += make_dup(len(shift_amounts) - i) + Op.SHL - v, i = select_shift_amount(shift_right_fn, v) - code_body += make_dup(len(shift_amounts) - i) + shift_right - - code = code_prefix + code_body + code_suffix - assert len(code) == max_code_size - 2 - - tx = Transaction( - to=pre.deploy_contract(code=code), - data=initial_value.to_bytes(32, byteorder="big"), - gas_limit=gas_benchmark_value, - sender=pre.fund_eoa(), - ) - - benchmark_test(tx=tx) - - -@pytest.mark.parametrize( - "blob_index, blobs_present", - [ - pytest.param(0, 0, id="no blobs"), - pytest.param(0, 1, id="one blob and accessed"), - pytest.param(1, 1, id="one blob but access non-existent index"), - pytest.param(5, 6, id="six blobs, access latest"), - ], -) -def test_worst_blobhash( - fork: Fork, - benchmark_test: BenchmarkTestFiller, - blob_index: int, - blobs_present: bool, -) -> None: - """Test running a block with as many BLOBHASH instructions as possible.""" - tx_kwargs: Dict[str, Any] = {} - if blobs_present > 0: - tx_kwargs["ty"] = TransactionType.BLOB_TRANSACTION - tx_kwargs["max_fee_per_blob_gas"] = fork.min_base_fee_per_blob_gas() - tx_kwargs["blob_versioned_hashes"] = add_kzg_version( - [i.to_bytes() * 32 for i in range(blobs_present)], - BlobsSpec.BLOB_COMMITMENT_VERSION_KZG, - ) - - benchmark_test( - code_generator=ExtCallGenerator( - attack_block=Op.BLOBHASH(blob_index), - tx_kwargs=tx_kwargs, - ), - ) - - -@pytest.mark.parametrize("mod_bits", [255, 191, 127, 63]) -@pytest.mark.parametrize("op", [Op.MOD, Op.SMOD]) -def test_worst_mod( - benchmark_test: BenchmarkTestFiller, - mod_bits: int, - op: Op, -) -> None: - """ - Test running a block with as many MOD instructions with arguments of the - parametrized range. - - The test program consists of code segments evaluating the "MOD chain": - mod[0] = calldataload(0) - mod[1] = numerators[indexes[0]] % mod[0] - mod[2] = numerators[indexes[1]] % mod[1] ... - - The "numerators" is a pool of 15 constants pushed to the EVM stack at the - program start. - - The order of accessing the numerators is selected in a way the mod value - remains in the range as long as possible. - """ - # For SMOD we negate both numerator and modulus. The underlying - # computation is the same, - # just the SMOD implementation will have to additionally handle the - # sign bits. - # The result stays negative. - should_negate = op == Op.SMOD - - num_numerators = 15 - numerator_bits = 256 if not should_negate else 255 - numerator_max = 2**numerator_bits - 1 - numerator_min = 2 ** (numerator_bits - 1) - - # Pick the modulus min value so that it is _unlikely_ to drop to the lower - # word count. - assert mod_bits >= 63 - mod_min = 2 ** (mod_bits - 63) - - # Select the random seed giving the longest found MOD chain. You can look - # for a longer one by increasing the numerators_min_len. This will activate - # the while loop below. - match op, mod_bits: - case Op.MOD, 255: - seed = 20393 - numerators_min_len = 750 - case Op.MOD, 191: - seed = 25979 - numerators_min_len = 770 - case Op.MOD, 127: - seed = 17671 - numerators_min_len = 750 - case Op.MOD, 63: - seed = 29181 - numerators_min_len = 730 - case Op.SMOD, 255: - seed = 4015 - numerators_min_len = 750 - case Op.SMOD, 191: - seed = 17355 - numerators_min_len = 750 - case Op.SMOD, 127: - seed = 897 - numerators_min_len = 750 - case Op.SMOD, 63: - seed = 7562 - numerators_min_len = 720 - case _: - raise ValueError(f"{mod_bits}-bit {op} not supported.") - - while True: - rng = random.Random(seed) - - # Create the list of random numerators. - numerators = [ - rng.randint(numerator_min, numerator_max) - for _ in range(num_numerators) - ] - - # Create the random initial modulus. - initial_mod = rng.randint(2 ** (mod_bits - 1), 2**mod_bits - 1) - - # Evaluate the MOD chain and collect the order of accessing numerators. - mod = initial_mod - indexes = [] - while mod >= mod_min: - # Compute results for each numerator. - results = [n % mod for n in numerators] - # And pick the best one. - i = max(range(len(results)), key=results.__getitem__) - mod = results[i] - indexes.append(i) - - # Disable if you want to find longer MOD chains. - assert len(indexes) > numerators_min_len - if len(indexes) > numerators_min_len: - break - seed += 1 - print(f"{seed=}") - - # TODO: Don't use fixed PUSH32. Let Bytecode helpers to select optimal - # push opcode. - setup = sum((Op.PUSH32[n] for n in numerators), Bytecode()) - attack_block = ( - Op.CALLDATALOAD(0) - + sum(make_dup(len(numerators) - i) + op for i in indexes) - + Op.POP - ) - - input_value = initial_mod if not should_negate else neg(initial_mod) - benchmark_test( - code_generator=JumpLoopGenerator( - setup=setup, - attack_block=attack_block, - tx_kwargs={"data": input_value.to_bytes(32, byteorder="big")}, - ), - ) - - -@pytest.mark.parametrize("opcode", [Op.MLOAD, Op.MSTORE, Op.MSTORE8]) -@pytest.mark.parametrize("offset", [0, 1, 31]) -@pytest.mark.parametrize("offset_initialized", [True, False]) -@pytest.mark.parametrize("big_memory_expansion", [True, False]) -def test_worst_memory_access( - benchmark_test: BenchmarkTestFiller, - opcode: Op, - offset: int, - offset_initialized: bool, - big_memory_expansion: bool, -) -> None: - """ - Test running a block with as many memory access instructions as - possible. - """ - mem_exp_code = ( - Op.MSTORE8(10 * 1024, 1) if big_memory_expansion else Bytecode() - ) - offset_set_code = ( - Op.MSTORE(offset, 43) if offset_initialized else Bytecode() - ) - setup = mem_exp_code + offset_set_code + Op.PUSH1(42) + Op.PUSH1(offset) - - attack_block = ( - Op.POP(Op.MLOAD(Op.DUP1)) - if opcode == Op.MLOAD - else opcode(Op.DUP2, Op.DUP2) - ) - - benchmark_test( - code_generator=JumpLoopGenerator( - setup=setup, attack_block=attack_block - ), - ) - - -@pytest.mark.parametrize("mod_bits", [255, 191, 127, 63]) -@pytest.mark.parametrize("op", [Op.ADDMOD, Op.MULMOD]) -def test_worst_modarith( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - mod_bits: int, - op: Op, - gas_benchmark_value: int, -) -> None: - """ - Test running a block with as many "op" instructions with arguments of the - parametrized range. - The test program consists of code segments evaluating the "op chain": - mod[0] = calldataload(0) - mod[1] = (fixed_arg op args[indexes[0]]) % mod[0] - mod[2] = (fixed_arg op args[indexes[1]]) % mod[1] - The "args" is a pool of 15 constants pushed to the EVM stack at the program - start. - The "fixed_arg" is the 0xFF...FF constant added to the EVM stack by PUSH32 - just before executing the "op". - The order of accessing the numerators is selected in a way the mod value - remains in the range as long as possible. - """ - fixed_arg = 2**256 - 1 - num_args = 15 - - max_code_size = fork.max_code_size() - - # Pick the modulus min value so that it is _unlikely_ to drop to the lower - # word count. - assert mod_bits >= 63 - mod_min = 2 ** (mod_bits - 63) - - # Select the random seed giving the longest found op chain. You can look - # for a longer one by increasing the op_chain_len. This will activate the - # while loop below. - op_chain_len = 666 - match op, mod_bits: - case Op.ADDMOD, 255: - seed = 4 - case Op.ADDMOD, 191: - seed = 2 - case Op.ADDMOD, 127: - seed = 2 - case Op.ADDMOD, 63: - seed = 64 - case Op.MULMOD, 255: - seed = 5 - case Op.MULMOD, 191: - seed = 389 - case Op.MULMOD, 127: - seed = 5 - case Op.MULMOD, 63: - # For this setup we were not able to find an op-chain longer than - # 600. - seed = 4193 - op_chain_len = 600 - case _: - raise ValueError(f"{mod_bits}-bit {op} not supported.") - - while True: - rng = random.Random(seed) - args = [rng.randint(2**255, 2**256 - 1) for _ in range(num_args)] - initial_mod = rng.randint(2 ** (mod_bits - 1), 2**mod_bits - 1) - - # Evaluate the op chain and collect the order of accessing numerators. - op_fn = operator.add if op == Op.ADDMOD else operator.mul - mod = initial_mod - indexes: list[int] = [] - while mod >= mod_min and len(indexes) < op_chain_len: - results = [op_fn(a, fixed_arg) % mod for a in args] - # And pick the best one. - i = max(range(len(results)), key=results.__getitem__) - mod = results[i] - indexes.append(i) - - # Disable if you want to find longer op chains. - assert len(indexes) == op_chain_len - if len(indexes) == op_chain_len: - break - seed += 1 - print(f"{seed=}") - - code_constant_pool = sum((Op.PUSH32[n] for n in args), Bytecode()) - code_segment = ( - Op.CALLDATALOAD(0) - + sum( - make_dup(len(args) - i) + Op.PUSH32[fixed_arg] + op - for i in indexes - ) - + Op.POP - ) - # Construct the final code. Because of the usage of PUSH32 the code segment - # is very long, so don't try to include multiple of these. - code = ( - code_constant_pool - + Op.JUMPDEST - + code_segment - + Op.JUMP(len(code_constant_pool)) - ) - assert (max_code_size - len(code_segment)) < len(code) <= max_code_size - - tx = Transaction( - to=pre.deploy_contract(code=code), - data=initial_mod.to_bytes(32, byteorder="big"), - gas_limit=gas_benchmark_value, - sender=pre.fund_eoa(), - ) - - benchmark_test(tx=tx) - - -def test_empty_block( - benchmark_test: BenchmarkTestFiller, -) -> None: - """Test running an empty block as a baseline for fixed proving costs.""" - benchmark_test( - blocks=[Block(txs=[])], - expected_benchmark_gas_used=0, - ) - - -def test_amortized_bn128_pairings( - benchmark_test: BenchmarkTestFiller, - fork: Fork, - gas_benchmark_value: int, -) -> None: - """Test running a block with as many BN128 pairings as possible.""" - base_cost = 45_000 - pairing_cost = 34_000 - size_per_pairing = 192 - - gsc = fork.gas_costs() - intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - mem_exp_gas_calculator = fork.memory_expansion_gas_calculator() - - # 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 = ( - gas_benchmark_value - 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 = ( - gas_benchmark_value - - 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), - ) - - # This is ignoring "glue" opcodes, but helps to have a rough idea of - # the right cutting point. - approx_gas_cost_per_call = ( - gsc.G_WARM_ACCOUNT_ACCESS + 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, 0x08, 0, Op.CALLDATASIZE, 0, 0) - ) - - benchmark_test( - code_generator=JumpLoopGenerator( - setup=setup, - attack_block=attack_block, - tx_kwargs={ - "data": _generate_bn128_pairs( - optimal_per_call_num_pairings, 42 - ) - }, - ), - ) - - -def _generate_bn128_pairs(n: int, seed: int = 0) -> Bytes: - rng = random.Random(seed) - calldata = Bytes() - - for _ in range(n): - priv_key_g1 = rng.randint(1, 2**32 - 1) - priv_key_g2 = rng.randint(1, 2**32 - 1) - - point_x_affine = multiply(G1, priv_key_g1) - point_y_affine = multiply(G2, priv_key_g2) - - assert point_x_affine is not None, ( - "G1 multiplication resulted in point at infinity" - ) - assert point_y_affine is not None, ( - "G2 multiplication resulted in point at infinity" - ) - - g1_x_bytes = point_x_affine[0].n.to_bytes(32, "big") - g1_y_bytes = point_x_affine[1].n.to_bytes(32, "big") - g1_serialized = g1_x_bytes + g1_y_bytes - - g2_x_c1_bytes = point_y_affine[0].coeffs[1].n.to_bytes(32, "big") # type: ignore - g2_x_c0_bytes = point_y_affine[0].coeffs[0].n.to_bytes(32, "big") # type: ignore - g2_y_c1_bytes = point_y_affine[1].coeffs[1].n.to_bytes(32, "big") # type: ignore - g2_y_c0_bytes = point_y_affine[1].coeffs[0].n.to_bytes(32, "big") # type: ignore - g2_serialized = ( - g2_x_c1_bytes + g2_x_c0_bytes + g2_y_c1_bytes + g2_y_c0_bytes - ) - - pair_calldata = g1_serialized + g2_serialized - calldata = Bytes(calldata + pair_calldata) - - return calldata - - -@pytest.mark.parametrize( - "calldata", - [ - pytest.param(b"", id="empty"), - pytest.param(b"\x00", id="zero-loop"), - pytest.param(b"\x00" * 31 + b"\x20", id="one-loop"), - ], -) -def test_worst_calldataload( - benchmark_test: BenchmarkTestFiller, - calldata: bytes, -) -> None: - """Test running a block with as many CALLDATALOAD as possible.""" - benchmark_test( - code_generator=JumpLoopGenerator( - setup=Op.PUSH0, - attack_block=Op.CALLDATALOAD, - tx_kwargs={"data": calldata}, - ), - ) - - -@pytest.mark.parametrize( - "opcode", - [ - Op.SWAP1, - Op.SWAP2, - Op.SWAP3, - Op.SWAP4, - Op.SWAP5, - Op.SWAP6, - Op.SWAP7, - Op.SWAP8, - Op.SWAP9, - Op.SWAP10, - Op.SWAP11, - Op.SWAP12, - Op.SWAP13, - Op.SWAP14, - Op.SWAP15, - Op.SWAP16, - ], -) -def test_worst_swap( - benchmark_test: BenchmarkTestFiller, - opcode: Opcode, -) -> None: - """Test running a block with as many SWAP as possible.""" - benchmark_test( - code_generator=JumpLoopGenerator( - attack_block=opcode, setup=Op.PUSH0 * opcode.min_stack_height - ), - ) - - -@pytest.mark.parametrize( - "opcode", - [ - pytest.param(Op.DUP1), - pytest.param(Op.DUP2), - pytest.param(Op.DUP3), - pytest.param(Op.DUP4), - pytest.param(Op.DUP5), - pytest.param(Op.DUP6), - pytest.param(Op.DUP7), - pytest.param(Op.DUP8), - pytest.param(Op.DUP9), - pytest.param(Op.DUP10), - pytest.param(Op.DUP11), - pytest.param(Op.DUP12), - pytest.param(Op.DUP13), - pytest.param(Op.DUP14), - pytest.param(Op.DUP15), - pytest.param(Op.DUP16), - ], -) -def test_worst_dup( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - opcode: Op, -) -> None: - """Test running a block with as many DUP as possible.""" - max_stack_height = fork.max_stack_height() - - min_stack_height = opcode.min_stack_height - code = Op.PUSH0 * min_stack_height + opcode * ( - max_stack_height - min_stack_height - ) - target_contract_address = pre.deploy_contract(code=code) - - attack_block = Op.POP( - Op.STATICCALL(Op.GAS, target_contract_address, 0, 0, 0, 0) - ) - - benchmark_test( - code_generator=JumpLoopGenerator(attack_block=attack_block), - ) - - -@pytest.mark.parametrize( - "opcode", - [ - pytest.param(Op.PUSH0), - pytest.param(Op.PUSH1), - pytest.param(Op.PUSH2), - pytest.param(Op.PUSH3), - pytest.param(Op.PUSH4), - pytest.param(Op.PUSH5), - pytest.param(Op.PUSH6), - pytest.param(Op.PUSH7), - pytest.param(Op.PUSH8), - pytest.param(Op.PUSH9), - pytest.param(Op.PUSH10), - pytest.param(Op.PUSH11), - pytest.param(Op.PUSH12), - pytest.param(Op.PUSH13), - pytest.param(Op.PUSH14), - pytest.param(Op.PUSH15), - pytest.param(Op.PUSH16), - pytest.param(Op.PUSH17), - pytest.param(Op.PUSH18), - pytest.param(Op.PUSH19), - pytest.param(Op.PUSH20), - pytest.param(Op.PUSH21), - pytest.param(Op.PUSH22), - pytest.param(Op.PUSH23), - pytest.param(Op.PUSH24), - pytest.param(Op.PUSH25), - pytest.param(Op.PUSH26), - pytest.param(Op.PUSH27), - pytest.param(Op.PUSH28), - pytest.param(Op.PUSH29), - pytest.param(Op.PUSH30), - pytest.param(Op.PUSH31), - pytest.param(Op.PUSH32), - ], -) -def test_worst_push( - benchmark_test: BenchmarkTestFiller, - opcode: Op, -) -> None: - """Test running a block with as many PUSH as possible.""" - benchmark_test( - code_generator=ExtCallGenerator( - attack_block=opcode[1] if opcode.has_data_portion() else opcode - ), - ) - - -@pytest.mark.parametrize( - "opcode", - [Op.RETURN, Op.REVERT], -) -@pytest.mark.parametrize( - "return_size, return_non_zero_data", - [ - pytest.param(0, False, id="empty"), - pytest.param(1024, True, id="1KiB of non-zero data"), - pytest.param(1024, False, id="1KiB of zero data"), - pytest.param(1024 * 1024, True, id="1MiB of non-zero data"), - pytest.param(1024 * 1024, False, id="1MiB of zero data"), - ], -) -def test_worst_return_revert( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - opcode: Op, - return_size: int, - return_non_zero_data: bool, -) -> None: - """Test running a block with as many RETURN or REVERT as possible.""" - max_code_size = fork.max_code_size() - - # Create the contract that will be called repeatedly. - # The bytecode of the contract is: - # ``` - # [CODECOPY(returned_size) -- Conditional if return_non_zero_data] - # opcode(returned_size) - # - # ``` - # Filling the contract up to the max size is a cheap way of leveraging - # CODECOPY to return non-zero bytes if requested. Note that since this - # is a pre-deploy this cost isn't - # relevant for the benchmark. - mem_preparation = ( - Op.CODECOPY(size=return_size) if return_non_zero_data else Bytecode() - ) - executable_code = mem_preparation + opcode(size=return_size) - code = executable_code - if return_non_zero_data: - code += Op.INVALID * (max_code_size - len(executable_code)) - target_contract_address = pre.deploy_contract(code=code) - - attack_block = Op.POP(Op.STATICCALL(address=target_contract_address)) - - benchmark_test( - code_generator=JumpLoopGenerator(attack_block=attack_block), - ) - - -@pytest.mark.valid_from("Osaka") -def test_worst_clz_same_input(benchmark_test: BenchmarkTestFiller) -> None: - """Test running a block with as many CLZ with same input as possible.""" - magic_value = 248 # CLZ(248) = 248 - benchmark_test( - code_generator=JumpLoopGenerator( - setup=Op.PUSH1(magic_value), attack_block=Op.CLZ - ), - ) - - -@pytest.mark.valid_from("Osaka") -def test_worst_clz_diff_input( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, -) -> None: - """ - Test running a block with as many CLZ with different input as - possible. - """ - max_code_size = fork.max_code_size() - - code_prefix = Op.JUMPDEST - code_suffix = Op.PUSH0 + Op.JUMP - - available_code_size = max_code_size - len(code_prefix) - len(code_suffix) - - code_seq = Bytecode() - - for i in range(available_code_size): - value = (2**256 - 1) >> (i % 256) - clz_op = Op.CLZ(value) + Op.POP - if len(code_seq) + len(clz_op) > available_code_size: - break - code_seq += clz_op - - attack_code = code_prefix + code_seq + code_suffix - assert len(attack_code) <= max_code_size - - tx = Transaction( - to=pre.deploy_contract(code=attack_code), - sender=pre.fund_eoa(), - ) - - benchmark_test(tx=tx) diff --git a/tests/benchmark/test_worst_stateful_opcodes.py b/tests/benchmark/test_worst_stateful_opcodes.py deleted file mode 100755 index fb9ed17090..0000000000 --- a/tests/benchmark/test_worst_stateful_opcodes.py +++ /dev/null @@ -1,889 +0,0 @@ -""" -Tests that benchmark EVMs for worst-case stateful opcodes. -""" - -import math -from enum import auto - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - BenchmarkTestFiller, - Block, - Bytecode, - Environment, - ExtCallGenerator, - Fork, - Hash, - JumpLoopGenerator, - Op, - StateTestFiller, - TestPhaseManager, - Transaction, - While, - compute_create2_address, - compute_create_address, -) - -REFERENCE_SPEC_GIT_PATH = "TODO" -REFERENCE_SPEC_VERSION = "TODO" - - -@pytest.mark.parametrize( - "opcode", - [ - Op.BALANCE, - ], -) -@pytest.mark.parametrize( - "absent_accounts", - [ - True, - False, - ], -) -def test_worst_address_state_cold( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - opcode: Op, - absent_accounts: bool, - env: Environment, - gas_benchmark_value: int, -) -> None: - """ - Test running a block with as many stateful opcodes accessing cold accounts. - """ - attack_gas_limit = gas_benchmark_value - - gas_costs = fork.gas_costs() - intrinsic_gas_cost_calc = fork.transaction_intrinsic_cost_calculator() - # For calculation robustness, the calculation below ignores "glue" opcodes - # like PUSH and POP. It should be considered a worst-case number of - # accounts, and a few of them might not be targeted before the attacking - # transaction runs out of gas. - num_target_accounts = ( - attack_gas_limit - intrinsic_gas_cost_calc() - ) // gas_costs.G_COLD_ACCOUNT_ACCESS - - blocks = [] - post = {} - - # Setup The target addresses are going to be constructed (in the case of - # absent=False) and called as addr_offset + i, where i is the index of the - # account. This is to avoid collisions with the addresses indirectly - # created by the testing framework. - addr_offset = int.from_bytes(pre.fund_eoa(amount=0)) - - if not absent_accounts: - factory_code = Op.PUSH4(num_target_accounts) + While( - body=Op.POP( - Op.CALL(address=Op.ADD(addr_offset, Op.DUP6), value=10) - ), - condition=Op.PUSH1(1) - + Op.SWAP1 - + Op.SUB - + Op.DUP1 - + Op.ISZERO - + Op.ISZERO, - ) - factory_address = pre.deploy_contract( - code=factory_code, balance=10**18 - ) - - setup_tx = Transaction( - to=factory_address, - gas_limit=env.gas_limit, - sender=pre.fund_eoa(), - ) - blocks.append(Block(txs=[setup_tx])) - - for i in range(num_target_accounts): - addr = Address(i + addr_offset + 1) - post[addr] = Account(balance=10) - - # Execution - op_code = Op.PUSH4(num_target_accounts) + While( - body=Op.POP(opcode(Op.ADD(addr_offset, Op.DUP1))), - condition=Op.PUSH1(1) - + Op.SWAP1 - + Op.SUB - + Op.DUP1 - + Op.ISZERO - + Op.ISZERO, - ) - op_address = pre.deploy_contract(code=op_code) - op_tx = Transaction( - to=op_address, - gas_limit=attack_gas_limit, - sender=pre.fund_eoa(), - ) - blocks.append(Block(txs=[op_tx])) - - benchmark_test( - post=post, - blocks=blocks, - ) - - -@pytest.mark.parametrize( - "opcode", - [ - Op.BALANCE, - Op.EXTCODESIZE, - Op.EXTCODEHASH, - Op.CALL, - Op.CALLCODE, - Op.DELEGATECALL, - Op.STATICCALL, - ], -) -@pytest.mark.parametrize( - "absent_target", - [ - True, - False, - ], -) -def test_worst_address_state_warm( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - opcode: Op, - absent_target: bool, -) -> None: - """ - Test running a block with as many stateful opcodes doing warm access - for an account. - """ - # Setup - target_addr = Address(100_000) - post = {} - if not absent_target: - code = Op.STOP + Op.JUMPDEST * 100 - target_addr = pre.deploy_contract(balance=100, code=code) - post[target_addr] = Account(balance=100, code=code) - - # Execution - setup = Op.MSTORE(0, target_addr) - attack_block = Op.POP(opcode(address=Op.MLOAD(0))) - benchmark_test( - post=post, - code_generator=JumpLoopGenerator( - setup=setup, attack_block=attack_block - ), - ) - - -class StorageAction: - """Enum for storage actions.""" - - READ = auto() - WRITE_SAME_VALUE = auto() - WRITE_NEW_VALUE = auto() - - -class TransactionResult: - """Enum for the possible transaction outcomes.""" - - SUCCESS = auto() - OUT_OF_GAS = auto() - REVERT = auto() - - -@pytest.mark.parametrize( - "storage_action,tx_result", - [ - pytest.param( - StorageAction.READ, - TransactionResult.SUCCESS, - id="SSLOAD", - ), - pytest.param( - StorageAction.WRITE_SAME_VALUE, - TransactionResult.SUCCESS, - id="SSTORE same value", - ), - pytest.param( - StorageAction.WRITE_SAME_VALUE, - TransactionResult.REVERT, - id="SSTORE same value, revert", - ), - pytest.param( - StorageAction.WRITE_SAME_VALUE, - TransactionResult.OUT_OF_GAS, - id="SSTORE same value, out of gas", - ), - pytest.param( - StorageAction.WRITE_NEW_VALUE, - TransactionResult.SUCCESS, - id="SSTORE new value", - ), - pytest.param( - StorageAction.WRITE_NEW_VALUE, - TransactionResult.REVERT, - id="SSTORE new value, revert", - ), - pytest.param( - StorageAction.WRITE_NEW_VALUE, - TransactionResult.OUT_OF_GAS, - id="SSTORE new value, out of gas", - ), - ], -) -@pytest.mark.parametrize( - "absent_slots", - [ - True, - False, - ], -) -def test_worst_storage_access_cold( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - storage_action: StorageAction, - absent_slots: bool, - env: Environment, - gas_benchmark_value: int, - tx_result: TransactionResult, -) -> None: - """ - Test running a block with as many cold storage slot accesses as possible. - """ - gas_costs = fork.gas_costs() - intrinsic_gas_cost_calc = fork.transaction_intrinsic_cost_calculator() - - loop_cost = gas_costs.G_COLD_SLOAD # All accesses are always cold - if storage_action == StorageAction.WRITE_NEW_VALUE: - if not absent_slots: - loop_cost += gas_costs.G_STORAGE_RESET - else: - loop_cost += gas_costs.G_STORAGE_SET - elif storage_action == StorageAction.WRITE_SAME_VALUE: - if absent_slots: - loop_cost += gas_costs.G_STORAGE_SET - else: - loop_cost += gas_costs.G_WARM_SLOAD - elif storage_action == StorageAction.READ: - loop_cost += 0 # Only G_COLD_SLOAD is charged - - # Contract code - execution_code_body = Bytecode() - if storage_action == StorageAction.WRITE_SAME_VALUE: - # All the storage slots in the contract are initialized to their index. - # That is, storage slot `i` is initialized to `i`. - execution_code_body = Op.SSTORE(Op.DUP1, Op.DUP1) - loop_cost += gas_costs.G_VERY_LOW * 2 - elif storage_action == StorageAction.WRITE_NEW_VALUE: - # The new value 2^256-1 is guaranteed to be different from the initial - # value. - execution_code_body = Op.SSTORE(Op.DUP2, Op.NOT(0)) - loop_cost += gas_costs.G_VERY_LOW * 3 - elif storage_action == StorageAction.READ: - execution_code_body = Op.POP(Op.SLOAD(Op.DUP1)) - loop_cost += gas_costs.G_VERY_LOW + gas_costs.G_BASE - - # Add costs jump-logic costs - loop_cost += ( - gas_costs.G_JUMPDEST # Prefix Jumpdest - + gas_costs.G_VERY_LOW * 7 # ISZEROs, PUSHs, SWAPs, SUB, DUP - + gas_costs.G_HIGH # JUMPI - ) - - prefix_cost = ( - gas_costs.G_VERY_LOW # Target slots push - ) - - suffix_cost = 0 - if tx_result == TransactionResult.REVERT: - suffix_cost = ( - gas_costs.G_VERY_LOW * 2 # Revert PUSHs - ) - - num_target_slots = ( - gas_benchmark_value - - intrinsic_gas_cost_calc() - - prefix_cost - - suffix_cost - ) // loop_cost - if tx_result == TransactionResult.OUT_OF_GAS: - # Add an extra slot to make it run out-of-gas - num_target_slots += 1 - - code_prefix = Op.PUSH4(num_target_slots) + Op.JUMPDEST - code_loop = execution_code_body + Op.JUMPI( - len(code_prefix) - 1, - Op.PUSH1(1) + Op.SWAP1 + Op.SUB + Op.DUP1 + Op.ISZERO + Op.ISZERO, - ) - execution_code = code_prefix + code_loop - - if tx_result == TransactionResult.REVERT: - execution_code += Op.REVERT(0, 0) - else: - execution_code += Op.STOP - - execution_code_address = pre.deploy_contract(code=execution_code) - - total_gas_used = ( - num_target_slots * loop_cost - + intrinsic_gas_cost_calc() - + prefix_cost - + suffix_cost - ) - - # Contract creation - slots_init = Bytecode() - if not absent_slots: - slots_init = Op.PUSH4(num_target_slots) + While( - body=Op.SSTORE(Op.DUP1, Op.DUP1), - condition=Op.PUSH1(1) - + Op.SWAP1 - + Op.SUB - + Op.DUP1 - + Op.ISZERO - + Op.ISZERO, - ) - - # To create the contract, we apply the slots_init code to initialize the - # storage slots (int the case of absent_slots=False) and then copy the - # execution code to the contract. - creation_code = ( - slots_init - + Op.EXTCODECOPY( - address=execution_code_address, - dest_offset=0, - offset=0, - size=Op.EXTCODESIZE(execution_code_address), - ) - + Op.RETURN(0, Op.MSIZE) - ) - sender_addr = pre.fund_eoa() - setup_tx = Transaction( - to=None, - gas_limit=env.gas_limit, - data=creation_code, - sender=sender_addr, - ) - - blocks = [Block(txs=[setup_tx])] - - contract_address = compute_create_address(address=sender_addr, nonce=0) - - op_tx = Transaction( - to=contract_address, - gas_limit=gas_benchmark_value, - sender=pre.fund_eoa(), - ) - blocks.append(Block(txs=[op_tx])) - - benchmark_test( - blocks=blocks, - expected_benchmark_gas_used=( - total_gas_used - if tx_result != TransactionResult.OUT_OF_GAS - else gas_benchmark_value - ), - ) - - -@pytest.mark.parametrize( - "storage_action", - [ - pytest.param(StorageAction.READ, id="SLOAD"), - pytest.param(StorageAction.WRITE_SAME_VALUE, id="SSTORE same value"), - pytest.param(StorageAction.WRITE_NEW_VALUE, id="SSTORE new value"), - ], -) -def test_worst_storage_access_warm( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - storage_action: StorageAction, - gas_benchmark_value: int, - env: Environment, -) -> None: - """ - Test running a block with as many warm storage slot accesses as - possible. - """ - blocks = [] - - # The target storage slot for the warm access is storage slot 0. - storage_slot_initial_value = 10 - - # Contract code - execution_code_body = Bytecode() - if storage_action == StorageAction.WRITE_SAME_VALUE: - execution_code_body = Op.SSTORE(0, Op.DUP1) - elif storage_action == StorageAction.WRITE_NEW_VALUE: - execution_code_body = Op.PUSH1(1) + Op.ADD + Op.SSTORE(0, Op.DUP1) - elif storage_action == StorageAction.READ: - execution_code_body = Op.POP(Op.SLOAD(0)) - - execution_code = Op.PUSH1(storage_slot_initial_value) + While( - body=execution_code_body, - ) - execution_code_address = pre.deploy_contract(code=execution_code) - - creation_code = ( - Op.SSTORE(0, storage_slot_initial_value) - + Op.EXTCODECOPY( - address=execution_code_address, - dest_offset=0, - offset=0, - size=Op.EXTCODESIZE(execution_code_address), - ) - + Op.RETURN(0, Op.MSIZE) - ) - - with TestPhaseManager.setup(): - sender_addr = pre.fund_eoa() - setup_tx = Transaction( - to=None, - gas_limit=env.gas_limit, - data=creation_code, - sender=sender_addr, - ) - blocks.append(Block(txs=[setup_tx])) - - contract_address = compute_create_address(address=sender_addr, nonce=0) - - with TestPhaseManager.execution(): - op_tx = Transaction( - to=contract_address, - gas_limit=gas_benchmark_value, - sender=pre.fund_eoa(), - ) - blocks.append(Block(txs=[op_tx])) - - benchmark_test(blocks=blocks) - - -def test_worst_blockhash( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - gas_benchmark_value: int, - tx_gas_limit_cap: int, -) -> None: - """ - Test running a block with as many blockhash accessing oldest allowed block - as possible. - """ - # Create 256 dummy blocks to fill the blockhash window. - blocks = [Block()] * 256 - - benchmark_test( - setup_blocks=blocks, - code_generator=ExtCallGenerator(attack_block=Op.BLOCKHASH(1)), - expected_benchmark_gas_used=gas_benchmark_value, - ) - - -@pytest.mark.parametrize("contract_balance", [0, 1]) -def test_worst_selfbalance( - benchmark_test: BenchmarkTestFiller, - contract_balance: int, -) -> None: - """Test running a block with as many SELFBALANCE opcodes as possible.""" - benchmark_test( - code_generator=ExtCallGenerator( - attack_block=Op.SELFBALANCE, - contract_balance=contract_balance, - ), - ) - - -@pytest.mark.parametrize( - "copied_size", - [ - pytest.param(512, id="512"), - pytest.param(1024, id="1KiB"), - pytest.param(5 * 1024, id="5KiB"), - ], -) -def test_worst_extcodecopy_warm( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - copied_size: int, - gas_benchmark_value: int, -) -> None: - """Test running a block with as many wamr EXTCODECOPY work as possible.""" - copied_contract_address = pre.deploy_contract( - code=Op.JUMPDEST * copied_size, - ) - - execution_code = ( - Op.PUSH10(copied_size) - + Op.PUSH20(copied_contract_address) - + While( - body=Op.EXTCODECOPY(Op.DUP4, 0, 0, Op.DUP2), - ) - ) - execution_code_address = pre.deploy_contract(code=execution_code) - tx = Transaction( - to=execution_code_address, - gas_limit=gas_benchmark_value, - sender=pre.fund_eoa(), - ) - - benchmark_test(tx=tx) - - -@pytest.mark.parametrize("value_bearing", [True, False]) -def test_worst_selfdestruct_existing( - benchmark_test: BenchmarkTestFiller, - fork: Fork, - pre: Alloc, - value_bearing: bool, - env: Environment, - gas_benchmark_value: int, -) -> None: - """ - Test running a block with as many SELFDESTRUCTs as possible for existing - contracts. - """ - attack_gas_limit = gas_benchmark_value - fee_recipient = pre.fund_eoa(amount=1) - - # Template code that will be used to deploy a large number of contracts. - selfdestructable_contract_addr = pre.deploy_contract( - code=Op.SELFDESTRUCT(Op.COINBASE) - ) - initcode = Op.EXTCODECOPY( - address=selfdestructable_contract_addr, - dest_offset=0, - offset=0, - size=Op.EXTCODESIZE(selfdestructable_contract_addr), - ) + Op.RETURN(0, Op.EXTCODESIZE(selfdestructable_contract_addr)) - initcode_address = pre.deploy_contract(code=initcode) - - # Calculate the number of contracts that can be deployed with the available - # gas. - gas_costs = fork.gas_costs() - intrinsic_gas_cost_calc = fork.transaction_intrinsic_cost_calculator() - loop_cost = ( - gas_costs.G_KECCAK_256 # KECCAK static cost - + math.ceil(85 / 32) * gas_costs.G_KECCAK_256_WORD # KECCAK dynamic - # cost for CREATE2 - + gas_costs.G_VERY_LOW * 3 # ~MSTOREs+ADDs - + gas_costs.G_COLD_ACCOUNT_ACCESS # CALL to self-destructing contract - + gas_costs.G_SELF_DESTRUCT - + 63 # ~Gluing opcodes - ) - final_storage_gas = ( - gas_costs.G_STORAGE_RESET - + gas_costs.G_COLD_SLOAD - + (gas_costs.G_VERY_LOW * 2) - ) - memory_expansion_cost = fork().memory_expansion_gas_calculator()( - new_bytes=96 - ) - base_costs = ( - intrinsic_gas_cost_calc() - + (gas_costs.G_VERY_LOW * 12) # 8 PUSHs + 4 MSTOREs - + final_storage_gas - + memory_expansion_cost - ) - num_contracts = (attack_gas_limit - base_costs) // loop_cost - expected_benchmark_gas_used = num_contracts * loop_cost + base_costs - - # Create a factory that deployes a new SELFDESTRUCT contract instance pre- - # funded depending on the value_bearing parameter. We use CREATE2 so the - # caller contract can easily reproduce the addresses in a loop for CALLs. - factory_code = ( - Op.EXTCODECOPY( - address=initcode_address, - dest_offset=0, - offset=0, - size=Op.EXTCODESIZE(initcode_address), - ) - + Op.MSTORE( - 0, - Op.CREATE2( - value=1 if value_bearing else 0, - offset=0, - size=Op.EXTCODESIZE(initcode_address), - salt=Op.SLOAD(0), - ), - ) - + Op.SSTORE(0, Op.ADD(Op.SLOAD(0), 1)) - + Op.RETURN(0, 32) - ) - - required_balance = num_contracts if value_bearing else 0 # 1 wei per - # contract - factory_address = pre.deploy_contract( - code=factory_code, balance=required_balance - ) - - factory_caller_code = Op.CALLDATALOAD(0) + While( - body=Op.POP(Op.CALL(address=factory_address)), - condition=Op.PUSH1(1) - + Op.SWAP1 - + Op.SUB - + Op.DUP1 - + Op.ISZERO - + Op.ISZERO, - ) - factory_caller_address = pre.deploy_contract(code=factory_caller_code) - - contracts_deployment_tx = Transaction( - to=factory_caller_address, - gas_limit=env.gas_limit, - data=Hash(num_contracts), - sender=pre.fund_eoa(), - ) - - code = ( - # Setup memory for later CREATE2 address generation loop. - # 0xFF+[Address(20bytes)]+[seed(32bytes)]+[initcode keccak(32bytes)] - Op.MSTORE(0, factory_address) - + Op.MSTORE8(32 - 20 - 1, 0xFF) - + Op.MSTORE(32, 0) - + Op.MSTORE(64, initcode.keccak256()) - # Main loop - + While( - body=Op.POP(Op.CALL(address=Op.SHA3(32 - 20 - 1, 85))) - + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), - # Only loop if we have enough gas to cover another iteration plus - # the final storage gas. - condition=Op.GT(Op.GAS, final_storage_gas + loop_cost), - ) - + Op.SSTORE(0, 42) # Done for successful tx execution assertion below. - ) - assert len(code) <= fork.max_code_size() - - # The 0 storage slot is initialize to avoid creation costs in SSTORE above. - code_addr = pre.deploy_contract(code=code, storage={0: 1}) - opcode_tx = Transaction( - to=code_addr, - gas_limit=attack_gas_limit, - sender=pre.fund_eoa(), - ) - - post = { - factory_address: Account(storage={0: num_contracts}), - code_addr: Account(storage={0: 42}), # Check for successful execution. - } - deployed_contract_addresses = [] - for i in range(num_contracts): - deployed_contract_address = compute_create2_address( - address=factory_address, - salt=i, - initcode=initcode, - ) - post[deployed_contract_address] = Account(nonce=1) - deployed_contract_addresses.append(deployed_contract_address) - - benchmark_test( - post=post, - blocks=[ - Block(txs=[contracts_deployment_tx]), - Block(txs=[opcode_tx], fee_recipient=fee_recipient), - ], - expected_benchmark_gas_used=expected_benchmark_gas_used, - ) - - -@pytest.mark.parametrize("value_bearing", [True, False]) -def test_worst_selfdestruct_created( - state_test: StateTestFiller, - pre: Alloc, - value_bearing: bool, - fork: Fork, - env: Environment, - gas_benchmark_value: int, -) -> None: - """ - Test running a block with as many SELFDESTRUCTs as possible for deployed - contracts in the same transaction. - """ - fee_recipient = pre.fund_eoa(amount=1) - env.fee_recipient = fee_recipient - - # SELFDESTRUCT(COINBASE) contract deployment - initcode = ( - Op.MSTORE8(0, Op.COINBASE.int()) - + Op.MSTORE8(1, Op.SELFDESTRUCT.int()) - + Op.RETURN(0, 2) - ) - gas_costs = fork.gas_costs() - memory_expansion_calc = fork().memory_expansion_gas_calculator() - intrinsic_gas_cost_calc = fork.transaction_intrinsic_cost_calculator() - - initcode_costs = ( - gas_costs.G_VERY_LOW * 8 # MSTOREs, PUSHs - + memory_expansion_calc(new_bytes=2) # return into memory - ) - create_costs = ( - initcode_costs - + gas_costs.G_CREATE - + gas_costs.G_VERY_LOW * 3 # Create Parameter PUSHs - + gas_costs.G_CODE_DEPOSIT_BYTE * 2 - + gas_costs.G_INITCODE_WORD - ) - call_costs = ( - gas_costs.G_WARM_ACCOUNT_ACCESS - + gas_costs.G_BASE # COINBASE - + gas_costs.G_SELF_DESTRUCT - + gas_costs.G_VERY_LOW * 5 # CALL Parameter PUSHs - + gas_costs.G_BASE # Parameter GAS - ) - extra_costs = ( - gas_costs.G_BASE # POP - + gas_costs.G_VERY_LOW * 6 # PUSHs, ADD, DUP, GT - + gas_costs.G_HIGH # JUMPI - + gas_costs.G_JUMPDEST - ) - loop_cost = create_costs + call_costs + extra_costs - - prefix_cost = ( - gas_costs.G_VERY_LOW * 3 - + gas_costs.G_BASE - + memory_expansion_calc(new_bytes=32) - ) - suffix_cost = ( - gas_costs.G_COLD_SLOAD - + gas_costs.G_STORAGE_RESET - + (gas_costs.G_VERY_LOW * 2) - ) - - base_costs = prefix_cost + suffix_cost + intrinsic_gas_cost_calc() - - iterations = (gas_benchmark_value - base_costs) // loop_cost - - code_prefix = Op.MSTORE(0, initcode.hex()) + Op.PUSH0 + Op.JUMPDEST - code_suffix = ( - Op.SSTORE(0, 42) # Done for successful tx execution assertion below. - + Op.STOP - ) - loop_body = ( - Op.POP( - Op.CALL( - address=Op.CREATE( - value=1 if value_bearing else 0, - offset=32 - len(initcode), - size=len(initcode), - ) - ) - ) - + Op.PUSH1[1] - + Op.ADD - + Op.JUMPI(len(code_prefix) - 1, Op.GT(iterations, Op.DUP1)) - ) - code = code_prefix + loop_body + code_suffix - # The 0 storage slot is initialize to avoid creation costs in SSTORE above. - code_addr = pre.deploy_contract( - code=code, - balance=iterations if value_bearing else 0, - storage={0: 1}, - ) - code_tx = Transaction( - to=code_addr, - gas_limit=gas_benchmark_value, - sender=pre.fund_eoa(), - ) - - post = {code_addr: Account(storage={0: 42})} # Check for successful - # execution. - state_test( - pre=pre, - post=post, - tx=code_tx, - expected_benchmark_gas_used=iterations * loop_cost + base_costs, - ) - - -@pytest.mark.parametrize("value_bearing", [True, False]) -def test_worst_selfdestruct_initcode( - state_test: StateTestFiller, - pre: Alloc, - value_bearing: bool, - fork: Fork, - env: Environment, - gas_benchmark_value: int, -) -> None: - """ - Test running a block with as many SELFDESTRUCTs as possible executed in - initcode. - """ - fee_recipient = pre.fund_eoa(amount=1) - env.fee_recipient = fee_recipient - - gas_costs = fork.gas_costs() - memory_expansion_calc = fork().memory_expansion_gas_calculator() - intrinsic_gas_cost_calc = fork.transaction_intrinsic_cost_calculator() - - initcode_costs = ( - gas_costs.G_BASE # COINBASE - + gas_costs.G_SELF_DESTRUCT - ) - create_costs = ( - initcode_costs - + gas_costs.G_CREATE - + gas_costs.G_VERY_LOW * 3 # Create Parameter PUSHs - + gas_costs.G_INITCODE_WORD - ) - extra_costs = ( - gas_costs.G_BASE # POP - + gas_costs.G_VERY_LOW * 6 # PUSHs, ADD, DUP, GT - + gas_costs.G_HIGH # JUMPI - + gas_costs.G_JUMPDEST - ) - loop_cost = create_costs + extra_costs - - prefix_cost = ( - gas_costs.G_VERY_LOW * 3 - + gas_costs.G_BASE - + memory_expansion_calc(new_bytes=32) - ) - suffix_cost = ( - gas_costs.G_COLD_SLOAD - + gas_costs.G_STORAGE_RESET - + (gas_costs.G_VERY_LOW * 2) - ) - - base_costs = prefix_cost + suffix_cost + intrinsic_gas_cost_calc() - - iterations = (gas_benchmark_value - base_costs) // loop_cost - - initcode = Op.SELFDESTRUCT(Op.COINBASE) - code_prefix = Op.MSTORE(0, initcode.hex()) + Op.PUSH0 + Op.JUMPDEST - code_suffix = ( - Op.SSTORE(0, 42) # Done for successful tx execution assertion below. - + Op.STOP - ) - - loop_body = ( - Op.POP( - Op.CREATE( - value=1 if value_bearing else 0, - offset=32 - len(initcode), - size=len(initcode), - ) - ) - + Op.PUSH1[1] - + Op.ADD - + Op.JUMPI(len(code_prefix) - 1, Op.GT(iterations, Op.DUP1)) - ) - code = code_prefix + loop_body + code_suffix - - # The 0 storage slot is initialize to avoid creation costs in SSTORE above. - code_addr = pre.deploy_contract(code=code, balance=100_000, storage={0: 1}) - code_tx = Transaction( - to=code_addr, - gas_limit=gas_benchmark_value, - gas_price=10, - sender=pre.fund_eoa(), - ) - - post = {code_addr: Account(storage={0: 42})} # Check for successful - # execution. - state_test( - pre=pre, - post=post, - tx=code_tx, - expected_benchmark_gas_used=iterations * loop_cost + base_costs, - )