From 598c233cd9834845c8ca76d04e7660e5517313be Mon Sep 17 00:00:00 2001 From: LouisTsai Date: Fri, 9 Jan 2026 19:12:24 +0800 Subject: [PATCH 1/5] feat: Introduce create2 helper for address computation --- .../testing/src/execution_testing/__init__.py | 2 + .../src/execution_testing/tools/__init__.py | 2 + .../tools/tools_code/__init__.py | 2 + .../tools/tools_code/generators.py | 49 ++++++++++++++ .../compute/instruction/test_system.py | 16 +++-- .../scenario/test_unchunkified_bytecode.py | 19 +++--- .../test_extcodesize_bytecode_sizes.py | 15 +++-- .../stateful/bloatnet/test_multi_opcode.py | 65 ++++++++----------- 8 files changed, 110 insertions(+), 60 deletions(-) diff --git a/packages/testing/src/execution_testing/__init__.py b/packages/testing/src/execution_testing/__init__.py index b10c00e353..06a157e91c 100644 --- a/packages/testing/src/execution_testing/__init__.py +++ b/packages/testing/src/execution_testing/__init__.py @@ -92,6 +92,7 @@ ParameterSet, Switch, While, + Create2Addr, extend_with_defaults, gas_test, generate_system_contract_deploy_test, @@ -199,6 +200,7 @@ "compute_create_address", "compute_create2_address", "compute_deterministic_create2_address", + "Create2Addr", "extend_with_defaults", "gas_test", "generate_system_contract_deploy_test", diff --git a/packages/testing/src/execution_testing/tools/__init__.py b/packages/testing/src/execution_testing/tools/__init__.py index d47d964305..5f14746281 100644 --- a/packages/testing/src/execution_testing/tools/__init__.py +++ b/packages/testing/src/execution_testing/tools/__init__.py @@ -11,6 +11,7 @@ Initcode, Switch, While, + Create2Addr, ) from .utility.generators import ( DeploymentTestType, @@ -31,6 +32,7 @@ "ParameterSet", "Switch", "While", + "Create2Addr", "extend_with_defaults", "gas_test", "generate_system_contract_deploy_test", diff --git a/packages/testing/src/execution_testing/tools/tools_code/__init__.py b/packages/testing/src/execution_testing/tools/tools_code/__init__.py index 1ef17fd240..1f7a98dd4c 100644 --- a/packages/testing/src/execution_testing/tools/tools_code/__init__.py +++ b/packages/testing/src/execution_testing/tools/tools_code/__init__.py @@ -8,6 +8,7 @@ Initcode, Switch, While, + Create2Addr, ) from .yul import Solc, Yul, YulCompiler @@ -22,4 +23,5 @@ "While", "Yul", "YulCompiler", + "Create2Addr", ) diff --git a/packages/testing/src/execution_testing/tools/tools_code/generators.py b/packages/testing/src/execution_testing/tools/tools_code/generators.py index 6d7985bfd9..2b3e91cb23 100644 --- a/packages/testing/src/execution_testing/tools/tools_code/generators.py +++ b/packages/testing/src/execution_testing/tools/tools_code/generators.py @@ -393,3 +393,52 @@ def __new__( instance.default_action = default_action instance.cases = cases return instance + + +class Create2Addr(Bytecode): + """ + Set up memory for CREATE2 address computation. + + Creates the standard memory layout required to compute a CREATE2 address + using keccak256(0xFF ++ factory_address ++ salt ++ init_code_hash). + + Memory layout after execution: + - MEM[offset + 0: offset + 32] = zero padding + factory_address (20 bytes) + - MEM[offset + 11] = 0xFF prefix byte + - MEM[offset + 32: offset + 64] = salt (32 bytes) + - MEM[offset + 64: offset + 96] = init_code_hash (32 bytes) + + To compute the CREATE2 address, use: `.compute` or `Op.SHA3(offset + 11, 85)` + The resulting hash's lower 20 bytes (bytes 12-31) form the address. + """ + + offset: int = 0 + + def __new__( + cls, + *, + factory_address: int | bytes | Bytecode, + salt: int | bytes | Bytecode, + init_code_hash: int | bytes | Bytecode, + offset: int = 0, + ) -> Self: + """ + Assemble the bytecode that sets up the memory layout for CREATE2 + address computation. + """ + bytecode = ( + Op.MSTORE(offset, factory_address) + + Op.MSTORE8(offset + 11, 0xFF) + + Op.MSTORE(offset + 32, salt) + + Op.MSTORE(offset + 64, init_code_hash) + ) + instance = super().__new__(cls, bytecode) + instance.offset = offset + return instance + + @property + def compute(self) -> Bytecode: + """ + Return the Op.SHA3 that computes the CREATE2 address. + """ + return Op.SHA3(self.offset + 11, self.offset + 85) diff --git a/tests/benchmark/compute/instruction/test_system.py b/tests/benchmark/compute/instruction/test_system.py index 6433f0eaa1..dab7c822bb 100644 --- a/tests/benchmark/compute/instruction/test_system.py +++ b/tests/benchmark/compute/instruction/test_system.py @@ -22,6 +22,7 @@ BenchmarkTestFiller, Block, Bytecode, + Create2Addr, Environment, ExtCallGenerator, Fork, @@ -387,15 +388,16 @@ def test_selfdestruct_existing( ) 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, Op.CALLDATALOAD(0)) # Starting address from calldata - + Op.MSTORE(64, initcode.keccak256()) + ( + create2_addr := Create2Addr( + factory_address=factory_address, + salt=Op.CALLDATALOAD(0), + init_code_hash=initcode.keccak256(), + ) + ) # Main loop + While( - body=Op.POP(Op.CALL(address=Op.SHA3(32 - 20 - 1, 85))) + body=Op.POP(Op.CALL(address=create2_addr.compute)) + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), # Loop while we have enough gas AND within target count condition=Op.GT(Op.GAS, final_storage_gas + loop_cost), diff --git a/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py b/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py index 5e60d85a2a..2786028e5d 100644 --- a/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py +++ b/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py @@ -13,6 +13,7 @@ Block, BlockchainTestFiller, Bytecode, + Create2Addr, Fork, Hash, Op, @@ -109,24 +110,22 @@ def test_unchunkified_bytecode( ) post[deployed_contract_address] = Account(nonce=1) + create2_addr = Create2Addr( + factory_address=factory_address, + salt=0, + init_code_hash=initcode.keccak256(), + ) attack_call = Bytecode() if opcode == Op.EXTCODECOPY: attack_call = Op.EXTCODECOPY( - address=Op.SHA3(32 - 20 - 1, 85), dest_offset=96, size=1000 + address=create2_addr.compute, 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_call = Op.POP(opcode(address=create2_addr.compute)) 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, Op.CALLDATALOAD(0) - ) # Calldata is the starting value of the CREATE2 salt - + Op.MSTORE(64, initcode.keccak256()) + create2_addr # Main loop + While( body=attack_call + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), diff --git a/tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py b/tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py index 42da6314c2..819f99ac3e 100644 --- a/tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py +++ b/tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py @@ -71,6 +71,7 @@ BlockchainTestFiller, Bytecode, Conditional, + Create2Addr, Op, Storage, Transaction, @@ -128,15 +129,17 @@ def build_attack_contract(factory_address: Address) -> Bytecode: ), if_false=Op.REVERT(0, 0), ) - # Setup CREATE2 memory: keccak256(0xFF ++ factory ++ salt ++ hash) - + Op.MSTORE(0, factory_address) - + Op.MSTORE8(11, 0xFF) - + Op.MSTORE(32, Op.SLOAD(0)) # Load salt directly to memory - + Op.MSTORE(64, Op.MLOAD(128)) # init_code_hash + + ( + create2_addr := Create2Addr( + factory_address=factory_address, + salt=Op.SLOAD(0), + init_code_hash=Op.MLOAD(128), + ) + ) + Op.MSTORE(160, 0) # Initialize last_size + While( body=( - Op.MSTORE(160, Op.EXTCODESIZE(Op.SHA3(11, 85))) + Op.MSTORE(160, Op.EXTCODESIZE(create2_addr.compute)) + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)) ), condition=( diff --git a/tests/benchmark/stateful/bloatnet/test_multi_opcode.py b/tests/benchmark/stateful/bloatnet/test_multi_opcode.py index a19be48830..1ea4480c2f 100755 --- a/tests/benchmark/stateful/bloatnet/test_multi_opcode.py +++ b/tests/benchmark/stateful/bloatnet/test_multi_opcode.py @@ -13,6 +13,7 @@ Block, BlockchainTestFiller, Bytecode, + Create2Addr, Fork, Op, Transaction, @@ -175,24 +176,19 @@ def test_bloatnet_balance_extcodesize( # Load results from memory # Memory[96:128] = num_deployed_contracts # Memory[128:160] = init_code_hash - + Op.MLOAD(96) # Load num_deployed_contracts - + Op.MLOAD(128) # Load init_code_hash - # Setup memory for CREATE2 address generation - # Memory layout at 0: 0xFF + factory_addr(20) + salt(32) + hash(32) - + Op.MSTORE( - 0, factory_address - ) # Store factory address at memory position 0 - + Op.MSTORE8(11, 0xFF) # Store 0xFF prefix at position (32 - 20 - 1) - + Op.MSTORE(32, 0) # Store salt at position 32 - # Stack now has: [num_contracts, init_code_hash] - + Op.PUSH1(64) # Push memory position - + Op.MSTORE # Store init_code_hash at memory[64] - # Stack now has: [num_contracts] + + Op.MLOAD(96) # Load num_deployed_contracts to stack + + ( + create2_addr := Create2Addr( + factory_address=factory_address, + salt=0, + init_code_hash=Op.MLOAD(128), + ) + ) # Main attack loop - iterate through all deployed contracts + While( body=( # Generate CREATE2 addr: keccak256(0xFF+factory+salt+hash) - Op.SHA3(11, 85) # Generate CREATE2 address from memory[11:96] + create2_addr.compute # Generate CREATE2 address from memory # The address is now on the stack + Op.DUP1 # Duplicate for second operation + benchmark_ops # Execute operations in specified order @@ -372,24 +368,19 @@ def test_bloatnet_balance_extcodecopy( # Load results from memory # Memory[96:128] = num_deployed_contracts # Memory[128:160] = init_code_hash - + Op.MLOAD(96) # Load num_deployed_contracts - + Op.MLOAD(128) # Load init_code_hash - # Setup memory for CREATE2 address generation - # Memory layout at 0: 0xFF + factory_addr(20) + salt(32) + hash(32) - + Op.MSTORE( - 0, factory_address - ) # Store factory address at memory position 0 - + Op.MSTORE8(11, 0xFF) # Store 0xFF prefix at position (32 - 20 - 1) - + Op.MSTORE(32, 0) # Store salt at position 32 - # Stack now has: [num_contracts, init_code_hash] - + Op.PUSH1(64) # Push memory position - + Op.MSTORE # Store init_code_hash at memory[64] - # Stack now has: [num_contracts] + + Op.MLOAD(96) # Load num_deployed_contracts to stack + + ( + create2_addr := Create2Addr( + factory_address=factory_address, + salt=0, + init_code_hash=Op.MLOAD(128), + ) + ) # Main attack loop - iterate through all deployed contracts + While( body=( # Generate CREATE2 address - Op.SHA3(11, 85) # Generate CREATE2 address from memory[11:96] + create2_addr.compute # The address is now on the stack + Op.DUP1 # Duplicate for later operations + benchmark_ops # Execute operations in specified order @@ -554,19 +545,19 @@ def test_bloatnet_balance_extcodehash( + Op.PUSH2(0x1000) # Jump to error handler if failed + Op.JUMPI # Load results from memory - + Op.MLOAD(96) # Load num_deployed_contracts - + Op.MLOAD(128) # Load init_code_hash - # Setup memory for CREATE2 address generation - + Op.MSTORE(0, factory_address) - + Op.MSTORE8(11, 0xFF) - + Op.MSTORE(32, 0) # Initial salt - + Op.PUSH1(64) - + Op.MSTORE # Store init_code_hash + + Op.MLOAD(96) # Load num_deployed_contracts to stack + + ( + create2_addr := Create2Addr( + factory_address=factory_address, + salt=0, + init_code_hash=Op.MLOAD(128), + ) + ) # Main attack loop + While( body=( # Generate CREATE2 address - Op.SHA3(11, 85) + create2_addr.compute + Op.DUP1 # Duplicate for second operation + benchmark_ops # Execute operations in specified order # Increment salt From fd15a4f5b6ca9255922aed87d411e9e53eac84ec Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 23 Jan 2026 20:56:53 +0000 Subject: [PATCH 2/5] fix: tox --- packages/testing/src/execution_testing/__init__.py | 2 +- packages/testing/src/execution_testing/tools/__init__.py | 2 +- .../testing/src/execution_testing/tools/tools_code/__init__.py | 2 +- .../src/execution_testing/tools/tools_code/generators.py | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/testing/src/execution_testing/__init__.py b/packages/testing/src/execution_testing/__init__.py index 06a157e91c..69a59772e2 100644 --- a/packages/testing/src/execution_testing/__init__.py +++ b/packages/testing/src/execution_testing/__init__.py @@ -87,12 +87,12 @@ Case, CodeGasMeasure, Conditional, + Create2Addr, DeploymentTestType, Initcode, ParameterSet, Switch, While, - Create2Addr, extend_with_defaults, gas_test, generate_system_contract_deploy_test, diff --git a/packages/testing/src/execution_testing/tools/__init__.py b/packages/testing/src/execution_testing/tools/__init__.py index 5f14746281..beb58984e5 100644 --- a/packages/testing/src/execution_testing/tools/__init__.py +++ b/packages/testing/src/execution_testing/tools/__init__.py @@ -8,10 +8,10 @@ Case, CodeGasMeasure, Conditional, + Create2Addr, Initcode, Switch, While, - Create2Addr, ) from .utility.generators import ( DeploymentTestType, diff --git a/packages/testing/src/execution_testing/tools/tools_code/__init__.py b/packages/testing/src/execution_testing/tools/tools_code/__init__.py index 1f7a98dd4c..845da16b07 100644 --- a/packages/testing/src/execution_testing/tools/tools_code/__init__.py +++ b/packages/testing/src/execution_testing/tools/tools_code/__init__.py @@ -5,10 +5,10 @@ Case, CodeGasMeasure, Conditional, + Create2Addr, Initcode, Switch, While, - Create2Addr, ) from .yul import Solc, Yul, YulCompiler diff --git a/packages/testing/src/execution_testing/tools/tools_code/generators.py b/packages/testing/src/execution_testing/tools/tools_code/generators.py index 2b3e91cb23..5fa7f59971 100644 --- a/packages/testing/src/execution_testing/tools/tools_code/generators.py +++ b/packages/testing/src/execution_testing/tools/tools_code/generators.py @@ -408,7 +408,8 @@ class Create2Addr(Bytecode): - MEM[offset + 32: offset + 64] = salt (32 bytes) - MEM[offset + 64: offset + 96] = init_code_hash (32 bytes) - To compute the CREATE2 address, use: `.compute` or `Op.SHA3(offset + 11, 85)` + To compute the CREATE2 address, use: `.compute` or + `Op.SHA3(offset + 11, 85)`. The resulting hash's lower 20 bytes (bytes 12-31) form the address. """ From c5aefcf1d3db0c2c1203c711e89a7850a39b43b9 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 23 Jan 2026 21:24:16 +0000 Subject: [PATCH 3/5] refactor: Rename class, method --- .../testing/src/execution_testing/__init__.py | 4 +-- .../src/execution_testing/tools/__init__.py | 4 +-- .../tools/tools_code/__init__.py | 4 +-- .../tools/tools_code/generators.py | 32 +++++++++++++------ .../compute/instruction/test_system.py | 6 ++-- .../scenario/test_unchunkified_bytecode.py | 10 +++--- .../test_extcodesize_bytecode_sizes.py | 6 ++-- .../stateful/bloatnet/test_multi_opcode.py | 18 +++++------ 8 files changed, 49 insertions(+), 35 deletions(-) diff --git a/packages/testing/src/execution_testing/__init__.py b/packages/testing/src/execution_testing/__init__.py index 69a59772e2..05f4914c83 100644 --- a/packages/testing/src/execution_testing/__init__.py +++ b/packages/testing/src/execution_testing/__init__.py @@ -87,7 +87,7 @@ Case, CodeGasMeasure, Conditional, - Create2Addr, + Create2PreimageLayout, DeploymentTestType, Initcode, ParameterSet, @@ -200,7 +200,7 @@ "compute_create_address", "compute_create2_address", "compute_deterministic_create2_address", - "Create2Addr", + "Create2PreimageLayout", "extend_with_defaults", "gas_test", "generate_system_contract_deploy_test", diff --git a/packages/testing/src/execution_testing/tools/__init__.py b/packages/testing/src/execution_testing/tools/__init__.py index beb58984e5..ce9268fcb7 100644 --- a/packages/testing/src/execution_testing/tools/__init__.py +++ b/packages/testing/src/execution_testing/tools/__init__.py @@ -8,7 +8,7 @@ Case, CodeGasMeasure, Conditional, - Create2Addr, + Create2PreimageLayout, Initcode, Switch, While, @@ -32,7 +32,7 @@ "ParameterSet", "Switch", "While", - "Create2Addr", + "Create2PreimageLayout", "extend_with_defaults", "gas_test", "generate_system_contract_deploy_test", diff --git a/packages/testing/src/execution_testing/tools/tools_code/__init__.py b/packages/testing/src/execution_testing/tools/tools_code/__init__.py index 845da16b07..44092c59bd 100644 --- a/packages/testing/src/execution_testing/tools/tools_code/__init__.py +++ b/packages/testing/src/execution_testing/tools/tools_code/__init__.py @@ -5,7 +5,7 @@ Case, CodeGasMeasure, Conditional, - Create2Addr, + Create2PreimageLayout, Initcode, Switch, While, @@ -23,5 +23,5 @@ "While", "Yul", "YulCompiler", - "Create2Addr", + "Create2PreimageLayout", ) diff --git a/packages/testing/src/execution_testing/tools/tools_code/generators.py b/packages/testing/src/execution_testing/tools/tools_code/generators.py index 5fa7f59971..ad88385d7e 100644 --- a/packages/testing/src/execution_testing/tools/tools_code/generators.py +++ b/packages/testing/src/execution_testing/tools/tools_code/generators.py @@ -395,9 +395,9 @@ def __new__( return instance -class Create2Addr(Bytecode): +class Create2PreimageLayout(Bytecode): """ - Set up memory for CREATE2 address computation. + Set up the preimage in memory for CREATE2 address computation. Creates the standard memory layout required to compute a CREATE2 address using keccak256(0xFF ++ factory_address ++ salt ++ init_code_hash). @@ -408,7 +408,7 @@ class Create2Addr(Bytecode): - MEM[offset + 32: offset + 64] = salt (32 bytes) - MEM[offset + 64: offset + 96] = init_code_hash (32 bytes) - To compute the CREATE2 address, use: `.compute` or + To compute the CREATE2 address, use: `.sha3_op` or `Op.SHA3(offset + 11, 85)`. The resulting hash's lower 20 bytes (bytes 12-31) form the address. """ @@ -422,24 +422,38 @@ def __new__( salt: int | bytes | Bytecode, init_code_hash: int | bytes | Bytecode, offset: int = 0, + old_memory_size: int = 0, ) -> Self: """ Assemble the bytecode that sets up the memory layout for CREATE2 address computation. """ + required_size = offset + 96 + new_memory_size = max(old_memory_size, required_size) bytecode = ( - Op.MSTORE(offset, factory_address) - + Op.MSTORE8(offset + 11, 0xFF) - + Op.MSTORE(offset + 32, salt) - + Op.MSTORE(offset + 64, init_code_hash) + Op.MSTORE(offset=offset, value=factory_address) + + Op.MSTORE8(offset=offset + 11, value=0xFF) + + Op.MSTORE(offset=offset + 32, value=salt) + + Op.MSTORE( + offset=offset + 64, + value=init_code_hash, + # Gas accounting + old_memory_size=old_memory_size, + new_memory_size=new_memory_size, + ) ) instance = super().__new__(cls, bytecode) instance.offset = offset return instance @property - def compute(self) -> Bytecode: + def sha3_op(self) -> Bytecode: """ Return the Op.SHA3 that computes the CREATE2 address. """ - return Op.SHA3(self.offset + 11, self.offset + 85) + return Op.SHA3( + offset=self.offset + 11, + size=85, + # Gas accounting + data_size=85, + ) diff --git a/tests/benchmark/compute/instruction/test_system.py b/tests/benchmark/compute/instruction/test_system.py index dab7c822bb..84dca7b1d5 100644 --- a/tests/benchmark/compute/instruction/test_system.py +++ b/tests/benchmark/compute/instruction/test_system.py @@ -22,7 +22,7 @@ BenchmarkTestFiller, Block, Bytecode, - Create2Addr, + Create2PreimageLayout, Environment, ExtCallGenerator, Fork, @@ -389,7 +389,7 @@ def test_selfdestruct_existing( code = ( ( - create2_addr := Create2Addr( + create2_preimage := Create2PreimageLayout( factory_address=factory_address, salt=Op.CALLDATALOAD(0), init_code_hash=initcode.keccak256(), @@ -397,7 +397,7 @@ def test_selfdestruct_existing( ) # Main loop + While( - body=Op.POP(Op.CALL(address=create2_addr.compute)) + body=Op.POP(Op.CALL(address=create2_preimage.sha3_op)) + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), # Loop while we have enough gas AND within target count condition=Op.GT(Op.GAS, final_storage_gas + loop_cost), diff --git a/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py b/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py index 2786028e5d..ed83b1ad3d 100644 --- a/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py +++ b/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py @@ -13,7 +13,7 @@ Block, BlockchainTestFiller, Bytecode, - Create2Addr, + Create2PreimageLayout, Fork, Hash, Op, @@ -110,7 +110,7 @@ def test_unchunkified_bytecode( ) post[deployed_contract_address] = Account(nonce=1) - create2_addr = Create2Addr( + create2_preimage = Create2PreimageLayout( factory_address=factory_address, salt=0, init_code_hash=initcode.keccak256(), @@ -118,14 +118,14 @@ def test_unchunkified_bytecode( attack_call = Bytecode() if opcode == Op.EXTCODECOPY: attack_call = Op.EXTCODECOPY( - address=create2_addr.compute, dest_offset=96, size=1000 + address=create2_preimage.sha3_op, 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=create2_addr.compute)) + attack_call = Op.POP(opcode(address=create2_preimage.sha3_op)) attack_code = ( - create2_addr + create2_preimage # Main loop + While( body=attack_call + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), diff --git a/tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py b/tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py index 819f99ac3e..92226f5f62 100644 --- a/tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py +++ b/tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py @@ -71,7 +71,7 @@ BlockchainTestFiller, Bytecode, Conditional, - Create2Addr, + Create2PreimageLayout, Op, Storage, Transaction, @@ -130,7 +130,7 @@ def build_attack_contract(factory_address: Address) -> Bytecode: if_false=Op.REVERT(0, 0), ) + ( - create2_addr := Create2Addr( + create2_preimage := Create2PreimageLayout( factory_address=factory_address, salt=Op.SLOAD(0), init_code_hash=Op.MLOAD(128), @@ -139,7 +139,7 @@ def build_attack_contract(factory_address: Address) -> Bytecode: + Op.MSTORE(160, 0) # Initialize last_size + While( body=( - Op.MSTORE(160, Op.EXTCODESIZE(create2_addr.compute)) + Op.MSTORE(160, Op.EXTCODESIZE(create2_preimage.sha3_op)) + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)) ), condition=( diff --git a/tests/benchmark/stateful/bloatnet/test_multi_opcode.py b/tests/benchmark/stateful/bloatnet/test_multi_opcode.py index 1ea4480c2f..111167d619 100755 --- a/tests/benchmark/stateful/bloatnet/test_multi_opcode.py +++ b/tests/benchmark/stateful/bloatnet/test_multi_opcode.py @@ -13,7 +13,7 @@ Block, BlockchainTestFiller, Bytecode, - Create2Addr, + Create2PreimageLayout, Fork, Op, Transaction, @@ -178,7 +178,7 @@ def test_bloatnet_balance_extcodesize( # Memory[128:160] = init_code_hash + Op.MLOAD(96) # Load num_deployed_contracts to stack + ( - create2_addr := Create2Addr( + create2_preimage := Create2PreimageLayout( factory_address=factory_address, salt=0, init_code_hash=Op.MLOAD(128), @@ -188,7 +188,7 @@ def test_bloatnet_balance_extcodesize( + While( body=( # Generate CREATE2 addr: keccak256(0xFF+factory+salt+hash) - create2_addr.compute # Generate CREATE2 address from memory + create2_preimage.sha3_op # Hash CREATE2 address from memory # The address is now on the stack + Op.DUP1 # Duplicate for second operation + benchmark_ops # Execute operations in specified order @@ -370,7 +370,7 @@ def test_bloatnet_balance_extcodecopy( # Memory[128:160] = init_code_hash + Op.MLOAD(96) # Load num_deployed_contracts to stack + ( - create2_addr := Create2Addr( + create2_preimage := Create2PreimageLayout( factory_address=factory_address, salt=0, init_code_hash=Op.MLOAD(128), @@ -379,8 +379,8 @@ def test_bloatnet_balance_extcodecopy( # Main attack loop - iterate through all deployed contracts + While( body=( - # Generate CREATE2 address - create2_addr.compute + # Hash CREATE2 address + create2_preimage.sha3_op # The address is now on the stack + Op.DUP1 # Duplicate for later operations + benchmark_ops # Execute operations in specified order @@ -547,7 +547,7 @@ def test_bloatnet_balance_extcodehash( # Load results from memory + Op.MLOAD(96) # Load num_deployed_contracts to stack + ( - create2_addr := Create2Addr( + create2_preimage := Create2PreimageLayout( factory_address=factory_address, salt=0, init_code_hash=Op.MLOAD(128), @@ -556,8 +556,8 @@ def test_bloatnet_balance_extcodehash( # Main attack loop + While( body=( - # Generate CREATE2 address - create2_addr.compute + # Hash CREATE2 address + create2_preimage.sha3_op + Op.DUP1 # Duplicate for second operation + benchmark_ops # Execute operations in specified order # Increment salt From 1842898f5bb67f99c273d169ea23ecb455f03cbb Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 23 Jan 2026 21:30:40 +0000 Subject: [PATCH 4/5] fix: bug --- tests/benchmark/compute/scenario/test_unchunkified_bytecode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py b/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py index ed83b1ad3d..af9d6cfe2d 100644 --- a/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py +++ b/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py @@ -112,7 +112,7 @@ def test_unchunkified_bytecode( create2_preimage = Create2PreimageLayout( factory_address=factory_address, - salt=0, + salt=Op.CALLDATALOAD(0), init_code_hash=initcode.keccak256(), ) attack_call = Bytecode() From d0dea54b52db617810a318ee69959ead2d007c43 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 23 Jan 2026 21:54:17 +0000 Subject: [PATCH 5/5] feat: Implement increment salt method --- .../tools/tools_code/generators.py | 19 ++++++++++++--- .../compute/instruction/test_system.py | 4 ++-- .../scenario/test_unchunkified_bytecode.py | 6 ++--- .../test_extcodesize_bytecode_sizes.py | 23 +++++++++++++------ .../stateful/bloatnet/test_multi_opcode.py | 17 ++++++-------- 5 files changed, 44 insertions(+), 25 deletions(-) diff --git a/packages/testing/src/execution_testing/tools/tools_code/generators.py b/packages/testing/src/execution_testing/tools/tools_code/generators.py index ad88385d7e..cf7e2c4d58 100644 --- a/packages/testing/src/execution_testing/tools/tools_code/generators.py +++ b/packages/testing/src/execution_testing/tools/tools_code/generators.py @@ -408,7 +408,7 @@ class Create2PreimageLayout(Bytecode): - MEM[offset + 32: offset + 64] = salt (32 bytes) - MEM[offset + 64: offset + 96] = init_code_hash (32 bytes) - To compute the CREATE2 address, use: `.sha3_op` or + To compute the CREATE2 address, use: `.address_op` or `Op.SHA3(offset + 11, 85)`. The resulting hash's lower 20 bytes (bytes 12-31) form the address. """ @@ -447,9 +447,15 @@ def __new__( return instance @property - def sha3_op(self) -> Bytecode: + def salt_offset(self) -> int: """ - Return the Op.SHA3 that computes the CREATE2 address. + Return the salt memory offset of the preimage. + """ + return self.offset + 32 + + def address_op(self) -> Bytecode: + """ + Return the bytecode that computes the CREATE2 address. """ return Op.SHA3( offset=self.offset + 11, @@ -457,3 +463,10 @@ def sha3_op(self) -> Bytecode: # Gas accounting data_size=85, ) + + def increment_salt_op(self, increment: int = 1) -> Bytecode: + """Return the bytecode that increments the current salt.""" + return Op.MSTORE( + self.salt_offset, + Op.ADD(Op.MLOAD(self.salt_offset), increment), + ) diff --git a/tests/benchmark/compute/instruction/test_system.py b/tests/benchmark/compute/instruction/test_system.py index 84dca7b1d5..2c3667fdb5 100644 --- a/tests/benchmark/compute/instruction/test_system.py +++ b/tests/benchmark/compute/instruction/test_system.py @@ -397,8 +397,8 @@ def test_selfdestruct_existing( ) # Main loop + While( - body=Op.POP(Op.CALL(address=create2_preimage.sha3_op)) - + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), + body=Op.POP(Op.CALL(address=create2_preimage.address_op())) + + create2_preimage.increment_salt_op(), # Loop while we have enough gas AND within target count condition=Op.GT(Op.GAS, final_storage_gas + loop_cost), ) diff --git a/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py b/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py index af9d6cfe2d..a0e9c9c413 100644 --- a/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py +++ b/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py @@ -118,17 +118,17 @@ def test_unchunkified_bytecode( attack_call = Bytecode() if opcode == Op.EXTCODECOPY: attack_call = Op.EXTCODECOPY( - address=create2_preimage.sha3_op, dest_offset=96, size=1000 + address=create2_preimage.address_op(), 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=create2_preimage.sha3_op)) + attack_call = Op.POP(opcode(address=create2_preimage.address_op())) attack_code = ( create2_preimage # Main loop + While( - body=attack_call + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), + body=attack_call + create2_preimage.increment_salt_op(), ) ) diff --git a/tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py b/tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py index 92226f5f62..83812420f7 100644 --- a/tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py +++ b/tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py @@ -115,7 +115,9 @@ def build_attack_contract(factory_address: Address) -> Bytecode: - MEM[64-95] = init_code_hash (32 bytes) """ gas_reserve = 50_000 # Reserve for 2x SSTORE + cleanup - + num_deployed_offset = 96 + init_code_hash_offset = num_deployed_offset + 32 + return_size = 64 return ( # Call factory.getConfig() -> (num_deployed, init_code_hash) Conditional( @@ -124,8 +126,10 @@ def build_attack_contract(factory_address: Address) -> Bytecode: address=factory_address, args_offset=0, args_size=0, - ret_offset=96, # MEM[96]=num_deployed, MEM[128]=init_code_hash - ret_size=64, + # MEM[num_deployed_offset]=num_deployed + # MEM[num_deployed_offset + 32]=init_code_hash + ret_offset=num_deployed_offset, + ret_size=return_size, ), if_false=Op.REVERT(0, 0), ) @@ -133,19 +137,24 @@ def build_attack_contract(factory_address: Address) -> Bytecode: create2_preimage := Create2PreimageLayout( factory_address=factory_address, salt=Op.SLOAD(0), - init_code_hash=Op.MLOAD(128), + init_code_hash=Op.MLOAD(init_code_hash_offset), + old_memory_size=num_deployed_offset + return_size, ) ) + Op.MSTORE(160, 0) # Initialize last_size + While( body=( - Op.MSTORE(160, Op.EXTCODESIZE(create2_preimage.sha3_op)) - + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)) + Op.MSTORE(160, Op.EXTCODESIZE(create2_preimage.address_op())) + + create2_preimage.increment_salt_op() ), condition=( Op.AND( Op.GT(Op.GAS, gas_reserve), - Op.GT(Op.MLOAD(96), Op.MLOAD(32)), # num_deployed > salt + # num_deployed > salt + Op.GT( + Op.MLOAD(num_deployed_offset), + Op.MLOAD(create2_preimage.salt_offset), + ), ) ), ) diff --git a/tests/benchmark/stateful/bloatnet/test_multi_opcode.py b/tests/benchmark/stateful/bloatnet/test_multi_opcode.py index 111167d619..691d39b46c 100755 --- a/tests/benchmark/stateful/bloatnet/test_multi_opcode.py +++ b/tests/benchmark/stateful/bloatnet/test_multi_opcode.py @@ -188,14 +188,13 @@ def test_bloatnet_balance_extcodesize( + While( body=( # Generate CREATE2 addr: keccak256(0xFF+factory+salt+hash) - create2_preimage.sha3_op # Hash CREATE2 address from memory + # Hash CREATE2 address from memory + create2_preimage.address_op() # The address is now on the stack + Op.DUP1 # Duplicate for second operation + benchmark_ops # Execute operations in specified order # Increment salt for next iteration - + Op.MSTORE( - 32, Op.ADD(Op.MLOAD(32), 1) - ) # Increment and store salt + + create2_preimage.increment_salt_op() ), # Continue while we haven't reached the limit condition=Op.DUP1 @@ -380,14 +379,12 @@ def test_bloatnet_balance_extcodecopy( + While( body=( # Hash CREATE2 address - create2_preimage.sha3_op + create2_preimage.address_op() # The address is now on the stack + Op.DUP1 # Duplicate for later operations + benchmark_ops # Execute operations in specified order # Increment salt for next iteration - + Op.MSTORE( - 32, Op.ADD(Op.MLOAD(32), 1) - ) # Increment and store salt + + create2_preimage.increment_salt_op() ), # Continue while counter > 0 condition=Op.DUP1 @@ -557,11 +554,11 @@ def test_bloatnet_balance_extcodehash( + While( body=( # Hash CREATE2 address - create2_preimage.sha3_op + create2_preimage.address_op() + Op.DUP1 # Duplicate for second operation + benchmark_ops # Execute operations in specified order # Increment salt - + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)) + + create2_preimage.increment_salt_op() ), condition=Op.DUP1 + Op.PUSH1(1)