diff --git a/packages/testing/src/execution_testing/benchmark/benchmark_code_generator.py b/packages/testing/src/execution_testing/benchmark/benchmark_code_generator.py index 6ced64ea5de..6698ea92a28 100644 --- a/packages/testing/src/execution_testing/benchmark/benchmark_code_generator.py +++ b/packages/testing/src/execution_testing/benchmark/benchmark_code_generator.py @@ -16,6 +16,8 @@ class JumpLoopGenerator(BenchmarkCodeGenerator): """Generates bytecode that loops execution using JUMP operations.""" + contract_balance: int = 0 + def deploy_contracts(self, *, pre: Alloc, fork: Fork) -> Address: """Deploy the looping contract.""" # Benchmark Test Structure: @@ -28,7 +30,9 @@ def deploy_contracts(self, *, pre: Alloc, fork: Fork) -> Address: cleanup=self.cleanup, fork=fork, ) - self._contract_address = pre.deploy_contract(code=code) + self._contract_address = pre.deploy_contract( + code=code, balance=self.contract_balance + ) return self._contract_address @@ -49,20 +53,39 @@ def deploy_contracts(self, *, pre: Alloc, fork: Fork) -> Address: # but not loop (e.g. PUSH) # 2. The loop contract that calls the target contract in a loop - pushed_stack_items = self.attack_block.pushed_stack_items - popped_stack_items = self.attack_block.popped_stack_items - stack_delta = pushed_stack_items - popped_stack_items + attack_block_stack_delta = ( + self.attack_block.pushed_stack_items + - self.attack_block.popped_stack_items + ) + assert attack_block_stack_delta >= 0, ( + "attack block stack delta must be non-negative" + ) + + setup_stack_delta = ( + self.setup.pushed_stack_items - self.setup.popped_stack_items + ) + assert setup_stack_delta >= 0, "setup stack delta must be non-negative" max_iterations = fork.max_code_size() // len(self.attack_block) + max_stack_height = fork.max_stack_height() - setup_stack_delta - if stack_delta > 0: + if attack_block_stack_delta > 0: max_iterations = min( - fork.max_stack_height() // stack_delta, max_iterations + max_stack_height // attack_block_stack_delta, max_iterations ) + code = self.setup + self.attack_block * max_iterations + # Pad the code to the maximum code size. + if self.code_padding_opcode is not None: + code += self.code_padding_opcode * ( + fork.max_code_size() - len(code) + ) + + self._validate_code_size(code, fork) + # Deploy target contract that contains the actual attack block self._target_contract_address = pre.deploy_contract( - code=self.setup + self.attack_block * max_iterations, + code=code, balance=self.contract_balance, ) @@ -74,11 +97,22 @@ def deploy_contracts(self, *, pre: Alloc, fork: Fork) -> Address: # setup + JUMPDEST + attack + attack + ... + attack + # JUMP(setup_length) code_sequence = Op.POP( - Op.STATICCALL(Op.GAS, self._target_contract_address, 0, 0, 0, 0) + Op.STATICCALL( + Op.GAS, + self._target_contract_address, + Op.PUSH0, + Op.CALLDATASIZE, + Op.PUSH0, + Op.PUSH0, + ) ) caller_code = self.generate_repeated_code( - repeated_code=code_sequence, cleanup=self.cleanup, fork=fork + setup=Op.CALLDATACOPY(Op.PUSH0, Op.PUSH0, Op.CALLDATASIZE), + repeated_code=code_sequence, + cleanup=self.cleanup, + fork=fork, ) + self._contract_address = pre.deploy_contract(code=caller_code) return self._contract_address diff --git a/packages/testing/src/execution_testing/specs/benchmark.py b/packages/testing/src/execution_testing/specs/benchmark.py index 1b0d7697f18..00078d68669 100644 --- a/packages/testing/src/execution_testing/specs/benchmark.py +++ b/packages/testing/src/execution_testing/specs/benchmark.py @@ -53,6 +53,7 @@ class BenchmarkCodeGenerator(ABC): setup: Bytecode = field(default_factory=Bytecode) cleanup: Bytecode = field(default_factory=Bytecode) tx_kwargs: Dict[str, Any] = field(default_factory=dict) + code_padding_opcode: Op | None = None _contract_address: Address | None = None @abstractmethod @@ -104,6 +105,9 @@ def generate_repeated_code( # TODO: Unify the PUSH0 and PUSH1 usage. code = setup + Op.JUMPDEST + repeated_code * max_iterations + cleanup code += Op.JUMP(len(setup)) if len(setup) > 0 else Op.PUSH0 + Op.JUMP + # Pad the code to the maximum code size. + if self.code_padding_opcode is not None: + code += self.code_padding_opcode * (max_code_size - len(code)) self._validate_code_size(code, fork) return code diff --git a/tests/benchmark/compute/instruction/test_account_query.py b/tests/benchmark/compute/instruction/test_account_query.py index 1190194f772..ad83dcb775c 100644 --- a/tests/benchmark/compute/instruction/test_account_query.py +++ b/tests/benchmark/compute/instruction/test_account_query.py @@ -4,6 +4,7 @@ """ import math +from typing import Any import pytest from execution_testing import ( @@ -72,7 +73,6 @@ def test_codesize( ) def test_codecopy( benchmark_test: BenchmarkTestFiller, - pre: Alloc, fork: Fork, max_code_size_ratio: float, fixed_src_dst: bool, @@ -86,26 +86,14 @@ def test_codecopy( 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( + code_generator=JumpLoopGenerator( + setup=setup, + attack_block=attack_block, + code_padding_opcode=Op.STOP, + ) ) - benchmark_test(tx=tx) - @pytest.mark.parametrize( "opcode", @@ -361,7 +349,21 @@ def test_extcodecopy_warm( ], ) @pytest.mark.parametrize( - "absent_target", + "empty_code", + [ + True, + False, + ], +) +@pytest.mark.parametrize( + "initial_balance", + [ + True, + False, + ], +) +@pytest.mark.parametrize( + "initial_storage", [ True, False, @@ -371,27 +373,39 @@ def test_ext_account_query_warm( benchmark_test: BenchmarkTestFiller, pre: Alloc, opcode: Op, - absent_target: bool, + empty_code: bool, + initial_balance: bool, + initial_storage: bool, ) -> None: """ Test running a block with as many stateful opcodes doing warm access for an account. """ # Setup - target_addr = pre.empty_account() post = {} - if not absent_target: - code = Op.STOP + Op.JUMPDEST * 100 - target_addr = pre.deploy_contract(balance=100, code=code) - post[target_addr] = Account(balance=100, code=code) - # Execution - setup = Op.MSTORE(0, target_addr) - attack_block = Op.POP(opcode(address=Op.MLOAD(0))) + if not initial_balance and not initial_storage and empty_code: + target_addr = pre.empty_account() + else: + kwargs: dict[str, Any] = {} + if initial_balance: + kwargs["balance"] = 100 + if initial_storage: + kwargs["storage"] = {0: 0x1337} + + if empty_code: + target_addr = pre.fund_eoa(**kwargs) + else: + code = Op.STOP + Op.JUMPDEST * 100 + kwargs["code"] = code + target_addr = pre.deploy_contract(**kwargs) + post[target_addr] = Account(**kwargs) + benchmark_test( post=post, code_generator=JumpLoopGenerator( - setup=setup, attack_block=attack_block + setup=Op.MSTORE(0, target_addr), + attack_block=Op.POP(opcode(address=Op.MLOAD(0))), ), ) diff --git a/tests/benchmark/compute/instruction/test_block_context.py b/tests/benchmark/compute/instruction/test_block_context.py index 204f87d1703..0f9b4fad447 100644 --- a/tests/benchmark/compute/instruction/test_block_context.py +++ b/tests/benchmark/compute/instruction/test_block_context.py @@ -36,14 +36,29 @@ def test_block_context_ops( ) +@pytest.mark.parametrize( + "index", + [ + 0, + 1, + 256, + 257, + pytest.param(None, id="random"), + ], +) def test_blockhash( benchmark_test: BenchmarkTestFiller, + index: int | None, ) -> None: """Benchmark BLOCKHASH instruction accessing oldest allowed block.""" # Create 256 dummy blocks to fill the blockhash window. blocks = [Block()] * 256 + block_number = Op.AND(Op.GAS, 0xFF) if index is None else index + benchmark_test( setup_blocks=blocks, - code_generator=ExtCallGenerator(attack_block=Op.BLOCKHASH(1)), + code_generator=ExtCallGenerator( + attack_block=Op.BLOCKHASH(block_number) + ), ) diff --git a/tests/benchmark/compute/instruction/test_call_context.py b/tests/benchmark/compute/instruction/test_call_context.py index 1405ccb7246..f9177683dc1 100644 --- a/tests/benchmark/compute/instruction/test_call_context.py +++ b/tests/benchmark/compute/instruction/test_call_context.py @@ -43,8 +43,8 @@ def test_calldatasize( ) -> None: """Benchmark CALLDATASIZE instruction.""" benchmark_test( - code_generator=JumpLoopGenerator( - attack_block=Op.POP(Op.CALLDATASIZE), + code_generator=ExtCallGenerator( + attack_block=Op.CALLDATASIZE, tx_kwargs={"data": b"\x00" * calldata_length}, ), ) @@ -149,7 +149,7 @@ def test_calldatacopy( size: int, fixed_src_dst: bool, non_zero_data: bool, - gas_benchmark_value: int, + tx_gas_limit: int, ) -> None: """Benchmark CALLDATACOPY instruction.""" if size == 0 and non_zero_data: @@ -162,7 +162,7 @@ def test_calldatacopy( intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() min_gas = intrinsic_gas_calculator(calldata=data) - if min_gas > gas_benchmark_value: + if min_gas > tx_gas_limit: pytest.skip( "Minimum gas required for calldata ({min_gas}) is greater " "than the gas limit" @@ -210,7 +210,6 @@ def test_calldatacopy( tx = Transaction( to=tx_target, - gas_limit=gas_benchmark_value, data=data, sender=pre.fund_eoa(), ) @@ -311,11 +310,6 @@ def test_returndatacopy( ) 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( diff --git a/tests/benchmark/compute/instruction/test_control_flow.py b/tests/benchmark/compute/instruction/test_control_flow.py index f3a2d696cd7..c44b0c51c2f 100644 --- a/tests/benchmark/compute/instruction/test_control_flow.py +++ b/tests/benchmark/compute/instruction/test_control_flow.py @@ -22,6 +22,15 @@ def test_gas_op( ) +def test_pc_op( + benchmark_test: BenchmarkTestFiller, +) -> None: + """Benchmark PC instruction.""" + benchmark_test( + code_generator=ExtCallGenerator(attack_block=Op.PC), + ) + + def test_jumps( benchmark_test: BenchmarkTestFiller, pre: Alloc, diff --git a/tests/benchmark/compute/instruction/test_keccak.py b/tests/benchmark/compute/instruction/test_keccak.py index 28edd306bd9..f34bd3e9eb8 100644 --- a/tests/benchmark/compute/instruction/test_keccak.py +++ b/tests/benchmark/compute/instruction/test_keccak.py @@ -2,6 +2,7 @@ import math +import pytest from execution_testing import ( BenchmarkTestFiller, Fork, @@ -15,15 +16,15 @@ KECCAK_RATE = 136 -def test_keccak( +def test_keccak_max_permutations( benchmark_test: BenchmarkTestFiller, fork: Fork, - gas_benchmark_value: int, + tx_gas_limit: int, ) -> None: - """Benchmark KECCAK256 instruction.""" + """Benchmark KECCAK256 instruction to maximize permutations per block.""" # Intrinsic gas cost is paid once. intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - available_gas = gas_benchmark_value - intrinsic_gas_calculator() + available_gas = tx_gas_limit - intrinsic_gas_calculator() gsc = fork.gas_costs() mem_exp_gas_calculator = fork.memory_expansion_gas_calculator() @@ -64,3 +65,20 @@ def test_keccak( attack_block=Op.POP(Op.SHA3(Op.PUSH0, Op.DUP1)), ), ) + + +@pytest.mark.parametrize("mem_alloc", [b"", b"ff", b"ff" * 32]) +@pytest.mark.parametrize("offset", [0, 31, 1024]) +def test_keccak( + benchmark_test: BenchmarkTestFiller, + offset: int, + mem_alloc: bytes, +) -> None: + """Benchmark KECCAK256 instruction with diff input data and offsets.""" + benchmark_test( + code_generator=JumpLoopGenerator( + setup=Op.CALLDATACOPY(offset, Op.PUSH0, Op.CALLDATASIZE), + attack_block=Op.POP(Op.SHA3(offset, Op.CALLDATASIZE)), + tx_kwargs={"data": mem_alloc}, + ), + ) diff --git a/tests/benchmark/compute/instruction/test_memory.py b/tests/benchmark/compute/instruction/test_memory.py index d939b1bce79..47b05205e98 100644 --- a/tests/benchmark/compute/instruction/test_memory.py +++ b/tests/benchmark/compute/instruction/test_memory.py @@ -18,14 +18,10 @@ def test_msize( benchmark_test: BenchmarkTestFiller, mem_size: int, ) -> None: - """ - Benchmark MSIZE instruction. - - - mem_size: by how much the memory is expanded. - """ + """Benchmark MSIZE instruction.""" benchmark_test( code_generator=ExtCallGenerator( - setup=Op.MLOAD(Op.SELFBALANCE) + Op.POP, + setup=Op.POP(Op.MLOAD(Op.SELFBALANCE)), attack_block=Op.MSIZE, contract_balance=mem_size, ), @@ -99,6 +95,6 @@ def test_mcopy( ) benchmark_test( code_generator=JumpLoopGenerator( - setup=mem_touch, attack_block=attack_block, cleanup=mem_touch + attack_block=attack_block, cleanup=mem_touch ), ) diff --git a/tests/benchmark/compute/instruction/test_stack.py b/tests/benchmark/compute/instruction/test_stack.py index 204852dde3d..21b24139130 100644 --- a/tests/benchmark/compute/instruction/test_stack.py +++ b/tests/benchmark/compute/instruction/test_stack.py @@ -2,10 +2,8 @@ import pytest from execution_testing import ( - Alloc, BenchmarkTestFiller, ExtCallGenerator, - Fork, JumpLoopGenerator, Op, ) @@ -70,25 +68,15 @@ def test_swap( ) 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), + code_generator=ExtCallGenerator( + setup=Op.PUSH0 * min_stack_height, + attack_block=opcode, + ), ) diff --git a/tests/benchmark/compute/instruction/test_storage.py b/tests/benchmark/compute/instruction/test_storage.py index a07673c35f4..ff4c02840ca 100644 --- a/tests/benchmark/compute/instruction/test_storage.py +++ b/tests/benchmark/compute/instruction/test_storage.py @@ -7,6 +7,7 @@ Block, Bytecode, Environment, + ExtCallGenerator, Fork, JumpLoopGenerator, Op, @@ -22,70 +23,55 @@ # SLOAD, SSTORE, TLOAD, TSTORE -# `key_mut` indicates the key isn't fixed. -@pytest.mark.parametrize("key_mut", [True, False]) -# `val_mut` indicates that at the end of each big-loop, the value of the target -# key changes. -@pytest.mark.parametrize("val_mut", [True, False]) +@pytest.mark.parametrize("fixed_key", [True, False]) +@pytest.mark.parametrize("fixed_value", [True, False]) def test_tload( benchmark_test: BenchmarkTestFiller, - key_mut: bool, - val_mut: bool, + fixed_key: bool, + fixed_value: 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 + if not fixed_key and not fixed_value: + setup = Op.GAS + Op.TSTORE(Op.DUP2, Op.GAS) + attack_block = Op.TLOAD(Op.DUP1) + if not fixed_key and fixed_value: + attack_block = Op.TLOAD(Op.GAS) + if fixed_key and not fixed_value: + setup = Op.TSTORE(Op.CALLDATASIZE, Op.GAS) + attack_block = Op.TLOAD(Op.CALLDATASIZE) + if fixed_key and fixed_value: + attack_block = Op.TLOAD(Op.CALLDATASIZE) + + tx_data = b"42" if fixed_key and not fixed_value else 0 benchmark_test( - code_generator=JumpLoopGenerator( + code_generator=ExtCallGenerator( setup=setup, attack_block=attack_block, - cleanup=cleanup, - tx_kwargs={ - "value": tx_value, - }, + tx_kwargs={"data": tx_data}, ), ) -@pytest.mark.parametrize("key_mut", [True, False]) -@pytest.mark.parametrize("dense_val_mut", [True, False]) +@pytest.mark.parametrize("fixed_key", [True, False]) +@pytest.mark.parametrize("fixed_value", [True, False]) def test_tstore( benchmark_test: BenchmarkTestFiller, - key_mut: bool, - dense_val_mut: bool, + fixed_key: bool, + fixed_value: 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 fixed_value is False, 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 not fixed_value else Op.DUP1) - # If `key_mut` is True, we mutate the key on every iteration of the + # If fixed_key is False, we mutate the key on every iteration of the # big loop. - cleanup = Op.POP + Op.GAS if key_mut else Bytecode() + cleanup = Op.POP + Op.GAS if not fixed_key else Bytecode() benchmark_test( code_generator=JumpLoopGenerator( diff --git a/tests/benchmark/compute/instruction/test_system.py b/tests/benchmark/compute/instruction/test_system.py index 8badf0a676b..2d54175b982 100644 --- a/tests/benchmark/compute/instruction/test_system.py +++ b/tests/benchmark/compute/instruction/test_system.py @@ -11,6 +11,7 @@ BlockchainTestFiller, Bytecode, Environment, + ExtCallGenerator, Fork, Hash, JumpLoopGenerator, @@ -335,22 +336,14 @@ def test_create( 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( + code_generator=JumpLoopGenerator( + setup=setup, + attack_block=attack_block, + contract_balance=1_000_000_000 if value > 0 else 0, + ) ) - benchmark_test(tx=tx) - @pytest.mark.parametrize( "opcode", @@ -439,15 +432,11 @@ def test_creates_collisions( ) 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: # ``` @@ -462,16 +451,12 @@ def test_return_revert( 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), + code_generator=ExtCallGenerator( + setup=mem_preparation, + attack_block=opcode(size=return_size), + code_padding_opcode=Op.INVALID, + ), ) diff --git a/tests/benchmark/compute/scenario/test_transaction_types.py b/tests/benchmark/compute/scenario/test_transaction_types.py index cb0a4f4b64a..8c337a0ef84 100644 --- a/tests/benchmark/compute/scenario/test_transaction_types.py +++ b/tests/benchmark/compute/scenario/test_transaction_types.py @@ -59,10 +59,12 @@ def get_distinct_sender_list(pre: Alloc) -> Generator[Address, None, None]: yield pre.fund_eoa() -def get_distinct_receiver_list(pre: Alloc) -> Generator[Address, None, None]: +def get_distinct_receiver_list( + pre: Alloc, balance: int +) -> Generator[Address, None, None]: """Get a list of distinct receiver accounts.""" while True: - yield pre.fund_eoa(0) + yield pre.fund_eoa(balance) def get_single_sender_list(pre: Alloc) -> Generator[Address, None, None]: @@ -72,19 +74,20 @@ def get_single_sender_list(pre: Alloc) -> Generator[Address, None, None]: yield sender -def get_single_receiver_list(pre: Alloc) -> Generator[Address, None, None]: +def get_single_receiver_list( + pre: Alloc, balance: int +) -> Generator[Address, None, None]: """Get a list of single receiver accounts.""" - receiver = pre.fund_eoa(0) + receiver = pre.fund_eoa(balance) while True: yield receiver @pytest.fixture def ether_transfer_case( - case_id: str, - pre: Alloc, + case_id: str, pre: Alloc, balance: int ) -> Tuple[Generator[Address, None, None], Generator[Address, None, None]]: - """Generate the test parameters based on the case ID.""" + """Generate sender and receiver generators based on the test case.""" if case_id == "a_to_a": """Sending to self.""" senders = get_single_sender_list(pre) @@ -93,22 +96,22 @@ def ether_transfer_case( elif case_id == "a_to_b": """One sender → one receiver.""" senders = get_single_sender_list(pre) - receivers = get_single_receiver_list(pre) + receivers = get_single_receiver_list(pre, balance) elif case_id == "diff_acc_to_b": """Multiple senders → one receiver.""" senders = get_distinct_sender_list(pre) - receivers = get_single_receiver_list(pre) + receivers = get_single_receiver_list(pre, balance) elif case_id == "a_to_diff_acc": """One sender → multiple receivers.""" senders = get_single_sender_list(pre) - receivers = get_distinct_receiver_list(pre) + receivers = get_distinct_receiver_list(pre, balance) elif case_id == "diff_acc_to_diff_acc": """Multiple senders → multiple receivers.""" senders = get_distinct_sender_list(pre) - receivers = get_distinct_receiver_list(pre) + receivers = get_distinct_receiver_list(pre, balance) else: raise ValueError(f"Unknown case: {case_id}") @@ -126,16 +129,18 @@ def ether_transfer_case( "diff_acc_to_diff_acc", ], ) +@pytest.mark.parametrize("balance", [0, 1]) 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] - ], + balance: int, iteration_count: int, transfer_amount: int, intrinsic_cost: int, + ether_transfer_case: Tuple[ + Generator[Address, None, None], Generator[Address, None, None] + ], ) -> None: """ Single test for ether transfer scenarios. @@ -151,10 +156,12 @@ def test_block_full_of_ether_transfers( # Create a single block with all transactions txs = [] - balances: dict[Address, int] = {} + token_transfers: dict[Address, int] = {} for _ in range(iteration_count): receiver = next(receivers) - balances[receiver] = balances.get(receiver, 0) + transfer_amount + token_transfers[receiver] = ( + token_transfers.get(receiver, 0) + transfer_amount + ) txs.append( Transaction( to=receiver, @@ -169,8 +176,8 @@ def test_block_full_of_ether_transfers( {} if case_id == "a_to_a" else { - receiver: Account(balance=balance) - for receiver, balance in balances.items() + receiver: Account(balance=balance + transferred_amount) + for receiver, transferred_amount in token_transfers.items() } ) @@ -393,7 +400,7 @@ def test_block_full_access_list_and_data( @pytest.mark.parametrize("empty_authority", [True, False]) @pytest.mark.parametrize("zero_delegation", [True, False]) -def test_worst_case_auth_block( +def test_auth_transaction( blockchain_test: BlockchainTestFiller, pre: Alloc, intrinsic_cost: int,