diff --git a/tests/amsterdam/eip7708_eth_transfer_logs/test_transfer_logs.py b/tests/amsterdam/eip7708_eth_transfer_logs/test_transfer_logs.py index 3e2e7bf1e46..23a89d74aab 100644 --- a/tests/amsterdam/eip7708_eth_transfer_logs/test_transfer_logs.py +++ b/tests/amsterdam/eip7708_eth_transfer_logs/test_transfer_logs.py @@ -22,6 +22,7 @@ Initcode, Op, StateTestFiller, + Storage, Transaction, TransactionLog, TransactionReceipt, @@ -905,6 +906,77 @@ def test_inner_call_succeeds_outer_reverts_no_log( state_test(env=env, pre=pre, post={}, tx=tx) +@pytest.mark.with_all_create_opcodes +def test_inner_create_succeeds_outer_reverts_no_log( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + sender: EOA, + fork: Fork, + create_opcode: Op, +) -> None: + """ + Test that a CREATE/CREATE2 transfer log is rolled back on outer revert. + + The factory CREATE/CREATE2s a child with value (the deployment succeeds + and a `factory -> created` log is emitted in the child frame), then the + factory itself REVERTs. Per EIP-7708 the rollback semantics mirror those + of CALL: the child log is discarded together with the rest of the + factory's frame, so the transaction receipt records no logs. + """ + create_value = 1 + initcode = Op.RETURN(0, 0) + initcode_len = len(initcode) + + factory_code = ( + Op.MSTORE(0, Op.PUSH32(bytes(initcode).rjust(32, b"\x00"))) + + Op.MSTORE( + 32, + create_opcode( + value=create_value, + offset=32 - initcode_len, + size=initcode_len, + ), + ) + + Op.REVERT(32, 32) + ) + factory = pre.deploy_contract(factory_code, balance=create_value) + + entry_storage = Storage() + expected_create_address = compute_create_address( + address=factory, + nonce=1, + salt=0, + initcode=initcode, + opcode=create_opcode, + ) + entry_code = Op.CALL( + address=factory, ret_offset=0, ret_size=32 + ) + Op.SSTORE( + entry_storage.store_next(expected_create_address), Op.MLOAD(0) + ) + entry = pre.deploy_contract(entry_code) + + gas_limit = 200_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 + + tx = Transaction( + sender=sender, + to=entry, + value=0, + gas_limit=gas_limit, + expected_receipt=TransactionReceipt(logs=[]), + ) + + state_test( + env=env, + pre=pre, + post={entry: Account(storage=entry_storage)}, + tx=tx, + ) + + @pytest.mark.parametrize( "call_depth", [