diff --git a/converted-ethereum-tests.txt b/converted-ethereum-tests.txt index 55df3bc2ca6..2ad8163109c 100644 --- a/converted-ethereum-tests.txt +++ b/converted-ethereum-tests.txt @@ -111,6 +111,7 @@ GeneralStateTests/Cancun/stEIP1153-transientStorage/20_oogUndoesTransientStoreIn ([#975](https://github.com/ethereum/execution-spec-tests/pull/975)) GeneralStateTests/VMTests/vmTests/push.json + ([#1067](https://github.com/ethereum/execution-spec-tests/pull/1067)) GeneralStateTests/stPreCompiledContracts/blake2B.json @@ -121,3 +122,6 @@ GeneralStateTests/stPreCompiledContracts/idPrecomps.json GeneralStateTests/stPreCompiledContracts/delegatecall09Undefined.json GeneralStateTests/stPreCompiledContracts2/CALLBlake2f.json GeneralStateTests/stPreCompiledContracts2/CALLCODEBlake2f.json + +([#1163](https://github.com/ethereum/execution-spec-tests/pull/1163)) +GeneralStateTests/VMTests/vmTests/swap.json diff --git a/tests/frontier/opcodes/test_swap.py b/tests/frontier/opcodes/test_swap.py new file mode 100644 index 00000000000..d527b41b6fd --- /dev/null +++ b/tests/frontier/opcodes/test_swap.py @@ -0,0 +1,129 @@ +""" +A State test for the set of `SWAP*` opcodes. +Ported from: https://github.com/ethereum/tests/ +blob/develop/src/GeneralStateTestsFiller/VMTests/vmTests/swapFiller.yml. +""" + +import pytest # noqa: I001 + +from ethereum_test_forks import Fork, Frontier, Homestead +from ethereum_test_tools import Account, Alloc, Bytecode, Environment +from ethereum_test_tools import Opcodes as Op +from ethereum_test_tools import StateTestFiller, Storage, Transaction + + +@pytest.mark.parametrize( + "swap_opcode", + [getattr(Op, f"SWAP{i}") for i in range(1, 17)], + ids=lambda op: str(op), +) +@pytest.mark.valid_from("Frontier") +def test_swap( + state_test: StateTestFiller, + fork: Fork, + pre: Alloc, + swap_opcode: Op +): + """ + The set of `SWAP*` opcodes swaps the top of the stack with a specific + element. + + In this test, we ensure that the set of `SWAP*` opcodes correctly swaps + the top element with the nth element and stores the result in storage. + """ + env = Environment() + + # Calculate which position we're swapping with (1-based index) + swap_pos = swap_opcode.int() - 0x90 + 1 + + # Generate stack values + stack_values = list(range(swap_pos + 1)) + + # Push the stack values onto the stack (in reverse order). + contract_code = Bytecode() + for value in reversed(stack_values): + contract_code += Op.PUSH1(value) + + # Perform the SWAP operation. + contract_code += swap_opcode + + # Store the top of the stack in storage slot 0. + contract_code += Op.PUSH1(0) + Op.SSTORE + + # Deploy the contract with the generated bytecode. + contract = pre.deploy_contract(contract_code) + + # Create a transaction to execute the contract. + tx = Transaction( + sender=pre.fund_eoa(), + to=contract, + gas_limit=500_000, + protected=False if fork in [Frontier, Homestead] else True, + ) + + # After SWAP, the top value will be the one at swap_pos + expected_value = swap_pos + + # Define the expected post-state. + post = {} + storage = Storage() + storage.store_next(expected_value, f"SWAP{swap_pos} result") + post[contract] = Account(storage=storage) + # Run the state test. + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize( + "swap_opcode", + [getattr(Op, f"SWAP{i}") for i in range(1, 17)], + ids=lambda op: str(op), +) +@pytest.mark.valid_from("Frontier") +def test_stack_underflow( + state_test: StateTestFiller, + fork: Fork, + pre: Alloc, + swap_opcode: Op, +): + """ + A test to ensure that the stack underflow when there are not enough + elements for the `SWAP*` opcode to operate. + + For each SWAPn operation, we push exactly (n-1) elements to cause an + underflow when trying to swap with the nth element. + """ + env = Environment() + + # Calculate which position we're swapping with (1-based index) + swap_pos = swap_opcode.int() - 0x90 + 1 + + # Push exactly (n-1) elements for SWAPn to cause underflow + contract_code = Bytecode() + for i in range(swap_pos - 1): + contract_code += Op.PUSH1(i % 256) + + # Attempt to perform the SWAP operation + contract_code += swap_opcode + + # Store the top of the stack in storage slot 0 + contract_code += Op.PUSH1(0) + Op.SSTORE + + # Deploy the contract with the generated bytecode. + contract = pre.deploy_contract(contract_code) + + # Create a transaction to execute the contract. + tx = Transaction( + sender=pre.fund_eoa(), + to=contract, + gas_limit=500_000, + protected=False if fork in [Frontier, Homestead] else True, + ) + + # Define the expected post-state. + post = {} + storage = Storage() + storage.store_next(0, f"SWAP{swap_pos} failed due to stack underflow") + post[contract] = Account(storage=storage) + + # Run the state test. + state_test(env=env, pre=pre, post=post, tx=tx)