From f6291c98e0dc1c4a908b2983a77b484ae28a8e28 Mon Sep 17 00:00:00 2001 From: LouisTsai Date: Mon, 27 Oct 2025 13:04:32 +0800 Subject: [PATCH 01/11] refactor: compute instruction folder --- .../compute/instruction/arithmetic.py | 373 ++++++++ .../benchmark/compute/instruction/bitwise.py | 228 +++++ tests/benchmark/compute/instruction/block.py | 46 + .../compute/instruction/comparison.py | 86 ++ .../compute/instruction/control_flow.py | 68 ++ .../compute/instruction/environment.py | 794 +++++++++++++++++ tests/benchmark/compute/instruction/keccak.py | 66 ++ tests/benchmark/compute/instruction/log.py | 76 ++ tests/benchmark/compute/instruction/memory.py | 104 +++ tests/benchmark/compute/instruction/stack.py | 142 +++ .../benchmark/compute/instruction/storage.py | 362 ++++++++ tests/benchmark/compute/instruction/system.py | 829 ++++++++++++++++++ 12 files changed, 3174 insertions(+) create mode 100644 tests/benchmark/compute/instruction/arithmetic.py create mode 100644 tests/benchmark/compute/instruction/bitwise.py create mode 100644 tests/benchmark/compute/instruction/block.py create mode 100644 tests/benchmark/compute/instruction/comparison.py create mode 100644 tests/benchmark/compute/instruction/control_flow.py create mode 100644 tests/benchmark/compute/instruction/environment.py create mode 100644 tests/benchmark/compute/instruction/keccak.py create mode 100644 tests/benchmark/compute/instruction/log.py create mode 100644 tests/benchmark/compute/instruction/memory.py create mode 100644 tests/benchmark/compute/instruction/stack.py create mode 100644 tests/benchmark/compute/instruction/storage.py create mode 100644 tests/benchmark/compute/instruction/system.py diff --git a/tests/benchmark/compute/instruction/arithmetic.py b/tests/benchmark/compute/instruction/arithmetic.py new file mode 100644 index 00000000000..168bd474994 --- /dev/null +++ b/tests/benchmark/compute/instruction/arithmetic.py @@ -0,0 +1,373 @@ +"""Benchmark arithmetic instructions.""" + +import operator +import random + +import pytest +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + Alloc, + BenchmarkTestFiller, + Bytecode, + JumpLoopGenerator, + Transaction, +) +from ethereum_test_vm import Opcodes as Op + +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/bitwise.py b/tests/benchmark/compute/instruction/bitwise.py new file mode 100644 index 00000000000..bcc00fdfd15 --- /dev/null +++ b/tests/benchmark/compute/instruction/bitwise.py @@ -0,0 +1,228 @@ +"""Benchmark bitwise instructions.""" + +import random +from typing import Callable + +import pytest +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + Alloc, + BenchmarkTestFiller, + Bytecode, + JumpLoopGenerator, + Transaction, +) +from ethereum_test_vm import Opcodes as Op + +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/block.py b/tests/benchmark/compute/instruction/block.py new file mode 100644 index 00000000000..baf4d1f85e9 --- /dev/null +++ b/tests/benchmark/compute/instruction/block.py @@ -0,0 +1,46 @@ +"""Benchmark block instructions.""" + +import pytest +from ethereum_test_tools import ( + BenchmarkTestFiller, + Block, + ExtCallGenerator, +) +from ethereum_test_vm import Opcodes as Op + +# Block instructions: +# BLOCKHASH, COINBASE, TIMESTAMP, NUMBER, PREVRANDAO, GASLIMIT, CHAINID + + +@pytest.mark.parametrize( + "opcode", + [ + Op.COINBASE, + Op.TIMESTAMP, + Op.NUMBER, + Op.PREVRANDAO, + Op.GASLIMIT, + Op.CHAINID, + ], +) +def test_block_ops( + benchmark_test: BenchmarkTestFiller, + opcode: Op, +) -> None: + """Benchmark block zero-parameter 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/compute/instruction/comparison.py b/tests/benchmark/compute/instruction/comparison.py new file mode 100644 index 00000000000..a0e5d014f9a --- /dev/null +++ b/tests/benchmark/compute/instruction/comparison.py @@ -0,0 +1,86 @@ +"""Benchmark comparison instructions.""" + +import pytest +from ethereum_test_tools import ( + BenchmarkTestFiller, + JumpLoopGenerator, +) +from ethereum_test_vm import Opcodes as 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/control_flow.py b/tests/benchmark/compute/instruction/control_flow.py new file mode 100644 index 00000000000..8c808f26533 --- /dev/null +++ b/tests/benchmark/compute/instruction/control_flow.py @@ -0,0 +1,68 @@ +"""Benchmark control flow instructions.""" + +from ethereum_test_tools import ( + Alloc, + BenchmarkTestFiller, + ExtCallGenerator, + JumpLoopGenerator, + Transaction, +) +from ethereum_test_vm import Opcodes as Op + +# 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/environment.py b/tests/benchmark/compute/instruction/environment.py new file mode 100644 index 00000000000..37a480b500c --- /dev/null +++ b/tests/benchmark/compute/instruction/environment.py @@ -0,0 +1,794 @@ +"""Benchmark environmental instructions.""" + +import math +from typing import Any, Dict + +import pytest +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + Account, + Address, + Alloc, + Block, + BlockchainTestFiller, + Bytecode, + Bytes, + Environment, + Hash, + Transaction, + While, + compute_create2_address, +) +from ethereum_test_types import TransactionType, add_kzg_version +from ethereum_test_vm import Opcodes as Op + +from tests.benchmark.compute.helpers import ( + XOR_TABLE, + CallDataOrigin, + ReturnDataStyle, +) +from tests.benchmark.test_worst_compute import ( + BenchmarkTestFiller, + ExtCallGenerator, + JumpLoopGenerator, +) +from tests.cancun.eip4844_blobs.spec import Spec as BlobsSpec + +# Environmental instructions: +# ADDRESS, BALANCE, ORIGIN, CALLER, CALLVALUE +# CALLDATALOAD, CALLDATASIZE, CALLDATACOPY, CODESIZE, CODECOPY, GASPRICE, +# EXTCODESIZE, EXTCODECOPY, RETURNDATASIZE, RETURNDATACOPY, EXTCODEHASH, +# SELFBALANCE, BASEFEE, BLOBHASH, BLOBBASEFEE + + +@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( + "opcode", + [ + Op.ADDRESS, + Op.ORIGIN, + Op.CALLER, + Op.CODESIZE, + Op.GASPRICE, + Op.BASEFEE, + Op.BLOBBASEFEE, + # Note that other 0-param opcodes are covered in separate tests. + ], +) +def test_environment_ops( + benchmark_test: BenchmarkTestFiller, + opcode: Op, +) -> None: + """Benchmark environmental zero-parameter instructions.""" + benchmark_test( + code_generator=ExtCallGenerator(attack_block=opcode), + ) + + +@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( + "return_data_style", + [ + ReturnDataStyle.RETURN, + ReturnDataStyle.REVERT, + ReturnDataStyle.IDENTITY, + ], +) +@pytest.mark.parametrize("returned_size", [1, 0]) +def test_returndatasize_nonzero( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + returned_size: int, + return_data_style: ReturnDataStyle, +) -> None: + """ + 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, + ) + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=setup, attack_block=Op.POP(Op.RETURNDATASIZE) + ), + ) + + +def test_returndatasize_zero( + benchmark_test: BenchmarkTestFiller, +) -> None: + """Benchmark RETURNDATASIZE instruction with zero buffer.""" + benchmark_test( + code_generator=ExtCallGenerator(attack_block=Op.RETURNDATASIZE), + ) + + +@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, + ), + ) + + +@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( + "origin", + [ + pytest.param(CallDataOrigin.TRANSACTION, id="transaction"), + pytest.param(CallDataOrigin.CALL, id="call"), + ], +) +@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, + ], +) +@pytest.mark.parametrize( + "non_zero_data", + [ + True, + False, + ], +) +def test_calldatacopy( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + origin: CallDataOrigin, + size: int, + fixed_src_dst: bool, + non_zero_data: bool, + gas_benchmark_value: int, +) -> None: + """Benchmark CALLDATACOPY instruction.""" + if size == 0 and non_zero_data: + pytest.skip("Non-zero data with size 0 is not applicable.") + + # If `non_zero_data` is True, we fill the calldata with deterministic + # random data. Note that if `size == 0` and `non_zero_data` is a skipped + # case. + data = Bytes([i % 256 for i in range(size)]) if non_zero_data else Bytes() + + intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() + min_gas = intrinsic_gas_calculator(calldata=data) + if min_gas > gas_benchmark_value: + pytest.skip( + "Minimum gas required for calldata ({min_gas}) is greater " + "than the gas limit" + ) + + # We create the contract that will be doing the CALLDATACOPY multiple + # times. + # + # If `non_zero_data` is True, we leverage CALLDATASIZE for the copy + # length. Otherwise, since we + # don't send zero data explicitly via calldata, PUSH the target size and + # use DUP1 to copy it. + setup = Bytecode() if non_zero_data or size == 0 else Op.PUSH3(size) + src_dst = 0 if fixed_src_dst else Op.MOD(Op.GAS, 7) + attack_block = Op.CALLDATACOPY( + src_dst, + src_dst, + Op.CALLDATASIZE if non_zero_data or size == 0 else Op.DUP1, + ) + + code_address = JumpLoopGenerator( + setup=setup, attack_block=attack_block + ).deploy_contracts(pre=pre, fork=fork) + + tx_target = code_address + + # If the origin is CALL, we need to create a contract that will call the + # target contract with the calldata. + if origin == CallDataOrigin.CALL: + # If `non_zero_data` is False we leverage just using zeroed memory. + # Otherwise, we copy the calldata received from the transaction. + setup = ( + Op.CALLDATACOPY(Op.PUSH0, Op.PUSH0, Op.CALLDATASIZE) + if non_zero_data + else Bytecode() + ) + Op.JUMPDEST + arg_size = Op.CALLDATASIZE if non_zero_data else size + attack_block = Op.STATICCALL( + address=code_address, args_offset=Op.PUSH0, args_size=arg_size + ) + + tx_target = JumpLoopGenerator( + setup=setup, attack_block=attack_block + ).deploy_contracts(pre=pre, fork=fork) + + tx = Transaction( + to=tx_target, + gas_limit=gas_benchmark_value, + data=data, + sender=pre.fund_eoa(), + ) + + benchmark_test(tx=tx) + + +@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( + "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_dst", + [ + True, + False, + ], +) +def test_returndatacopy( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + size: int, + fixed_dst: bool, +) -> None: + """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 + # making the content + # predictable. If `size` is 0, this helper contract won't be used. + code = ( + Op.MSTORE8(0, Op.GAS) + + Op.MSTORE8(size // 2, Op.GAS) + + Op.MSTORE8(size - 1, Op.GAS) + + Op.RETURN(0, size) + ) + helper_contract = pre.deploy_contract(code=code) + + returndata_gen = ( + Op.STATICCALL(address=helper_contract) if size > 0 else Bytecode() + ) + dst = 0 if fixed_dst else Op.MOD(Op.GAS, 7) + + # We create the contract that will be doing the RETURNDATACOPY multiple + # times. + returndata_gen = ( + Op.STATICCALL(address=helper_contract) if size > 0 else Bytecode() + ) + attack_block = Op.RETURNDATACOPY(dst, Op.PUSH0, Op.RETURNDATASIZE) + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=returndata_gen, + attack_block=attack_block, + cleanup=returndata_gen, + ), + ) + + +@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: + """ + 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, + ) + + +@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, + ), + ) + + +@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) diff --git a/tests/benchmark/compute/instruction/keccak.py b/tests/benchmark/compute/instruction/keccak.py new file mode 100644 index 00000000000..c73f28eaedc --- /dev/null +++ b/tests/benchmark/compute/instruction/keccak.py @@ -0,0 +1,66 @@ +"""Benchmark keccak instructions.""" + +import math + +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + BenchmarkTestFiller, + JumpLoopGenerator, +) +from ethereum_test_vm import Opcodes as 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/compute/instruction/log.py b/tests/benchmark/compute/instruction/log.py new file mode 100644 index 00000000000..177e5c297d2 --- /dev/null +++ b/tests/benchmark/compute/instruction/log.py @@ -0,0 +1,76 @@ +"""Benchmark log instructions.""" + +import pytest +from ethereum_test_tools import ( + BenchmarkTestFiller, + Bytecode, + JumpLoopGenerator, +) +from ethereum_test_vm import Opcode +from ethereum_test_vm import Opcodes as Op + +# Log instructions: +# LOG0, LOG1, LOG2, LOG3, LOG4 + + +@pytest.mark.parametrize( + "opcode", + [ + pytest.param(Op.LOG0, id="log0"), + pytest.param(Op.LOG1, id="log1"), + pytest.param(Op.LOG2, id="log2"), + pytest.param(Op.LOG3, id="log3"), + pytest.param(Op.LOG4, id="log4"), + ], +) +@pytest.mark.parametrize( + "size,non_zero_data", + [ + pytest.param(0, False, id="0_bytes_data"), + pytest.param(1024 * 1024, False, id="1_MiB_zeros_data"), # 1 MiB + pytest.param(1024 * 1024, True, id="1_MiB_non_zero_data"), # 1 MiB + ], +) +@pytest.mark.parametrize( + "zeros_topic", + [ + pytest.param(True, id="zeros_topic"), + pytest.param(False, id="non_zero_topic"), + ], +) +@pytest.mark.parametrize("fixed_offset", [True, False]) +def test_log( + benchmark_test: BenchmarkTestFiller, + opcode: Opcode, + zeros_topic: bool, + size: int, + fixed_offset: bool, + non_zero_data: bool, +) -> None: + """Benchmark LOG instructions.""" + setup = Bytecode() + + # For non-zero data, load into memory. + if non_zero_data: + setup += Op.CODECOPY(dest_offset=0, offset=0, size=Op.CODESIZE) + + # Push the size value onto the stack and access it using the DUP opcode. + setup += Op.PUSH3(size) + + # For non-zeros topic, push a non-zero value for topic. + setup += Op.PUSH0 if zeros_topic else Op.PUSH32(2**256 - 1) + + topic_count = len(opcode.kwargs or []) - 2 + offset = Op.PUSH0 if fixed_offset else Op.MOD(Op.GAS, 7) + + # Calculate the appropriate DUP opcode based on topic count + # 0 topics -> DUP1, 1 topic -> DUP2, N topics -> DUP(N+1) + size_op = getattr(Op, f"DUP{topic_count + 2}") + + attack_block = Op.DUP1 * topic_count + size_op + offset + opcode + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=setup, attack_block=attack_block + ), + ) diff --git a/tests/benchmark/compute/instruction/memory.py b/tests/benchmark/compute/instruction/memory.py new file mode 100644 index 00000000000..52d847e9163 --- /dev/null +++ b/tests/benchmark/compute/instruction/memory.py @@ -0,0 +1,104 @@ +"""Benchmark memory instructions.""" + +import pytest +from ethereum_test_tools import ( + BenchmarkTestFiller, + Bytecode, + ExtCallGenerator, + JumpLoopGenerator, +) +from ethereum_test_vm import Opcodes as 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_worst_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/stack.py b/tests/benchmark/compute/instruction/stack.py new file mode 100644 index 00000000000..148fd4616f4 --- /dev/null +++ b/tests/benchmark/compute/instruction/stack.py @@ -0,0 +1,142 @@ +"""Benchmark stack instructions.""" + +import pytest +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + Alloc, + BenchmarkTestFiller, + ExtCallGenerator, + JumpLoopGenerator, +) +from ethereum_test_vm import Opcodes as 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/storage.py b/tests/benchmark/compute/instruction/storage.py new file mode 100644 index 00000000000..a0a169281b4 --- /dev/null +++ b/tests/benchmark/compute/instruction/storage.py @@ -0,0 +1,362 @@ +"""Benchmark storage instructions.""" + +import pytest +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + Alloc, + BenchmarkTestFiller, + Block, + Bytecode, + Environment, + JumpLoopGenerator, + Transaction, + While, + compute_create_address, +) +from ethereum_test_types import TestPhaseManager +from ethereum_test_vm import Opcodes as Op + +from tests.benchmark.compute.helpers import StorageAction, TransactionResult + +# Storage instructions: +# SLOAD, SSTORE, TLOAD, TSTORE + + +# test_worst_tload -> test_tload +# `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/compute/instruction/system.py b/tests/benchmark/compute/instruction/system.py new file mode 100644 index 00000000000..a1fccf9c42a --- /dev/null +++ b/tests/benchmark/compute/instruction/system.py @@ -0,0 +1,829 @@ +"""Benchmark system instructions.""" + +import math + +import pytest +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + Account, + Alloc, + BenchmarkTestFiller, + Block, + BlockchainTestFiller, + Bytecode, + Environment, + Hash, + JumpLoopGenerator, + StateTestFiller, + Transaction, + While, + compute_create2_address, + compute_create_address, +) +from ethereum_test_vm import Opcodes as Op + +from tests.benchmark.compute.helpers import XOR_TABLE + +# System instructions: +# CREATE, CREATE2 +# RETURN, REVERT +# CALL, CALLCODE, SELFDESTRUCT, DELEGATECALL, STATICCALL + + +@pytest.mark.parametrize( + "opcode", + [ + Op.CALL, + Op.CALLCODE, + Op.DELEGATECALL, + Op.STATICCALL, + ], +) +def test_xcall( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + opcode: Op, + env: Environment, + gas_benchmark_value: int, +) -> None: + """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 + # 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( + "opcode", + [ + Op.CREATE, + Op.CREATE2, + ], +) +@pytest.mark.parametrize( + "max_code_size_ratio, non_zero_data, value", + [ + # To avoid a blowup of combinations, the value dimension is only + # explored for the non-zero data case, so isn't affected by code size + # influence. + pytest.param(0, False, 0, id="0 bytes without value"), + pytest.param(0, False, 1, id="0 bytes with value"), + pytest.param( + 0.25, True, 0, id="0.25x max code size with non-zero data" + ), + pytest.param(0.25, False, 0, id="0.25x max code size with zero data"), + pytest.param( + 0.50, True, 0, id="0.50x max code size with non-zero data" + ), + pytest.param(0.50, False, 0, id="0.50x max code size with zero data"), + pytest.param( + 0.75, True, 0, id="0.75x max code size with non-zero data" + ), + pytest.param(0.75, False, 0, id="0.75x max code size with zero data"), + pytest.param(1.00, True, 0, id="max code size with non-zero data"), + pytest.param(1.00, False, 0, id="max code size with zero data"), + ], +) +def test_create( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + opcode: Op, + max_code_size_ratio: float, + non_zero_data: bool, + value: int, +) -> None: + """Benchmark CREATE and CREATE2 instructions.""" + max_code_size = fork.max_code_size() + + code_size = int(max_code_size * max_code_size_ratio) + + # Deploy the initcode template which has following design: + # ``` + # PUSH3(code_size) + # [CODECOPY(DUP1) -- Conditional that non_zero_data is True] + # RETURN(0, DUP1) + # [] -- Conditional that non_zero_data is True] + # ``` + code = ( + Op.PUSH3(code_size) + + (Op.CODECOPY(size=Op.DUP1) if non_zero_data else Bytecode()) + + Op.RETURN(0, Op.DUP1) + ) + if non_zero_data: # Pad to code_size. + code += bytes([i % 256 for i in range(code_size - len(code))]) + + initcode_template_contract = pre.deploy_contract(code=code) + + # Create the benchmark contract which has the following design: + # ``` + # PUSH(value) + # [EXTCODECOPY(full initcode_template_contract) + # -> Conditional that non_zero_data is True] + # + # JUMPDEST (#) + # (CREATE|CREATE2) + # (CREATE|CREATE2) + # ... + # JUMP(#) + # ``` + setup = ( + Op.PUSH3(code_size) + + Op.PUSH1(value) + + Op.EXTCODECOPY( + address=initcode_template_contract, + size=Op.DUP2, # DUP2 refers to the EXTCODESIZE value above. + ) + ) + + if opcode == Op.CREATE2: + # For CREATE2, we provide an initial salt. + setup += Op.PUSH1(42) + + attack_block = ( + # For CREATE: + # - DUP2 refers to the EXTOCODESIZE value pushed in code_prefix. + # - DUP3 refers to PUSH1(value) above. + Op.POP(Op.CREATE(value=Op.DUP3, offset=0, size=Op.DUP2)) + if opcode == Op.CREATE + # For CREATE2: we manually push the arguments because we leverage the + # return value of previous CREATE2 calls as salt for the next CREATE2 + # call. + # - DUP4 is targeting the PUSH1(value) from the code_prefix. + # - DUP3 is targeting the EXTCODESIZE value pushed in code_prefix. + else Op.DUP3 + Op.PUSH0 + Op.DUP4 + Op.CREATE2 + ) + + code = JumpLoopGenerator( + setup=setup, attack_block=attack_block + ).generate_repeated_code( + repeated_code=attack_block, setup=setup, fork=fork + ) + + tx = Transaction( + # Set enough balance in the pre-alloc for `value > 0` configurations. + to=pre.deploy_contract( + code=code, balance=1_000_000_000 if value > 0 else 0 + ), + sender=pre.fund_eoa(), + ) + + benchmark_test(tx=tx) + + +@pytest.mark.parametrize( + "opcode", + [ + Op.CREATE, + Op.CREATE2, + ], +) +def test_creates_collisions( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + opcode: Op, + gas_benchmark_value: int, +) -> None: + """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 + # "proxy contract" is that CREATE(2) failing with a collision will consume + # all the available gas. If we try to execute the CREATE(2) directly + # without being wrapped **and capped in gas** in a previous CALL, we would + # run out of gas very fast! + # The proxy contract calls CREATE(2) with empty initcode. The current call + # frame gas will be exhausted because of the collision. For this reason the + # caller will carefully give us the minimal gas necessary to execute the + # CREATE(2) and not waste any extra gas in the CREATE(2)-failure. + # Note that these CREATE(2) calls will fail because in (**) below we pre- + # alloc contracts with the same address as the ones that CREATE(2) will try + # to create. + proxy_contract = pre.deploy_contract( + code=Op.CREATE2( + value=Op.PUSH0, salt=Op.PUSH0, offset=Op.PUSH0, size=Op.PUSH0 + ) + if opcode == Op.CREATE2 + else Op.CREATE(value=Op.PUSH0, offset=Op.PUSH0, size=Op.PUSH0) + ) + + gas_costs = fork.gas_costs() + # The CALL to the proxy contract needs at a minimum gas corresponding to + # the CREATE(2) plus extra required PUSH0s for arguments. + min_gas_required = gas_costs.G_CREATE + gas_costs.G_BASE * ( + 3 if opcode == Op.CREATE else 4 + ) + setup = Op.PUSH20(proxy_contract) + Op.PUSH3(min_gas_required) + attack_block = Op.POP( + # DUP7 refers to the PUSH3 above. + # DUP7 refers to the proxy contract address. + Op.CALL(gas=Op.DUP7, address=Op.DUP7) + ) + + # (**) We deploy the contract that CREATE(2) will attempt to create so any + # attempt will fail. + if opcode == Op.CREATE2: + addr = compute_create2_address( + address=proxy_contract, salt=0, initcode=[] + ) + pre.deploy_contract(address=addr, code=Op.INVALID) + else: + # Heuristic to have an upper bound. + max_contract_count = 2 * gas_benchmark_value // gas_costs.G_CREATE + for nonce in range(max_contract_count): + addr = compute_create_address(address=proxy_contract, nonce=nonce) + pre.deploy_contract(address=addr, code=Op.INVALID) + + benchmark_test( + code_generator=JumpLoopGenerator( + 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, + ) From 76ca86a511e71f1878d83dc03d7a8e98f2b48cc7 Mon Sep 17 00:00:00 2001 From: LouisTsai Date: Mon, 27 Oct 2025 13:05:30 +0800 Subject: [PATCH 02/11] refactor: compute precompile folder --- .../benchmark/compute/precompile/alt_bn128.py | 488 ++++++++++++++++++ tests/benchmark/compute/precompile/blake2f.py | 55 ++ .../benchmark/compute/precompile/bls12_381.py | 123 +++++ .../benchmark/compute/precompile/ecrecover.py | 56 ++ .../benchmark/compute/precompile/identity.py | 41 ++ tests/benchmark/compute/precompile/modexp.py | 416 +++++++++++++++ .../compute/precompile/p256verify.py | 87 ++++ .../compute/precompile/point_evaluation.py | 56 ++ .../benchmark/compute/precompile/ripemd160.py | 41 ++ tests/benchmark/compute/precompile/sha256.py | 41 ++ 10 files changed, 1404 insertions(+) create mode 100644 tests/benchmark/compute/precompile/alt_bn128.py create mode 100644 tests/benchmark/compute/precompile/blake2f.py create mode 100644 tests/benchmark/compute/precompile/bls12_381.py create mode 100644 tests/benchmark/compute/precompile/ecrecover.py create mode 100644 tests/benchmark/compute/precompile/identity.py create mode 100644 tests/benchmark/compute/precompile/modexp.py create mode 100644 tests/benchmark/compute/precompile/p256verify.py create mode 100644 tests/benchmark/compute/precompile/point_evaluation.py create mode 100644 tests/benchmark/compute/precompile/ripemd160.py create mode 100644 tests/benchmark/compute/precompile/sha256.py diff --git a/tests/benchmark/compute/precompile/alt_bn128.py b/tests/benchmark/compute/precompile/alt_bn128.py new file mode 100644 index 00000000000..6e4d35748d1 --- /dev/null +++ b/tests/benchmark/compute/precompile/alt_bn128.py @@ -0,0 +1,488 @@ +"""Benchmark ALT_BN128 precompile.""" + +import random + +import pytest +from ethereum_test_benchmark import JumpLoopGenerator +from ethereum_test_forks import Fork +from ethereum_test_tools import Address, BenchmarkTestFiller, Bytes +from ethereum_test_vm import Opcodes as 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/blake2f.py b/tests/benchmark/compute/precompile/blake2f.py new file mode 100644 index 00000000000..3c3b7e529d0 --- /dev/null +++ b/tests/benchmark/compute/precompile/blake2f.py @@ -0,0 +1,55 @@ +"""Benchmark BLAKE2F precompile.""" + +import pytest +from ethereum_test_benchmark import JumpLoopGenerator +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + Address, + BenchmarkTestFiller, +) +from ethereum_test_vm import Opcodes as 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/bls12_381.py b/tests/benchmark/compute/precompile/bls12_381.py new file mode 100644 index 00000000000..fe931124f64 --- /dev/null +++ b/tests/benchmark/compute/precompile/bls12_381.py @@ -0,0 +1,123 @@ +"""Benchmark BLS12_381 precompile.""" + +import pytest +from ethereum_test_benchmark import JumpLoopGenerator +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + Address, + BenchmarkTestFiller, +) +from ethereum_test_vm import Opcodes as 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/ecrecover.py b/tests/benchmark/compute/precompile/ecrecover.py new file mode 100644 index 00000000000..bfdf3213621 --- /dev/null +++ b/tests/benchmark/compute/precompile/ecrecover.py @@ -0,0 +1,56 @@ +"""Benchmark ECRECOVER precompile.""" + +import pytest +from ethereum_test_benchmark import JumpLoopGenerator +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + Address, + BenchmarkTestFiller, +) +from ethereum_test_vm import Opcodes as 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/identity.py b/tests/benchmark/compute/precompile/identity.py new file mode 100644 index 00000000000..ffa0c61900d --- /dev/null +++ b/tests/benchmark/compute/precompile/identity.py @@ -0,0 +1,41 @@ +"""Benchmark IDENTITY precompile.""" + +from ethereum_test_benchmark import JumpLoopGenerator +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + BenchmarkTestFiller, +) +from ethereum_test_vm import Opcodes as 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/modexp.py b/tests/benchmark/compute/precompile/modexp.py new file mode 100644 index 00000000000..52c97f9c8dc --- /dev/null +++ b/tests/benchmark/compute/precompile/modexp.py @@ -0,0 +1,416 @@ +"""Benchmark MODEXP precompile.""" + +import pytest +from _pytest.mark import ParameterSet +from ethereum_test_tools import ( + BenchmarkTestFiller, + JumpLoopGenerator, +) +from ethereum_test_vm import Opcodes as 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_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")}, + ), + ) diff --git a/tests/benchmark/compute/precompile/p256verify.py b/tests/benchmark/compute/precompile/p256verify.py new file mode 100644 index 00000000000..cfddb9b6dc6 --- /dev/null +++ b/tests/benchmark/compute/precompile/p256verify.py @@ -0,0 +1,87 @@ +"""Benchmark P256VERIFY precompile.""" + +import pytest +from ethereum_test_benchmark import JumpLoopGenerator +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + Address, + BenchmarkTestFiller, +) +from ethereum_test_vm import Opcodes as 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/point_evaluation.py b/tests/benchmark/compute/precompile/point_evaluation.py new file mode 100644 index 00000000000..7caf84b16f7 --- /dev/null +++ b/tests/benchmark/compute/precompile/point_evaluation.py @@ -0,0 +1,56 @@ +"""Benchmark POINT EVALUATION precompile.""" + +import pytest +from ethereum_test_benchmark import JumpLoopGenerator +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + Address, + BenchmarkTestFiller, +) +from ethereum_test_vm import Opcodes as 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/ripemd160.py b/tests/benchmark/compute/precompile/ripemd160.py new file mode 100644 index 00000000000..28e91beeb62 --- /dev/null +++ b/tests/benchmark/compute/precompile/ripemd160.py @@ -0,0 +1,41 @@ +"""Benchmark RIPEMD-160 precompile.""" + +from ethereum_test_benchmark import JumpLoopGenerator +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + BenchmarkTestFiller, +) +from ethereum_test_vm import Opcodes as 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/sha256.py b/tests/benchmark/compute/precompile/sha256.py new file mode 100644 index 00000000000..a09d88f1dc0 --- /dev/null +++ b/tests/benchmark/compute/precompile/sha256.py @@ -0,0 +1,41 @@ +"""Benchmark SHA256 precompile.""" + +from ethereum_test_benchmark import JumpLoopGenerator +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + BenchmarkTestFiller, +) +from ethereum_test_vm import Opcodes as 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, + ), + ) From 58cd9d0a07ae2323b9363cba9d37f6112e857a72 Mon Sep 17 00:00:00 2001 From: LouisTsai Date: Mon, 27 Oct 2025 13:06:23 +0800 Subject: [PATCH 03/11] refactor: compute scenario folder --- .../compute/scenario/mix_operations.py | 138 ++++++ .../compute/scenario/transaction_types.py | 456 ++++++++++++++++++ 2 files changed, 594 insertions(+) create mode 100644 tests/benchmark/compute/scenario/mix_operations.py create mode 100644 tests/benchmark/compute/scenario/transaction_types.py diff --git a/tests/benchmark/compute/scenario/mix_operations.py b/tests/benchmark/compute/scenario/mix_operations.py new file mode 100644 index 00000000000..c9e31bf5727 --- /dev/null +++ b/tests/benchmark/compute/scenario/mix_operations.py @@ -0,0 +1,138 @@ +"""Benchmark mixed operations.""" + +import pytest +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + Account, + Address, + BenchmarkTestFiller, + Bytecode, + JumpLoopGenerator, +) +from ethereum_test_types import Alloc +from ethereum_test_vm import Opcodes as 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}, + ), + ) + + +@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 + ), + ) diff --git a/tests/benchmark/compute/scenario/transaction_types.py b/tests/benchmark/compute/scenario/transaction_types.py new file mode 100644 index 00000000000..7cc913449fd --- /dev/null +++ b/tests/benchmark/compute/scenario/transaction_types.py @@ -0,0 +1,456 @@ +"""Benchmark different transaction types.""" + +import math +import random +from typing import Generator, Tuple + +import pytest +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + AccessList, + Account, + Address, + Alloc, + AuthorizationTuple, + BenchmarkTestFiller, + Block, + BlockchainTestFiller, + Hash, + Transaction, +) +from ethereum_test_vm import Opcodes as Op + + +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: + """ + Calculate the number of iterations based on the gas limit and intrinsic + cost. + """ + return gas_benchmark_value // intrinsic_cost + + +@pytest.fixture +def transfer_amount() -> int: + """Ether to transfer in each transaction.""" + return 1 + + +@pytest.fixture +def intrinsic_cost(fork: Fork) -> int: + """Transaction intrinsic cost.""" + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + return intrinsic_cost() + + +def get_distinct_sender_list(pre: Alloc) -> Generator[Address, None, None]: + """Get a list of distinct sender accounts.""" + while True: + yield pre.fund_eoa() + + +def get_distinct_receiver_list(pre: Alloc) -> Generator[Address, None, None]: + """Get a list of distinct receiver accounts.""" + while True: + yield pre.fund_eoa(0) + + +def get_single_sender_list(pre: Alloc) -> Generator[Address, None, None]: + """Get a list of single sender accounts.""" + sender = pre.fund_eoa() + while True: + yield sender + + +def get_single_receiver_list(pre: Alloc) -> Generator[Address, None, None]: + """Get a list of single receiver accounts.""" + receiver = pre.fund_eoa(0) + while True: + yield receiver + + +@pytest.fixture +def ether_transfer_case( + case_id: str, + pre: Alloc, +) -> Tuple[Generator[Address, None, None], Generator[Address, None, None]]: + """Generate the test parameters based on the case ID.""" + if case_id == "a_to_a": + """Sending to self.""" + senders = get_single_sender_list(pre) + receivers = senders + + elif case_id == "a_to_b": + """One sender → one receiver.""" + senders = get_single_sender_list(pre) + receivers = get_single_receiver_list(pre) + + elif case_id == "diff_acc_to_b": + """Multiple senders → one receiver.""" + senders = get_distinct_sender_list(pre) + receivers = get_single_receiver_list(pre) + + elif case_id == "a_to_diff_acc": + """One sender → multiple receivers.""" + senders = get_single_sender_list(pre) + receivers = get_distinct_receiver_list(pre) + + elif case_id == "diff_acc_to_diff_acc": + """Multiple senders → multiple receivers.""" + senders = get_distinct_sender_list(pre) + receivers = get_distinct_receiver_list(pre) + + else: + raise ValueError(f"Unknown case: {case_id}") + + return senders, receivers + + +@pytest.mark.parametrize( + "case_id", + [ + "a_to_a", + "a_to_b", + "diff_acc_to_b", + "a_to_diff_acc", + "diff_acc_to_diff_acc", + ], +) +def test_block_full_of_ether_transfers( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + case_id: str, + ether_transfer_case: Tuple[ + Generator[Address, None, None], Generator[Address, None, None] + ], + iteration_count: int, + transfer_amount: int, + intrinsic_cost: int, +) -> None: + """ + Single test for ether transfer scenarios. + + Scenarios: + - a_to_a: one sender → one sender + - a_to_b: one sender → one receiver + - diff_acc_to_b: multiple senders → one receiver + - a_to_diff_acc: one sender → multiple receivers + - diff_acc_to_diff_acc: multiple senders → multiple receivers + """ + senders, receivers = ether_transfer_case + + # Create a single block with all transactions + txs = [] + balances: dict[Address, int] = {} + for _ in range(iteration_count): + receiver = next(receivers) + balances[receiver] = balances.get(receiver, 0) + transfer_amount + txs.append( + Transaction( + to=receiver, + value=transfer_amount, + gas_limit=intrinsic_cost, + sender=next(senders), + ) + ) + + # Only include post state for non a_to_a cases + post_state = ( + {} + if case_id == "a_to_a" + else { + receiver: Account(balance=balance) + for receiver, balance in balances.items() + } + ) + + benchmark_test( + pre=pre, + post=post_state, + blocks=[Block(txs=txs)], + expected_benchmark_gas_used=iteration_count * intrinsic_cost, + ) + + +@pytest.fixture +def total_cost_floor_per_token() -> int: + """Total cost floor per token.""" + return 10 + + +@pytest.fixture +def total_cost_standard_per_token() -> int: + """Total cost floor per token.""" + return 4 + + +def calldata_generator( + gas_amount: int, + zero_byte: int, + total_cost_floor_per_token: int, +) -> bytes: + """Calculate the calldata based on the gas amount and zero byte.""" + # Gas cost calculation based on EIP-7683: (https://eips.ethereum.org/EIPS/eip-7683) + # + # tx.gasUsed = 21000 + max( + # STANDARD_TOKEN_COST * tokens_in_calldata + # + execution_gas_used + # + isContractCreation * (32000 + + # INITCODE_WORD_COST * words(calldata)), + # TOTAL_COST_FLOOR_PER_TOKEN * tokens_in_calldata) + # + # Simplified in this test case: + # - No execution gas used (no opcodes are executed) + # - Not a contract creation (no initcode) + # + # Therefore: + # max_token_cost = max(STANDARD_TOKEN_COST, TOTAL_COST_FLOOR_PER_TOKEN) + # tx.gasUsed = 21000 + tokens_in_calldata * max_token_cost + # + # Since max(STANDARD_TOKEN_COST, TOTAL_COST_FLOOR_PER_TOKEN) = 10: + # tx.gasUsed = 21000 + tokens_in_calldata * 10 + # + # Token accounting: + # tokens_in_calldata = zero_bytes + 4 * non_zero_bytes + # + # So we calculate how many bytes we can fit into calldata based on + # available gas. + max_tokens_in_calldata = gas_amount // total_cost_floor_per_token + num_of_bytes = ( + max_tokens_in_calldata if zero_byte else max_tokens_in_calldata // 4 + ) + byte_data = b"\x00" if zero_byte else b"\xff" + return byte_data * num_of_bytes + + +@pytest.mark.parametrize("zero_byte", [True, False]) +def test_block_full_data( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + zero_byte: bool, + intrinsic_cost: int, + total_cost_floor_per_token: int, + gas_benchmark_value: int, + tx_gas_limit_cap: int, + fork: Fork, +) -> None: + """Test a block with empty payload.""" + iteration_count = math.ceil(gas_benchmark_value / tx_gas_limit_cap) + + 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 + data = calldata_generator( + gas_available, + zero_byte, + total_cost_floor_per_token, + ) + + total_gas_used += fork.transaction_intrinsic_cost_calculator()( + calldata=data + ) + gas_remaining -= gas_available + intrinsic_cost + + txs.append( + Transaction( + to=pre.fund_eoa(), + data=data, + gas_limit=gas_available + intrinsic_cost, + sender=pre.fund_eoa(), + ) + ) + + benchmark_test( + blocks=[Block(txs=txs)], + expected_benchmark_gas_used=total_gas_used, + ) + + +def test_block_full_access_list_and_data( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + intrinsic_cost: int, + total_cost_standard_per_token: int, + fork: Fork, + gas_benchmark_value: int, + tx_gas_limit_cap: 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) + + 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 + + # Split available gas: 60% for access lists, 40% for calldata + gas_for_access_list = int(gas_available * 0.6) + gas_for_calldata = int(gas_available * 0.4) + + # Access list gas costs from fork's gas_costs + gas_costs = fork.gas_costs() + gas_per_address = gas_costs.G_ACCESS_LIST_ADDRESS + gas_per_storage_key = gas_costs.G_ACCESS_LIST_STORAGE + + # Calculate number of storage keys we can fit + gas_after_address = gas_for_access_list - gas_per_address + num_storage_keys = gas_after_address // gas_per_storage_key + + # Create access list with 1 address and many storage keys + access_address = Address("0x1234567890123456789012345678901234567890") + storage_keys = [] + for i in range(num_storage_keys): + # Generate random-looking storage keys + storage_keys.append(Hash(i)) + + access_list = [ + AccessList( + address=access_address, + storage_keys=storage_keys, + ) + ] + + # Calculate calldata with 29% of gas for zero bytes and 71% for + # non-zero bytes + # Token accounting: tokens_in_calldata = zero_bytes + 4 * + # non_zero_bytes + # We want to split the gas budget: + # - 29% of gas_for_calldata for zero bytes + # - 71% of gas_for_calldata for non-zero bytes + + max_tokens_in_calldata = ( + gas_for_calldata // total_cost_standard_per_token + ) + + # Calculate how many tokens to allocate to each type + tokens_for_zero_bytes = int(max_tokens_in_calldata * 0.29) + tokens_for_non_zero_bytes = ( + max_tokens_in_calldata - tokens_for_zero_bytes + ) + + # Convert tokens to actual byte counts + # Zero bytes: 1 token per byte + # Non-zero bytes: 4 tokens per byte + num_zero_bytes = tokens_for_zero_bytes # 1 token = 1 zero byte + num_non_zero_bytes = ( + tokens_for_non_zero_bytes // 4 + ) # 4 tokens = 1 non-zero byte + + # Create calldata with mixed bytes + calldata = bytearray() + + # Add zero bytes + calldata.extend(b"\x00" * num_zero_bytes) + + # Add non-zero bytes (random values from 0x01 to 0xff) + rng = random.Random(42) # For reproducibility + for _ in range(num_non_zero_bytes): + calldata.append(rng.randint(1, 255)) + + # Shuffle the bytes to mix zero and non-zero bytes + calldata_list = list(calldata) + rng.shuffle(calldata_list) + shuffled_calldata = bytes(calldata_list) + + txs.append( + Transaction( + to=pre.fund_eoa(amount=0), + data=shuffled_calldata, + gas_limit=gas_available + intrinsic_cost, + sender=pre.fund_eoa(), + access_list=access_list, + ) + ) + + gas_remaining -= gas_for_access_list + intrinsic_cost + total_gas_used += fork.transaction_intrinsic_cost_calculator()( + calldata=shuffled_calldata, + access_list=access_list, + ) + + benchmark_test( + blocks=[Block(txs=txs)], + expected_benchmark_gas_used=total_gas_used, + ) + + +@pytest.mark.parametrize("empty_authority", [True, False]) +@pytest.mark.parametrize("zero_delegation", [True, False]) +def test_worst_case_auth_block( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + intrinsic_cost: int, + gas_benchmark_value: int, + fork: Fork, + empty_authority: bool, + zero_delegation: bool, +) -> None: + """Test an auth block.""" + gas_costs = fork.gas_costs() + + iteration_count = ( + gas_benchmark_value - intrinsic_cost + ) // gas_costs.G_AUTHORIZATION + + code = Op.STOP * fork.max_code_size() + auth_target = ( + Address(0) if zero_delegation else pre.deploy_contract(code=code) + ) + + auth_tuples = [] + for _ in range(iteration_count): + signer = ( + pre.fund_eoa(amount=0, delegation=None) + if empty_authority + else pre.fund_eoa(amount=0, delegation=auth_target) + ) + auth_tuple = AuthorizationTuple( + address=auth_target, nonce=signer.nonce, signer=signer + ) + auth_tuples.append(auth_tuple) + + tx = Transaction( + to=pre.empty_account(), + gas_limit=gas_benchmark_value, + sender=pre.fund_eoa(), + authorization_list=auth_tuples, + ) + + gas_used = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=auth_tuples + ) + + refund = 0 + if not empty_authority: + refund = min( + gas_used // 5, + ( + gas_costs.G_AUTHORIZATION + - gas_costs.R_AUTHORIZATION_EXISTING_AUTHORITY + ) + * iteration_count, + ) + + blockchain_test( + pre=pre, + post={}, + blocks=[Block(txs=[tx])], + expected_benchmark_gas_used=gas_used - refund, + ) From b8473a949a54d1de6f899b0cf14dc5ec01af7790 Mon Sep 17 00:00:00 2001 From: LouisTsai Date: Mon, 27 Oct 2025 13:06:50 +0800 Subject: [PATCH 04/11] feat: add general benchmark helper --- tests/benchmark/compute/helpers.py | 199 +++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 tests/benchmark/compute/helpers.py diff --git a/tests/benchmark/compute/helpers.py b/tests/benchmark/compute/helpers.py new file mode 100644 index 00000000000..7456b13d850 --- /dev/null +++ b/tests/benchmark/compute/helpers.py @@ -0,0 +1,199 @@ +"""Helper functions for the EVM benchmark worst-case tests.""" + +import math +from enum import Enum, auto +from typing import cast + +from ethereum_test_base_types.base_types import Hash +from ethereum_test_forks import Fork +from ethereum_test_vm import Opcodes as Op + +from tests.osaka.eip7951_p256verify_precompiles.spec import FieldElement +from tests.prague.eip2537_bls_12_381_precompiles.spec import ( + BytesConcatenation, +) + +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: list[str] | list[BytesConcatenation] | list[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(list[str], parameters) + concatenated_hex_string = "".join(parameters_str) + return 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 + ) + ] + return 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)." + ) + + +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 From 97e96bb9993f2120e4c56d366828988a8fa27bb2 Mon Sep 17 00:00:00 2001 From: LouisTsai Date: Wed, 29 Oct 2025 13:14:20 +0800 Subject: [PATCH 05/11] refactor: update import path --- tests/benchmark/compute/helpers.py | 4 +--- .../benchmark/compute/instruction/arithmetic.py | 6 +++--- tests/benchmark/compute/instruction/bitwise.py | 6 +++--- tests/benchmark/compute/instruction/block.py | 4 ++-- .../benchmark/compute/instruction/comparison.py | 4 ++-- .../compute/instruction/control_flow.py | 4 ++-- .../compute/instruction/environment.py | 17 ++++++++--------- tests/benchmark/compute/instruction/keccak.py | 6 +++--- tests/benchmark/compute/instruction/log.py | 7 +++---- tests/benchmark/compute/instruction/memory.py | 4 ++-- tests/benchmark/compute/instruction/stack.py | 6 +++--- tests/benchmark/compute/instruction/storage.py | 8 ++++---- tests/benchmark/compute/instruction/system.py | 6 +++--- tests/benchmark/compute/precompile/alt_bn128.py | 12 ++++++++---- tests/benchmark/compute/precompile/blake2f.py | 8 ++++---- tests/benchmark/compute/precompile/bls12_381.py | 8 ++++---- tests/benchmark/compute/precompile/ecrecover.py | 8 ++++---- tests/benchmark/compute/precompile/identity.py | 8 ++++---- tests/benchmark/compute/precompile/modexp.py | 4 ++-- .../benchmark/compute/precompile/p256verify.py | 8 ++++---- .../compute/precompile/point_evaluation.py | 8 ++++---- tests/benchmark/compute/precompile/ripemd160.py | 8 ++++---- tests/benchmark/compute/precompile/sha256.py | 8 ++++---- .../compute/scenario/mix_operations.py | 8 ++++---- .../compute/scenario/transaction_types.py | 6 +++--- 25 files changed, 88 insertions(+), 88 deletions(-) diff --git a/tests/benchmark/compute/helpers.py b/tests/benchmark/compute/helpers.py index 7456b13d850..f03bab10803 100644 --- a/tests/benchmark/compute/helpers.py +++ b/tests/benchmark/compute/helpers.py @@ -4,9 +4,7 @@ from enum import Enum, auto from typing import cast -from ethereum_test_base_types.base_types import Hash -from ethereum_test_forks import Fork -from ethereum_test_vm import Opcodes as Op +from execution_testing import Fork, Hash, Op from tests.osaka.eip7951_p256verify_precompiles.spec import FieldElement from tests.prague.eip2537_bls_12_381_precompiles.spec import ( diff --git a/tests/benchmark/compute/instruction/arithmetic.py b/tests/benchmark/compute/instruction/arithmetic.py index 168bd474994..14c76bba3c8 100644 --- a/tests/benchmark/compute/instruction/arithmetic.py +++ b/tests/benchmark/compute/instruction/arithmetic.py @@ -4,15 +4,15 @@ import random import pytest -from ethereum_test_forks import Fork -from ethereum_test_tools import ( +from execution_testing import ( Alloc, BenchmarkTestFiller, Bytecode, + Fork, JumpLoopGenerator, + Op, Transaction, ) -from ethereum_test_vm import Opcodes as Op from tests.benchmark.compute.helpers import DEFAULT_BINOP_ARGS, make_dup, neg diff --git a/tests/benchmark/compute/instruction/bitwise.py b/tests/benchmark/compute/instruction/bitwise.py index bcc00fdfd15..18d59082d81 100644 --- a/tests/benchmark/compute/instruction/bitwise.py +++ b/tests/benchmark/compute/instruction/bitwise.py @@ -4,15 +4,15 @@ from typing import Callable import pytest -from ethereum_test_forks import Fork -from ethereum_test_tools import ( +from execution_testing import ( Alloc, BenchmarkTestFiller, Bytecode, + Fork, JumpLoopGenerator, + Op, Transaction, ) -from ethereum_test_vm import Opcodes as Op from tests.benchmark.compute.helpers import ( DEFAULT_BINOP_ARGS, diff --git a/tests/benchmark/compute/instruction/block.py b/tests/benchmark/compute/instruction/block.py index baf4d1f85e9..18595e833eb 100644 --- a/tests/benchmark/compute/instruction/block.py +++ b/tests/benchmark/compute/instruction/block.py @@ -1,12 +1,12 @@ """Benchmark block instructions.""" import pytest -from ethereum_test_tools import ( +from execution_testing import ( BenchmarkTestFiller, Block, ExtCallGenerator, + Op, ) -from ethereum_test_vm import Opcodes as Op # Block instructions: # BLOCKHASH, COINBASE, TIMESTAMP, NUMBER, PREVRANDAO, GASLIMIT, CHAINID diff --git a/tests/benchmark/compute/instruction/comparison.py b/tests/benchmark/compute/instruction/comparison.py index a0e5d014f9a..aeb97dfbcc1 100644 --- a/tests/benchmark/compute/instruction/comparison.py +++ b/tests/benchmark/compute/instruction/comparison.py @@ -1,11 +1,11 @@ """Benchmark comparison instructions.""" import pytest -from ethereum_test_tools import ( +from execution_testing import ( BenchmarkTestFiller, JumpLoopGenerator, + Op, ) -from ethereum_test_vm import Opcodes as Op # Comparison instructions: # LT, SLT, GT, SGT, EQ, ISZERO diff --git a/tests/benchmark/compute/instruction/control_flow.py b/tests/benchmark/compute/instruction/control_flow.py index 8c808f26533..f3a2d696cd7 100644 --- a/tests/benchmark/compute/instruction/control_flow.py +++ b/tests/benchmark/compute/instruction/control_flow.py @@ -1,13 +1,13 @@ """Benchmark control flow instructions.""" -from ethereum_test_tools import ( +from execution_testing import ( Alloc, BenchmarkTestFiller, ExtCallGenerator, JumpLoopGenerator, + Op, Transaction, ) -from ethereum_test_vm import Opcodes as Op # Control flow instructions: # STOP, JUMP, JUMPI, PC, GAS, JUMPDEST diff --git a/tests/benchmark/compute/instruction/environment.py b/tests/benchmark/compute/instruction/environment.py index 37a480b500c..ebad51f36a8 100644 --- a/tests/benchmark/compute/instruction/environment.py +++ b/tests/benchmark/compute/instruction/environment.py @@ -4,34 +4,33 @@ from typing import Any, Dict import pytest -from ethereum_test_forks import Fork -from ethereum_test_tools import ( +from execution_testing import ( Account, Address, Alloc, + BenchmarkTestFiller, Block, BlockchainTestFiller, Bytecode, Bytes, Environment, + ExtCallGenerator, + Fork, Hash, + JumpLoopGenerator, + Op, Transaction, + TransactionType, While, + add_kzg_version, compute_create2_address, ) -from ethereum_test_types import TransactionType, add_kzg_version -from ethereum_test_vm import Opcodes as Op from tests.benchmark.compute.helpers import ( XOR_TABLE, CallDataOrigin, ReturnDataStyle, ) -from tests.benchmark.test_worst_compute import ( - BenchmarkTestFiller, - ExtCallGenerator, - JumpLoopGenerator, -) from tests.cancun.eip4844_blobs.spec import Spec as BlobsSpec # Environmental instructions: diff --git a/tests/benchmark/compute/instruction/keccak.py b/tests/benchmark/compute/instruction/keccak.py index c73f28eaedc..28edd306bd9 100644 --- a/tests/benchmark/compute/instruction/keccak.py +++ b/tests/benchmark/compute/instruction/keccak.py @@ -2,12 +2,12 @@ import math -from ethereum_test_forks import Fork -from ethereum_test_tools import ( +from execution_testing import ( BenchmarkTestFiller, + Fork, JumpLoopGenerator, + Op, ) -from ethereum_test_vm import Opcodes as Op # Keccak instructions: # KECCAK256 diff --git a/tests/benchmark/compute/instruction/log.py b/tests/benchmark/compute/instruction/log.py index 177e5c297d2..fed038f2f30 100644 --- a/tests/benchmark/compute/instruction/log.py +++ b/tests/benchmark/compute/instruction/log.py @@ -1,13 +1,12 @@ """Benchmark log instructions.""" import pytest -from ethereum_test_tools import ( +from execution_testing import ( BenchmarkTestFiller, Bytecode, JumpLoopGenerator, + Op, ) -from ethereum_test_vm import Opcode -from ethereum_test_vm import Opcodes as Op # Log instructions: # LOG0, LOG1, LOG2, LOG3, LOG4 @@ -41,7 +40,7 @@ @pytest.mark.parametrize("fixed_offset", [True, False]) def test_log( benchmark_test: BenchmarkTestFiller, - opcode: Opcode, + opcode: Op, zeros_topic: bool, size: int, fixed_offset: bool, diff --git a/tests/benchmark/compute/instruction/memory.py b/tests/benchmark/compute/instruction/memory.py index 52d847e9163..07613a8f073 100644 --- a/tests/benchmark/compute/instruction/memory.py +++ b/tests/benchmark/compute/instruction/memory.py @@ -1,13 +1,13 @@ """Benchmark memory instructions.""" import pytest -from ethereum_test_tools import ( +from execution_testing import ( BenchmarkTestFiller, Bytecode, ExtCallGenerator, JumpLoopGenerator, + Op, ) -from ethereum_test_vm import Opcodes as Op # Memory instructions: # MSTORE, MSTORE8, MLOAD, MSIZE, MCOPY diff --git a/tests/benchmark/compute/instruction/stack.py b/tests/benchmark/compute/instruction/stack.py index 148fd4616f4..204852dde3d 100644 --- a/tests/benchmark/compute/instruction/stack.py +++ b/tests/benchmark/compute/instruction/stack.py @@ -1,14 +1,14 @@ """Benchmark stack instructions.""" import pytest -from ethereum_test_forks import Fork -from ethereum_test_tools import ( +from execution_testing import ( Alloc, BenchmarkTestFiller, ExtCallGenerator, + Fork, JumpLoopGenerator, + Op, ) -from ethereum_test_vm import Opcodes as Op # Stack instructions: # POP, PUSHx, DUPx, SWAPx diff --git a/tests/benchmark/compute/instruction/storage.py b/tests/benchmark/compute/instruction/storage.py index a0a169281b4..4ff106b5fad 100644 --- a/tests/benchmark/compute/instruction/storage.py +++ b/tests/benchmark/compute/instruction/storage.py @@ -1,20 +1,20 @@ """Benchmark storage instructions.""" import pytest -from ethereum_test_forks import Fork -from ethereum_test_tools import ( +from execution_testing import ( Alloc, BenchmarkTestFiller, Block, Bytecode, Environment, + Fork, JumpLoopGenerator, + Op, + TestPhaseManager, Transaction, While, compute_create_address, ) -from ethereum_test_types import TestPhaseManager -from ethereum_test_vm import Opcodes as Op from tests.benchmark.compute.helpers import StorageAction, TransactionResult diff --git a/tests/benchmark/compute/instruction/system.py b/tests/benchmark/compute/instruction/system.py index a1fccf9c42a..80933bc979f 100644 --- a/tests/benchmark/compute/instruction/system.py +++ b/tests/benchmark/compute/instruction/system.py @@ -3,8 +3,7 @@ import math import pytest -from ethereum_test_forks import Fork -from ethereum_test_tools import ( +from execution_testing import ( Account, Alloc, BenchmarkTestFiller, @@ -12,15 +11,16 @@ BlockchainTestFiller, Bytecode, Environment, + Fork, Hash, JumpLoopGenerator, + Op, StateTestFiller, Transaction, While, compute_create2_address, compute_create_address, ) -from ethereum_test_vm import Opcodes as Op from tests.benchmark.compute.helpers import XOR_TABLE diff --git a/tests/benchmark/compute/precompile/alt_bn128.py b/tests/benchmark/compute/precompile/alt_bn128.py index 6e4d35748d1..55026a44eb7 100644 --- a/tests/benchmark/compute/precompile/alt_bn128.py +++ b/tests/benchmark/compute/precompile/alt_bn128.py @@ -3,10 +3,14 @@ import random import pytest -from ethereum_test_benchmark import JumpLoopGenerator -from ethereum_test_forks import Fork -from ethereum_test_tools import Address, BenchmarkTestFiller, Bytes -from ethereum_test_vm import Opcodes as Op +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 diff --git a/tests/benchmark/compute/precompile/blake2f.py b/tests/benchmark/compute/precompile/blake2f.py index 3c3b7e529d0..20cfe717164 100644 --- a/tests/benchmark/compute/precompile/blake2f.py +++ b/tests/benchmark/compute/precompile/blake2f.py @@ -1,13 +1,13 @@ """Benchmark BLAKE2F precompile.""" import pytest -from ethereum_test_benchmark import JumpLoopGenerator -from ethereum_test_forks import Fork -from ethereum_test_tools import ( +from execution_testing import ( Address, BenchmarkTestFiller, + Fork, + JumpLoopGenerator, + Op, ) -from ethereum_test_vm import Opcodes as Op from tests.benchmark.compute.helpers import concatenate_parameters from tests.istanbul.eip152_blake2.common import Blake2bInput diff --git a/tests/benchmark/compute/precompile/bls12_381.py b/tests/benchmark/compute/precompile/bls12_381.py index fe931124f64..faa86c07cc7 100644 --- a/tests/benchmark/compute/precompile/bls12_381.py +++ b/tests/benchmark/compute/precompile/bls12_381.py @@ -1,13 +1,13 @@ """Benchmark BLS12_381 precompile.""" import pytest -from ethereum_test_benchmark import JumpLoopGenerator -from ethereum_test_forks import Fork -from ethereum_test_tools import ( +from execution_testing import ( Address, BenchmarkTestFiller, + Fork, + JumpLoopGenerator, + Op, ) -from ethereum_test_vm import Opcodes as Op from tests.benchmark.compute.helpers import concatenate_parameters from tests.prague.eip2537_bls_12_381_precompiles import spec as bls12381_spec diff --git a/tests/benchmark/compute/precompile/ecrecover.py b/tests/benchmark/compute/precompile/ecrecover.py index bfdf3213621..0871c3281f2 100644 --- a/tests/benchmark/compute/precompile/ecrecover.py +++ b/tests/benchmark/compute/precompile/ecrecover.py @@ -1,13 +1,13 @@ """Benchmark ECRECOVER precompile.""" import pytest -from ethereum_test_benchmark import JumpLoopGenerator -from ethereum_test_forks import Fork -from ethereum_test_tools import ( +from execution_testing import ( Address, BenchmarkTestFiller, + Fork, + JumpLoopGenerator, + Op, ) -from ethereum_test_vm import Opcodes as Op from tests.benchmark.compute.helpers import concatenate_parameters diff --git a/tests/benchmark/compute/precompile/identity.py b/tests/benchmark/compute/precompile/identity.py index ffa0c61900d..64e81a865c3 100644 --- a/tests/benchmark/compute/precompile/identity.py +++ b/tests/benchmark/compute/precompile/identity.py @@ -1,11 +1,11 @@ """Benchmark IDENTITY precompile.""" -from ethereum_test_benchmark import JumpLoopGenerator -from ethereum_test_forks import Fork -from ethereum_test_tools import ( +from execution_testing import ( BenchmarkTestFiller, + Fork, + JumpLoopGenerator, + Op, ) -from ethereum_test_vm import Opcodes as Op from tests.benchmark.compute.helpers import calculate_optimal_input_length diff --git a/tests/benchmark/compute/precompile/modexp.py b/tests/benchmark/compute/precompile/modexp.py index 52c97f9c8dc..b1cca3e5976 100644 --- a/tests/benchmark/compute/precompile/modexp.py +++ b/tests/benchmark/compute/precompile/modexp.py @@ -2,11 +2,11 @@ import pytest from _pytest.mark import ParameterSet -from ethereum_test_tools import ( +from execution_testing import ( BenchmarkTestFiller, JumpLoopGenerator, + Op, ) -from ethereum_test_vm import Opcodes as Op from tests.byzantium.eip198_modexp_precompile.helpers import ModExpInput diff --git a/tests/benchmark/compute/precompile/p256verify.py b/tests/benchmark/compute/precompile/p256verify.py index cfddb9b6dc6..6218dd65fa8 100644 --- a/tests/benchmark/compute/precompile/p256verify.py +++ b/tests/benchmark/compute/precompile/p256verify.py @@ -1,13 +1,13 @@ """Benchmark P256VERIFY precompile.""" import pytest -from ethereum_test_benchmark import JumpLoopGenerator -from ethereum_test_forks import Fork -from ethereum_test_tools import ( +from execution_testing import ( Address, BenchmarkTestFiller, + Fork, + JumpLoopGenerator, + Op, ) -from ethereum_test_vm import Opcodes as Op from tests.benchmark.compute.helpers import concatenate_parameters from tests.osaka.eip7951_p256verify_precompiles import spec as p256verify_spec diff --git a/tests/benchmark/compute/precompile/point_evaluation.py b/tests/benchmark/compute/precompile/point_evaluation.py index 7caf84b16f7..4ffcbb04709 100644 --- a/tests/benchmark/compute/precompile/point_evaluation.py +++ b/tests/benchmark/compute/precompile/point_evaluation.py @@ -1,13 +1,13 @@ """Benchmark POINT EVALUATION precompile.""" import pytest -from ethereum_test_benchmark import JumpLoopGenerator -from ethereum_test_forks import Fork -from ethereum_test_tools import ( +from execution_testing import ( Address, BenchmarkTestFiller, + Fork, + JumpLoopGenerator, + Op, ) -from ethereum_test_vm import Opcodes as Op from tests.benchmark.compute.helpers import concatenate_parameters from tests.cancun.eip4844_blobs.spec import Spec as BlobsSpec diff --git a/tests/benchmark/compute/precompile/ripemd160.py b/tests/benchmark/compute/precompile/ripemd160.py index 28e91beeb62..24821320d6f 100644 --- a/tests/benchmark/compute/precompile/ripemd160.py +++ b/tests/benchmark/compute/precompile/ripemd160.py @@ -1,11 +1,11 @@ """Benchmark RIPEMD-160 precompile.""" -from ethereum_test_benchmark import JumpLoopGenerator -from ethereum_test_forks import Fork -from ethereum_test_tools import ( +from execution_testing import ( BenchmarkTestFiller, + Fork, + JumpLoopGenerator, + Op, ) -from ethereum_test_vm import Opcodes as Op from tests.benchmark.compute.helpers import calculate_optimal_input_length diff --git a/tests/benchmark/compute/precompile/sha256.py b/tests/benchmark/compute/precompile/sha256.py index a09d88f1dc0..8096d5dfdec 100644 --- a/tests/benchmark/compute/precompile/sha256.py +++ b/tests/benchmark/compute/precompile/sha256.py @@ -1,11 +1,11 @@ """Benchmark SHA256 precompile.""" -from ethereum_test_benchmark import JumpLoopGenerator -from ethereum_test_forks import Fork -from ethereum_test_tools import ( +from execution_testing import ( BenchmarkTestFiller, + Fork, + JumpLoopGenerator, + Op, ) -from ethereum_test_vm import Opcodes as Op from tests.benchmark.compute.helpers import calculate_optimal_input_length diff --git a/tests/benchmark/compute/scenario/mix_operations.py b/tests/benchmark/compute/scenario/mix_operations.py index c9e31bf5727..828c7bbe70c 100644 --- a/tests/benchmark/compute/scenario/mix_operations.py +++ b/tests/benchmark/compute/scenario/mix_operations.py @@ -1,16 +1,16 @@ """Benchmark mixed operations.""" import pytest -from ethereum_test_forks import Fork -from ethereum_test_tools import ( +from execution_testing import ( Account, Address, + Alloc, BenchmarkTestFiller, Bytecode, + Fork, JumpLoopGenerator, + Op, ) -from ethereum_test_types import Alloc -from ethereum_test_vm import Opcodes as Op @pytest.mark.parametrize( diff --git a/tests/benchmark/compute/scenario/transaction_types.py b/tests/benchmark/compute/scenario/transaction_types.py index 7cc913449fd..1c27cf5a9fb 100644 --- a/tests/benchmark/compute/scenario/transaction_types.py +++ b/tests/benchmark/compute/scenario/transaction_types.py @@ -5,8 +5,7 @@ from typing import Generator, Tuple import pytest -from ethereum_test_forks import Fork -from ethereum_test_tools import ( +from execution_testing import ( AccessList, Account, Address, @@ -15,10 +14,11 @@ BenchmarkTestFiller, Block, BlockchainTestFiller, + Fork, Hash, + Op, Transaction, ) -from ethereum_test_vm import Opcodes as Op def test_empty_block( From d6f439796cf941085fe885ee82523baa3ec03a2f Mon Sep 17 00:00:00 2001 From: LouisTsai Date: Wed, 29 Oct 2025 16:08:17 +0800 Subject: [PATCH 06/11] fix: resolve mypy issue --- tests/benchmark/compute/helpers.py | 42 +++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/tests/benchmark/compute/helpers.py b/tests/benchmark/compute/helpers.py index f03bab10803..0343ff3cf66 100644 --- a/tests/benchmark/compute/helpers.py +++ b/tests/benchmark/compute/helpers.py @@ -2,13 +2,18 @@ import math from enum import Enum, auto -from typing import cast +from typing import Sequence, cast from execution_testing import Fork, Hash, Op -from tests.osaka.eip7951_p256verify_precompiles.spec import FieldElement +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, + BytesConcatenation as BLSBytesConcatenation, ) DEFAULT_BINOP_ARGS = ( @@ -92,7 +97,12 @@ def sar(x: int, s: int) -> int: def concatenate_parameters( - parameters: list[str] | list[BytesConcatenation] | list[bytes], + parameters: ( + Sequence[str] + | Sequence[P256BytesConcatenation] + | Sequence[BLSBytesConcatenation] + | Sequence[bytes] + ), ) -> bytes: """ Concatenate precompile parameters into bytes. @@ -106,24 +116,38 @@ def concatenate_parameters( """ if all(isinstance(p, str) for p in parameters): - parameters_str = cast(list[str], parameters) + parameters_str = cast(Sequence[str], parameters) concatenated_hex_string = "".join(parameters_str) return bytes.fromhex(concatenated_hex_string) elif all( - isinstance(p, (bytes, BytesConcatenation, FieldElement)) + isinstance( + p, + ( + bytes, + P256BytesConcatenation, + BLSBytesConcatenation, + FieldElement, + ), + ) for p in parameters ): parameters_bytes_list = [ bytes(p) for p in cast( - list[BytesConcatenation | bytes | FieldElement], parameters + Sequence[ + P256BytesConcatenation + | BLSBytesConcatenation + | bytes + | FieldElement + ], + parameters, ) ] return 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 " + "parameters must be a sequence of strings (hex) " + "or a sequence of byte-like objects (bytes, BytesConcatenation or " "FieldElement)." ) From ac2613c1168c78303465688e50bfdc00d89cd635 Mon Sep 17 00:00:00 2001 From: LouisTsai Date: Wed, 29 Oct 2025 16:46:47 +0800 Subject: [PATCH 07/11] fix: add missing variable --- tests/benchmark/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/benchmark/conftest.py b/tests/benchmark/conftest.py index 6ec31dfd6f9..c4f711c3627 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 From c433c8cbf9fc76cbcdc16b70cc762819a34a289a Mon Sep 17 00:00:00 2001 From: LouisTsai Date: Thu, 30 Oct 2025 16:16:42 +0800 Subject: [PATCH 08/11] chore: remove ported files --- tests/benchmark/helpers.py | 28 - tests/benchmark/test_worst_blocks.py | 449 ---- tests/benchmark/test_worst_bytecode.py | 516 ---- tests/benchmark/test_worst_compute.py | 2351 ----------------- tests/benchmark/test_worst_memory.py | 300 --- tests/benchmark/test_worst_opcode.py | 75 - .../benchmark/test_worst_stateful_opcodes.py | 889 ------- 7 files changed, 4608 deletions(-) delete mode 100755 tests/benchmark/helpers.py delete mode 100755 tests/benchmark/test_worst_blocks.py delete mode 100755 tests/benchmark/test_worst_bytecode.py delete mode 100755 tests/benchmark/test_worst_compute.py delete mode 100755 tests/benchmark/test_worst_memory.py delete mode 100755 tests/benchmark/test_worst_opcode.py delete mode 100755 tests/benchmark/test_worst_stateful_opcodes.py diff --git a/tests/benchmark/helpers.py b/tests/benchmark/helpers.py deleted file mode 100755 index 830bc33455f..00000000000 --- 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_blocks.py b/tests/benchmark/test_worst_blocks.py deleted file mode 100755 index f661efe4aa1..00000000000 --- a/tests/benchmark/test_worst_blocks.py +++ /dev/null @@ -1,449 +0,0 @@ -""" -Tests that benchmark EVMs in worst-case block scenarios. -""" - -import math -import random -from typing import Generator, Tuple - -import pytest -from execution_testing import ( - AccessList, - Account, - Address, - Alloc, - AuthorizationTuple, - BenchmarkTestFiller, - Block, - BlockchainTestFiller, - Fork, - Hash, - Op, - Transaction, -) - - -@pytest.fixture -def iteration_count(intrinsic_cost: int, gas_benchmark_value: int) -> int: - """ - Calculate the number of iterations based on the gas limit and intrinsic - cost. - """ - return gas_benchmark_value // intrinsic_cost - - -@pytest.fixture -def transfer_amount() -> int: - """Ether to transfer in each transaction.""" - return 1 - - -@pytest.fixture -def intrinsic_cost(fork: Fork) -> int: - """Transaction intrinsic cost.""" - intrinsic_cost = fork.transaction_intrinsic_cost_calculator() - return intrinsic_cost() - - -def get_distinct_sender_list(pre: Alloc) -> Generator[Address, None, None]: - """Get a list of distinct sender accounts.""" - while True: - yield pre.fund_eoa() - - -def get_distinct_receiver_list(pre: Alloc) -> Generator[Address, None, None]: - """Get a list of distinct receiver accounts.""" - while True: - yield pre.fund_eoa(0) - - -def get_single_sender_list(pre: Alloc) -> Generator[Address, None, None]: - """Get a list of single sender accounts.""" - sender = pre.fund_eoa() - while True: - yield sender - - -def get_single_receiver_list(pre: Alloc) -> Generator[Address, None, None]: - """Get a list of single receiver accounts.""" - receiver = pre.fund_eoa(0) - while True: - yield receiver - - -@pytest.fixture -def ether_transfer_case( - case_id: str, - pre: Alloc, -) -> Tuple[Generator[Address, None, None], Generator[Address, None, None]]: - """Generate the test parameters based on the case ID.""" - if case_id == "a_to_a": - """Sending to self.""" - senders = get_single_sender_list(pre) - receivers = senders - - elif case_id == "a_to_b": - """One sender → one receiver.""" - senders = get_single_sender_list(pre) - receivers = get_single_receiver_list(pre) - - elif case_id == "diff_acc_to_b": - """Multiple senders → one receiver.""" - senders = get_distinct_sender_list(pre) - receivers = get_single_receiver_list(pre) - - elif case_id == "a_to_diff_acc": - """One sender → multiple receivers.""" - senders = get_single_sender_list(pre) - receivers = get_distinct_receiver_list(pre) - - elif case_id == "diff_acc_to_diff_acc": - """Multiple senders → multiple receivers.""" - senders = get_distinct_sender_list(pre) - receivers = get_distinct_receiver_list(pre) - - else: - raise ValueError(f"Unknown case: {case_id}") - - return senders, receivers - - -@pytest.mark.parametrize( - "case_id", - [ - "a_to_a", - "a_to_b", - "diff_acc_to_b", - "a_to_diff_acc", - "diff_acc_to_diff_acc", - ], -) -def test_block_full_of_ether_transfers( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - case_id: str, - ether_transfer_case: Tuple[ - Generator[Address, None, None], Generator[Address, None, None] - ], - iteration_count: int, - transfer_amount: int, - intrinsic_cost: int, -) -> None: - """ - Single test for ether transfer scenarios. - - Scenarios: - - a_to_a: one sender → one sender - - a_to_b: one sender → one receiver - - diff_acc_to_b: multiple senders → one receiver - - a_to_diff_acc: one sender → multiple receivers - - diff_acc_to_diff_acc: multiple senders → multiple receivers - """ - senders, receivers = ether_transfer_case - - # Create a single block with all transactions - txs = [] - balances: dict[Address, int] = {} - for _ in range(iteration_count): - receiver = next(receivers) - balances[receiver] = balances.get(receiver, 0) + transfer_amount - txs.append( - Transaction( - to=receiver, - value=transfer_amount, - gas_limit=intrinsic_cost, - sender=next(senders), - ) - ) - - # Only include post state for non a_to_a cases - post_state = ( - {} - if case_id == "a_to_a" - else { - receiver: Account(balance=balance) - for receiver, balance in balances.items() - } - ) - - benchmark_test( - pre=pre, - post=post_state, - blocks=[Block(txs=txs)], - expected_benchmark_gas_used=iteration_count * intrinsic_cost, - ) - - -@pytest.fixture -def total_cost_floor_per_token() -> int: - """Total cost floor per token.""" - return 10 - - -@pytest.fixture -def total_cost_standard_per_token() -> int: - """Total cost floor per token.""" - return 4 - - -def calldata_generator( - gas_amount: int, - zero_byte: int, - total_cost_floor_per_token: int, -) -> bytes: - """Calculate the calldata based on the gas amount and zero byte.""" - # Gas cost calculation based on EIP-7683: (https://eips.ethereum.org/EIPS/eip-7683) - # - # tx.gasUsed = 21000 + max( - # STANDARD_TOKEN_COST * tokens_in_calldata - # + execution_gas_used - # + isContractCreation * (32000 + - # INITCODE_WORD_COST * words(calldata)), - # TOTAL_COST_FLOOR_PER_TOKEN * tokens_in_calldata) - # - # Simplified in this test case: - # - No execution gas used (no opcodes are executed) - # - Not a contract creation (no initcode) - # - # Therefore: - # max_token_cost = max(STANDARD_TOKEN_COST, TOTAL_COST_FLOOR_PER_TOKEN) - # tx.gasUsed = 21000 + tokens_in_calldata * max_token_cost - # - # Since max(STANDARD_TOKEN_COST, TOTAL_COST_FLOOR_PER_TOKEN) = 10: - # tx.gasUsed = 21000 + tokens_in_calldata * 10 - # - # Token accounting: - # tokens_in_calldata = zero_bytes + 4 * non_zero_bytes - # - # So we calculate how many bytes we can fit into calldata based on - # available gas. - max_tokens_in_calldata = gas_amount // total_cost_floor_per_token - num_of_bytes = ( - max_tokens_in_calldata if zero_byte else max_tokens_in_calldata // 4 - ) - byte_data = b"\x00" if zero_byte else b"\xff" - return byte_data * num_of_bytes - - -@pytest.mark.parametrize("zero_byte", [True, False]) -def test_block_full_data( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - zero_byte: bool, - intrinsic_cost: int, - total_cost_floor_per_token: int, - gas_benchmark_value: int, - tx_gas_limit_cap: int, - total_cost_standard_per_token: int, - fork: Fork, -) -> None: - """Test a block with empty payload.""" - iteration_count = math.ceil(gas_benchmark_value / tx_gas_limit_cap) - - 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 - data = calldata_generator( - gas_available, - zero_byte, - total_cost_floor_per_token, - ) - - total_gas_used += fork.transaction_intrinsic_cost_calculator()( - calldata=data - ) - gas_remaining -= gas_available + intrinsic_cost - - txs.append( - Transaction( - to=pre.fund_eoa(), - data=data, - gas_limit=gas_available + intrinsic_cost, - sender=pre.fund_eoa(), - ) - ) - - benchmark_test( - blocks=[Block(txs=txs)], - expected_benchmark_gas_used=total_gas_used, - ) - - -def test_block_full_access_list_and_data( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - intrinsic_cost: int, - total_cost_standard_per_token: int, - fork: Fork, - gas_benchmark_value: int, - tx_gas_limit_cap: 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) - - 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 - - # Split available gas: 60% for access lists, 40% for calldata - gas_for_access_list = int(gas_available * 0.6) - gas_for_calldata = int(gas_available * 0.4) - - # Access list gas costs from fork's gas_costs - gas_costs = fork.gas_costs() - gas_per_address = gas_costs.G_ACCESS_LIST_ADDRESS - gas_per_storage_key = gas_costs.G_ACCESS_LIST_STORAGE - - # Calculate number of storage keys we can fit - gas_after_address = gas_for_access_list - gas_per_address - num_storage_keys = gas_after_address // gas_per_storage_key - - # Create access list with 1 address and many storage keys - access_address = Address("0x1234567890123456789012345678901234567890") - storage_keys = [] - for i in range(num_storage_keys): - # Generate random-looking storage keys - storage_keys.append(Hash(i)) - - access_list = [ - AccessList( - address=access_address, - storage_keys=storage_keys, - ) - ] - - # Calculate calldata with 29% of gas for zero bytes and 71% for - # non-zero bytes - # Token accounting: tokens_in_calldata = zero_bytes + 4 * - # non_zero_bytes - # We want to split the gas budget: - # - 29% of gas_for_calldata for zero bytes - # - 71% of gas_for_calldata for non-zero bytes - - max_tokens_in_calldata = ( - gas_for_calldata // total_cost_standard_per_token - ) - - # Calculate how many tokens to allocate to each type - tokens_for_zero_bytes = int(max_tokens_in_calldata * 0.29) - tokens_for_non_zero_bytes = ( - max_tokens_in_calldata - tokens_for_zero_bytes - ) - - # Convert tokens to actual byte counts - # Zero bytes: 1 token per byte - # Non-zero bytes: 4 tokens per byte - num_zero_bytes = tokens_for_zero_bytes # 1 token = 1 zero byte - num_non_zero_bytes = ( - tokens_for_non_zero_bytes // 4 - ) # 4 tokens = 1 non-zero byte - - # Create calldata with mixed bytes - calldata = bytearray() - - # Add zero bytes - calldata.extend(b"\x00" * num_zero_bytes) - - # Add non-zero bytes (random values from 0x01 to 0xff) - rng = random.Random(42) # For reproducibility - for _ in range(num_non_zero_bytes): - calldata.append(rng.randint(1, 255)) - - # Shuffle the bytes to mix zero and non-zero bytes - calldata_list = list(calldata) - rng.shuffle(calldata_list) - shuffled_calldata = bytes(calldata_list) - - txs.append( - Transaction( - to=pre.fund_eoa(amount=0), - data=shuffled_calldata, - gas_limit=gas_available + intrinsic_cost, - sender=pre.fund_eoa(), - access_list=access_list, - ) - ) - - gas_remaining -= gas_for_access_list + intrinsic_cost - total_gas_used += fork.transaction_intrinsic_cost_calculator()( - calldata=shuffled_calldata, - access_list=access_list, - ) - - benchmark_test( - blocks=[Block(txs=txs)], - expected_benchmark_gas_used=total_gas_used, - ) - - -@pytest.mark.parametrize("empty_authority", [True, False]) -@pytest.mark.parametrize("zero_delegation", [True, False]) -def test_worst_case_auth_block( - blockchain_test: BlockchainTestFiller, - pre: Alloc, - intrinsic_cost: int, - gas_benchmark_value: int, - fork: Fork, - empty_authority: bool, - zero_delegation: bool, -) -> None: - """Test an auth block.""" - gas_costs = fork.gas_costs() - - iteration_count = ( - gas_benchmark_value - intrinsic_cost - ) // gas_costs.G_AUTHORIZATION - - code = Op.STOP * fork.max_code_size() - auth_target = ( - Address(0) if zero_delegation else pre.deploy_contract(code=code) - ) - - auth_tuples = [] - for _ in range(iteration_count): - signer = ( - pre.fund_eoa(amount=0, delegation=None) - if empty_authority - else pre.fund_eoa(amount=0, delegation=auth_target) - ) - auth_tuple = AuthorizationTuple( - address=auth_target, nonce=signer.nonce, signer=signer - ) - auth_tuples.append(auth_tuple) - - tx = Transaction( - to=pre.empty_account(), - gas_limit=gas_benchmark_value, - sender=pre.fund_eoa(), - authorization_list=auth_tuples, - ) - - gas_used = fork.transaction_intrinsic_cost_calculator()( - authorization_list_or_count=auth_tuples - ) - - refund = 0 - if not empty_authority: - refund = min( - gas_used // 5, - ( - gas_costs.G_AUTHORIZATION - - gas_costs.R_AUTHORIZATION_EXISTING_AUTHORITY - ) - * iteration_count, - ) - - blockchain_test( - pre=pre, - post={}, - blocks=[Block(txs=[tx])], - expected_benchmark_gas_used=gas_used - refund, - ) diff --git a/tests/benchmark/test_worst_bytecode.py b/tests/benchmark/test_worst_bytecode.py deleted file mode 100755 index ce2a3ce84fb..00000000000 --- a/tests/benchmark/test_worst_bytecode.py +++ /dev/null @@ -1,516 +0,0 @@ -""" -Tests that benchmark EVMs in worst-case opcode scenarios. -""" - -import math - -import pytest -from execution_testing import ( - Account, - Alloc, - BenchmarkTestFiller, - Block, - BlockchainTestFiller, - Bytecode, - Environment, - Fork, - Hash, - JumpLoopGenerator, - Op, - Transaction, - While, - compute_create2_address, - compute_create_address, -) - -REFERENCE_SPEC_GIT_PATH = "TODO" -REFERENCE_SPEC_VERSION = "TODO" - -XOR_TABLE_SIZE = 256 -XOR_TABLE = [Hash(i).sha256() for i in range(XOR_TABLE_SIZE)] - - -@pytest.mark.parametrize( - "opcode", - [ - Op.EXTCODESIZE, - Op.EXTCODEHASH, - Op.CALL, - Op.CALLCODE, - Op.DELEGATECALL, - Op.STATICCALL, - Op.EXTCODECOPY, - ], -) -def test_worst_bytecode_single_opcode( - blockchain_test: BlockchainTestFiller, - pre: Alloc, - fork: Fork, - opcode: Op, - 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. - """ - # 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( - "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", - [ - Op.CREATE, - Op.CREATE2, - ], -) -@pytest.mark.parametrize( - "max_code_size_ratio, non_zero_data, value", - [ - # To avoid a blowup of combinations, the value dimension is only - # explored for the non-zero data case, so isn't affected by code size - # influence. - pytest.param(0, False, 0, id="0 bytes without value"), - pytest.param(0, False, 1, id="0 bytes with value"), - pytest.param( - 0.25, True, 0, id="0.25x max code size with non-zero data" - ), - pytest.param(0.25, False, 0, id="0.25x max code size with zero data"), - pytest.param( - 0.50, True, 0, id="0.50x max code size with non-zero data" - ), - pytest.param(0.50, False, 0, id="0.50x max code size with zero data"), - pytest.param( - 0.75, True, 0, id="0.75x max code size with non-zero data" - ), - pytest.param(0.75, False, 0, id="0.75x max code size with zero data"), - pytest.param(1.00, True, 0, id="max code size with non-zero data"), - pytest.param(1.00, False, 0, id="max code size with zero data"), - ], -) -def test_worst_create( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - opcode: Op, - max_code_size_ratio: float, - non_zero_data: bool, - value: int, -) -> None: - """ - Test the CREATE and CREATE2 performance with different configurations. - """ - max_code_size = fork.max_code_size() - - code_size = int(max_code_size * max_code_size_ratio) - - # Deploy the initcode template which has following design: - # ``` - # PUSH3(code_size) - # [CODECOPY(DUP1) -- Conditional that non_zero_data is True] - # RETURN(0, DUP1) - # [] -- Conditional that non_zero_data is True] - # ``` - code = ( - Op.PUSH3(code_size) - + (Op.CODECOPY(size=Op.DUP1) if non_zero_data else Bytecode()) - + Op.RETURN(0, Op.DUP1) - ) - if non_zero_data: # Pad to code_size. - code += bytes([i % 256 for i in range(code_size - len(code))]) - - initcode_template_contract = pre.deploy_contract(code=code) - - # Create the benchmark contract which has the following design: - # ``` - # PUSH(value) - # [EXTCODECOPY(full initcode_template_contract) - # -> Conditional that non_zero_data is True] - # - # JUMPDEST (#) - # (CREATE|CREATE2) - # (CREATE|CREATE2) - # ... - # JUMP(#) - # ``` - setup = ( - Op.PUSH3(code_size) - + Op.PUSH1(value) - + Op.EXTCODECOPY( - address=initcode_template_contract, - size=Op.DUP2, # DUP2 refers to the EXTCODESIZE value above. - ) - ) - - if opcode == Op.CREATE2: - # For CREATE2, we provide an initial salt. - setup += Op.PUSH1(42) - - attack_block = ( - # For CREATE: - # - DUP2 refers to the EXTOCODESIZE value pushed in code_prefix. - # - DUP3 refers to PUSH1(value) above. - Op.POP(Op.CREATE(value=Op.DUP3, offset=0, size=Op.DUP2)) - if opcode == Op.CREATE - # For CREATE2: we manually push the arguments because we leverage the - # return value of previous CREATE2 calls as salt for the next CREATE2 - # call. - # - DUP4 is targeting the PUSH1(value) from the code_prefix. - # - DUP3 is targeting the EXTCODESIZE value pushed in code_prefix. - else Op.DUP3 + Op.PUSH0 + Op.DUP4 + Op.CREATE2 - ) - - code = JumpLoopGenerator( - setup=setup, attack_block=attack_block - ).generate_repeated_code( - repeated_code=attack_block, setup=setup, fork=fork - ) - - tx = Transaction( - # Set enough balance in the pre-alloc for `value > 0` configurations. - to=pre.deploy_contract( - code=code, balance=1_000_000_000 if value > 0 else 0 - ), - sender=pre.fund_eoa(), - ) - - benchmark_test(tx=tx) - - -@pytest.mark.parametrize( - "opcode", - [ - Op.CREATE, - Op.CREATE2, - ], -) -def test_worst_creates_collisions( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - opcode: Op, - gas_benchmark_value: int, -) -> None: - """Test the CREATE and CREATE2 collisions performance.""" - # 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 - # "proxy contract" is that CREATE(2) failing with a collision will consume - # all the available gas. If we try to execute the CREATE(2) directly - # without being wrapped **and capped in gas** in a previous CALL, we would - # run out of gas very fast! - # The proxy contract calls CREATE(2) with empty initcode. The current call - # frame gas will be exhausted because of the collision. For this reason the - # caller will carefully give us the minimal gas necessary to execute the - # CREATE(2) and not waste any extra gas in the CREATE(2)-failure. - # Note that these CREATE(2) calls will fail because in (**) below we pre- - # alloc contracts with the same address as the ones that CREATE(2) will try - # to create. - proxy_contract = pre.deploy_contract( - code=Op.CREATE2( - value=Op.PUSH0, salt=Op.PUSH0, offset=Op.PUSH0, size=Op.PUSH0 - ) - if opcode == Op.CREATE2 - else Op.CREATE(value=Op.PUSH0, offset=Op.PUSH0, size=Op.PUSH0) - ) - - gas_costs = fork.gas_costs() - # The CALL to the proxy contract needs at a minimum gas corresponding to - # the CREATE(2) plus extra required PUSH0s for arguments. - min_gas_required = gas_costs.G_CREATE + gas_costs.G_BASE * ( - 3 if opcode == Op.CREATE else 4 - ) - setup = Op.PUSH20(proxy_contract) + Op.PUSH3(min_gas_required) - attack_block = Op.POP( - # DUP7 refers to the PUSH3 above. - # DUP7 refers to the proxy contract address. - Op.CALL(gas=Op.DUP7, address=Op.DUP7) - ) - - # (**) We deploy the contract that CREATE(2) will attempt to create so any - # attempt will fail. - if opcode == Op.CREATE2: - addr = compute_create2_address( - address=proxy_contract, salt=0, initcode=[] - ) - pre.deploy_contract(address=addr, code=Op.INVALID) - else: - # Heuristic to have an upper bound. - max_contract_count = 2 * gas_benchmark_value // gas_costs.G_CREATE - for nonce in range(max_contract_count): - addr = compute_create_address(address=proxy_contract, nonce=nonce) - pre.deploy_contract(address=addr, code=Op.INVALID) - - benchmark_test( - code_generator=JumpLoopGenerator( - setup=setup, attack_block=attack_block - ), - ) diff --git a/tests/benchmark/test_worst_compute.py b/tests/benchmark/test_worst_compute.py deleted file mode 100755 index bb839eb3e26..00000000000 --- 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_memory.py b/tests/benchmark/test_worst_memory.py deleted file mode 100755 index b71eac031d2..00000000000 --- a/tests/benchmark/test_worst_memory.py +++ /dev/null @@ -1,300 +0,0 @@ -""" -Tests that benchmark EVMs in the worst-case memory opcodes. -""" - -from enum import auto - -import pytest -from execution_testing import ( - Alloc, - BenchmarkTestFiller, - Bytecode, - Bytes, - Fork, - JumpLoopGenerator, - Op, - Transaction, -) - -REFERENCE_SPEC_GIT_PATH = "TODO" -REFERENCE_SPEC_VERSION = "TODO" - - -class CallDataOrigin: - """Enum for calldata origins.""" - - TRANSACTION = auto() - CALL = auto() - - -@pytest.mark.parametrize( - "origin", - [ - pytest.param(CallDataOrigin.TRANSACTION, id="transaction"), - pytest.param(CallDataOrigin.CALL, id="call"), - ], -) -@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, - ], -) -@pytest.mark.parametrize( - "non_zero_data", - [ - True, - False, - ], -) -def test_worst_calldatacopy( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - origin: CallDataOrigin, - size: int, - fixed_src_dst: bool, - non_zero_data: bool, - gas_benchmark_value: int, -) -> None: - """Test running a block filled with CALLDATACOPY executions.""" - if size == 0 and non_zero_data: - pytest.skip("Non-zero data with size 0 is not applicable.") - - # If `non_zero_data` is True, we fill the calldata with deterministic - # random data. Note that if `size == 0` and `non_zero_data` is a skipped - # case. - data = Bytes([i % 256 for i in range(size)]) if non_zero_data else Bytes() - - intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - min_gas = intrinsic_gas_calculator(calldata=data) - if min_gas > gas_benchmark_value: - pytest.skip( - "Minimum gas required for calldata ({min_gas}) is greater " - "than the gas limit" - ) - - # We create the contract that will be doing the CALLDATACOPY multiple - # times. - # - # If `non_zero_data` is True, we leverage CALLDATASIZE for the copy - # length. Otherwise, since we - # don't send zero data explicitly via calldata, PUSH the target size and - # use DUP1 to copy it. - setup = Bytecode() if non_zero_data or size == 0 else Op.PUSH3(size) - src_dst = 0 if fixed_src_dst else Op.MOD(Op.GAS, 7) - attack_block = Op.CALLDATACOPY( - src_dst, - src_dst, - Op.CALLDATASIZE if non_zero_data or size == 0 else Op.DUP1, - ) - - code_address = JumpLoopGenerator( - setup=setup, attack_block=attack_block - ).deploy_contracts(pre=pre, fork=fork) - - tx_target = code_address - - # If the origin is CALL, we need to create a contract that will call the - # target contract with the calldata. - if origin == CallDataOrigin.CALL: - # If `non_zero_data` is False we leverage just using zeroed memory. - # Otherwise, we copy the calldata received from the transaction. - setup = ( - Op.CALLDATACOPY(Op.PUSH0, Op.PUSH0, Op.CALLDATASIZE) - if non_zero_data - else Bytecode() - ) + Op.JUMPDEST - arg_size = Op.CALLDATASIZE if non_zero_data else size - attack_block = Op.STATICCALL( - address=code_address, args_offset=Op.PUSH0, args_size=arg_size - ) - - tx_target = JumpLoopGenerator( - setup=setup, attack_block=attack_block - ).deploy_contracts(pre=pre, fork=fork) - - tx = Transaction( - to=tx_target, - gas_limit=gas_benchmark_value, - data=data, - sender=pre.fund_eoa(), - ) - - benchmark_test(tx=tx) - - -@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_worst_codecopy( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - max_code_size_ratio: float, - fixed_src_dst: bool, -) -> 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. - - code = JumpLoopGenerator( - setup=setup, attack_block=attack_block - ).generate_repeated_code( - repeated_code=attack_block, setup=setup, fork=fork - ) - - # 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(), - ) - - benchmark_test(tx=tx) - - -@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_dst", - [ - True, - False, - ], -) -def test_worst_returndatacopy( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - size: int, - fixed_dst: bool, -) -> None: - """Test running a block filled with RETURNDATACOPY executions.""" - # 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 - # making the content - # predictable. If `size` is 0, this helper contract won't be used. - code = ( - Op.MSTORE8(0, Op.GAS) - + Op.MSTORE8(size // 2, Op.GAS) - + Op.MSTORE8(size - 1, Op.GAS) - + Op.RETURN(0, size) - ) - helper_contract = pre.deploy_contract(code=code) - - returndata_gen = ( - Op.STATICCALL(address=helper_contract) if size > 0 else Bytecode() - ) - dst = 0 if fixed_dst else Op.MOD(Op.GAS, 7) - - # We create the contract that will be doing the RETURNDATACOPY multiple - # times. - returndata_gen = ( - Op.STATICCALL(address=helper_contract) if size > 0 else Bytecode() - ) - 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, - attack_block=attack_block, - 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/test_worst_opcode.py b/tests/benchmark/test_worst_opcode.py deleted file mode 100755 index 18f488edd37..00000000000 --- a/tests/benchmark/test_worst_opcode.py +++ /dev/null @@ -1,75 +0,0 @@ -""" -Tests benchmark worst-case opcode scenarios. -""" - -import pytest -from execution_testing import ( - BenchmarkTestFiller, - Bytecode, - JumpLoopGenerator, - Op, - Opcode, -) - - -@pytest.mark.parametrize( - "opcode", - [ - pytest.param(Op.LOG0, id="log0"), - pytest.param(Op.LOG1, id="log1"), - pytest.param(Op.LOG2, id="log2"), - pytest.param(Op.LOG3, id="log3"), - pytest.param(Op.LOG4, id="log4"), - ], -) -@pytest.mark.parametrize( - "size,non_zero_data", - [ - pytest.param(0, False, id="0_bytes_data"), - pytest.param(1024 * 1024, False, id="1_MiB_zeros_data"), # 1 MiB - pytest.param(1024 * 1024, True, id="1_MiB_non_zero_data"), # 1 MiB - ], -) -@pytest.mark.parametrize( - "zeros_topic", - [ - pytest.param(True, id="zeros_topic"), - pytest.param(False, id="non_zero_topic"), - ], -) -@pytest.mark.parametrize("fixed_offset", [True, False]) -def test_worst_log_opcodes( - benchmark_test: BenchmarkTestFiller, - opcode: Opcode, - zeros_topic: bool, - size: int, - fixed_offset: bool, - non_zero_data: bool, -) -> None: - """Test running a block with as many LOG opcodes as possible.""" - setup = Bytecode() - - # For non-zero data, load into memory. - if non_zero_data: - setup += Op.CODECOPY(dest_offset=0, offset=0, size=Op.CODESIZE) - - # Push the size value onto the stack and access it using the DUP opcode. - setup += Op.PUSH3(size) - - # For non-zeros topic, push a non-zero value for topic. - setup += Op.PUSH0 if zeros_topic else Op.PUSH32(2**256 - 1) - - topic_count = len(opcode.kwargs or []) - 2 - offset = Op.PUSH0 if fixed_offset else Op.MOD(Op.GAS, 7) - - # Calculate the appropriate DUP opcode based on topic count - # 0 topics -> DUP1, 1 topic -> DUP2, N topics -> DUP(N+1) - size_op = getattr(Op, f"DUP{topic_count + 2}") - - attack_block = Op.DUP1 * topic_count + size_op + offset + opcode - - benchmark_test( - code_generator=JumpLoopGenerator( - setup=setup, attack_block=attack_block - ), - ) diff --git a/tests/benchmark/test_worst_stateful_opcodes.py b/tests/benchmark/test_worst_stateful_opcodes.py deleted file mode 100755 index fb9ed17090d..00000000000 --- 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, - ) From b63cde3cca1104af5186e1d1ac1f3f2079e22efa Mon Sep 17 00:00:00 2001 From: LouisTsai Date: Thu, 30 Oct 2025 22:20:08 +0800 Subject: [PATCH 09/11] refactor: rename the test name with prefix --- .../compute/instruction/{arithmetic.py => test_arithmetic.py} | 0 .../benchmark/compute/instruction/{bitwise.py => test_bitwise.py} | 0 tests/benchmark/compute/instruction/{block.py => test_block.py} | 0 .../compute/instruction/{comparison.py => test_comparison.py} | 0 .../compute/instruction/{control_flow.py => test_control_flow.py} | 0 .../compute/instruction/{environment.py => test_environment.py} | 0 tests/benchmark/compute/instruction/{keccak.py => test_keccak.py} | 0 tests/benchmark/compute/instruction/{log.py => test_log.py} | 0 tests/benchmark/compute/instruction/{memory.py => test_memory.py} | 0 tests/benchmark/compute/instruction/{stack.py => test_stack.py} | 0 .../benchmark/compute/instruction/{storage.py => test_storage.py} | 0 tests/benchmark/compute/instruction/{system.py => test_system.py} | 0 .../compute/precompile/{alt_bn128.py => test_alt_bn128.py} | 0 .../benchmark/compute/precompile/{blake2f.py => test_blake2f.py} | 0 .../compute/precompile/{bls12_381.py => test_bls12_381.py} | 0 .../compute/precompile/{ecrecover.py => test_ecrecover.py} | 0 .../compute/precompile/{identity.py => test_identity.py} | 0 tests/benchmark/compute/precompile/{modexp.py => test_modexp.py} | 0 .../compute/precompile/{p256verify.py => test_p256verify.py} | 0 .../precompile/{point_evaluation.py => test_point_evaluation.py} | 0 .../compute/precompile/{ripemd160.py => test_ripemd160.py} | 0 tests/benchmark/compute/precompile/{sha256.py => test_sha256.py} | 0 .../scenario/{mix_operations.py => test_mix_operations.py} | 0 .../scenario/{transaction_types.py => test_transaction_types.py} | 0 24 files changed, 0 insertions(+), 0 deletions(-) rename tests/benchmark/compute/instruction/{arithmetic.py => test_arithmetic.py} (100%) rename tests/benchmark/compute/instruction/{bitwise.py => test_bitwise.py} (100%) rename tests/benchmark/compute/instruction/{block.py => test_block.py} (100%) rename tests/benchmark/compute/instruction/{comparison.py => test_comparison.py} (100%) rename tests/benchmark/compute/instruction/{control_flow.py => test_control_flow.py} (100%) rename tests/benchmark/compute/instruction/{environment.py => test_environment.py} (100%) rename tests/benchmark/compute/instruction/{keccak.py => test_keccak.py} (100%) rename tests/benchmark/compute/instruction/{log.py => test_log.py} (100%) rename tests/benchmark/compute/instruction/{memory.py => test_memory.py} (100%) rename tests/benchmark/compute/instruction/{stack.py => test_stack.py} (100%) rename tests/benchmark/compute/instruction/{storage.py => test_storage.py} (100%) rename tests/benchmark/compute/instruction/{system.py => test_system.py} (100%) rename tests/benchmark/compute/precompile/{alt_bn128.py => test_alt_bn128.py} (100%) rename tests/benchmark/compute/precompile/{blake2f.py => test_blake2f.py} (100%) rename tests/benchmark/compute/precompile/{bls12_381.py => test_bls12_381.py} (100%) rename tests/benchmark/compute/precompile/{ecrecover.py => test_ecrecover.py} (100%) rename tests/benchmark/compute/precompile/{identity.py => test_identity.py} (100%) rename tests/benchmark/compute/precompile/{modexp.py => test_modexp.py} (100%) rename tests/benchmark/compute/precompile/{p256verify.py => test_p256verify.py} (100%) rename tests/benchmark/compute/precompile/{point_evaluation.py => test_point_evaluation.py} (100%) rename tests/benchmark/compute/precompile/{ripemd160.py => test_ripemd160.py} (100%) rename tests/benchmark/compute/precompile/{sha256.py => test_sha256.py} (100%) rename tests/benchmark/compute/scenario/{mix_operations.py => test_mix_operations.py} (100%) rename tests/benchmark/compute/scenario/{transaction_types.py => test_transaction_types.py} (100%) diff --git a/tests/benchmark/compute/instruction/arithmetic.py b/tests/benchmark/compute/instruction/test_arithmetic.py similarity index 100% rename from tests/benchmark/compute/instruction/arithmetic.py rename to tests/benchmark/compute/instruction/test_arithmetic.py diff --git a/tests/benchmark/compute/instruction/bitwise.py b/tests/benchmark/compute/instruction/test_bitwise.py similarity index 100% rename from tests/benchmark/compute/instruction/bitwise.py rename to tests/benchmark/compute/instruction/test_bitwise.py diff --git a/tests/benchmark/compute/instruction/block.py b/tests/benchmark/compute/instruction/test_block.py similarity index 100% rename from tests/benchmark/compute/instruction/block.py rename to tests/benchmark/compute/instruction/test_block.py diff --git a/tests/benchmark/compute/instruction/comparison.py b/tests/benchmark/compute/instruction/test_comparison.py similarity index 100% rename from tests/benchmark/compute/instruction/comparison.py rename to tests/benchmark/compute/instruction/test_comparison.py diff --git a/tests/benchmark/compute/instruction/control_flow.py b/tests/benchmark/compute/instruction/test_control_flow.py similarity index 100% rename from tests/benchmark/compute/instruction/control_flow.py rename to tests/benchmark/compute/instruction/test_control_flow.py diff --git a/tests/benchmark/compute/instruction/environment.py b/tests/benchmark/compute/instruction/test_environment.py similarity index 100% rename from tests/benchmark/compute/instruction/environment.py rename to tests/benchmark/compute/instruction/test_environment.py diff --git a/tests/benchmark/compute/instruction/keccak.py b/tests/benchmark/compute/instruction/test_keccak.py similarity index 100% rename from tests/benchmark/compute/instruction/keccak.py rename to tests/benchmark/compute/instruction/test_keccak.py diff --git a/tests/benchmark/compute/instruction/log.py b/tests/benchmark/compute/instruction/test_log.py similarity index 100% rename from tests/benchmark/compute/instruction/log.py rename to tests/benchmark/compute/instruction/test_log.py diff --git a/tests/benchmark/compute/instruction/memory.py b/tests/benchmark/compute/instruction/test_memory.py similarity index 100% rename from tests/benchmark/compute/instruction/memory.py rename to tests/benchmark/compute/instruction/test_memory.py diff --git a/tests/benchmark/compute/instruction/stack.py b/tests/benchmark/compute/instruction/test_stack.py similarity index 100% rename from tests/benchmark/compute/instruction/stack.py rename to tests/benchmark/compute/instruction/test_stack.py diff --git a/tests/benchmark/compute/instruction/storage.py b/tests/benchmark/compute/instruction/test_storage.py similarity index 100% rename from tests/benchmark/compute/instruction/storage.py rename to tests/benchmark/compute/instruction/test_storage.py diff --git a/tests/benchmark/compute/instruction/system.py b/tests/benchmark/compute/instruction/test_system.py similarity index 100% rename from tests/benchmark/compute/instruction/system.py rename to tests/benchmark/compute/instruction/test_system.py diff --git a/tests/benchmark/compute/precompile/alt_bn128.py b/tests/benchmark/compute/precompile/test_alt_bn128.py similarity index 100% rename from tests/benchmark/compute/precompile/alt_bn128.py rename to tests/benchmark/compute/precompile/test_alt_bn128.py diff --git a/tests/benchmark/compute/precompile/blake2f.py b/tests/benchmark/compute/precompile/test_blake2f.py similarity index 100% rename from tests/benchmark/compute/precompile/blake2f.py rename to tests/benchmark/compute/precompile/test_blake2f.py diff --git a/tests/benchmark/compute/precompile/bls12_381.py b/tests/benchmark/compute/precompile/test_bls12_381.py similarity index 100% rename from tests/benchmark/compute/precompile/bls12_381.py rename to tests/benchmark/compute/precompile/test_bls12_381.py diff --git a/tests/benchmark/compute/precompile/ecrecover.py b/tests/benchmark/compute/precompile/test_ecrecover.py similarity index 100% rename from tests/benchmark/compute/precompile/ecrecover.py rename to tests/benchmark/compute/precompile/test_ecrecover.py diff --git a/tests/benchmark/compute/precompile/identity.py b/tests/benchmark/compute/precompile/test_identity.py similarity index 100% rename from tests/benchmark/compute/precompile/identity.py rename to tests/benchmark/compute/precompile/test_identity.py diff --git a/tests/benchmark/compute/precompile/modexp.py b/tests/benchmark/compute/precompile/test_modexp.py similarity index 100% rename from tests/benchmark/compute/precompile/modexp.py rename to tests/benchmark/compute/precompile/test_modexp.py diff --git a/tests/benchmark/compute/precompile/p256verify.py b/tests/benchmark/compute/precompile/test_p256verify.py similarity index 100% rename from tests/benchmark/compute/precompile/p256verify.py rename to tests/benchmark/compute/precompile/test_p256verify.py diff --git a/tests/benchmark/compute/precompile/point_evaluation.py b/tests/benchmark/compute/precompile/test_point_evaluation.py similarity index 100% rename from tests/benchmark/compute/precompile/point_evaluation.py rename to tests/benchmark/compute/precompile/test_point_evaluation.py diff --git a/tests/benchmark/compute/precompile/ripemd160.py b/tests/benchmark/compute/precompile/test_ripemd160.py similarity index 100% rename from tests/benchmark/compute/precompile/ripemd160.py rename to tests/benchmark/compute/precompile/test_ripemd160.py diff --git a/tests/benchmark/compute/precompile/sha256.py b/tests/benchmark/compute/precompile/test_sha256.py similarity index 100% rename from tests/benchmark/compute/precompile/sha256.py rename to tests/benchmark/compute/precompile/test_sha256.py diff --git a/tests/benchmark/compute/scenario/mix_operations.py b/tests/benchmark/compute/scenario/test_mix_operations.py similarity index 100% rename from tests/benchmark/compute/scenario/mix_operations.py rename to tests/benchmark/compute/scenario/test_mix_operations.py diff --git a/tests/benchmark/compute/scenario/transaction_types.py b/tests/benchmark/compute/scenario/test_transaction_types.py similarity index 100% rename from tests/benchmark/compute/scenario/transaction_types.py rename to tests/benchmark/compute/scenario/test_transaction_types.py From ff299677205637e19638006736b1fb6f070f4507 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Thu, 30 Oct 2025 16:28:36 +0000 Subject: [PATCH 10/11] fix(tests): Fixture name --- .../compute/scenario/test_transaction_types.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/benchmark/compute/scenario/test_transaction_types.py b/tests/benchmark/compute/scenario/test_transaction_types.py index 1c27cf5a9fb..cb0a4f4b64a 100644 --- a/tests/benchmark/compute/scenario/test_transaction_types.py +++ b/tests/benchmark/compute/scenario/test_transaction_types.py @@ -241,17 +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, + 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, @@ -285,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) From 5f001b6ce89a8539776e9fe7da2f40a504bc41c9 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Thu, 30 Oct 2025 18:20:26 +0000 Subject: [PATCH 11/11] refactor(tests): Split test_environment.py --- ...t_environment.py => test_account_query.py} | 552 ++++-------------- .../{test_block.py => test_block_context.py} | 13 +- .../compute/instruction/test_call_context.py | 327 +++++++++++ .../compute/instruction/test_memory.py | 2 +- .../compute/instruction/test_storage.py | 1 - .../compute/instruction/test_tx_context.py | 65 +++ .../compute/precompile/test_modexp.py | 2 +- .../compute/scenario/test_mix_operations.py | 51 -- 8 files changed, 528 insertions(+), 485 deletions(-) rename tests/benchmark/compute/instruction/{test_environment.py => test_account_query.py} (57%) rename tests/benchmark/compute/instruction/{test_block.py => test_block_context.py} (78%) create mode 100644 tests/benchmark/compute/instruction/test_call_context.py create mode 100644 tests/benchmark/compute/instruction/test_tx_context.py diff --git a/tests/benchmark/compute/instruction/test_environment.py b/tests/benchmark/compute/instruction/test_account_query.py similarity index 57% rename from tests/benchmark/compute/instruction/test_environment.py rename to tests/benchmark/compute/instruction/test_account_query.py index ebad51f36a8..1190194f772 100644 --- a/tests/benchmark/compute/instruction/test_environment.py +++ b/tests/benchmark/compute/instruction/test_account_query.py @@ -1,7 +1,9 @@ -"""Benchmark environmental instructions.""" +""" +Benchmark operations that require querying the account state, either on the +current executing account or on a target account. +""" import math -from typing import Any, Dict import pytest from execution_testing import ( @@ -12,7 +14,6 @@ Block, BlockchainTestFiller, Bytecode, - Bytes, Environment, ExtCallGenerator, Fork, @@ -20,24 +21,90 @@ JumpLoopGenerator, Op, Transaction, - TransactionType, While, - add_kzg_version, compute_create2_address, ) from tests.benchmark.compute.helpers import ( XOR_TABLE, - CallDataOrigin, - ReturnDataStyle, ) -from tests.cancun.eip4844_blobs.spec import Spec as BlobsSpec -# Environmental instructions: -# ADDRESS, BALANCE, ORIGIN, CALLER, CALLVALUE -# CALLDATALOAD, CALLDATASIZE, CALLDATACOPY, CODESIZE, CODECOPY, GASPRICE, -# EXTCODESIZE, EXTCODECOPY, RETURNDATASIZE, RETURNDATACOPY, EXTCODEHASH, -# SELFBALANCE, BASEFEE, BLOBHASH, BLOBBASEFEE + +@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( @@ -246,343 +313,35 @@ def test_extcode_ops( @pytest.mark.parametrize( - "opcode", - [ - Op.ADDRESS, - Op.ORIGIN, - Op.CALLER, - Op.CODESIZE, - Op.GASPRICE, - Op.BASEFEE, - Op.BLOBBASEFEE, - # Note that other 0-param opcodes are covered in separate tests. - ], -) -def test_environment_ops( - benchmark_test: BenchmarkTestFiller, - opcode: Op, -) -> None: - """Benchmark environmental zero-parameter instructions.""" - benchmark_test( - code_generator=ExtCallGenerator(attack_block=opcode), - ) - - -@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( - "return_data_style", - [ - ReturnDataStyle.RETURN, - ReturnDataStyle.REVERT, - ReturnDataStyle.IDENTITY, - ], -) -@pytest.mark.parametrize("returned_size", [1, 0]) -def test_returndatasize_nonzero( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - returned_size: int, - return_data_style: ReturnDataStyle, -) -> None: - """ - 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, - ) - - benchmark_test( - code_generator=JumpLoopGenerator( - setup=setup, attack_block=Op.POP(Op.RETURNDATASIZE) - ), - ) - - -def test_returndatasize_zero( - benchmark_test: BenchmarkTestFiller, -) -> None: - """Benchmark RETURNDATASIZE instruction with zero buffer.""" - benchmark_test( - code_generator=ExtCallGenerator(attack_block=Op.RETURNDATASIZE), - ) - - -@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, - ), - ) - - -@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( - "origin", - [ - pytest.param(CallDataOrigin.TRANSACTION, id="transaction"), - pytest.param(CallDataOrigin.CALL, id="call"), - ], -) -@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, - ], -) -@pytest.mark.parametrize( - "non_zero_data", + "copied_size", [ - True, - False, + pytest.param(512, id="512"), + pytest.param(1024, id="1KiB"), + pytest.param(5 * 1024, id="5KiB"), ], ) -def test_calldatacopy( +def test_extcodecopy_warm( benchmark_test: BenchmarkTestFiller, pre: Alloc, - fork: Fork, - origin: CallDataOrigin, - size: int, - fixed_src_dst: bool, - non_zero_data: bool, + copied_size: int, gas_benchmark_value: int, ) -> None: - """Benchmark CALLDATACOPY instruction.""" - if size == 0 and non_zero_data: - pytest.skip("Non-zero data with size 0 is not applicable.") - - # If `non_zero_data` is True, we fill the calldata with deterministic - # random data. Note that if `size == 0` and `non_zero_data` is a skipped - # case. - data = Bytes([i % 256 for i in range(size)]) if non_zero_data else Bytes() - - intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - min_gas = intrinsic_gas_calculator(calldata=data) - if min_gas > gas_benchmark_value: - pytest.skip( - "Minimum gas required for calldata ({min_gas}) is greater " - "than the gas limit" - ) - - # We create the contract that will be doing the CALLDATACOPY multiple - # times. - # - # If `non_zero_data` is True, we leverage CALLDATASIZE for the copy - # length. Otherwise, since we - # don't send zero data explicitly via calldata, PUSH the target size and - # use DUP1 to copy it. - setup = Bytecode() if non_zero_data or size == 0 else Op.PUSH3(size) - src_dst = 0 if fixed_src_dst else Op.MOD(Op.GAS, 7) - attack_block = Op.CALLDATACOPY( - src_dst, - src_dst, - Op.CALLDATASIZE if non_zero_data or size == 0 else Op.DUP1, + """Benchmark EXTCODECOPY instruction.""" + copied_contract_address = pre.deploy_contract( + code=Op.JUMPDEST * copied_size, ) - code_address = JumpLoopGenerator( - setup=setup, attack_block=attack_block - ).deploy_contracts(pre=pre, fork=fork) - - tx_target = code_address - - # If the origin is CALL, we need to create a contract that will call the - # target contract with the calldata. - if origin == CallDataOrigin.CALL: - # If `non_zero_data` is False we leverage just using zeroed memory. - # Otherwise, we copy the calldata received from the transaction. - setup = ( - Op.CALLDATACOPY(Op.PUSH0, Op.PUSH0, Op.CALLDATASIZE) - if non_zero_data - else Bytecode() - ) + Op.JUMPDEST - arg_size = Op.CALLDATASIZE if non_zero_data else size - attack_block = Op.STATICCALL( - address=code_address, args_offset=Op.PUSH0, args_size=arg_size + execution_code = ( + Op.PUSH10(copied_size) + + Op.PUSH20(copied_contract_address) + + While( + body=Op.EXTCODECOPY(Op.DUP4, 0, 0, Op.DUP2), ) - - tx_target = JumpLoopGenerator( - setup=setup, attack_block=attack_block - ).deploy_contracts(pre=pre, fork=fork) - - tx = Transaction( - to=tx_target, - gas_limit=gas_benchmark_value, - data=data, - sender=pre.fund_eoa(), - ) - - benchmark_test(tx=tx) - - -@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}." - ) - + execution_code_address = pre.deploy_contract(code=execution_code) tx = Transaction( - to=pre.deploy_contract(code=code), + to=execution_code_address, + gas_limit=gas_benchmark_value, sender=pre.fund_eoa(), ) @@ -590,58 +349,49 @@ def test_codecopy( @pytest.mark.parametrize( - "size", + "opcode", [ - pytest.param(0, id="0 bytes"), - pytest.param(100, id="100 bytes"), - pytest.param(10 * 1024, id="10KiB"), - pytest.param(1024 * 1024, id="1MiB"), + Op.BALANCE, + Op.EXTCODESIZE, + Op.EXTCODEHASH, + Op.CALL, + Op.CALLCODE, + Op.DELEGATECALL, + Op.STATICCALL, ], ) @pytest.mark.parametrize( - "fixed_dst", + "absent_target", [ True, False, ], ) -def test_returndatacopy( +def test_ext_account_query_warm( benchmark_test: BenchmarkTestFiller, pre: Alloc, - size: int, - fixed_dst: bool, + opcode: Op, + absent_target: bool, ) -> None: - """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 - # making the content - # predictable. If `size` is 0, this helper contract won't be used. - code = ( - Op.MSTORE8(0, Op.GAS) - + Op.MSTORE8(size // 2, Op.GAS) - + Op.MSTORE8(size - 1, Op.GAS) - + Op.RETURN(0, size) - ) - helper_contract = pre.deploy_contract(code=code) - - returndata_gen = ( - Op.STATICCALL(address=helper_contract) if size > 0 else Bytecode() - ) - dst = 0 if fixed_dst else Op.MOD(Op.GAS, 7) - - # We create the contract that will be doing the RETURNDATACOPY multiple - # times. - returndata_gen = ( - Op.STATICCALL(address=helper_contract) if size > 0 else Bytecode() - ) - attack_block = Op.RETURNDATACOPY(dst, Op.PUSH0, Op.RETURNDATASIZE) + """ + 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=returndata_gen, - attack_block=attack_block, - cleanup=returndata_gen, + setup=setup, attack_block=attack_block ), ) @@ -659,7 +409,7 @@ def test_returndatacopy( False, ], ) -def test_worst_address_state_cold( +def test_ext_account_query_cold( benchmark_test: BenchmarkTestFiller, pre: Alloc, fork: Fork, @@ -741,53 +491,3 @@ def test_worst_address_state_cold( post=post, blocks=blocks, ) - - -@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, - ), - ) - - -@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) diff --git a/tests/benchmark/compute/instruction/test_block.py b/tests/benchmark/compute/instruction/test_block_context.py similarity index 78% rename from tests/benchmark/compute/instruction/test_block.py rename to tests/benchmark/compute/instruction/test_block_context.py index 18595e833eb..204f87d1703 100644 --- a/tests/benchmark/compute/instruction/test_block.py +++ b/tests/benchmark/compute/instruction/test_block_context.py @@ -1,4 +1,4 @@ -"""Benchmark block instructions.""" +"""Benchmark block context instructions.""" import pytest from execution_testing import ( @@ -8,8 +8,9 @@ Op, ) -# Block instructions: -# BLOCKHASH, COINBASE, TIMESTAMP, NUMBER, PREVRANDAO, GASLIMIT, CHAINID +# Block context instructions: +# BLOCKHASH, COINBASE, TIMESTAMP, NUMBER, PREVRANDAO, GASLIMIT, CHAINID, +# BASEFEE, BLOBBASEFEE @pytest.mark.parametrize( @@ -21,13 +22,15 @@ Op.PREVRANDAO, Op.GASLIMIT, Op.CHAINID, + Op.BASEFEE, + Op.BLOBBASEFEE, ], ) -def test_block_ops( +def test_block_context_ops( benchmark_test: BenchmarkTestFiller, opcode: Op, ) -> None: - """Benchmark block zero-parameter instructions.""" + """Benchmark zero-parameter block context instructions.""" benchmark_test( code_generator=ExtCallGenerator(attack_block=opcode), ) diff --git a/tests/benchmark/compute/instruction/test_call_context.py b/tests/benchmark/compute/instruction/test_call_context.py new file mode 100644 index 00000000000..1405ccb7246 --- /dev/null +++ b/tests/benchmark/compute/instruction/test_call_context.py @@ -0,0 +1,327 @@ +"""Benchmark call frame context instructions.""" + +import pytest +from execution_testing import ( + Alloc, + BenchmarkTestFiller, + Bytecode, + Bytes, + ExtCallGenerator, + Fork, + JumpLoopGenerator, + Op, + Transaction, +) + +from tests.benchmark.compute.helpers import ( + CallDataOrigin, + ReturnDataStyle, +) + + +@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), + ) + + +@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( + "origin", + [ + pytest.param(CallDataOrigin.TRANSACTION, id="transaction"), + pytest.param(CallDataOrigin.CALL, id="call"), + ], +) +@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, + ], +) +@pytest.mark.parametrize( + "non_zero_data", + [ + True, + False, + ], +) +def test_calldatacopy( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + origin: CallDataOrigin, + size: int, + fixed_src_dst: bool, + non_zero_data: bool, + gas_benchmark_value: int, +) -> None: + """Benchmark CALLDATACOPY instruction.""" + if size == 0 and non_zero_data: + pytest.skip("Non-zero data with size 0 is not applicable.") + + # If `non_zero_data` is True, we fill the calldata with deterministic + # random data. Note that if `size == 0` and `non_zero_data` is a skipped + # case. + data = Bytes([i % 256 for i in range(size)]) if non_zero_data else Bytes() + + intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() + min_gas = intrinsic_gas_calculator(calldata=data) + if min_gas > gas_benchmark_value: + pytest.skip( + "Minimum gas required for calldata ({min_gas}) is greater " + "than the gas limit" + ) + + # We create the contract that will be doing the CALLDATACOPY multiple + # times. + # + # If `non_zero_data` is True, we leverage CALLDATASIZE for the copy + # length. Otherwise, since we + # don't send zero data explicitly via calldata, PUSH the target size and + # use DUP1 to copy it. + setup = Bytecode() if non_zero_data or size == 0 else Op.PUSH3(size) + src_dst = 0 if fixed_src_dst else Op.MOD(Op.GAS, 7) + attack_block = Op.CALLDATACOPY( + src_dst, + src_dst, + Op.CALLDATASIZE if non_zero_data or size == 0 else Op.DUP1, + ) + + code_address = JumpLoopGenerator( + setup=setup, attack_block=attack_block + ).deploy_contracts(pre=pre, fork=fork) + + tx_target = code_address + + # If the origin is CALL, we need to create a contract that will call the + # target contract with the calldata. + if origin == CallDataOrigin.CALL: + # If `non_zero_data` is False we leverage just using zeroed memory. + # Otherwise, we copy the calldata received from the transaction. + setup = ( + Op.CALLDATACOPY(Op.PUSH0, Op.PUSH0, Op.CALLDATASIZE) + if non_zero_data + else Bytecode() + ) + Op.JUMPDEST + arg_size = Op.CALLDATASIZE if non_zero_data else size + attack_block = Op.STATICCALL( + address=code_address, args_offset=Op.PUSH0, args_size=arg_size + ) + + tx_target = JumpLoopGenerator( + setup=setup, attack_block=attack_block + ).deploy_contracts(pre=pre, fork=fork) + + tx = Transaction( + to=tx_target, + gas_limit=gas_benchmark_value, + data=data, + sender=pre.fund_eoa(), + ) + + benchmark_test(tx=tx) + + +@pytest.mark.parametrize( + "return_data_style", + [ + ReturnDataStyle.RETURN, + ReturnDataStyle.REVERT, + ReturnDataStyle.IDENTITY, + ], +) +@pytest.mark.parametrize("returned_size", [1, 0]) +def test_returndatasize_nonzero( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + returned_size: int, + return_data_style: ReturnDataStyle, +) -> None: + """ + 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, + ) + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=setup, attack_block=Op.POP(Op.RETURNDATASIZE) + ), + ) + + +def test_returndatasize_zero( + benchmark_test: BenchmarkTestFiller, +) -> None: + """Benchmark RETURNDATASIZE instruction with zero buffer.""" + benchmark_test( + code_generator=ExtCallGenerator(attack_block=Op.RETURNDATASIZE), + ) + + +@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_dst", + [ + True, + False, + ], +) +def test_returndatacopy( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + size: int, + fixed_dst: bool, +) -> None: + """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 + # making the content + # predictable. If `size` is 0, this helper contract won't be used. + code = ( + Op.MSTORE8(0, Op.GAS) + + Op.MSTORE8(size // 2, Op.GAS) + + Op.MSTORE8(size - 1, Op.GAS) + + Op.RETURN(0, size) + ) + helper_contract = pre.deploy_contract(code=code) + + returndata_gen = ( + Op.STATICCALL(address=helper_contract) if size > 0 else Bytecode() + ) + dst = 0 if fixed_dst else Op.MOD(Op.GAS, 7) + + # We create the contract that will be doing the RETURNDATACOPY multiple + # times. + returndata_gen = ( + Op.STATICCALL(address=helper_contract) if size > 0 else Bytecode() + ) + attack_block = Op.RETURNDATACOPY(dst, Op.PUSH0, Op.RETURNDATASIZE) + + benchmark_test( + code_generator=JumpLoopGenerator( + setup=returndata_gen, + attack_block=attack_block, + cleanup=returndata_gen, + ), + ) diff --git a/tests/benchmark/compute/instruction/test_memory.py b/tests/benchmark/compute/instruction/test_memory.py index 07613a8f073..d939b1bce79 100644 --- a/tests/benchmark/compute/instruction/test_memory.py +++ b/tests/benchmark/compute/instruction/test_memory.py @@ -81,7 +81,7 @@ def test_memory_access( False, ], ) -def test_worst_mcopy( +def test_mcopy( benchmark_test: BenchmarkTestFiller, size: int, fixed_src_dst: bool, diff --git a/tests/benchmark/compute/instruction/test_storage.py b/tests/benchmark/compute/instruction/test_storage.py index 4ff106b5fad..a07673c35f4 100644 --- a/tests/benchmark/compute/instruction/test_storage.py +++ b/tests/benchmark/compute/instruction/test_storage.py @@ -22,7 +22,6 @@ # SLOAD, SSTORE, TLOAD, TSTORE -# test_worst_tload -> test_tload # `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 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 00000000000..44d128f27aa --- /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_modexp.py b/tests/benchmark/compute/precompile/test_modexp.py index b1cca3e5976..ef196735d46 100644 --- a/tests/benchmark/compute/precompile/test_modexp.py +++ b/tests/benchmark/compute/precompile/test_modexp.py @@ -394,7 +394,7 @@ def create_modexp_test_cases() -> list[ParameterSet]: ["mod_exp_input"], create_modexp_test_cases(), ) -def test_worst_modexp( +def test_modexp( benchmark_test: BenchmarkTestFiller, mod_exp_input: ModExpInput, ) -> None: diff --git a/tests/benchmark/compute/scenario/test_mix_operations.py b/tests/benchmark/compute/scenario/test_mix_operations.py index 828c7bbe70c..bd534a0d356 100644 --- a/tests/benchmark/compute/scenario/test_mix_operations.py +++ b/tests/benchmark/compute/scenario/test_mix_operations.py @@ -2,9 +2,6 @@ import pytest from execution_testing import ( - Account, - Address, - Alloc, BenchmarkTestFiller, Bytecode, Fork, @@ -88,51 +85,3 @@ def test_jumpdest_analysis( tx_kwargs={"data": tx_data}, ), ) - - -@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 - ), - )