From 81272f4f0f4476b4bb227b4ef00cbd139b438802 Mon Sep 17 00:00:00 2001 From: LouisTsai Date: Tue, 2 Jun 2026 15:23:54 +0800 Subject: [PATCH 1/2] refactor: account access bench and add keccak overhead --- .../stateful/bloatnet/test_keccak_overhead.py | 173 ++++++++++++++++++ .../stateful/bloatnet/test_single_opcode.py | 25 +-- 2 files changed, 183 insertions(+), 15 deletions(-) create mode 100644 tests/benchmark/stateful/bloatnet/test_keccak_overhead.py diff --git a/tests/benchmark/stateful/bloatnet/test_keccak_overhead.py b/tests/benchmark/stateful/bloatnet/test_keccak_overhead.py new file mode 100644 index 00000000000..d385957b135 --- /dev/null +++ b/tests/benchmark/stateful/bloatnet/test_keccak_overhead.py @@ -0,0 +1,173 @@ +"""Keccak overhead baseline for the account-access benchmark.""" + +import pytest +from execution_testing import ( + DETERMINISTIC_FACTORY_ADDRESS, + Alloc, + BenchmarkTestFiller, + Block, + Bytecode, + Create2PreimageLayout, + Fork, + Hash, + IteratingBytecode, + Op, + TestPhaseManager, + While, + keccak256, +) + +from tests.benchmark.stateful.helpers import CacheStrategy + +REFERENCE_SPEC_GIT_PATH = "DUMMY/bloatnet.md" +REFERENCE_SPEC_VERSION = "1.0" + + +def account_access_keccak_params() -> list: + """Mirror test_account_access's EXISTING_CONTRACT (opcode, value) set.""" + params = [] + for op in [Op.CALL, Op.CALLCODE]: + params.append(pytest.param(op, 0)) + params.append(pytest.param(op, 1)) + for op in [Op.BALANCE, Op.STATICCALL, Op.DELEGATECALL]: + params.append(pytest.param(op, 0)) + for op in [Op.EXTCODECOPY, Op.EXTCODESIZE, Op.EXTCODEHASH]: + params.append(pytest.param(op, 0)) + return params + + +def _account_access_loop( + *, + opcode: Op, + value_sent: int, + cache_strategy: CacheStrategy, + address_retriever: Create2PreimageLayout, + increment_op: Bytecode, +) -> Bytecode: + """Rebuild test_account_access's EXISTING_CONTRACT loop (count only).""" + cache_op = ( + Op.POP( + Op.BALANCE( + address=address_retriever.address_op(), + address_warm=False, + ) + ) + if cache_strategy == CacheStrategy.CACHE_TX + else Bytecode() + ) + access_warm = cache_strategy == CacheStrategy.CACHE_TX + + if opcode == Op.EXTCODECOPY: + attack_call = opcode( + address=address_retriever.address_op(), + size=1024, + address_warm=access_warm, + ) + elif opcode in (Op.CALL, Op.CALLCODE): + attack_call = Op.POP( + opcode( + address=address_retriever.address_op(), + value=value_sent, + # gas accounting + address_warm=access_warm, + value_transfer=value_sent > 0, + account_new=False, + ) + ) + else: + attack_call = Op.POP( + opcode( + address=address_retriever.address_op(), + address_warm=access_warm, + ) + ) + + return While( + body=cache_op + attack_call + increment_op, + condition=Op.GT(Op.GAS, 0x9000) if value_sent > 0 else None, + ) + + +@pytest.mark.repricing +@pytest.mark.parametrize("cache_strategy", list(CacheStrategy)) +@pytest.mark.parametrize("opcode,value_sent", account_access_keccak_params()) +def test_account_access_keccak( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + opcode: Op, + value_sent: int, + gas_benchmark_value: int, + cache_strategy: CacheStrategy, +) -> None: + """ + Benchmark the keccak256 work of the account-access address derivation. + + Compute the iteration count test_account_access[EXISTING_CONTRACT] runs + for this gas budget, then execute only the CREATE2 address derivation + (SHA3 over 0xff ++ factory ++ salt ++ keccak(initcode)) for that count. + Same factory, initcode and salt sequence as the full benchmark, so the + keccak count and input match: one per iteration, two for cache_tx. + """ + calldataload_start = Op.CALLDATALOAD(0) + + init_code = Op.PUSH1(1) + Op.PUSH1(0) + Op.RETURN + address_retriever = Create2PreimageLayout( + factory_address=DETERMINISTIC_FACTORY_ADDRESS, + salt=calldataload_start, + init_code_hash=keccak256(bytes(init_code)), + ) + increment_op = address_retriever.increment_salt_op() + + def calldata(iteration_count: int, start_iteration: int) -> bytes: + del iteration_count + return Hash(start_iteration) + + account_access_code = IteratingBytecode( + setup=address_retriever, + iterating=_account_access_loop( + opcode=opcode, + value_sent=value_sent, + cache_strategy=cache_strategy, + address_retriever=address_retriever, + increment_op=increment_op, + ), + iterating_subcall=Op.STOP, + ) + total_iterations = sum( + account_access_code.tx_iterations_by_gas_limit( + fork=fork, + gas_limit=gas_benchmark_value, + calldata=calldata, + ) + ) + + keccak_op = Op.POP(address_retriever.address_op()) + if cache_strategy == CacheStrategy.CACHE_TX: + keccak_op = keccak_op * 2 + keccak_code = IteratingBytecode( + setup=address_retriever, + iterating=While(body=keccak_op + increment_op), + ) + + attack_address = pre.deploy_contract(code=keccak_code) + + with TestPhaseManager.execution(): + attack_sender = pre.fund_eoa() + attack_txs = list( + keccak_code.transactions_by_total_iteration_count( + fork=fork, + total_iterations=total_iterations, + sender=attack_sender, + to=attack_address, + calldata=calldata, + ) + ) + + benchmark_test( + pre=pre, + blocks=[Block(txs=attack_txs)], + target_opcode=Op.SHA3, + skip_gas_used_validation=True, + expected_receipt_status=1, + ) diff --git a/tests/benchmark/stateful/bloatnet/test_single_opcode.py b/tests/benchmark/stateful/bloatnet/test_single_opcode.py index 6f97d3ff8ec..4bcd7de9281 100644 --- a/tests/benchmark/stateful/bloatnet/test_single_opcode.py +++ b/tests/benchmark/stateful/bloatnet/test_single_opcode.py @@ -13,6 +13,7 @@ import pytest from execution_testing import ( + DETERMINISTIC_FACTORY_ADDRESS, EOA, AccessList, Address, @@ -25,7 +26,7 @@ Block, BlockAccessListExpectation, Bytecode, - CreatePreimageLayout, + Create2PreimageLayout, Fork, Hash, IteratingBytecode, @@ -1532,16 +1533,14 @@ def test_account_access( # the previous one left off instead of re-targeting the same accounts. calldataload_start = Op.CALLDATALOAD(0) if account_mode == AccountMode.EXISTING_CONTRACT: - # Use Bittrex Controller as target. Created 1586350 contracts, - # which cannot selfdestruct, so guaranteed to be on-chain. - # This is safe for a gas benchmark up to 300M. (300_000_000 / 2000) - # (2000 is the min cost to target a cold address) - target_address = Address(0xA3C1E324CA1CE40DB73ED6026C4A177F099B5770) - address_retriever = CreatePreimageLayout( - sender_address=target_address, - nonce=Op.ADD(1, calldataload_start), + # initcode returns a single zero byte (STOP) as the runtime. + init_code = Op.PUSH1(1) + Op.PUSH1(0) + Op.RETURN + address_retriever = Create2PreimageLayout( + factory_address=DETERMINISTIC_FACTORY_ADDRESS, + salt=calldataload_start, + init_code_hash=keccak256(bytes(init_code)), ) - increment_op = address_retriever.increment_nonce_op() + increment_op = address_retriever.increment_salt_op() elif account_mode == AccountMode.EXISTING_EOA: # Spamoor EOA creator (https://github.com/CPerezz/spamoor/pull/12) # created these accounts on bloatnet with these values (are also the @@ -1625,13 +1624,9 @@ def test_account_access( ) # Calldata generator for each transaction of the iterating bytecode. - # Start from 1 to skip the Bittrex Controller's nonce=1 contract - # which has a non-payable fallback that reverts when receiving value. - calldata_offset = 1 if account_mode == AccountMode.EXISTING_CONTRACT else 0 - def calldata(iteration_count: int, start_iteration: int) -> bytes: del iteration_count - return Hash(start_iteration + calldata_offset) + return Hash(start_iteration) attack_address = pre.deploy_contract(code=attack_code, balance=10**21) From 82d55b4c0d118adc91f2672aa54396b6cc979685 Mon Sep 17 00:00:00 2001 From: LouisTsai Date: Fri, 5 Jun 2026 15:17:00 +0800 Subject: [PATCH 2/2] refactor: add same/diff contract parametrization --- .../stateful/bloatnet/test_keccak_overhead.py | 79 ++++++++++++++++--- .../stateful/bloatnet/test_single_opcode.py | 52 ++++++++++-- 2 files changed, 113 insertions(+), 18 deletions(-) diff --git a/tests/benchmark/stateful/bloatnet/test_keccak_overhead.py b/tests/benchmark/stateful/bloatnet/test_keccak_overhead.py index d385957b135..1fb59e0446f 100644 --- a/tests/benchmark/stateful/bloatnet/test_keccak_overhead.py +++ b/tests/benchmark/stateful/bloatnet/test_keccak_overhead.py @@ -1,5 +1,7 @@ """Keccak overhead baseline for the account-access benchmark.""" +from enum import Enum, auto + import pytest from execution_testing import ( DETERMINISTIC_FACTORY_ADDRESS, @@ -23,19 +25,63 @@ REFERENCE_SPEC_VERSION = "1.0" -def account_access_keccak_params() -> list: - """Mirror test_account_access's EXISTING_CONTRACT (opcode, value) set.""" +def account_access_params() -> list: + """Generate (opcode, value_sent, account_mode) triples.""" params = [] - for op in [Op.CALL, Op.CALLCODE]: - params.append(pytest.param(op, 0)) - params.append(pytest.param(op, 1)) - for op in [Op.BALANCE, Op.STATICCALL, Op.DELEGATECALL]: - params.append(pytest.param(op, 0)) + + for mode in AccountMode: + for op in [Op.CALL, Op.CALLCODE]: + params.append(pytest.param(op, 0, mode)) + params.append(pytest.param(op, 1, mode)) + + for op in [Op.BALANCE, Op.STATICCALL, Op.DELEGATECALL]: + params.append(pytest.param(op, 0, mode)) + for op in [Op.EXTCODECOPY, Op.EXTCODESIZE, Op.EXTCODEHASH]: - params.append(pytest.param(op, 0)) + for mode in [ + AccountMode.EXISTING_CONTRACT_MINIMAL, + AccountMode.EXISTING_CONTRACT_SAME, + AccountMode.EXISTING_CONTRACT_DIFF, + AccountMode.NON_EXISTING_ACCOUNT, + ]: + params.append(pytest.param(op, 0, mode)) + return params +class AccountMode(Enum): + """Target Account Mode.""" + + EXISTING_CONTRACT_MINIMAL = auto() + EXISTING_CONTRACT_SAME = auto() + EXISTING_CONTRACT_DIFF = auto() + EXISTING_EOA = auto() + NON_EXISTING_ACCOUNT = auto() + + +def build_existing_contract_initcode( + fork: Fork, account_mode: AccountMode +) -> Bytecode: + """ + Build the initcode for an existing contract. + """ + max_code_size = fork.max_code_size() + + # MCOPY fills MEM[0:0x8000] with JUMPDEST. + # Runtime only uses MEM[0:0x6000]. + code = Op.MSTORE(0, bytes(Op.JUMPDEST * 32)) + for size in (1 << s for s in range(5, 15)): + code += Op.MCOPY(size, 0, size) + + if account_mode == AccountMode.EXISTING_CONTRACT_DIFF: + code += Op.MSTORE(0, Op.ADDRESS) + else: + code += Op.MSTORE8(0, 0) + code += Op.RETURN(0, max_code_size) + + return code + + def _account_access_loop( *, opcode: Op, @@ -90,7 +136,9 @@ def _account_access_loop( @pytest.mark.repricing @pytest.mark.parametrize("cache_strategy", list(CacheStrategy)) -@pytest.mark.parametrize("opcode,value_sent", account_access_keccak_params()) +@pytest.mark.parametrize( + "opcode,value_sent,account_mode", account_access_params() +) def test_account_access_keccak( benchmark_test: BenchmarkTestFiller, pre: Alloc, @@ -98,6 +146,7 @@ def test_account_access_keccak( opcode: Op, value_sent: int, gas_benchmark_value: int, + account_mode: AccountMode, cache_strategy: CacheStrategy, ) -> None: """ @@ -111,11 +160,19 @@ def test_account_access_keccak( """ calldataload_start = Op.CALLDATALOAD(0) - init_code = Op.PUSH1(1) + Op.PUSH1(0) + Op.RETURN + initcode = Bytecode() + if account_mode == AccountMode.EXISTING_CONTRACT_MINIMAL: + initcode += Op.PUSH1(1) + Op.PUSH1(0) + Op.RETURN + elif account_mode in ( + AccountMode.EXISTING_CONTRACT_SAME, + AccountMode.EXISTING_CONTRACT_DIFF, + ): + initcode += build_existing_contract_initcode(fork, account_mode) + address_retriever = Create2PreimageLayout( factory_address=DETERMINISTIC_FACTORY_ADDRESS, salt=calldataload_start, - init_code_hash=keccak256(bytes(init_code)), + init_code_hash=keccak256(bytes(initcode)), ) increment_op = address_retriever.increment_salt_op() diff --git a/tests/benchmark/stateful/bloatnet/test_single_opcode.py b/tests/benchmark/stateful/bloatnet/test_single_opcode.py index 4bcd7de9281..8a997930439 100644 --- a/tests/benchmark/stateful/bloatnet/test_single_opcode.py +++ b/tests/benchmark/stateful/bloatnet/test_single_opcode.py @@ -1480,6 +1480,16 @@ def test_storage_sload_same_key_benchmark( ) +class AccountMode(Enum): + """Target Account Mode.""" + + EXISTING_CONTRACT_MINIMAL = auto() + EXISTING_CONTRACT_SAME = auto() + EXISTING_CONTRACT_DIFF = auto() + EXISTING_EOA = auto() + NON_EXISTING_ACCOUNT = auto() + + def account_access_params() -> list: """Generate (opcode, value_sent, account_mode) triples.""" params = [] @@ -1494,7 +1504,9 @@ def account_access_params() -> list: for op in [Op.EXTCODECOPY, Op.EXTCODESIZE, Op.EXTCODEHASH]: for mode in [ - AccountMode.EXISTING_CONTRACT, + AccountMode.EXISTING_CONTRACT_MINIMAL, + AccountMode.EXISTING_CONTRACT_SAME, + AccountMode.EXISTING_CONTRACT_DIFF, AccountMode.NON_EXISTING_ACCOUNT, ]: params.append(pytest.param(op, 0, mode)) @@ -1502,12 +1514,27 @@ def account_access_params() -> list: return params -class AccountMode(Enum): - """Target Account Mode.""" +def build_existing_contract_initcode( + fork: Fork, account_mode: AccountMode +) -> Bytecode: + """ + Build the initcode for an existing contract. + """ + max_code_size = fork.max_code_size() - EXISTING_CONTRACT = auto() - EXISTING_EOA = auto() - NON_EXISTING_ACCOUNT = auto() + # MCOPY fills MEM[0:0x8000] with JUMPDEST. + # Runtime only uses MEM[0:0x6000]. + code = Op.MSTORE(0, bytes(Op.JUMPDEST * 32)) + for size in (1 << s for s in range(5, 15)): + code += Op.MCOPY(size, 0, size) + + if account_mode == AccountMode.EXISTING_CONTRACT_DIFF: + code += Op.MSTORE(0, Op.ADDRESS) + else: + code += Op.MSTORE8(0, 0) + code += Op.RETURN(0, max_code_size) + + return code @pytest.mark.repricing @@ -1532,7 +1559,7 @@ def test_account_access( # split across gas limits, each transaction continues from where # the previous one left off instead of re-targeting the same accounts. calldataload_start = Op.CALLDATALOAD(0) - if account_mode == AccountMode.EXISTING_CONTRACT: + if account_mode == AccountMode.EXISTING_CONTRACT_MINIMAL: # initcode returns a single zero byte (STOP) as the runtime. init_code = Op.PUSH1(1) + Op.PUSH1(0) + Op.RETURN address_retriever = Create2PreimageLayout( @@ -1541,6 +1568,17 @@ def test_account_access( init_code_hash=keccak256(bytes(init_code)), ) increment_op = address_retriever.increment_salt_op() + elif account_mode in ( + AccountMode.EXISTING_CONTRACT_SAME, + AccountMode.EXISTING_CONTRACT_DIFF, + ): + init_code = build_existing_contract_initcode(fork, account_mode) + address_retriever = Create2PreimageLayout( + factory_address=DETERMINISTIC_FACTORY_ADDRESS, + salt=calldataload_start, + init_code_hash=keccak256(bytes(init_code)), + ) + increment_op = address_retriever.increment_salt_op() elif account_mode == AccountMode.EXISTING_EOA: # Spamoor EOA creator (https://github.com/CPerezz/spamoor/pull/12) # created these accounts on bloatnet with these values (are also the