Skip to content

test_extcodehash_subcall_create2_oog()

Documentation for tests/constantinople/eip1052_extcodehash/test_extcodehash.py::test_extcodehash_subcall_create2_oog@ffe48572.

Generate fixtures for these test cases for Bogota with:

fill -v tests/constantinople/eip1052_extcodehash/test_extcodehash.py::test_extcodehash_subcall_create2_oog --fork Bogota

Test EXTCODEHASH after CREATE2 in a subcall that goes out of gas.

A factory contract creates a contract via CREATE2, then either returns normally or consumes all remaining gas. When the subcall OOGs, the entire frame reverts — including the CREATE2 — so the created contract should not exist. The caller checks EXTCODEHASH/EXTCODESIZE/EXTCODECOPY of the expected CREATE2 address.

Source code in tests/constantinople/eip1052_extcodehash/test_extcodehash.py
@pytest.mark.ported_from(
    [
        "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stExtCodeHash/extCodeHashSubcallOOGFiller.yml",  # noqa: E501
    ],
    pr=[
        "https://github.com/ethereum/execution-specs/pull/2458",
    ],
)
@pytest.mark.parametrize(
    "call_opcode",
    [
        pytest.param(Op.CALL, id="call"),
        pytest.param(Op.CALLCODE, id="callcode"),
        pytest.param(Op.DELEGATECALL, id="delegatecall"),
    ],
)
@pytest.mark.parametrize("oog", [False, True], ids=["success", "oog"])
def test_extcodehash_subcall_create2_oog(
    state_test: StateTestFiller,
    pre: Alloc,
    call_opcode: Opcodes,
    oog: bool,
) -> None:
    """
    Test EXTCODEHASH after CREATE2 in a subcall that goes out of gas.

    A factory contract creates a contract via CREATE2, then either
    returns normally or consumes all remaining gas. When the subcall
    OOGs, the entire frame reverts — including the CREATE2 — so the
    created contract should not exist. The caller checks
    EXTCODEHASH/EXTCODESIZE/EXTCODECOPY of the expected CREATE2 address.
    """
    storage = Storage()
    deploy_code = Op.SSTORE(0x20, 0x20)
    deploy_code_bytes = bytes(deploy_code)
    initcode = Initcode(deploy_code=deploy_code)

    # Factory: CREATE2, optionally consume all gas to trigger OOG.
    factory_code = Om.MSTORE(initcode, 0) + Op.MSTORE(
        0, Op.CREATE2(value=0, offset=0, size=len(initcode), salt=0)
    )
    if oog:
        factory_code += Om.OOG
    factory_code += Op.RETURN(0, 32)

    factory = pre.deploy_contract(factory_code)

    # Pass the pre-computed CREATE2 address as calldata so the test
    # does not depend on return data from a potentially OOG'd subcall.
    hash_slot = storage.store_next(0, "extcodehash")
    size_slot = storage.store_next(0, "extcodesize")
    code_slot = storage.store_next(0, "extcodecopy")
    code = (
        Op.SSTORE(
            storage.store_next(int(not oog), "call_result"),
            call_opcode(
                address=factory,
                ret_offset=0,
                ret_size=32,
            ),
        )
        + Op.SSTORE(hash_slot, Op.EXTCODEHASH(Op.CALLDATALOAD(0)))
        + Op.SSTORE(size_slot, Op.EXTCODESIZE(Op.CALLDATALOAD(0)))
        + Op.EXTCODECOPY(Op.CALLDATALOAD(0), 0, 0, 32)
        + Op.SSTORE(code_slot, Op.MLOAD(0))
    )

    code_address = pre.deploy_contract(code, storage=storage.canary())

    # Compute the CREATE2 address to verify existence in post-state.
    if call_opcode == Op.CALL:
        deployer = factory
    else:
        # CALLCODE/DELEGATECALL: factory code runs in caller's context.
        deployer = code_address
    created = compute_create2_address(
        address=deployer, salt=0, initcode=initcode
    )

    if not oog:
        storage[hash_slot] = keccak256(deploy_code_bytes)
        storage[size_slot] = len(deploy_code)
        storage[code_slot] = deploy_code_bytes.ljust(32, b"\0")

    post: dict[Address, Account | None] = {
        code_address: Account(storage=storage),
    }
    if oog:
        post[created] = Account.NONEXISTENT
    else:
        post[created] = Account(nonce=1, code=deploy_code)

    tx = Transaction(
        sender=pre.fund_eoa(),
        to=code_address,
        data=created.rjust(32, b"\0"),
    )

    state_test(pre=pre, post=post, tx=tx)

Parametrized Test Cases

This test generates 6 parametrized test cases across 11 forks.