Skip to content

Commit

Permalink
new(tests): EIP-5656/7692 - use new marker to EOF-ize MCOPY test (#754)
Browse files Browse the repository at this point in the history
* new(tests): EIP-5656/7692 - use new marker to EOF-ize MCOPY test

* new(tests): EIP-5656/7692 - EOFize remainder of MCOPY tests
  • Loading branch information
pdobacz authored Aug 27, 2024
1 parent aee35f0 commit d3d3219
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 72 deletions.
2 changes: 2 additions & 0 deletions tests/cancun/eip5656_mcopy/test_mcopy.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ def post(code_address: Address, code_storage: Storage) -> Mapping: # noqa: D103
"out_of_bounds_memory_extension",
],
)
@pytest.mark.with_all_evm_code_types
@pytest.mark.valid_from("Cancun")
def test_valid_mcopy_operations(
state_test: StateTestFiller,
Expand Down Expand Up @@ -202,6 +203,7 @@ def test_valid_mcopy_operations(
@pytest.mark.parametrize("src", [0x00, 0x20])
@pytest.mark.parametrize("length", [0x00, 0x01])
@pytest.mark.parametrize("initial_memory", [bytes()], ids=["empty_memory"])
@pytest.mark.with_all_evm_code_types
@pytest.mark.valid_from("Cancun")
def test_mcopy_on_empty_memory(
state_test: StateTestFiller,
Expand Down
44 changes: 15 additions & 29 deletions tests/cancun/eip5656_mcopy/test_mcopy_contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def initial_memory_length() -> int: # noqa: D103
@pytest.fixture
def callee_bytecode(
initial_memory_length: int,
opcode: Op,
call_opcode: Op,
) -> Bytecode:
"""
Callee simply performs mcopy operations that should not have any effect on the
Expand All @@ -44,7 +44,7 @@ def callee_bytecode(
bytecode += Op.MCOPY(0x00, initial_memory_length * 2, 1)
bytecode += Op.MCOPY(initial_memory_length * 2, 0x00, 1)

if opcode != Op.STATICCALL:
if call_opcode != Op.STATICCALL and call_opcode != Op.EXTSTATICCALL:
# Simple sstore to make sure we actually ran the code
bytecode += Op.SSTORE(200_000, 1)

Expand All @@ -58,7 +58,7 @@ def callee_bytecode(
def initial_memory(
callee_bytecode: Bytecode,
initial_memory_length: int,
opcode: Op,
call_opcode: Op,
) -> bytes:
"""
Initial memory for the test.
Expand All @@ -67,7 +67,7 @@ def initial_memory(

ret = bytes(list(islice(cycle(range(0x01, 0x100)), initial_memory_length)))

if opcode in [Op.CREATE, Op.CREATE2]:
if call_opcode in [Op.CREATE, Op.CREATE2]:
# We also need to put the callee_bytecode as initcode in memory for create operations
ret = bytes(callee_bytecode) + ret[len(callee_bytecode) :]

Expand All @@ -85,7 +85,7 @@ def caller_bytecode(
initial_memory: bytes,
callee_address: Address,
callee_bytecode: Bytecode,
opcode: Op,
call_opcode: Op,
caller_storage: Storage,
) -> Bytecode:
"""
Expand All @@ -99,10 +99,10 @@ def caller_bytecode(
bytecode += Op.MSTORE(i, Op.PUSH32(initial_memory[i : i + 0x20]))

# Perform the call to the contract that is going to perform mcopy
if opcode in [Op.CALL, Op.CALLCODE, Op.DELEGATECALL, Op.STATICCALL]:
bytecode += opcode(address=callee_address)
elif opcode in [Op.CREATE, Op.CREATE2]:
bytecode += opcode(size=len(callee_bytecode))
if call_opcode in [Op.CREATE, Op.CREATE2]:
bytecode += call_opcode(size=len(callee_bytecode))
else:
bytecode += call_opcode(address=callee_address)

# First save msize
bytecode += Op.SSTORE(100_000, Op.MSIZE())
Expand Down Expand Up @@ -141,28 +141,20 @@ def post( # noqa: D103
caller_address: Address,
caller_storage: Storage,
callee_address: Address,
opcode: Op,
call_opcode: Op,
) -> Mapping:
callee_storage: Storage.StorageDictType = {}
if opcode in [Op.DELEGATECALL, Op.CALLCODE]:
if call_opcode in [Op.DELEGATECALL, Op.CALLCODE, Op.EXTDELEGATECALL]:
caller_storage[200_000] = 1
elif opcode in [Op.CALL]:
elif call_opcode in [Op.CALL, Op.EXTCALL]:
callee_storage[200_000] = 1
return {
caller_address: Account(storage=caller_storage),
callee_address: Account(storage=callee_storage),
}


@pytest.mark.parametrize(
"opcode",
[
Op.CALL,
Op.DELEGATECALL,
Op.STATICCALL,
Op.CALLCODE,
],
)
@pytest.mark.with_all_call_opcodes
@pytest.mark.valid_from("Cancun")
def test_no_memory_corruption_on_upper_call_stack_levels(
state_test: StateTestFiller,
Expand All @@ -172,13 +164,7 @@ def test_no_memory_corruption_on_upper_call_stack_levels(
):
"""
Perform a subcall with any of the following opcodes, which uses MCOPY during its execution,
and verify that the caller's memory is unaffected:
- `CALL`
- `CALLCODE`
- `DELEGATECALL`
- `STATICCALL`
TODO: [EOF] Add EOF EXT*CALL opcodes
and verify that the caller's memory is unaffected
"""
state_test(
env=Environment(),
Expand All @@ -189,7 +175,7 @@ def test_no_memory_corruption_on_upper_call_stack_levels(


@pytest.mark.parametrize(
"opcode",
"call_opcode",
[
Op.CREATE,
Op.CREATE2,
Expand Down
81 changes: 38 additions & 43 deletions tests/cancun/eip5656_mcopy/test_mcopy_memory_expansion.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,29 @@
that produce a memory expansion, and potentially an out-of-gas error.
""" # noqa: E501
from typing import Mapping, Tuple
import itertools
from typing import Mapping

import pytest

from ethereum_test_tools import Account, Address, Alloc, Bytecode, Environment
from ethereum_test_tools import Opcodes as Op
from ethereum_test_tools import StateTestFiller, Storage, Transaction, cost_memory_bytes
from ethereum_test_tools import StateTestFiller, Transaction, cost_memory_bytes
from ethereum_test_types.helpers import eip_2028_transaction_data_cost

from .common import REFERENCE_SPEC_GIT_PATH, REFERENCE_SPEC_VERSION

REFERENCE_SPEC_GIT_PATH = REFERENCE_SPEC_GIT_PATH
REFERENCE_SPEC_VERSION = REFERENCE_SPEC_VERSION

"""Storage addresses for common testing fields"""
_slot = itertools.count(1)
slot_code_worked = next(_slot)
slot_last_slot = next(_slot)

"""Storage values for common testing fields"""
value_code_worked = 0x2015


@pytest.fixture
def callee_bytecode(dest: int, src: int, length: int) -> Bytecode:
Expand All @@ -31,6 +41,8 @@ def callee_bytecode(dest: int, src: int, length: int) -> Bytecode:
# Pushes for the return operation
bytecode += Op.PUSH1(0x00) + Op.PUSH1(0x00)

bytecode += Op.SSTORE(slot_code_worked, value_code_worked)

# Perform the mcopy operation
bytecode += Op.MCOPY(dest, src, length)

Expand All @@ -40,14 +52,16 @@ def callee_bytecode(dest: int, src: int, length: int) -> Bytecode:


@pytest.fixture
def subcall_exact_cost(
def call_exact_cost(
initial_memory: bytes,
dest: int,
length: int,
) -> int:
"""
Returns the exact cost of the subcall, based on the initial memory and the length of the copy.
"""
intrinsic_cost = 21000 + eip_2028_transaction_data_cost(initial_memory)

mcopy_cost = 3
mcopy_cost += 3 * ((length + 31) // 32)
if length > 0 and dest + length > len(initial_memory):
Expand All @@ -57,36 +71,18 @@ def subcall_exact_cost(
calldatacopy_cost += 3 * ((len(initial_memory) + 31) // 32)
calldatacopy_cost += cost_memory_bytes(len(initial_memory), 0)

pushes_cost = 3 * 7
pushes_cost = 3 * 9
calldatasize_cost = 2
return mcopy_cost + calldatacopy_cost + pushes_cost + calldatasize_cost


@pytest.fixture
def bytecode_storage(
subcall_exact_cost: int,
successful: bool,
memory_expansion_address: Address,
) -> Tuple[Bytecode, Storage.StorageDictType]:
"""
Prepares the bytecode and storage for the test, based on the expected result of the subcall
(whether it succeeds or fails depending on the length of the memory expansion).
"""
bytecode = Bytecode()
storage = {}

# Pass on the calldata
bytecode += Op.CALLDATACOPY(0x00, 0x00, Op.CALLDATASIZE())

subcall_gas = subcall_exact_cost if successful else subcall_exact_cost - 1

# Perform the subcall and store a one in the result location
bytecode += Op.SSTORE(
Op.CALL(subcall_gas, memory_expansion_address, 0, 0, Op.CALLDATASIZE(), 0, 0), 1
sstore_cost = 22100
return (
intrinsic_cost
+ mcopy_cost
+ calldatacopy_cost
+ pushes_cost
+ calldatasize_cost
+ sstore_cost
)
storage[int(successful)] = 1

return (bytecode, storage)


@pytest.fixture
Expand All @@ -101,10 +97,11 @@ def block_gas_limit() -> int: # noqa: D103

@pytest.fixture
def tx_gas_limit( # noqa: D103
subcall_exact_cost: int,
call_exact_cost: int,
block_gas_limit: int,
successful: bool,
) -> int:
return min(max(500_000, subcall_exact_cost * 2), block_gas_limit)
return min(call_exact_cost - (0 if successful else 1), block_gas_limit)


@pytest.fixture
Expand All @@ -115,14 +112,7 @@ def env( # noqa: D103


@pytest.fixture
def caller_address( # noqa: D103
pre: Alloc, bytecode_storage: Tuple[bytes, Storage.StorageDictType]
) -> Address:
return pre.deploy_contract(code=bytecode_storage[0])


@pytest.fixture
def memory_expansion_address(pre: Alloc, callee_bytecode: Bytecode) -> Address: # noqa: D103
def caller_address(pre: Alloc, callee_bytecode: bytes) -> Address: # noqa: D103
return pre.deploy_contract(code=callee_bytecode)


Expand Down Expand Up @@ -151,10 +141,13 @@ def tx( # noqa: D103

@pytest.fixture
def post( # noqa: D103
caller_address: Address, bytecode_storage: Tuple[bytes, Storage.StorageDictType]
caller_address: Address,
successful: bool,
) -> Mapping:
return {
caller_address: Account(storage=bytecode_storage[1]),
caller_address: Account(
storage={slot_code_worked: value_code_worked} if successful else {}
)
}


Expand Down Expand Up @@ -197,6 +190,7 @@ def post( # noqa: D103
"from_empty_memory",
],
)
@pytest.mark.with_all_evm_code_types
@pytest.mark.valid_from("Cancun")
def test_mcopy_memory_expansion(
state_test: StateTestFiller,
Expand Down Expand Up @@ -242,7 +236,7 @@ def test_mcopy_memory_expansion(
],
)
@pytest.mark.parametrize(
"subcall_exact_cost",
"call_exact_cost",
[2**128 - 1],
ids=[""],
) # Limit subcall gas, otherwise it would be impossibly large
Expand All @@ -258,6 +252,7 @@ def test_mcopy_memory_expansion(
"from_empty_memory",
],
)
@pytest.mark.with_all_evm_code_types
@pytest.mark.valid_from("Cancun")
def test_mcopy_huge_memory_expansion(
state_test: StateTestFiller,
Expand Down

0 comments on commit d3d3219

Please sign in to comment.