Skip to content

Refactor(benchmark): optimize storage access worst case #1813

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
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
92 changes: 49 additions & 43 deletions tests/zkevm/test_worst_stateful_opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,21 +185,26 @@ def test_worst_address_state_warm(
)


class StorageAction:
"""Enum for storage actions."""
class LoadAction:
"""Enum for sload actions."""

READ = 1
WRITE_SAME_VALUE = 2
WRITE_NEW_VALUE = 3


class StoreAction:
"""Enum for sstore actions."""

WRITE_SAME_VALUE = 1
WRITE_NEW_VALUE = 2


@pytest.mark.valid_from("Cancun")
@pytest.mark.parametrize(
"storage_action",
[
pytest.param(StorageAction.READ, id="SSLOAD"),
pytest.param(StorageAction.WRITE_SAME_VALUE, id="SSTORE same value"),
pytest.param(StorageAction.WRITE_NEW_VALUE, id="SSTORE new value"),
pytest.param(LoadAction.READ, id="SSLOAD"),
pytest.param(StoreAction.WRITE_SAME_VALUE, id="SSTORE same value"),
pytest.param(StoreAction.WRITE_NEW_VALUE, id="SSTORE new value"),
],
)
@pytest.mark.parametrize(
Expand All @@ -214,7 +219,7 @@ def test_worst_storage_access_cold(
blockchain_test: BlockchainTestFiller,
pre: Alloc,
fork: Fork,
storage_action: StorageAction,
storage_action: LoadAction | StoreAction,
absent_slots: bool,
):
"""Test running a block with as many cold storage slot accesses as possible."""
Expand All @@ -223,17 +228,17 @@ def test_worst_storage_access_cold(
attack_gas_limit = Environment().gas_limit

cost = gas_costs.G_COLD_SLOAD # All accesses are always cold
if storage_action == StorageAction.WRITE_NEW_VALUE:
if storage_action == StoreAction.WRITE_NEW_VALUE:
if not absent_slots:
cost += gas_costs.G_STORAGE_RESET
else:
cost += gas_costs.G_STORAGE_SET
elif storage_action == StorageAction.WRITE_SAME_VALUE:
elif storage_action == StoreAction.WRITE_SAME_VALUE:
if absent_slots:
cost += gas_costs.G_STORAGE_SET
else:
cost += gas_costs.G_WARM_SLOAD
elif storage_action == StorageAction.READ:
elif storage_action == LoadAction.READ:
cost += gas_costs.G_WARM_SLOAD

intrinsic_gas_cost_calc = fork.transaction_intrinsic_cost_calculator()
Expand All @@ -242,30 +247,29 @@ def test_worst_storage_access_cold(
blocks = []

# Contract code
execution_code_body = Bytecode()
if storage_action == StorageAction.WRITE_SAME_VALUE:
# All the storage slots in the contract are initialized to their index.
# That is, storage slot `i` is initialized to `i`.
execution_code_body = Op.SSTORE(Op.DUP1, Op.DUP1)
elif storage_action == StorageAction.WRITE_NEW_VALUE:
# The new value 2^256-1 is guaranteed to be different from the initial value.
execution_code_body = Op.SSTORE(Op.DUP2, Op.NOT(0))
elif storage_action == StorageAction.READ:
execution_code_body = Op.POP(Op.SLOAD(Op.DUP1))

execution_code = Op.PUSH4(num_target_slots) + While(
body=execution_code_body,
condition=Op.PUSH1(1) + Op.SWAP1 + Op.SUB + Op.DUP1 + Op.ISZERO + Op.ISZERO,
)
execution_code = Bytecode()

if isinstance(storage_action, LoadAction):
execution_code += sum(Op.SLOAD(i) for i in reversed(range(num_target_slots)))
elif isinstance(storage_action, StoreAction):
if storage_action == StoreAction.WRITE_SAME_VALUE:
# All the storage slots in the contract are initialized to their index.
# That is, storage slot `i` is initialized to `i`.
execution_code += sum(Op.SSTORE(i, i) for i in reversed(range(num_target_slots)))
elif storage_action == StoreAction.WRITE_NEW_VALUE:
# The new value 2^256-1 is guaranteed to be different from the initial value.
execution_code += sum(
Op.SSTORE(i, Op.NOT(0)) for i in reversed(range(num_target_slots))
)

execution_code_address = pre.deploy_contract(code=execution_code)

# Contract creation
slots_init = Bytecode()
if not absent_slots:
slots_init = Op.PUSH4(num_target_slots) + While(
body=Op.SSTORE(Op.DUP1, Op.DUP1),
condition=Op.PUSH1(1) + Op.SWAP1 + Op.SUB + Op.DUP1 + Op.ISZERO + Op.ISZERO,
)
slots_init = (
Bytecode()
if absent_slots
else Bytecode() + sum(Op.SSTORE(i, i) for i in reversed(range(num_target_slots)))
)

# To create the contract, we apply the slots_init code to initialize the storage slots
# (int the case of absent_slots=False) and then copy the execution code to the contract.
Expand Down Expand Up @@ -310,16 +314,17 @@ def test_worst_storage_access_cold(
@pytest.mark.parametrize(
"storage_action",
[
pytest.param(StorageAction.READ, id="SLOAD"),
pytest.param(StorageAction.WRITE_SAME_VALUE, id="SSTORE same value"),
pytest.param(StorageAction.WRITE_NEW_VALUE, id="SSTORE new value"),
pytest.param(LoadAction.READ, id="SLOAD"),
pytest.param(StoreAction.WRITE_SAME_VALUE, id="SSTORE same value"),
pytest.param(StoreAction.WRITE_NEW_VALUE, id="SSTORE new value"),
],
)
@pytest.mark.slow()
def test_worst_storage_access_warm(
blockchain_test: BlockchainTestFiller,
pre: Alloc,
storage_action: StorageAction,
storage_action: LoadAction | StoreAction,
fork: Fork,
):
"""Test running a block with as many warm storage slot accesses as possible."""
env = Environment(gas_limit=100_000_000_000)
Expand All @@ -332,16 +337,17 @@ def test_worst_storage_access_warm(

# Contract code
execution_code_body = Bytecode()
if storage_action == StorageAction.WRITE_SAME_VALUE:
if storage_action == StoreAction.WRITE_SAME_VALUE:
execution_code_body = Op.SSTORE(0, Op.DUP1)
elif storage_action == StorageAction.WRITE_NEW_VALUE:
execution_code_body = Op.PUSH1(1) + Op.ADD + Op.SSTORE(0, Op.DUP1)
elif storage_action == StorageAction.READ:
elif storage_action == StoreAction.WRITE_NEW_VALUE:
execution_code_body = Op.SSTORE(0, Op.NOT(0))
elif storage_action == LoadAction.READ:
execution_code_body = Op.POP(Op.SLOAD(0))

execution_code = Op.PUSH1(storage_slot_initial_value) + While(
body=execution_code_body,
)
calldata = Op.PUSH1(storage_slot_initial_value)
execution_code = code_loop_precompile_call(calldata, execution_code_body, fork)
assert len(execution_code) <= fork.max_code_size()

execution_code_address = pre.deploy_contract(code=execution_code)

creation_code = (
Expand Down
Loading