Skip to content
Open
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
8 changes: 7 additions & 1 deletion tests/benchmark/stateful/bloatnet/stubs.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,11 @@
"test_mixed_sload_sstore_IMT": "0x13119e34e140097a507b07a5564bde1bc375d9e6",
"test_sload_empty_erc20_balanceof_STR": "0x7c4d13a8e743b036f6ba71c5dcadfe4fc9aa7a17",
"test_sstore_erc20_approve_STR": "0x7c4d13a8e743b036f6ba71c5dcadfe4fc9aa7a17",
"test_mixed_sload_sstore_STR": "0x7c4d13a8e743b036f6ba71c5dcadfe4fc9aa7a17"
"test_mixed_sload_sstore_STR": "0x7c4d13a8e743b036f6ba71c5dcadfe4fc9aa7a17",
"bloatnet_factory_0_5kb": "0xDf9da7E8660d38654C8aA267635222c9f5Ac0530",
"bloatnet_factory_1kb": "0xA52178573dc859741499946148CA1Fc3822Af600",
"bloatnet_factory_2kb": "0x88c159584059308C4B466bafC82c45d52De9951D",
"bloatnet_factory_5kb": "0x3f59809dac4b33e251e518acDadf923184151334",
"bloatnet_factory_10kb": "0xf90E8a6Ce32A949Ce5F58803BBa07476404cD89F",
"bloatnet_factory_24kb": "0x091988Afd81E0a2A3E20530A2510c089fF4bfAef"
}
214 changes: 105 additions & 109 deletions tests/benchmark/stateful/bloatnet/test_multi_opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
Block,
BlockchainTestFiller,
Bytecode,
Conditional,
Create2PreimageLayout,
Fork,
Op,
Transaction,
Expand Down Expand Up @@ -51,7 +53,30 @@
# 3. All contracts share same initcode hash for deterministic addresses
# 4. Attack rapidly accesses all contracts, stressing client's state handling

# Load stubs from stubs.json for test parametrization
_STUBS_FILE = Path(__file__).parent / "stubs.json"
with open(_STUBS_FILE) as f:
_STUBS = json.load(f)

# Factory getConfig() memory layout offsets
_NUM_DEPLOYED_OFFSET = 96
_INIT_CODE_HASH_OFFSET = _NUM_DEPLOYED_OFFSET + 32
_FACTORY_RETURN_SIZE = 64

# Extract factory stub names for factory-based tests
FACTORY_STUBS = sorted(
[k for k in _STUBS if k.startswith("bloatnet_factory_")],
key=lambda name: float(
name.replace("bloatnet_factory_", "").replace("kb", "").replace("_", ".")
),
)


@pytest.mark.parametrize(
"factory_stub",
FACTORY_STUBS,
ids=lambda s: s.replace("bloatnet_factory_", "").upper(),
)
@pytest.mark.parametrize(
"balance_first",
[True, False],
Expand All @@ -65,6 +90,7 @@ def test_bloatnet_balance_extcodesize(
gas_benchmark_value: int,
tx_gas_limit: int,
balance_first: bool,
factory_stub: str,
) -> None:
"""
BloatNet test using BALANCE + EXTCODESIZE with "on-the-fly" CREATE2
Expand Down Expand Up @@ -98,14 +124,10 @@ def test_bloatnet_balance_extcodesize(
+ 10 # While loop overhead
)

# Deploy factory using stub contract - NO HARDCODED VALUES
# The stub "bloatnet_factory" must be provided via --address-stubs flag
# The factory at that address MUST have:
# - Slot 0: Number of deployed contracts
# - Slot 1: Init code hash for CREATE2 address calculation
# Deploy factory using stub contract
factory_address = pre.deploy_contract(
code=Bytecode(), # Required parameter, but will be ignored for stubs
stub="bloatnet_factory",
code=Bytecode(),
stub=factory_stub,
)

# Calculate number of transactions needed (EIP-7825 compliance)
Expand Down Expand Up @@ -150,47 +172,34 @@ def test_bloatnet_balance_extcodesize(

# Build attack contract that reads config from factory
attack_code = (
# Call getConfig() on factory to get config
Op.STATICCALL(
gas=Op.GAS,
address=factory_address,
args_offset=0,
args_size=0,
ret_offset=96,
ret_size=64,
Conditional(
condition=Op.STATICCALL(
gas=Op.GAS,
address=factory_address,
args_offset=0,
args_size=0,
ret_offset=_NUM_DEPLOYED_OFFSET,
ret_size=_FACTORY_RETURN_SIZE,
),
if_false=Op.REVERT(0, 0),
)
+ (
create2_preimage := Create2PreimageLayout(
factory_address=factory_address,
salt=salt_offset,
init_code_hash=Op.MLOAD(_INIT_CODE_HASH_OFFSET),
old_memory_size=_NUM_DEPLOYED_OFFSET + _FACTORY_RETURN_SIZE,
)
)
# Check if call succeeded
+ Op.ISZERO
+ Op.PUSH2(0x1000) # Jump to error handler if failed (far jump)
+ Op.JUMPI
# Load results from memory
# Memory[96:128] = num_deployed_contracts
# Memory[128:160] = init_code_hash
+ 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 byte 11
+ Op.MSTORE(32, salt_offset) # Store starting salt at position 32
# Stack now has: [init_code_hash]
+ Op.PUSH1(64) # Push memory position
+ Op.MSTORE # Store init_code_hash at memory[64]
# Push our iteration count onto stack
+ Op.PUSH4(tx_contracts)
# Main attack loop - iterate through contracts for this tx
+ While(
body=(
# Generate CREATE2 addr: keccak256(0xFF+factory+salt+hash)
Op.SHA3(11, 85) # CREATE2 addr from memory[11:96]
# The address is now on the stack
create2_preimage.address_op()
+ 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 @@ -233,6 +242,11 @@ def test_bloatnet_balance_extcodesize(
)


@pytest.mark.parametrize(
"factory_stub",
FACTORY_STUBS,
ids=lambda s: s.replace("bloatnet_factory_", "").upper(),
)
@pytest.mark.parametrize(
"balance_first",
[True, False],
Expand All @@ -246,6 +260,7 @@ def test_bloatnet_balance_extcodecopy(
gas_benchmark_value: int,
tx_gas_limit: int,
balance_first: bool,
factory_stub: str,
) -> None:
"""
BloatNet test using BALANCE + EXTCODECOPY with on-the-fly CREATE2
Expand Down Expand Up @@ -281,14 +296,10 @@ def test_bloatnet_balance_extcodecopy(
+ 10 # While loop overhead
)

# Deploy factory using stub contract - NO HARDCODED VALUES
# The stub "bloatnet_factory" must be provided via --address-stubs flag
# The factory at that address MUST have:
# - Slot 0: Number of deployed contracts
# - Slot 1: Init code hash for CREATE2 address calculation
# Deploy factory using stub contract
factory_address = pre.deploy_contract(
code=Bytecode(), # Required parameter, but will be ignored for stubs
stub="bloatnet_factory",
code=Bytecode(),
stub=factory_stub,
)

# Calculate number of transactions needed (EIP-7825 compliance)
Expand Down Expand Up @@ -340,46 +351,34 @@ def test_bloatnet_balance_extcodecopy(

# Build attack contract that reads config from factory
attack_code = (
# Call getConfig() on factory to get config
Op.STATICCALL(
gas=Op.GAS,
address=factory_address,
args_offset=0,
args_size=0,
ret_offset=96,
ret_size=64,
Conditional(
condition=Op.STATICCALL(
gas=Op.GAS,
address=factory_address,
args_offset=0,
args_size=0,
ret_offset=_NUM_DEPLOYED_OFFSET,
ret_size=_FACTORY_RETURN_SIZE,
),
if_false=Op.REVERT(0, 0),
)
+ (
create2_preimage := Create2PreimageLayout(
factory_address=factory_address,
salt=salt_offset,
init_code_hash=Op.MLOAD(_INIT_CODE_HASH_OFFSET),
old_memory_size=_NUM_DEPLOYED_OFFSET + _FACTORY_RETURN_SIZE,
)
)
# Check if call succeeded
+ Op.ISZERO
+ Op.PUSH2(0x1000) # Jump to error handler if failed (far jump)
+ Op.JUMPI
# Load results from memory
# Memory[128:160] = init_code_hash
+ 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 byte 11
+ Op.MSTORE(32, salt_offset) # Store starting salt at position 32
# Stack now has: [init_code_hash]
+ Op.PUSH1(64) # Push memory position
+ Op.MSTORE # Store init_code_hash at memory[64]
# Push our iteration count onto stack
+ Op.PUSH4(tx_contracts)
# Main attack loop - iterate through contracts for this tx
+ While(
body=(
# Generate CREATE2 address
Op.SHA3(11, 85) # CREATE2 addr from memory[11:96]
# The address is now on the stack
create2_preimage.address_op()
+ 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 @@ -422,6 +421,11 @@ def test_bloatnet_balance_extcodecopy(
)


@pytest.mark.parametrize(
"factory_stub",
FACTORY_STUBS,
ids=lambda s: s.replace("bloatnet_factory_", "").upper(),
)
@pytest.mark.parametrize(
"balance_first",
[True, False],
Expand All @@ -435,6 +439,7 @@ def test_bloatnet_balance_extcodehash(
gas_benchmark_value: int,
tx_gas_limit: int,
balance_first: bool,
factory_stub: str,
) -> None:
"""
BloatNet test using BALANCE + EXTCODEHASH with on-the-fly CREATE2
Expand Down Expand Up @@ -471,7 +476,7 @@ def test_bloatnet_balance_extcodehash(
# Deploy factory using stub contract
factory_address = pre.deploy_contract(
code=Bytecode(),
stub="bloatnet_factory",
stub=factory_stub,
)

# Calculate number of transactions needed (EIP-7825 compliance)
Expand Down Expand Up @@ -516,38 +521,34 @@ def test_bloatnet_balance_extcodehash(

# Build attack contract that reads config from factory
attack_code = (
# Call getConfig() on factory to get config
Op.STATICCALL(
gas=Op.GAS,
address=factory_address,
args_offset=0,
args_size=0,
ret_offset=96,
ret_size=64,
Conditional(
condition=Op.STATICCALL(
gas=Op.GAS,
address=factory_address,
args_offset=0,
args_size=0,
ret_offset=_NUM_DEPLOYED_OFFSET,
ret_size=_FACTORY_RETURN_SIZE,
),
if_false=Op.REVERT(0, 0),
)
+ (
create2_preimage := Create2PreimageLayout(
factory_address=factory_address,
salt=salt_offset,
init_code_hash=Op.MLOAD(_INIT_CODE_HASH_OFFSET),
old_memory_size=_NUM_DEPLOYED_OFFSET + _FACTORY_RETURN_SIZE,
)
)
# Check if call succeeded
+ Op.ISZERO
+ Op.PUSH2(0x1000) # Jump to error handler if failed
+ Op.JUMPI
# Load results from memory
+ 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, salt_offset) # Starting salt for this tx
+ Op.PUSH1(64)
+ Op.MSTORE # Store init_code_hash
# Push our iteration count onto stack
+ Op.PUSH4(tx_contracts)
# Main attack loop
+ While(
body=(
# Generate CREATE2 address
Op.SHA3(11, 85)
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 Expand Up @@ -593,11 +594,6 @@ def test_bloatnet_balance_extcodehash(
BALANCEOF_SELECTOR = 0x70A08231 # balanceOf(address)
APPROVE_SELECTOR = 0x095EA7B3 # approve(address,uint256)

# Load token names from stubs.json for test parametrization
_STUBS_FILE = Path(__file__).parent / "stubs.json"
with open(_STUBS_FILE) as f:
_STUBS = json.load(f)

# Extract unique token names for mixed sload/sstore tests
MIXED_TOKENS = [
k.replace("test_mixed_sload_sstore_", "")
Expand Down