Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
35 changes: 34 additions & 1 deletion packages/testing/src/execution_testing/tools/tests/test_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
)
from execution_testing.specs import StateTest
from execution_testing.test_types import Alloc, Environment, Transaction
from execution_testing.vm import Op
from execution_testing.vm import Bytecode, Op

from ..tools_code import CalldataCase, Case, Conditional, Initcode, Switch

Expand Down Expand Up @@ -184,6 +184,39 @@ def test_initcode(initcode: Initcode, bytecode: bytes) -> None: # noqa: D103
assert bytes(initcode) == bytecode


@pytest.mark.parametrize(
"initcode,reference",
[
pytest.param(
Initcode(),
Initcode(),
id="empty-deployed-code",
),
pytest.param(
# Both initcodes deploy code of the same size, but the execution
# cost of the deployed codes differ, make sure that `gas_cost`
# is not influenced by the deployed code's execution cost.
Initcode(deploy_code=Op.MSTORE(0, 0, new_memory_size=0)),
Initcode(deploy_code=Op.MSTORE(0xFF, 0, new_memory_size=0xFF)),
id="non-empty-deployed-code",
),
],
)
def test_initcode_gas_cost(initcode: Initcode, reference: Initcode) -> None:
"""
Test that the gas cost of the initcode is calculated correctly.
"""
assert initcode.gas_cost(Cancun) == reference.gas_cost(Cancun)
if initcode.deploy_code != reference.deploy_code:
initcode_deploy_code = initcode.deploy_code
assert isinstance(initcode_deploy_code, Bytecode)
reference_deploy_code = reference.deploy_code
assert isinstance(reference_deploy_code, Bytecode)
assert initcode_deploy_code.gas_cost(
Cancun
) != reference_deploy_code.gas_cost(Cancun)


@pytest.mark.parametrize(
"conditional_bytecode,expected",
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from pydantic import Field

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

Expand All @@ -25,44 +25,33 @@ class Initcode(Bytecode):
EIP-3860 are *not* taken into account by any of these calculated costs.
"""

deploy_code: SupportsBytes | Bytes
deploy_code: Bytes | Bytecode
"""
Bytecode to be deployed by the initcode.
"""
execution_gas: int
"""
Gas cost of executing the initcode, without considering deployment gas
costs.
"""
deployment_gas: int
"""
Gas cost of deploying the cost, subtracted after initcode execution,
"""

def __new__(
cls,
*,
deploy_code: SupportsBytes | Bytes | None = None,
deploy_code: Bytecode | SupportsBytes | None = None,
initcode_length: int | None = None,
initcode_prefix: Bytecode | None = None,
padding_byte: int = 0x00,
name: str = "",
fork: Fork = Frontier,
) -> Self:
"""
Generate legacy initcode that inits a contract with the specified code.
Generate an initcode that returns 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()
elif not isinstance(deploy_code, Bytecode):
deploy_code = Bytes(deploy_code)
if initcode_prefix is None:
initcode_prefix = Bytecode()

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

# PUSH2: length=<bytecode length>
initcode += Op.PUSH2(code_length)
Expand All @@ -89,7 +78,7 @@ def __new__(
)

# RETURN: offset=0, length
initcode += Op.RETURN
initcode += Op.RETURN(code_deposit_size=len(deploy_code))

initcode_plus_deploy_code = bytes(initcode) + bytes(deploy_code)
padding_bytes = bytes()
Expand All @@ -112,16 +101,48 @@ def __new__(
pushed_stack_items=initcode.pushed_stack_items,
max_stack_height=initcode.max_stack_height,
min_stack_height=initcode.min_stack_height,
name=name,
opcode_list=initcode.opcode_list,
)
instance._name_ = name
instance.deploy_code = deploy_code
instance.execution_gas = initcode.gas_cost(fork)
instance.deployment_gas = Op.RETURN(
code_deposit_size=len(bytes(instance.deploy_code))
).gas_cost(fork)

return instance

def execution_gas(
self,
fork: Type[ForkOpcodeInterface],
*,
block_number: int = 0,
timestamp: int = 0,
) -> int:
"""
Gas cost of executing the initcode, charged before the code
deposit fee.
"""
return self.gas_cost(
fork,
block_number=block_number,
timestamp=timestamp,
) - self.deployment_gas(
fork,
block_number=block_number,
timestamp=timestamp,
)

def deployment_gas(
self,
fork: Type[ForkOpcodeInterface],
*,
block_number: int = 0,
timestamp: int = 0,
) -> int:
"""
Gas cost of deploying the contract.
"""
return Op.RETURN(code_deposit_size=len(self.deploy_code)).gas_cost(
fork, block_number=block_number, timestamp=timestamp
)


class CodeGasMeasure(Bytecode):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1207,8 +1207,7 @@ def test_bal_7702_delegated_create(
authorization_list_or_count=tx.authorization_list,
)
+ deployer_code.gas_cost(fork)
+ init_code.execution_gas
+ gsc.G_CODE_DEPOSIT_BYTE * len(deploy_code)
+ init_code.gas_cost(fork)
)

refund_counter = gsc.R_AUTHORIZATION_EXISTING_AUTHORITY
Expand Down
4 changes: 1 addition & 3 deletions tests/cancun/eip1153_tstore/test_tstorage_clear_after_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ def test_tstore_clear_after_deployment_tx(
init_code = Op.TSTORE(1, 1)
deploy_code = Op.SSTORE(1, Op.TLOAD(1))

code = Initcode(
deploy_code=deploy_code, initcode_prefix=init_code, fork=fork
)
code = Initcode(deploy_code=deploy_code, initcode_prefix=init_code)

sender = pre.fund_eoa()

Expand Down
6 changes: 1 addition & 5 deletions tests/cancun/eip1153_tstore/test_tstorage_create_contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
Transaction,
compute_create_address,
)
from execution_testing.forks.helpers import Fork

from . import CreateOpcodeParams, PytestParameterEnum
from .spec import ref_spec_1153
Expand Down Expand Up @@ -165,12 +164,9 @@ def initcode( # noqa: D102
self,
deploy_code: Bytecode,
constructor_code: Bytecode,
fork: Fork,
) -> Initcode:
return Initcode(
deploy_code=deploy_code,
initcode_prefix=constructor_code,
fork=fork,
deploy_code=deploy_code, initcode_prefix=constructor_code
)

@pytest.fixture()
Expand Down
37 changes: 9 additions & 28 deletions tests/shanghai/eip3860_initcode/test_initcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,78 +46,65 @@ def initcode(fork: Fork, initcode_name: str) -> Initcode:
if initcode_name == "max_size_ones":
return Initcode(
name=initcode_name,
fork=fork,
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=fork.max_initcode_size(),
padding_byte=0x01,
)
elif initcode_name == "max_size_zeros":
return Initcode(
name=initcode_name,
fork=fork,
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=fork.max_initcode_size(),
padding_byte=0x00,
)
elif initcode_name == "over_limit_ones":
return Initcode(
name=initcode_name,
fork=fork,
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=fork.max_initcode_size() + 1,
padding_byte=0x01,
)
elif initcode_name == "over_limit_zeros":
return Initcode(
name=initcode_name,
fork=fork,
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=fork.max_initcode_size() + 1,
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 == "max_size_minus_word":
return Initcode(
name=initcode_name,
fork=fork,
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=fork.max_initcode_size() - 32,
padding_byte=0x00,
)
elif initcode_name == "max_size_minus_word_plus_byte":
return Initcode(
name=initcode_name,
fork=fork,
deploy_code=INITCODE_RESULTING_DEPLOYED_CODE,
initcode_length=fork.max_initcode_size() - 32 + 1,
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
elif initcode_name == "empty" or initcode_name == "single_byte":
ic_bytecode = Op.STOP if initcode_name == "single_byte" else Bytecode()
# We insist on using `Initcode` to preserve `initcode.deploy_code`
ic = Initcode(name=initcode_name)
ic._bytes_ = bytes(ic_bytecode)
ic.opcode_list = ic_bytecode.opcode_list
return ic
else:
raise ValueError(f"Unknown initcode_name: {initcode_name}")
Expand Down Expand Up @@ -284,16 +271,12 @@ def exact_intrinsic_gas(

@pytest.fixture
def exact_execution_gas(
self, exact_intrinsic_gas: int, initcode: Initcode
self, fork: Fork, exact_intrinsic_gas: int, initcode: Initcode
) -> int:
"""
Calculate total execution gas cost.
"""
return (
exact_intrinsic_gas
+ initcode.deployment_gas
+ initcode.execution_gas
)
return exact_intrinsic_gas + initcode.gas_cost(fork)

@pytest.fixture
def tx_error(self, gas_test_case: str) -> TransactionException | None:
Expand Down Expand Up @@ -593,9 +576,7 @@ def test_create_opcode_initcode(
else:
expected_gas_usage = contract_creation_gas_cost
# The initcode is only executed if the length check succeeds
expected_gas_usage += initcode.execution_gas
# The code is only deployed if the length check succeeds
expected_gas_usage += initcode.deployment_gas
expected_gas_usage += initcode.gas_cost(fork)

# CREATE2 hashing cost should only be deducted if the initcode
# does not exceed the max length
Expand Down
Loading