Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@
from pydantic import Field

from execution_testing.base_types import Address, Bytes
from execution_testing.forks import Fork
from execution_testing.forks import Fork, Frontier
from execution_testing.test_types import EOA, Transaction, ceiling_division
from execution_testing.vm import Bytecode, ForkOpcodeInterface, Op

GAS_PER_DEPLOYED_CODE_BYTE = 0xC8


class Initcode(Bytecode):
"""
Expand Down Expand Up @@ -50,52 +48,56 @@ def __new__(
initcode_prefix_execution_gas: int = 0,
padding_byte: int = 0x00,
name: str = "",
fork: Fork = Frontier,
) -> Self:
"""
Generate legacy initcode that inits a contract with the specified code.
The initcode can be padded to a specified length for testing purposes.

Gas costs are calculated using the fork's gas costs and memory
expansion formula. Defaults to Frontier if no fork is provided.
"""
if deploy_code is None:
deploy_code = Bytecode()
if initcode_prefix is None:
initcode_prefix = Bytecode()

gas_costs = fork.gas_costs()
memory_calculator = fork.memory_expansion_gas_calculator()

initcode = initcode_prefix
code_length = len(bytes(deploy_code))
execution_gas = initcode_prefix_execution_gas

# PUSH2: length=<bytecode length>
initcode += Op.PUSH2(code_length)
execution_gas = 3
execution_gas = gas_costs.G_VERY_LOW

# PUSH1: offset=0
initcode += Op.PUSH1(0)
execution_gas += 3
execution_gas += gas_costs.G_VERY_LOW

# DUP2
initcode += Op.DUP2
execution_gas += 3
execution_gas += gas_costs.G_VERY_LOW

# PUSH1: initcode_length=11 + len(initcode_prefix_bytes) (constant)
no_prefix_length = 0x0B
assert no_prefix_length + len(initcode_prefix) <= 0xFF, (
"initcode prefix too long"
)
initcode += Op.PUSH1(no_prefix_length + len(initcode_prefix))
execution_gas += 3
execution_gas += gas_costs.G_VERY_LOW

# DUP3
initcode += Op.DUP3
execution_gas += 3
execution_gas += gas_costs.G_VERY_LOW

# CODECOPY: destinationOffset=0, offset=0, length
initcode += Op.CODECOPY
execution_gas += (
3
+ (3 * ceiling_division(code_length, 32))
+ (3 * code_length)
+ ((code_length * code_length) // 512)
)
word_copy_cost = gas_costs.G_COPY * ceiling_division(code_length, 32)
memory_cost = memory_calculator(new_bytes=code_length)
execution_gas += gas_costs.G_VERY_LOW + word_copy_cost + memory_cost

# RETURN: offset=0, length
initcode += Op.RETURN
Expand Down Expand Up @@ -126,7 +128,7 @@ def __new__(
instance._name_ = name
instance.deploy_code = deploy_code
instance.execution_gas = execution_gas
Comment thread
pdobacz marked this conversation as resolved.
Outdated
Comment thread
pdobacz marked this conversation as resolved.
Outdated
Comment thread
pdobacz marked this conversation as resolved.
Outdated
instance.deployment_gas = GAS_PER_DEPLOYED_CODE_BYTE * len(
instance.deployment_gas = gas_costs.G_CODE_DEPOSIT_BYTE * len(
bytes(instance.deploy_code)
)

Expand Down
224 changes: 116 additions & 108 deletions tests/shanghai/eip3860_initcode/test_initcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
from .helpers import (
INITCODE_RESULTING_DEPLOYED_CODE,
get_create_id,
get_initcode_name,
)
from .spec import Spec, ref_spec_3860

Expand All @@ -41,94 +40,101 @@
pytestmark = pytest.mark.valid_from("Shanghai")


"""Initcode templates used throughout the tests"""
INITCODE_ONES_MAX_LIMIT = Initcode(
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=Spec.MAX_INITCODE_SIZE,
padding_byte=0x01,
name="max_size_ones",
)

INITCODE_ZEROS_MAX_LIMIT = Initcode(
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=Spec.MAX_INITCODE_SIZE,
padding_byte=0x00,
name="max_size_zeros",
)

INITCODE_ONES_OVER_LIMIT = Initcode(
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=Spec.MAX_INITCODE_SIZE + 1,
padding_byte=0x01,
name="over_limit_ones",
)

INITCODE_ZEROS_OVER_LIMIT = Initcode(
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=Spec.MAX_INITCODE_SIZE + 1,
padding_byte=0x00,
name="over_limit_zeros",
)

INITCODE_ZEROS_32_BYTES = Initcode(
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=32,
padding_byte=0x00,
name="32_bytes",
)

INITCODE_ZEROS_33_BYTES = Initcode(
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=33,
padding_byte=0x00,
name="33_bytes",
)

INITCODE_ZEROS_49120_BYTES = Initcode(
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=49120,
padding_byte=0x00,
name="49120_bytes",
)

INITCODE_ZEROS_49121_BYTES = Initcode(
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=49121,
padding_byte=0x00,
name="49121_bytes",
)

EMPTY_INITCODE = Initcode(
name="empty",
)
EMPTY_INITCODE._bytes_ = bytes()
EMPTY_INITCODE.deployment_gas = 0
EMPTY_INITCODE.execution_gas = 0
@pytest.fixture
def initcode(fork: Fork, initcode_name: str) -> Initcode:
"""Create an Initcode object with fork-specific gas calculations."""
if initcode_name == "max_size_ones":
return Initcode(
name=initcode_name,
fork=fork,
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=Spec.MAX_INITCODE_SIZE,
Comment thread
pdobacz marked this conversation as resolved.
Outdated
padding_byte=0x01,
)
elif initcode_name == "max_size_zeros":
return Initcode(
name=initcode_name,
fork=fork,
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=Spec.MAX_INITCODE_SIZE,
Comment thread
pdobacz marked this conversation as resolved.
Outdated
padding_byte=0x00,
)
elif initcode_name == "over_limit_ones":
return Initcode(
name=initcode_name,
fork=fork,
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=Spec.MAX_INITCODE_SIZE + 1,
Comment thread
pdobacz marked this conversation as resolved.
Outdated
padding_byte=0x01,
)
elif initcode_name == "over_limit_zeros":
return Initcode(
name=initcode_name,
fork=fork,
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=Spec.MAX_INITCODE_SIZE + 1,
Comment thread
pdobacz marked this conversation as resolved.
Outdated
padding_byte=0x00,
)
elif initcode_name == "32_bytes":
return Initcode(
name=initcode_name,
fork=fork,
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=32,
padding_byte=0x00,
)
elif initcode_name == "33_bytes":
return Initcode(
name=initcode_name,
fork=fork,
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=33,
padding_byte=0x00,
)
elif initcode_name == "49120_bytes":
return Initcode(
name=initcode_name,
fork=fork,
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=49120,
Comment thread
pdobacz marked this conversation as resolved.
Outdated
padding_byte=0x00,
)
elif initcode_name == "49121_bytes":
return Initcode(
name=initcode_name,
fork=fork,
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=49121,
Comment thread
pdobacz marked this conversation as resolved.
Outdated
padding_byte=0x00,
)
elif initcode_name == "empty":
ic = Initcode(name=initcode_name, fork=fork)
ic._bytes_ = bytes()
ic.deployment_gas = 0
ic.execution_gas = 0
return ic
elif initcode_name == "single_byte":
ic = Initcode(name=initcode_name, fork=fork)
ic._bytes_ = bytes(Op.STOP)
ic.deployment_gas = 0
ic.execution_gas = 0
return ic
else:
raise ValueError(f"Unknown initcode_name: {initcode_name}")

SINGLE_BYTE_INITCODE = Initcode(
name="single_byte",
)
SINGLE_BYTE_INITCODE._bytes_ = bytes(Op.STOP)
SINGLE_BYTE_INITCODE.deployment_gas = 0
SINGLE_BYTE_INITCODE.execution_gas = 0

"""Test cases using a contract creating transaction"""


@pytest.mark.xdist_group(name="bigmem")
@pytest.mark.parametrize(
"initcode",
"initcode_name",
[
INITCODE_ZEROS_MAX_LIMIT,
INITCODE_ONES_MAX_LIMIT,
pytest.param(
INITCODE_ZEROS_OVER_LIMIT, marks=pytest.mark.exception_test
),
pytest.param(
INITCODE_ONES_OVER_LIMIT, marks=pytest.mark.exception_test
),
pytest.param("max_size_zeros"),
pytest.param("max_size_ones"),
pytest.param("over_limit_zeros", marks=pytest.mark.exception_test),
pytest.param("over_limit_ones", marks=pytest.mark.exception_test),
],
ids=get_initcode_name,
)
def test_contract_creating_tx(
state_test: StateTestFiller,
Expand Down Expand Up @@ -173,15 +179,21 @@ def test_contract_creating_tx(
)


def valid_gas_test_case(initcode: Initcode, gas_test_case: str) -> bool:
"""Filter out invalid gas test case/initcode combinations."""
if gas_test_case == "too_little_execution_gas":
return (initcode.deployment_gas + initcode.execution_gas) > 0
ZERO_GAS_SPECS = {"empty", "single_byte"}


def valid_gas_test_case(initcode_name: str, gas_case: str) -> bool:
"""Filter invalid gas test case combinations."""
if (
gas_case == "too_little_execution_gas"
and initcode_name in ZERO_GAS_SPECS
):
return False
return True


@pytest.mark.parametrize(
"initcode,gas_test_case",
"initcode_name,gas_test_case",
[
pytest.param(
i,
Expand All @@ -193,14 +205,14 @@ def valid_gas_test_case(initcode: Initcode, gas_test_case: str) -> bool:
),
)
for i in [
INITCODE_ZEROS_MAX_LIMIT,
INITCODE_ONES_MAX_LIMIT,
EMPTY_INITCODE,
SINGLE_BYTE_INITCODE,
INITCODE_ZEROS_32_BYTES,
INITCODE_ZEROS_33_BYTES,
INITCODE_ZEROS_49120_BYTES,
INITCODE_ZEROS_49121_BYTES,
"max_size_zeros",
"max_size_ones",
"empty",
"single_byte",
"32_bytes",
"33_bytes",
"49120_bytes",
"49121_bytes",
]
for g in [
"too_little_intrinsic_gas",
Expand All @@ -210,9 +222,6 @@ def valid_gas_test_case(initcode: Initcode, gas_test_case: str) -> bool:
]
if valid_gas_test_case(i, g)
],
ids=lambda x: (
f"{get_initcode_name(x[0])}-{x[1]}" if isinstance(x, tuple) else x
),
)
class TestContractCreationGasUsage:
"""
Expand Down Expand Up @@ -391,20 +400,19 @@ def test_gas_usage(


@pytest.mark.parametrize(
"initcode",
"initcode_name",
[
INITCODE_ZEROS_MAX_LIMIT,
INITCODE_ONES_MAX_LIMIT,
INITCODE_ZEROS_OVER_LIMIT,
INITCODE_ONES_OVER_LIMIT,
EMPTY_INITCODE,
SINGLE_BYTE_INITCODE,
INITCODE_ZEROS_32_BYTES,
INITCODE_ZEROS_33_BYTES,
INITCODE_ZEROS_49120_BYTES,
INITCODE_ZEROS_49121_BYTES,
"max_size_zeros",
"max_size_ones",
"over_limit_zeros",
"over_limit_ones",
"empty",
"single_byte",
"32_bytes",
"33_bytes",
"49120_bytes",
"49121_bytes",
],
ids=get_initcode_name,
)
@pytest.mark.parametrize("opcode", [Op.CREATE, Op.CREATE2], ids=get_create_id)
class TestCreateInitcode:
Expand Down
Loading