diff --git a/tests/amsterdam/eip7843_slotnum/spec.py b/tests/amsterdam/eip7843_slotnum/spec.py new file mode 100644 index 0000000000..a96172631f --- /dev/null +++ b/tests/amsterdam/eip7843_slotnum/spec.py @@ -0,0 +1,22 @@ +"""Reference spec for [EIP-7843: SLOTNUM](https://eips.ethereum.org/EIPS/eip-7843).""" + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class ReferenceSpec: + """Reference specification.""" + + git_path: str + version: str + + +ref_spec_7843 = ReferenceSpec( + git_path="EIPS/eip-7843.md", + version="6bc5d6b7acbc016a79fa573f98975093b5c2ca52", +) + + +@dataclass(frozen=True) +class Spec: + """Constants and parameters from EIP-7843.""" diff --git a/tests/amsterdam/eip7843_slotnum/test_eip_mainnet.py b/tests/amsterdam/eip7843_slotnum/test_eip_mainnet.py new file mode 100644 index 0000000000..90924e6f17 --- /dev/null +++ b/tests/amsterdam/eip7843_slotnum/test_eip_mainnet.py @@ -0,0 +1,53 @@ +""" +Mainnet marked execute checklist tests for +[EIP-7843: SLOTNUM](https://eips.ethereum.org/EIPS/eip-7843). +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Environment, + Op, + StateTestFiller, + Transaction, +) + +from .spec import ref_spec_7843 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7843.git_path +REFERENCE_SPEC_VERSION = ref_spec_7843.version + +pytestmark = [pytest.mark.valid_at("EIP7843"), pytest.mark.mainnet] + + +def test_slotnum_mainnet( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + Test that SLOTNUM is callable and returns a non-zero slot number. + + Asserts on ``ISZERO(ISZERO(SLOTNUM))`` rather than the slot value itself + so the test remains valid when ``execute``-ed against a live network, + where the slot number is whatever the consensus layer transmits and + cannot be controlled by the test. + """ + contract = pre.deploy_contract( + code=Op.SSTORE(0, Op.ISZERO(Op.ISZERO(Op.SLOTNUM))), + storage={"0x00": "0xdeadbeef"}, + ) + tx = Transaction( + ty=0x02, + to=contract, + sender=pre.fund_eoa(), + gas_limit=200_000, + ) + post = {contract: Account(storage={"0x00": 1})} + + state_test( + env=Environment(slot_number=1), + pre=pre, + tx=tx, + post=post, + ) diff --git a/tests/amsterdam/eip7843_slotnum/test_fork_transition.py b/tests/amsterdam/eip7843_slotnum/test_fork_transition.py new file mode 100644 index 0000000000..867f065ac9 --- /dev/null +++ b/tests/amsterdam/eip7843_slotnum/test_fork_transition.py @@ -0,0 +1,73 @@ +"""Tests for EIP-7843 fork transition behavior.""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Block, + BlockchainTestFiller, + Op, + Transaction, +) + +from .spec import ref_spec_7843 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7843.git_path +REFERENCE_SPEC_VERSION = ref_spec_7843.version + + +@pytest.mark.valid_at_transition_to("EIP7843") +def test_slotnum_at_fork_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, +) -> None: + """ + Test SLOTNUM behavior across the EIP-7843 fork transition. + + Before EIP-7843, opcode 0x4B is undefined: execution halts with an + invalid-opcode exception and consumes all gas, so no SSTORE is observed. + + From EIP-7843 onward, SLOTNUM pushes the block's slot number provided + by the consensus layer and the SSTORE succeeds. + + The contract keys storage by block number so each block's outcome is + independently visible in the final post-state: + + * block 1 (pre-fork): slot 1 stays 0 — execution halted before SSTORE. + * block 2 (transition): slot 2 == ``at_fork_slot``. + * block 3 (post-fork): slot 3 == ``post_fork_slot``. + """ + sender = pre.fund_eoa() + contract = pre.deploy_contract(Op.SSTORE(Op.NUMBER, Op.SLOTNUM) + Op.STOP) + + at_fork_slot = 200 + post_fork_slot = 201 + + blocks = [ + Block( + timestamp=14_999, + txs=[Transaction(sender=sender, to=contract, gas_limit=100_000)], + ), + Block( + timestamp=15_000, + slot_number=at_fork_slot, + txs=[Transaction(sender=sender, to=contract, gas_limit=100_000)], + ), + Block( + timestamp=15_001, + slot_number=post_fork_slot, + txs=[Transaction(sender=sender, to=contract, gas_limit=100_000)], + ), + ] + + post = { + contract: Account( + storage={ + 1: 0, + 2: at_fork_slot, + 3: post_fork_slot, + }, + ), + } + + blockchain_test(pre=pre, blocks=blocks, post=post) diff --git a/tests/amsterdam/eip7843_slotnum/test_slotnum.py b/tests/amsterdam/eip7843_slotnum/test_slotnum.py index 65358ebb31..c3387c957e 100644 --- a/tests/amsterdam/eip7843_slotnum/test_slotnum.py +++ b/tests/amsterdam/eip7843_slotnum/test_slotnum.py @@ -1,11 +1,11 @@ """Tests for EIP-7843 (SLOTNUM).""" -from dataclasses import dataclass - import pytest from execution_testing import ( Account, Alloc, + Block, + BlockchainTestFiller, Environment, Fork, Op, @@ -13,19 +13,7 @@ Transaction, ) - -@dataclass(frozen=True) -class ReferenceSpec: - """Reference specification.""" - - git_path: str - version: str - - -ref_spec_7843 = ReferenceSpec( - git_path="EIPS/eip-7843.md", - version="6bc5d6b7acbc016a79fa573f98975093b5c2ca52", -) +from .spec import ref_spec_7843 REFERENCE_SPEC_GIT_PATH = ref_spec_7843.git_path REFERENCE_SPEC_VERSION = ref_spec_7843.version @@ -124,3 +112,39 @@ def test_slotnum_gas_cost( tx=tx, post=post, ) + + +def test_slotnum_distinct_per_block( + blockchain_test: BlockchainTestFiller, + pre: Alloc, +) -> None: + """ + Test that SLOTNUM returns each block's own slot number. + + Runs four consecutive blocks with deliberately non-monotonic slot + numbers to disprove any caching or ordering assumption in the opcode + implementation. Each block runs the same contract, which keys storage + by block ``NUMBER`` so every block's outcome is independently visible + in the final post-state. + """ + sender = pre.fund_eoa() + contract = pre.deploy_contract(Op.SSTORE(Op.NUMBER, Op.SLOTNUM) + Op.STOP) + + # Non-monotonic on purpose: decrease, increase, jump to large value. + slot_numbers = [100, 42, 7, 2**32] + + blocks = [ + Block( + slot_number=slot, + txs=[Transaction(sender=sender, to=contract, gas_limit=100_000)], + ) + for slot in slot_numbers + ] + + post = { + contract: Account( + storage={i + 1: slot for i, slot in enumerate(slot_numbers)}, + ), + } + + blockchain_test(pre=pre, blocks=blocks, post=post)