diff --git a/converted-ethereum-tests.txt b/converted-ethereum-tests.txt index eea6791d547..0125989ea46 100644 --- a/converted-ethereum-tests.txt +++ b/converted-ethereum-tests.txt @@ -1,6 +1,9 @@ ([#1056](https://github.com/ethereum/execution-spec-tests/pull/1056)) GeneralStateTests/VMTests/vmTests/calldatacopy.json +([#1248](https://github.com/ethereum/execution-spec-tests/pull/1248)) +GeneralStateTests/VMTests/vmTests/calldataload.json + ([#748](https://github.com/ethereum/execution-spec-tests/pull/748)) GeneralStateTests/stBadOpcode/badOpcodes.json GeneralStateTests/stBugs/evmBytecode.json diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index fac540f6f28..c121dc98f95 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -29,6 +29,7 @@ Test fixtures for use by clients are available for each release on the [Github r - ✨ Add gas cost of delegation access in CALL opcode ([#1208](https://github.com/ethereum/execution-spec-tests/pull/1208)). - ✨ Add EIP-7698 failed nonce and short data tests ([#1211](https://github.com/ethereum/execution-spec-tests/pull/1211)). +- ✨ Port [calldataload test](https://github.com/ethereum/tests/blob/2cd297403439b6eea6e0eb5c2fab2111cb210c6f/src/GeneralStateTestsFiller/VMTests/vmTests/calldataloadFiller.yml#L4) ([#1056](https://github.com/ethereum/execution-spec-tests/pull/1248)). ## [v4.0.0](https://github.com/ethereum/execution-spec-tests/releases/tag/v3.0.0) - 2025-02-14 - 💕 diff --git a/tests/frontier/opcodes/test_calldataload.py b/tests/frontier/opcodes/test_calldataload.py new file mode 100644 index 00000000000..fd858240e67 --- /dev/null +++ b/tests/frontier/opcodes/test_calldataload.py @@ -0,0 +1,145 @@ +""" +abstract: Test `CALLDATALOAD` + Test the `CALLDATALOAD` opcodes. + +""" + +import pytest + +from ethereum_test_forks import Fork, Frontier, Homestead +from ethereum_test_tools import ( + Account, + Alloc, + Bytecode, + Environment, + StateTestFiller, + Transaction, +) +from ethereum_test_tools import Opcodes as Op + + +@pytest.mark.parametrize( + "calldataload_offset,memory_preset_code,call_args_size,expected_calldataload_memory", + [ + pytest.param( + 0x00, + Op.MSTORE8(offset=0x0, value=0x25) + Op.MSTORE8(offset=0x1, value=0x60), + 0x02, + 0x2560000000000000000000000000000000000000000000000000000000000000, + id="calldata_short_start_0", + ), + pytest.param( + 0x01, + Op.MSTORE( + offset=0x0, + value=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + ) + + Op.MSTORE8(offset=0x20, value=0x23), + 0x21, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF23, + id="calldata_sufficient_with_offset", + ), + pytest.param( + 0x05, + Op.MSTORE( + offset=0x0, + value=0x123456789ABCDEF0000000000000000000000000000000000000000000000000, + ) + + Op.MSTORE8(offset=0x20, value=0x0) + + Op.MSTORE8(offset=0x21, value=0x24), + 0x22, + 0xBCDEF00000000000000000000000000000000000000000000000000024000000, + id="calldata_partial_word_at_offset", + ), + ], +) +@pytest.mark.valid_from("Frontier") +def test_calldataload( + state_test: StateTestFiller, + fork: Fork, + calldataload_offset: int, + memory_preset_code: Bytecode, + call_args_size: int, + expected_calldataload_memory: int, + pre: Alloc, +): + """ + Test `CALLDATACOPY` opcode. + + This test verifies that `CALLDATALOAD` correctly retrieves a 32-byte word + from calldata at different offsets, handling various edge cases. + + Parameter: + + calldataload_offset: int + The offset that determines the memory reading position, controlling whether the test case + retrieves a partial or full 32-byte word from calldata. + memory_preset_code: Bytecode + Simulates different memory layouts to test how `CALLDATALOAD` behaves when calldata + is smaller or larger than 32 bytes. + call_args_size: int + Specifies the number of bytes from `memory_preset_code` that are actually forwarded + as calldata during execution. + expected_calldataload_memory: int + Validates whether `CALLDATALOAD` correctly retrieves and zero-pads data by comparing + the stored result in contract storage with this expected reference value. + + Test Cases: + + calldata_short_start_0: + - The calldata size is less than 32 bytes. + - `CALLDATALOAD` starts at offset 0x00. + - The result should correctly pad missing bytes with zeros. + + calldata_sufficient_with_offset: + - The calldata size is greater than 32 bytes. + - `CALLDATALOAD` starts at a valid offset within calldata**. + - A full 32-byte word is retrieved successfully. + + calldata_partial_word_at_offset: + - The calldata size is greater than 32 bytes but `CALLDATALOAD` starts at an offset + where `offset + 32` exceeds the calldata size. + - The result should return the available bytes, and the rest should be zero-padded. + + Based on https://github.com/ethereum/tests/blob/develop/src/GeneralStateTestsFiller/VMTests/vmTests/calldataloadFiller.yml + """ + env = Environment() + sender = pre.fund_eoa() + post = {} + + # Deploy the contract that will store the calldata + sstore_contract = pre.deploy_contract( + code=(Op.SSTORE(key=0x0, value=Op.CALLDATALOAD(offset=calldataload_offset))) + ) + + # Deploy the contract that will forward the calldata to the first contract + calldata_contract = pre.deploy_contract( + code=( + memory_preset_code + + Op.CALL( + gas=Op.SUB(Op.GAS, 20000), + address=sstore_contract, + args_size=call_args_size, + ) + ) + ) + + validation_contract = pre.deploy_contract( + code=( + Op.CALL( + gas=Op.SUB(Op.GAS, 20000), + address=calldata_contract, + ) + ) + ) + + tx = Transaction( + gas_limit=100000, + protected=False if fork in [Frontier, Homestead] else True, + to=validation_contract, + sender=sender, + ) + + post[sstore_contract] = Account(storage={0x0: expected_calldataload_memory}) + + state_test(env=env, pre=pre, post=post, tx=tx)