Skip to content

test_mcopy_repeated()

Documentation for tests/cancun/eip5656_mcopy/test_mcopy.py::test_mcopy_repeated@ffe48572.

Generate fixtures for these test cases for Bogota with:

fill -v tests/cancun/eip5656_mcopy/test_mcopy.py::test_mcopy_repeated --fork Bogota

Perform the same MCOPY twice and verify the memory hash after each.

For non-overlapping or idempotent copies the hash is the same both times; for overlapping forward/backward moves the second copy operates on already-modified memory, so the hashes differ.

Source code in tests/cancun/eip5656_mcopy/test_mcopy.py
@pytest.mark.parametrize(
    "dest,src,length",
    [
        pytest.param(0x1010, 0x0000, 0x0020, id="clear_low_half_0"),
        pytest.param(0x1020, 0x1010, 0x0010, id="clear_low_half_1"),
        pytest.param(0x1020, 0x1040, 0x0010, id="clear_low_half_2"),
        pytest.param(0x1030, 0x0000, 0x1020, id="clear_high_half_0"),
        pytest.param(0x1021, 0x1020, 0x0123, id="memmove_forward"),
        pytest.param(0x1020, 0x1023, 0x001D, id="memmove_backward"),
    ],
)
@pytest.mark.ported_from(
    [
        "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/Cancun/stEIP5656_MCOPY/MCOPY_memory_hashFiller.yml",  # noqa: E501
    ],
    pr=["https://github.com/ethereum/execution-specs/pull/2490"],
)
@pytest.mark.valid_from("Cancun")
def test_mcopy_repeated(
    state_test: StateTestFiller,
    pre: Alloc,
    dest: int,
    src: int,
    length: int,
) -> None:
    """
    Perform the same MCOPY twice and verify the memory hash after each.

    For non-overlapping or idempotent copies the hash is the same both
    times; for overlapping forward/backward moves the second copy
    operates on already-modified memory, so the hashes differ.
    """
    # Build initial memory: zeros with PATTERN at offset 0x1020.
    buf = bytearray(0x1020 + len(PATTERN))
    buf[0x1020 : 0x1020 + len(PATTERN)] = PATTERN
    initial = bytes(buf)

    # Compute memory after first and second MCOPY.
    after_first = mcopy(dest=dest, src=src, length=length, memory=initial)
    after_second = mcopy(dest=dest, src=src, length=length, memory=after_first)

    first_size = ceiling_division(len(after_first), 0x20) * 0x20
    second_size = ceiling_division(len(after_second), 0x20) * 0x20
    hash_first = keccak256(after_first.ljust(first_size, b"\x00"))
    hash_second = keccak256(after_second.ljust(second_size, b"\x00"))

    storage = Storage()

    # Build bytecode: fill memory, MCOPY, hash, MCOPY again, hash.
    code = Bytecode()
    code += Op.MSTORE(
        0x1020,
        Op.PUSH32(PATTERN),
    )
    code += Op.MCOPY(
        Op.CALLDATALOAD(0x00),
        Op.CALLDATALOAD(0x20),
        Op.CALLDATALOAD(0x40),
    )
    code += Op.SSTORE(
        storage.store_next(hash_first, "hash_after_first_mcopy"),
        Op.SHA3(0, Op.MSIZE),
    )
    code += Op.MCOPY(
        Op.CALLDATALOAD(0x00),
        Op.CALLDATALOAD(0x20),
        Op.CALLDATALOAD(0x40),
    )
    code += Op.SSTORE(
        storage.store_next(hash_second, "hash_after_second_mcopy"),
        Op.SHA3(0, Op.MSIZE),
    )

    contract = pre.deploy_contract(code)
    post = {contract: Account(storage=storage)}

    state_test(
        pre=pre,
        post=post,
        tx=Transaction(
            sender=pre.fund_eoa(),
            to=contract,
            data=Hash(dest) + Hash(src) + Hash(length),
        ),
    )

Parametrized Test Cases

This test generates 6 parametrized test cases across 5 forks.