From c3813a57a01cd28d3ca1401f833cc5b1bc82b0dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BD=B3=E8=AA=A0=20Louis=20Tsai?= <72684086+LouisTsai-Csie@users.noreply.github.com> Date: Sat, 24 Jan 2026 07:31:10 +0800 Subject: [PATCH] feat(test-benchmark): Introduce create2 helper for address computation (#1996) --- .../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 | 77 ++++++++++++++++++ .../compute/instruction/test_system.py | 18 +++-- .../scenario/test_unchunkified_bytecode.py | 21 +++-- .../test_extcodesize_bytecode_sizes.py | 34 +++++--- .../stateful/bloatnet/test_multi_opcode.py | 80 ++++++++----------- 8 files changed, 160 insertions(+), 76 deletions(-) diff --git a/packages/testing/src/execution_testing/__init__.py b/packages/testing/src/execution_testing/__init__.py index b10c00e353..05f4914c83 100644 --- a/packages/testing/src/execution_testing/__init__.py +++ b/packages/testing/src/execution_testing/__init__.py @@ -87,6 +87,7 @@ Case, CodeGasMeasure, Conditional, + Create2PreimageLayout, DeploymentTestType, Initcode, ParameterSet, @@ -199,6 +200,7 @@ "compute_create_address", "compute_create2_address", "compute_deterministic_create2_address", + "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 d47d964305..ce9268fcb7 100644 --- a/packages/testing/src/execution_testing/tools/__init__.py +++ b/packages/testing/src/execution_testing/tools/__init__.py @@ -8,6 +8,7 @@ Case, CodeGasMeasure, Conditional, + Create2PreimageLayout, Initcode, Switch, While, @@ -31,6 +32,7 @@ "ParameterSet", "Switch", "While", + "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 1ef17fd240..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,6 +5,7 @@ Case, CodeGasMeasure, Conditional, + Create2PreimageLayout, Initcode, Switch, While, @@ -22,4 +23,5 @@ "While", "Yul", "YulCompiler", + "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 6d7985bfd9..cf7e2c4d58 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,80 @@ def __new__( instance.default_action = default_action instance.cases = cases return instance + + +class Create2PreimageLayout(Bytecode): + """ + 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). + + 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: `.address_op` 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, + 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=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 salt_offset(self) -> int: + """ + 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, + size=85, + # 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 6433f0eaa1..2c3667fdb5 100644 --- a/tests/benchmark/compute/instruction/test_system.py +++ b/tests/benchmark/compute/instruction/test_system.py @@ -22,6 +22,7 @@ BenchmarkTestFiller, Block, Bytecode, + Create2PreimageLayout, Environment, ExtCallGenerator, Fork, @@ -387,16 +388,17 @@ 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_preimage := Create2PreimageLayout( + 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))) - + 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 5e60d85a2a..a0e9c9c413 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, + Create2PreimageLayout, Fork, Hash, Op, @@ -109,27 +110,25 @@ def test_unchunkified_bytecode( ) post[deployed_contract_address] = Account(nonce=1) + create2_preimage = Create2PreimageLayout( + factory_address=factory_address, + salt=Op.CALLDATALOAD(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_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=Op.SHA3(32 - 20 - 1, 85))) + attack_call = Op.POP(opcode(address=create2_preimage.address_op())) 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_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 42da6314c2..83812420f7 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, + Create2PreimageLayout, Op, Storage, Transaction, @@ -114,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( @@ -123,26 +126,35 @@ 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), ) - # 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_preimage := Create2PreimageLayout( + factory_address=factory_address, + salt=Op.SLOAD(0), + 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(Op.SHA3(11, 85))) - + 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 a19be48830..691d39b46c 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, + Create2PreimageLayout, Fork, Op, Transaction, @@ -175,31 +176,25 @@ 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_preimage := Create2PreimageLayout( + 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] + # 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 @@ -372,31 +367,24 @@ 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_preimage := Create2PreimageLayout( + 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] + # Hash CREATE2 address + 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 @@ -554,23 +542,23 @@ 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_preimage := Create2PreimageLayout( + factory_address=factory_address, + salt=0, + init_code_hash=Op.MLOAD(128), + ) + ) # Main attack loop + While( body=( - # Generate CREATE2 address - Op.SHA3(11, 85) + # Hash CREATE2 address + 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)