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
183 changes: 4 additions & 179 deletions tests/benchmark/stateful/bloatnet/test_single_opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from enum import Enum, auto
from functools import partial
from typing import Callable, List
from typing import List

import pytest
from execution_testing import (
Expand Down Expand Up @@ -44,6 +44,9 @@
MINT_SELECTOR,
CacheStrategy,
build_cache_strategy_blocks,
build_delegated_storage_setup,
create_sstore_initializer,
initializer_calldata_generator,
)

REFERENCE_SPEC_GIT_PATH = "DUMMY/bloatnet.md"
Expand Down Expand Up @@ -1215,50 +1218,6 @@ def test_sstore_erc20_mint(
)


def create_sstore_initializer(init_val: int) -> IteratingBytecode:
"""
Create a contract that initializes storage slots from calldata parameters.

- CALLDATA[0..32] start slot (index)
- CALLDATA[32..64] slot count (num)

storage[i] = init_val for i in [index, index + num).

Returns: IteratingBytecode representing the storage initializer.
"""
# Setup: [index, index + num]
prefix = (
Op.CALLDATALOAD(0) # [index]
+ Op.DUP1 # [index, index]
+ Op.CALLDATALOAD(32) # [index, index, num]
+ Op.ADD # [index, index + num]
)

# Loop: decrement counter and store at current position
# Stack after subtraction: [index, current]
# where current goes from index+num-1 down to index
loop = (
Op.JUMPDEST
+ Op.PUSH1(1) # [index, current, 1]
+ Op.SWAP1 # [index, 1, current]
+ Op.SUB # [index, current - 1]
+ Op.SSTORE( # STORAGE[current-1] = initial_value
Op.DUP2,
init_val,
key_warm=False,
# gas accounting
original_value=0,
current_value=0,
new_value=init_val,
)
# After SSTORE: [index, current - 1]
# Continue while current - 1 > index
+ Op.JUMPI(len(prefix), Op.GT(Op.DUP2, Op.DUP2))
)

return IteratingBytecode(setup=prefix, iterating=loop)


def create_sstore_executor(
sloads_before_sstore: bool,
key_warm: bool,
Expand Down Expand Up @@ -1420,140 +1379,6 @@ def executor_calldata_generator(
return result


def initializer_calldata_generator(
iteration_count: int, start_iteration: int
) -> bytes:
"""Calldata generator for the storage: Hash(start) + Hash(count)."""
return Hash(start_iteration) + Hash(iteration_count)


def pack_transactions_into_blocks(
transactions: List[Transaction],
gas_limit: int,
) -> List[Block]:
"""
Pack transactions into blocks without exceeding gas_limit per block.

Greedily adds transactions to the current block until adding another
would exceed the gas limit, then starts a new block.
"""
if not transactions:
return []

blocks: List[Block] = []
current_txs: List[Transaction] = []
current_gas = 0

for tx in transactions:
if current_gas + tx.gas_limit > gas_limit and current_txs:
blocks.append(Block(txs=current_txs))
current_txs = []
current_gas = 0

current_txs.append(tx)
current_gas += tx.gas_limit

if current_txs:
blocks.append(Block(txs=current_txs))

return blocks


def build_delegated_storage_setup(
*,
pre: Alloc,
fork: Fork,
tx_gas_limit: int,
needs_init: bool,
num_target_slots: int,
initializer_code: IteratingBytecode,
initializer_addr: Address,
executor_addr: Address,
authority: EOA,
authority_nonce: int,
delegation_sender: EOA,
initializer_calldata_generator: Callable[[int, int], bytes],
) -> List[Block]:
"""
Build setup blocks for delegated storage benchmarks.

Returns:
List of blocks for the setup phase.

"""
blocks: List[Block] = []

if needs_init:
# Block 1: Authorize to initializer
blocks.append(
Block(
txs=[
Transaction(
to=delegation_sender,
gas_limit=tx_gas_limit,
sender=delegation_sender,
authorization_list=[
AuthorizationTuple(
address=initializer_addr,
nonce=authority_nonce,
signer=authority,
),
],
)
]
)
)
authority_nonce += 1

# Calculate max slots per transaction based on gas cost
iteration_cost = initializer_code.tx_gas_limit_by_iteration_count(
fork=fork,
iteration_count=1,
start_iteration=1,
calldata=initializer_calldata_generator,
)
iteration_count = max(1, tx_gas_limit // iteration_cost)

init_txs: List[Transaction] = []
for start in range(1, num_target_slots + 1, iteration_count):
chunk_size = min(iteration_count, num_target_slots - start + 1)
init_txs.extend(
initializer_code.transactions_by_total_iteration_count(
fork=fork,
total_iterations=chunk_size,
sender=pre.fund_eoa(),
to=authority,
start_iteration=start,
calldata=initializer_calldata_generator,
)
)

# Pack init transactions into blocks
blocks.extend(pack_transactions_into_blocks(init_txs, tx_gas_limit))

# Final block: Authorize to executor
blocks.append(
Block(
txs=[
Transaction(
to=delegation_sender,
gas_limit=tx_gas_limit,
sender=delegation_sender,
authorization_list=[
AuthorizationTuple(
address=executor_addr,
nonce=authority_nonce,
signer=authority,
),
],
)
]
)
)

return blocks


@pytest.mark.parametrize("access_warm", [True, False])
@pytest.mark.parametrize("sloads_before_sstore", [True, False])
@pytest.mark.parametrize(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
from dataclasses import dataclass

from execution_testing import (
EOA,
Account,
Address,
Alloc,
AuthorizationTuple,
BalAccountExpectation,
BalNonceChange,
BalStorageSlot,
Expand All @@ -24,10 +26,15 @@
Bytecode,
Fork,
Op,
Storage,
TestPhaseManager,
Transaction,
)
from execution_testing.base_types.base_types import Number

from tests.benchmark.stateful.helpers import (
StorageInitRange,
build_sequential_storage_init,
)

CURSOR_SLOT = 0
CURSOR_INIT = 1
Expand Down Expand Up @@ -207,38 +214,83 @@ def _iters(tx_gas: int) -> int:

def run_bal_benchmark(
pre: Alloc,
fork: Fork,
benchmark_test: BenchmarkTestFiller,
contract_code: Bytecode,
contract_storage: Storage,
plan: BenchmarkPlan,
tx_gas_limit: int,
authority: EOA,
storage_init_ranges: list[StorageInitRange] | None = None,
data_slot_reads: list[int] | None = None,
extra_expectations: (dict[Address, BalAccountExpectation] | None) = None,
) -> None:
"""Deploy contract, create txs, BAL expectations, and run."""
contract = pre.deploy_contract(
code=contract_code, storage=contract_storage
)
"""
Run a BAL benchmark using EIP-7702 delegated storage initialization.

Deploy the executor contract, optionally initialize the
*authority*'s storage via delegation, then delegate to the
executor and run the benchmark transactions. The authority's
nonce is incremented in-place.
"""
executor_addr = pre.deploy_contract(code=contract_code)

blocks: list[Block] = []

with TestPhaseManager.setup():
if storage_init_ranges:
blocks.extend(
build_sequential_storage_init(
pre=pre,
fork=fork,
tx_gas_limit=tx_gas_limit,
authority=authority,
storage_init_ranges=storage_init_ranges,
)
)

# Delegate authority to executor
delegation_sender = pre.fund_eoa()
blocks.append(
Block(
txs=[
Transaction(
to=delegation_sender,
gas_limit=tx_gas_limit,
sender=delegation_sender,
authorization_list=[
AuthorizationTuple(
address=executor_addr,
nonce=authority.nonce,
signer=authority,
),
],
)
]
)
)
authority.nonce = Number(authority.nonce + 1)

# Execution phase
num_txs = len(plan.gas_limits)
with TestPhaseManager.execution():
sender = pre.fund_eoa()
transactions = [
Transaction(
sender=sender,
to=contract,
to=authority,
gas_limit=plan.gas_limits[i],
data=b"",
)
for i in range(num_txs)
]

# BAL expectations: contract slots + sender nonces.
# BAL expectations: authority slots + sender nonces.
# Use validate_any_change for cursor — exact values depend
# on gas dynamics and are verified by consensus test suites.
# All txs share a single sender to prevent trivial per-sender
# optimizations in BAL implementations.
expectations: dict[Address, BalAccountExpectation] = {
contract: BalAccountExpectation(
authority: BalAccountExpectation(
storage_reads=sorted(set(data_slot_reads or [])),
storage_changes=[
BalStorageSlot(
Expand All @@ -260,13 +312,15 @@ def run_bal_benchmark(
if extra_expectations:
expectations.update(extra_expectations)

block = Block(
exec_block = Block(
txs=transactions,
expected_block_access_list=BlockAccessListExpectation(
account_expectations=expectations
),
)

blocks.append(exec_block)

# Post-state: only check sender nonce (sanity).
# Exact storage values depend on gas dynamics and may be
# slightly off; consensus correctness is verified elsewhere.
Expand All @@ -275,5 +329,8 @@ def run_bal_benchmark(
}

benchmark_test(
pre=pre, post=post, blocks=[block], skip_gas_used_validation=True
pre=pre,
post=post,
blocks=blocks,
skip_gas_used_validation=True,
)
Loading