Skip to content

Commit c3813a5

Browse files
feat(test-benchmark): Introduce create2 helper for address computation (#1996)
1 parent fd9789f commit c3813a5

8 files changed

Lines changed: 160 additions & 76 deletions

File tree

packages/testing/src/execution_testing/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
Case,
8888
CodeGasMeasure,
8989
Conditional,
90+
Create2PreimageLayout,
9091
DeploymentTestType,
9192
Initcode,
9293
ParameterSet,
@@ -199,6 +200,7 @@
199200
"compute_create_address",
200201
"compute_create2_address",
201202
"compute_deterministic_create2_address",
203+
"Create2PreimageLayout",
202204
"extend_with_defaults",
203205
"gas_test",
204206
"generate_system_contract_deploy_test",

packages/testing/src/execution_testing/tools/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
Case,
99
CodeGasMeasure,
1010
Conditional,
11+
Create2PreimageLayout,
1112
Initcode,
1213
Switch,
1314
While,
@@ -31,6 +32,7 @@
3132
"ParameterSet",
3233
"Switch",
3334
"While",
35+
"Create2PreimageLayout",
3436
"extend_with_defaults",
3537
"gas_test",
3638
"generate_system_contract_deploy_test",

packages/testing/src/execution_testing/tools/tools_code/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
Case,
66
CodeGasMeasure,
77
Conditional,
8+
Create2PreimageLayout,
89
Initcode,
910
Switch,
1011
While,
@@ -22,4 +23,5 @@
2223
"While",
2324
"Yul",
2425
"YulCompiler",
26+
"Create2PreimageLayout",
2527
)

packages/testing/src/execution_testing/tools/tools_code/generators.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,3 +393,80 @@ def __new__(
393393
instance.default_action = default_action
394394
instance.cases = cases
395395
return instance
396+
397+
398+
class Create2PreimageLayout(Bytecode):
399+
"""
400+
Set up the preimage in memory for CREATE2 address computation.
401+
402+
Creates the standard memory layout required to compute a CREATE2 address
403+
using keccak256(0xFF ++ factory_address ++ salt ++ init_code_hash).
404+
405+
Memory layout after execution:
406+
- MEM[offset + 0: offset + 32] = zero padding + factory_address (20 bytes)
407+
- MEM[offset + 11] = 0xFF prefix byte
408+
- MEM[offset + 32: offset + 64] = salt (32 bytes)
409+
- MEM[offset + 64: offset + 96] = init_code_hash (32 bytes)
410+
411+
To compute the CREATE2 address, use: `.address_op` or
412+
`Op.SHA3(offset + 11, 85)`.
413+
The resulting hash's lower 20 bytes (bytes 12-31) form the address.
414+
"""
415+
416+
offset: int = 0
417+
418+
def __new__(
419+
cls,
420+
*,
421+
factory_address: int | bytes | Bytecode,
422+
salt: int | bytes | Bytecode,
423+
init_code_hash: int | bytes | Bytecode,
424+
offset: int = 0,
425+
old_memory_size: int = 0,
426+
) -> Self:
427+
"""
428+
Assemble the bytecode that sets up the memory layout for CREATE2
429+
address computation.
430+
"""
431+
required_size = offset + 96
432+
new_memory_size = max(old_memory_size, required_size)
433+
bytecode = (
434+
Op.MSTORE(offset=offset, value=factory_address)
435+
+ Op.MSTORE8(offset=offset + 11, value=0xFF)
436+
+ Op.MSTORE(offset=offset + 32, value=salt)
437+
+ Op.MSTORE(
438+
offset=offset + 64,
439+
value=init_code_hash,
440+
# Gas accounting
441+
old_memory_size=old_memory_size,
442+
new_memory_size=new_memory_size,
443+
)
444+
)
445+
instance = super().__new__(cls, bytecode)
446+
instance.offset = offset
447+
return instance
448+
449+
@property
450+
def salt_offset(self) -> int:
451+
"""
452+
Return the salt memory offset of the preimage.
453+
"""
454+
return self.offset + 32
455+
456+
def address_op(self) -> Bytecode:
457+
"""
458+
Return the bytecode that computes the CREATE2 address.
459+
"""
460+
return Op.SHA3(
461+
offset=self.offset + 11,
462+
size=85,
463+
# Gas accounting
464+
data_size=85,
465+
)
466+
467+
def increment_salt_op(self, increment: int = 1) -> Bytecode:
468+
"""Return the bytecode that increments the current salt."""
469+
return Op.MSTORE(
470+
self.salt_offset,
471+
Op.ADD(Op.MLOAD(self.salt_offset), increment),
472+
)

tests/benchmark/compute/instruction/test_system.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
BenchmarkTestFiller,
2323
Block,
2424
Bytecode,
25+
Create2PreimageLayout,
2526
Environment,
2627
ExtCallGenerator,
2728
Fork,
@@ -387,16 +388,17 @@ def test_selfdestruct_existing(
387388
)
388389

389390
code = (
390-
# Setup memory for later CREATE2 address generation loop.
391-
# 0xFF+[Address(20bytes)]+[seed(32bytes)]+[initcode keccak(32bytes)]
392-
Op.MSTORE(0, factory_address)
393-
+ Op.MSTORE8(32 - 20 - 1, 0xFF)
394-
+ Op.MSTORE(32, Op.CALLDATALOAD(0)) # Starting address from calldata
395-
+ Op.MSTORE(64, initcode.keccak256())
391+
(
392+
create2_preimage := Create2PreimageLayout(
393+
factory_address=factory_address,
394+
salt=Op.CALLDATALOAD(0),
395+
init_code_hash=initcode.keccak256(),
396+
)
397+
)
396398
# Main loop
397399
+ While(
398-
body=Op.POP(Op.CALL(address=Op.SHA3(32 - 20 - 1, 85)))
399-
+ Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)),
400+
body=Op.POP(Op.CALL(address=create2_preimage.address_op()))
401+
+ create2_preimage.increment_salt_op(),
400402
# Loop while we have enough gas AND within target count
401403
condition=Op.GT(Op.GAS, final_storage_gas + loop_cost),
402404
)

tests/benchmark/compute/scenario/test_unchunkified_bytecode.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
Block,
1414
BlockchainTestFiller,
1515
Bytecode,
16+
Create2PreimageLayout,
1617
Fork,
1718
Hash,
1819
Op,
@@ -109,27 +110,25 @@ def test_unchunkified_bytecode(
109110
)
110111
post[deployed_contract_address] = Account(nonce=1)
111112

113+
create2_preimage = Create2PreimageLayout(
114+
factory_address=factory_address,
115+
salt=Op.CALLDATALOAD(0),
116+
init_code_hash=initcode.keccak256(),
117+
)
112118
attack_call = Bytecode()
113119
if opcode == Op.EXTCODECOPY:
114120
attack_call = Op.EXTCODECOPY(
115-
address=Op.SHA3(32 - 20 - 1, 85), dest_offset=96, size=1000
121+
address=create2_preimage.address_op(), dest_offset=96, size=1000
116122
)
117123
else:
118124
# For the rest of the opcodes, we can use the same generic attack call
119125
# since all only minimally need the `address` of the target.
120-
attack_call = Op.POP(opcode(address=Op.SHA3(32 - 20 - 1, 85)))
126+
attack_call = Op.POP(opcode(address=create2_preimage.address_op()))
121127
attack_code = (
122-
# Setup memory for later CREATE2 address generation loop.
123-
# 0xFF+[Address(20bytes)]+[seed(32bytes)]+[initcode keccak(32bytes)]
124-
Op.MSTORE(0, factory_address)
125-
+ Op.MSTORE8(32 - 20 - 1, 0xFF)
126-
+ Op.MSTORE(
127-
32, Op.CALLDATALOAD(0)
128-
) # Calldata is the starting value of the CREATE2 salt
129-
+ Op.MSTORE(64, initcode.keccak256())
128+
create2_preimage
130129
# Main loop
131130
+ While(
132-
body=attack_call + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)),
131+
body=attack_call + create2_preimage.increment_salt_op(),
133132
)
134133
)
135134

tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
BlockchainTestFiller,
7272
Bytecode,
7373
Conditional,
74+
Create2PreimageLayout,
7475
Op,
7576
Storage,
7677
Transaction,
@@ -114,7 +115,9 @@ def build_attack_contract(factory_address: Address) -> Bytecode:
114115
- MEM[64-95] = init_code_hash (32 bytes)
115116
"""
116117
gas_reserve = 50_000 # Reserve for 2x SSTORE + cleanup
117-
118+
num_deployed_offset = 96
119+
init_code_hash_offset = num_deployed_offset + 32
120+
return_size = 64
118121
return (
119122
# Call factory.getConfig() -> (num_deployed, init_code_hash)
120123
Conditional(
@@ -123,26 +126,35 @@ def build_attack_contract(factory_address: Address) -> Bytecode:
123126
address=factory_address,
124127
args_offset=0,
125128
args_size=0,
126-
ret_offset=96, # MEM[96]=num_deployed, MEM[128]=init_code_hash
127-
ret_size=64,
129+
# MEM[num_deployed_offset]=num_deployed
130+
# MEM[num_deployed_offset + 32]=init_code_hash
131+
ret_offset=num_deployed_offset,
132+
ret_size=return_size,
128133
),
129134
if_false=Op.REVERT(0, 0),
130135
)
131-
# Setup CREATE2 memory: keccak256(0xFF ++ factory ++ salt ++ hash)
132-
+ Op.MSTORE(0, factory_address)
133-
+ Op.MSTORE8(11, 0xFF)
134-
+ Op.MSTORE(32, Op.SLOAD(0)) # Load salt directly to memory
135-
+ Op.MSTORE(64, Op.MLOAD(128)) # init_code_hash
136+
+ (
137+
create2_preimage := Create2PreimageLayout(
138+
factory_address=factory_address,
139+
salt=Op.SLOAD(0),
140+
init_code_hash=Op.MLOAD(init_code_hash_offset),
141+
old_memory_size=num_deployed_offset + return_size,
142+
)
143+
)
136144
+ Op.MSTORE(160, 0) # Initialize last_size
137145
+ While(
138146
body=(
139-
Op.MSTORE(160, Op.EXTCODESIZE(Op.SHA3(11, 85)))
140-
+ Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1))
147+
Op.MSTORE(160, Op.EXTCODESIZE(create2_preimage.address_op()))
148+
+ create2_preimage.increment_salt_op()
141149
),
142150
condition=(
143151
Op.AND(
144152
Op.GT(Op.GAS, gas_reserve),
145-
Op.GT(Op.MLOAD(96), Op.MLOAD(32)), # num_deployed > salt
153+
# num_deployed > salt
154+
Op.GT(
155+
Op.MLOAD(num_deployed_offset),
156+
Op.MLOAD(create2_preimage.salt_offset),
157+
),
146158
)
147159
),
148160
)

tests/benchmark/stateful/bloatnet/test_multi_opcode.py

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
Block,
1414
BlockchainTestFiller,
1515
Bytecode,
16+
Create2PreimageLayout,
1617
Fork,
1718
Op,
1819
Transaction,
@@ -175,31 +176,25 @@ def test_bloatnet_balance_extcodesize(
175176
# Load results from memory
176177
# Memory[96:128] = num_deployed_contracts
177178
# Memory[128:160] = init_code_hash
178-
+ Op.MLOAD(96) # Load num_deployed_contracts
179-
+ Op.MLOAD(128) # Load init_code_hash
180-
# Setup memory for CREATE2 address generation
181-
# Memory layout at 0: 0xFF + factory_addr(20) + salt(32) + hash(32)
182-
+ Op.MSTORE(
183-
0, factory_address
184-
) # Store factory address at memory position 0
185-
+ Op.MSTORE8(11, 0xFF) # Store 0xFF prefix at position (32 - 20 - 1)
186-
+ Op.MSTORE(32, 0) # Store salt at position 32
187-
# Stack now has: [num_contracts, init_code_hash]
188-
+ Op.PUSH1(64) # Push memory position
189-
+ Op.MSTORE # Store init_code_hash at memory[64]
190-
# Stack now has: [num_contracts]
179+
+ Op.MLOAD(96) # Load num_deployed_contracts to stack
180+
+ (
181+
create2_preimage := Create2PreimageLayout(
182+
factory_address=factory_address,
183+
salt=0,
184+
init_code_hash=Op.MLOAD(128),
185+
)
186+
)
191187
# Main attack loop - iterate through all deployed contracts
192188
+ While(
193189
body=(
194190
# Generate CREATE2 addr: keccak256(0xFF+factory+salt+hash)
195-
Op.SHA3(11, 85) # Generate CREATE2 address from memory[11:96]
191+
# Hash CREATE2 address from memory
192+
create2_preimage.address_op()
196193
# The address is now on the stack
197194
+ Op.DUP1 # Duplicate for second operation
198195
+ benchmark_ops # Execute operations in specified order
199196
# Increment salt for next iteration
200-
+ Op.MSTORE(
201-
32, Op.ADD(Op.MLOAD(32), 1)
202-
) # Increment and store salt
197+
+ create2_preimage.increment_salt_op()
203198
),
204199
# Continue while we haven't reached the limit
205200
condition=Op.DUP1
@@ -372,31 +367,24 @@ def test_bloatnet_balance_extcodecopy(
372367
# Load results from memory
373368
# Memory[96:128] = num_deployed_contracts
374369
# Memory[128:160] = init_code_hash
375-
+ Op.MLOAD(96) # Load num_deployed_contracts
376-
+ Op.MLOAD(128) # Load init_code_hash
377-
# Setup memory for CREATE2 address generation
378-
# Memory layout at 0: 0xFF + factory_addr(20) + salt(32) + hash(32)
379-
+ Op.MSTORE(
380-
0, factory_address
381-
) # Store factory address at memory position 0
382-
+ Op.MSTORE8(11, 0xFF) # Store 0xFF prefix at position (32 - 20 - 1)
383-
+ Op.MSTORE(32, 0) # Store salt at position 32
384-
# Stack now has: [num_contracts, init_code_hash]
385-
+ Op.PUSH1(64) # Push memory position
386-
+ Op.MSTORE # Store init_code_hash at memory[64]
387-
# Stack now has: [num_contracts]
370+
+ Op.MLOAD(96) # Load num_deployed_contracts to stack
371+
+ (
372+
create2_preimage := Create2PreimageLayout(
373+
factory_address=factory_address,
374+
salt=0,
375+
init_code_hash=Op.MLOAD(128),
376+
)
377+
)
388378
# Main attack loop - iterate through all deployed contracts
389379
+ While(
390380
body=(
391-
# Generate CREATE2 address
392-
Op.SHA3(11, 85) # Generate CREATE2 address from memory[11:96]
381+
# Hash CREATE2 address
382+
create2_preimage.address_op()
393383
# The address is now on the stack
394384
+ Op.DUP1 # Duplicate for later operations
395385
+ benchmark_ops # Execute operations in specified order
396386
# Increment salt for next iteration
397-
+ Op.MSTORE(
398-
32, Op.ADD(Op.MLOAD(32), 1)
399-
) # Increment and store salt
387+
+ create2_preimage.increment_salt_op()
400388
),
401389
# Continue while counter > 0
402390
condition=Op.DUP1
@@ -554,23 +542,23 @@ def test_bloatnet_balance_extcodehash(
554542
+ Op.PUSH2(0x1000) # Jump to error handler if failed
555543
+ Op.JUMPI
556544
# Load results from memory
557-
+ Op.MLOAD(96) # Load num_deployed_contracts
558-
+ Op.MLOAD(128) # Load init_code_hash
559-
# Setup memory for CREATE2 address generation
560-
+ Op.MSTORE(0, factory_address)
561-
+ Op.MSTORE8(11, 0xFF)
562-
+ Op.MSTORE(32, 0) # Initial salt
563-
+ Op.PUSH1(64)
564-
+ Op.MSTORE # Store init_code_hash
545+
+ Op.MLOAD(96) # Load num_deployed_contracts to stack
546+
+ (
547+
create2_preimage := Create2PreimageLayout(
548+
factory_address=factory_address,
549+
salt=0,
550+
init_code_hash=Op.MLOAD(128),
551+
)
552+
)
565553
# Main attack loop
566554
+ While(
567555
body=(
568-
# Generate CREATE2 address
569-
Op.SHA3(11, 85)
556+
# Hash CREATE2 address
557+
create2_preimage.address_op()
570558
+ Op.DUP1 # Duplicate for second operation
571559
+ benchmark_ops # Execute operations in specified order
572560
# Increment salt
573-
+ Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1))
561+
+ create2_preimage.increment_salt_op()
574562
),
575563
condition=Op.DUP1
576564
+ Op.PUSH1(1)

0 commit comments

Comments
 (0)