diff --git a/tests/constantinople/eip1052_extcodehash/__init__.py b/tests/constantinople/eip1052_extcodehash/__init__.py new file mode 100644 index 0000000000..d9d661752c --- /dev/null +++ b/tests/constantinople/eip1052_extcodehash/__init__.py @@ -0,0 +1 @@ +"""Cross-client EIP-1052 EXTCODEHASH Tests.""" diff --git a/tests/constantinople/eip1052_extcodehash/test_extcodehash.py b/tests/constantinople/eip1052_extcodehash/test_extcodehash.py new file mode 100644 index 0000000000..3029c03aa2 --- /dev/null +++ b/tests/constantinople/eip1052_extcodehash/test_extcodehash.py @@ -0,0 +1,158 @@ +""" +Test EIP-1052 EXTCODEHASH. +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Initcode, + Op, + StateTestFiller, + Storage, + Transaction, + compute_create2_address, +) + +from ethereum.crypto.hash import keccak256 + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-1052.md" +REFERENCE_SPEC_VERSION = "b0a21c42cb76b9f4f120dd4bd2bd3d83f6682b8a" + + +@pytest.mark.valid_from("Constantinople") +@pytest.mark.ported_from( + [ + "https://github.com/ethereum/tests/tree/v13.3/src/GeneralStateTestsFiller/stExtCodeHash/dynamicAccountOverwriteEmpty_ParisFiller.yml", # noqa: E501 + ], + pr=["https://github.com/ethereum/execution-specs/pull/2032"], +) +@pytest.mark.parametrize( + "target_exists", + [True, False], +) +def test_extcodehash_dynamic_account_overwrite( + state_test: StateTestFiller, + pre: Alloc, + target_exists: bool, +) -> None: + """ + Test EXTCODEHASH of non-existent/no-code account, + then with code deployed at the address via CREATE2. + + This verifies that the code hash cache is correctly updated during the + transaction when an account is overwritten by CREATE2. + + The target address is computed after the caller contract code is deployed, + and passed as calldata to the caller contract. + + The target account code sets a fixed storage slot. This code is executed + at the caller account via DELEGATECALL and at the target account via CALL. + + Modified from original test: target account has no storage to avoid + EIP-7610 collision behavior. + """ + target_storage_slot = 0x4A + caller_storage = Storage() + target_storage = Storage() + + deploy_code = Op.SSTORE(target_storage_slot, 1) + create2_initcode = Initcode(deploy_code=deploy_code) + + caller_code = ( + # EXTCODEHASH of the pre-CREATE2 target account. + Op.SSTORE( + caller_storage.store_next(keccak256(b"") if target_exists else 0), + Op.EXTCODEHASH(Op.CALLDATALOAD(0)), + ) + # EXTCODESIZE of non-existent target account. + + Op.SSTORE( + caller_storage.store_next(0), Op.EXTCODESIZE(Op.CALLDATALOAD(0)) + ) + # EXTCODECOPY of non-existent target account. + + Op.EXTCODECOPY(Op.CALLDATALOAD(0), 0, 0, 32) + + Op.SSTORE(caller_storage.store_next(0), Op.MLOAD(0)) + # DELEGATECALL the non-existent target account. + + Op.SSTORE( + caller_storage.store_next(1), + Op.DELEGATECALL( + address=Op.CALLDATALOAD(0), + gas=0, # Pass zero gas to ensure no execution. + ), + ) + ) + # Target address to be set later. + target_address_slot = caller_storage.store_next(0, "target_address") + caller_code += ( + # CREATE2 to overwrite the account + Op.MSTORE(0, Op.PUSH32(bytes(create2_initcode).ljust(32, b"\0"))) + + Op.SSTORE( + target_address_slot, + Op.CREATE2(value=0, offset=0, size=len(create2_initcode), salt=0), + ) + # EXTCODEHASH of the target account. + + Op.SSTORE( + caller_storage.store_next(deploy_code.keccak256()), + Op.EXTCODEHASH(Op.CALLDATALOAD(0)), + ) + # EXTCODESIZE of the target account. + + Op.SSTORE( + caller_storage.store_next(len(deploy_code)), + Op.EXTCODESIZE(Op.CALLDATALOAD(0)), + ) + # EXTCODECOPY of the target account. + + Op.EXTCODECOPY(Op.CALLDATALOAD(0), 0, 0, 32) + + Op.SSTORE( + caller_storage.store_next(bytes(deploy_code).ljust(32, b"\0")), + Op.MLOAD(0), + ) + # DELEGATECALL the target account. + + Op.SSTORE( + caller_storage.store_next(1), + Op.DELEGATECALL( + address=Op.CALLDATALOAD(0), + gas=Op.GAS, + ), + ) + # Call the deployed contract to execute its "deploy_code". + + Op.SSTORE( + caller_storage.store_next(1), + Op.CALL(address=Op.CALLDATALOAD(0), gas=Op.GAS), + ) + ) + + caller_address = pre.deploy_contract(caller_code, balance=1) + + target_address = compute_create2_address( + address=caller_address, + salt=0, + initcode=create2_initcode, + ) + + if target_exists: + pre.fund_address(target_address, 1) + + caller_storage[target_address_slot] = target_address + caller_storage[target_storage_slot] = 1 + target_storage[target_storage_slot] = 1 + + sender = pre.fund_eoa() + tx = Transaction( + sender=sender, + to=caller_address, + data=bytes(target_address).rjust(32, b"\0"), + gas_limit=400_000, + ) + + state_test( + pre=pre, + post={ + caller_address: Account(storage=caller_storage), + target_address: Account( + nonce=1, + code=deploy_code, + storage=target_storage, + ), + }, + tx=tx, + ) diff --git a/tests/static/state_tests/stExtCodeHash/dynamicAccountOverwriteEmpty_ParisFiller.yml b/tests/static/state_tests/stExtCodeHash/dynamicAccountOverwriteEmpty_ParisFiller.yml deleted file mode 100644 index b9f7bf33ea..0000000000 --- a/tests/static/state_tests/stExtCodeHash/dynamicAccountOverwriteEmpty_ParisFiller.yml +++ /dev/null @@ -1,95 +0,0 @@ -# EXTCODEHASH of empty account, then CREATE or CREATE2 over it, EXTCODEHASH again. -# This should check that code hash cache is correctly updated during the transaction. ---- -dynamicAccountOverwriteEmpty_Paris: - env: - currentCoinbase: 2adc25665018aa1fe0e6bc666dac8fc2697ff9ba - currentDifficulty: '0x20000' - currentGasLimit: "1000000" - currentNumber: "1" - currentTimestamp: "1000" - _info: - comment: "EXTCODEHASH of empty account, then CREATE or CREATE2 over it, EXTCODEHASH again. -This should check that code hash cache is correctly updated during the transaction." - pre: - 095e7baea6a6c7c4c2dfeb977efac326af552d87: - balance: '1000000000000000000' - code: | - { - ;; Check stats of empty account - [[0]] (EXTCODEHASH 0xc5691dc90d9fd2a2e9a5fa5bd28bf77ffd60aa78) - [[1]] (EXTCODESIZE 0xc5691dc90d9fd2a2e9a5fa5bd28bf77ffd60aa78) - (EXTCODECOPY 0xc5691dc90d9fd2a2e9a5fa5bd28bf77ffd60aa78 0 0 32) - (SSTORE 2 (MLOAD 0)) - (SSTORE 3 (CALLCODE 50000 0xc5691dc90d9fd2a2e9a5fa5bd28bf77ffd60aa78 0 0 0 0 0)) - - - ;; Create account 0xc5691dc90d9fd2a2e9a5fa5bd28bf77ffd60aa78 again. should be overwritten without collision - [[10]](CREATE2 0 0 (lll - { - ;; Put some code into it - (MSTORE 0 (EXTCODESIZE 0xdddddddd00000000000000000000000000000000)) - (EXTCODECOPY 0xdddddddd00000000000000000000000000000000 - 32 - 0 - (MLOAD 0)) - (RETURN 32 (MLOAD 0)) - } - 0) - 0) - - ;; Check stats of same account after overwrite - [[4]] (EXTCODEHASH 0xc5691dc90d9fd2a2e9a5fa5bd28bf77ffd60aa78) - [[5]] (EXTCODESIZE 0xc5691dc90d9fd2a2e9a5fa5bd28bf77ffd60aa78) - (EXTCODECOPY 0xc5691dc90d9fd2a2e9a5fa5bd28bf77ffd60aa78 0 0 32) - (SSTORE 6 (MLOAD 0)) - (SSTORE 7 (CALLCODE 50000 0xc5691dc90d9fd2a2e9a5fa5bd28bf77ffd60aa78 0 0 0 0 0)) - - (STOP) - } - nonce: '0' - storage: {} - # empty account - c5691dc90d9fd2a2e9a5fa5bd28bf77ffd60aa78: - balance: '10' - code: '' - nonce: '0' - storage: { - "0x01": '1' - } - dddddddd00000000000000000000000000000000: - balance: '1000000000000000000' - code: | - { - [[80]] 11 - } - nonce: '0' - storage: {} - a94f5374fce5edbc8e2a8697c15331677e6ebf0b: - balance: '1000000000000000000' - code: '' - nonce: '0' - storage: {} - expect: - - indexes: - data: !!int -1 - gas: !!int -1 - value: !!int -1 - network: - - '>=Cancun' - result: - 095e7baea6a6c7c4c2dfeb977efac326af552d87: - balance: '1000000000000000000' - storage: { - } - transaction: - data: - - '' - gasLimit: - - '400000' - gasPrice: '10' - nonce: '0' - secretKey: 45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8 - to: 095e7baea6a6c7c4c2dfeb977efac326af552d87 - value: - - '1'