Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/testing/src/execution_testing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
Case,
CodeGasMeasure,
Conditional,
Create2PreimageLayout,
DeploymentTestType,
Initcode,
ParameterSet,
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions packages/testing/src/execution_testing/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Case,
CodeGasMeasure,
Conditional,
Create2PreimageLayout,
Initcode,
Switch,
While,
Expand All @@ -31,6 +32,7 @@
"ParameterSet",
"Switch",
"While",
"Create2PreimageLayout",
"extend_with_defaults",
"gas_test",
"generate_system_contract_deploy_test",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Case,
CodeGasMeasure,
Conditional,
Create2PreimageLayout,
Initcode,
Switch,
While,
Expand All @@ -22,4 +23,5 @@
"While",
"Yul",
"YulCompiler",
"Create2PreimageLayout",
)
Original file line number Diff line number Diff line change
Expand Up @@ -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),
)
18 changes: 10 additions & 8 deletions tests/benchmark/compute/instruction/test_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
BenchmarkTestFiller,
Block,
Bytecode,
Create2PreimageLayout,
Environment,
ExtCallGenerator,
Fork,
Expand Down Expand Up @@ -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),
)
Expand Down
21 changes: 10 additions & 11 deletions tests/benchmark/compute/scenario/test_unchunkified_bytecode.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
Block,
BlockchainTestFiller,
Bytecode,
Create2PreimageLayout,
Fork,
Hash,
Op,
Expand Down Expand Up @@ -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(),
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
BlockchainTestFiller,
Bytecode,
Conditional,
Create2PreimageLayout,
Op,
Storage,
Transaction,
Expand Down Expand Up @@ -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(
Expand All @@ -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),
),
)
),
)
Expand Down
80 changes: 34 additions & 46 deletions tests/benchmark/stateful/bloatnet/test_multi_opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
Block,
BlockchainTestFiller,
Bytecode,
Create2PreimageLayout,
Fork,
Op,
Transaction,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Loading