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
2 changes: 2 additions & 0 deletions packages/testing/src/execution_testing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
)
from .fixtures import BaseFixture, FixtureCollector
from .forks import Fork, GasCosts, RefundTypes, TransitionFork
from .recipient_type import RecipientType
from .specs import (
BaseTest,
BenchmarkTest,
Expand Down Expand Up @@ -195,6 +196,7 @@
"OpcodeCallArg",
"Opcodes",
"ParameterSet",
"RecipientType",
"ReferenceSpec",
"ReferenceSpecTypes",
"RefundTypes",
Expand Down
99 changes: 99 additions & 0 deletions packages/testing/src/execution_testing/forks/base_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
Opcodes,
)

from ..recipient_type import RecipientType
from .gas_costs import GasCosts


Expand Down Expand Up @@ -116,6 +117,8 @@ def __call__(
access_list: List[AccessList] | None = None,
authorization_list_or_count: Sized | int | None = None,
return_cost_deducted_prior_execution: bool = False,
sends_value: bool = False,
recipient_type: RecipientType = RecipientType.CONTRACT,
) -> int:
"""
Return the intrinsic gas cost of a transaction given its properties.
Expand All @@ -135,13 +138,64 @@ def __call__(
that is deducted from the gas
limit before the transaction
starts execution.
sends_value: Whether the transaction transfers a non-zero value.
Forks that itemize the value-transfer charge in
intrinsic gas use this flag; ignored by older forks.
recipient_type: Category of the transaction recipient. Forks
that vary intrinsic gas by recipient kind
(e.g. no access cost for precompiles, no value
charge for self-transfers) use this; ignored
by older forks.

Returns: Gas cost of a transaction

"""
pass


class TopFrameGasCalculator(Protocol):
"""
A protocol to calculate the additional regular gas charged at the
top-level transaction frame, after intrinsic gas is deducted but
before EVM execution begins.

Returns only the regular-gas portion of the post-intrinsic
state-aware preparation (e.g. the delegated-recipient access
charge). The state-gas portion is exposed separately by
``BaseFork.transaction_top_frame_state_gas`` so tests can model the
two-dimensional reservoir explicitly or sum the two via
``oog_budget_lift`` when targeting the spillover boundary.

Returns 0 for forks that do not perform any such preparation.
"""

def __call__(
self,
*,
contract_creation: bool = False,
sends_value: bool = False,
recipient_type: RecipientType = RecipientType.CONTRACT,
) -> int:
"""
Return the regular gas consumed by top-frame preparation for a
transaction at this fork.

Args:
contract_creation: Whether the transaction creates a contract.
Top-frame charges are zero for creates;
equivalent charges are paid via intrinsic
gas.
sends_value: Whether the transaction transfers a non-zero
value.
recipient_type: Category of the transaction recipient.
Drives the conditional charges.

Returns: Regular gas added by top-frame preparation.

"""
pass


class BlobGasPriceCalculator(Protocol):
"""
A protocol to calculate the blob gas price given the excess blob gas at a
Expand Down Expand Up @@ -705,6 +759,51 @@ def transaction_intrinsic_state_gas(
del contract_creation, authorization_count
return 0

@classmethod
def transaction_top_frame_gas_calculator(
cls,
) -> TopFrameGasCalculator:
"""
Return a callable that calculates the additional regular gas
charged at the top-level transaction frame, after intrinsic
gas is deducted but before EVM execution begins.

Defaults to returning 0 for forks that do not perform such
post-intrinsic preparation.
"""

def fn(
*,
contract_creation: bool = False,
sends_value: bool = False,
recipient_type: RecipientType = RecipientType.CONTRACT,
) -> int:
del contract_creation, sends_value, recipient_type
return 0

return fn

@classmethod
def transaction_top_frame_state_gas(
cls,
*,
contract_creation: bool = False,
sends_value: bool = False,
recipient_type: RecipientType = RecipientType.CONTRACT,
) -> int:
"""
Return the state gas charged at the top-level transaction
frame, after intrinsic gas is deducted but before EVM execution
begins. Companion to ``transaction_top_frame_gas_calculator``;
tests targeting the spillover boundary feed this through
``oog_budget_lift`` to get the equivalent regular-gas budget.

Defaults to 0 for forks that do not perform such
post-intrinsic preparation.
"""
del contract_creation, sends_value, recipient_type
return 0

@classmethod
def system_call_gas_limit(cls) -> int:
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
"""
EIP-2780: Resource-based intrinsic transaction gas.

Decompose the intrinsic transaction gas into explicit recipient-access
and value-transfer primitives so that the cost paid before execution
reflects the actual work the transaction will perform.

https://eips.ethereum.org/EIPS/eip-2780
"""

from dataclasses import replace
from typing import List, Sized

from execution_testing.base_types import AccessList
from execution_testing.base_types.conversions import BytesConvertible

from .....recipient_type import RecipientType
from ....base_fork import (
BaseFork,
TopFrameGasCalculator,
TransactionIntrinsicCostCalculator,
)
from ....gas_costs import GasCosts


class EIP2780(BaseFork):
"""EIP-2780 class."""

@classmethod
def gas_costs(cls) -> GasCosts:
"""
Lower ``TX_BASE`` to 12_000 to reflect the removal of the
bundled recipient access and account-write charges, and add
the transfer-log and value-transfer constants.
"""
parent = super(EIP2780, cls).gas_costs()
return replace(
parent,
TX_BASE=12_000,
TRANSFER_LOG_COST=1_756,
TX_VALUE_COST=4_244,
)

@classmethod
def transaction_intrinsic_cost_calculator(
cls,
) -> TransactionIntrinsicCostCalculator:
"""
Decompose intrinsic gas into explicit recipient and
value-transfer primitives.

Non-create, non-self targets pay ``COLD_ACCOUNT_ACCESS``
unconditionally; access lists do not warm transaction-level
accounts. Value-bearing transactions pay
``TRANSFER_LOG_COST`` plus ``TX_VALUE_COST``; self-transfers
suppress the value-transfer charge entirely.
"""
super_fn = super(EIP2780, cls).transaction_intrinsic_cost_calculator()
gas_costs = cls.gas_costs()

def fn(
*,
calldata: BytesConvertible = b"",
contract_creation: bool = False,
access_list: List[AccessList] | None = None,
authorization_list_or_count: Sized | int | None = None,
return_cost_deducted_prior_execution: bool = False,
sends_value: bool = False,
recipient_type: RecipientType = RecipientType.CONTRACT,
) -> int:
intrinsic_cost: int = super_fn(
calldata=calldata,
contract_creation=contract_creation,
access_list=access_list,
authorization_list_or_count=authorization_list_or_count,
return_cost_deducted_prior_execution=True,
)

is_self_transfer = recipient_type == RecipientType.SELF

if contract_creation:
if sends_value:
intrinsic_cost += gas_costs.TRANSFER_LOG_COST
elif not is_self_transfer:
intrinsic_cost += gas_costs.COLD_ACCOUNT_ACCESS
if sends_value:
intrinsic_cost += (
gas_costs.TRANSFER_LOG_COST + gas_costs.TX_VALUE_COST
)

if return_cost_deducted_prior_execution:
return intrinsic_cost

transaction_data_floor_cost_calculator = (
cls.transaction_data_floor_cost_calculator()
)
transaction_floor_data_cost = (
transaction_data_floor_cost_calculator(
data=calldata, access_list=access_list
)
)
return max(intrinsic_cost, transaction_floor_data_cost)

return fn

@classmethod
def transaction_top_frame_gas_calculator(
cls,
) -> TopFrameGasCalculator:
"""
Return the additional regular gas charged at the top-level
transaction frame, after intrinsic gas is deducted but before
the EVM dispatches.

Charges ``COLD_ACCOUNT_ACCESS`` when the recipient is an
existing delegated account. The empty-recipient
``NEW_ACCOUNT`` charge is state gas, returned separately by
``transaction_top_frame_state_gas``.
"""
gas_costs = cls.gas_costs()

def fn(
*,
contract_creation: bool = False,
sends_value: bool = False,
recipient_type: RecipientType = RecipientType.CONTRACT,
) -> int:
del sends_value
if contract_creation:
return 0

if recipient_type == RecipientType.DELEGATION_7702:
return gas_costs.COLD_ACCOUNT_ACCESS
return 0

return fn

@classmethod
def transaction_top_frame_state_gas(
cls,
*,
contract_creation: bool = False,
sends_value: bool = False,
recipient_type: RecipientType = RecipientType.CONTRACT,
) -> int:
"""
Return the state gas charged at the top-level transaction
frame. Charges ``NEW_ACCOUNT`` when value is transferred to an
empty recipient; zero otherwise.
"""
gas_costs = cls.gas_costs()
if contract_creation:
return 0
if sends_value and recipient_type == RecipientType.EMPTY_ACCOUNT:
return gas_costs.NEW_ACCOUNT
return 0
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from execution_testing.base_types import AccessList
from execution_testing.base_types.conversions import BytesConvertible

from .....recipient_type import RecipientType
from ....base_fork import (
BaseFork,
TransactionDataFloorCostCalculator,
Expand Down Expand Up @@ -85,7 +86,11 @@ def fn(
access_list: List[AccessList] | None = None,
authorization_list_or_count: Sized | int | None = None,
return_cost_deducted_prior_execution: bool = False,
sends_value: bool = False,
recipient_type: RecipientType = RecipientType.CONTRACT,
) -> int:
del sends_value, recipient_type

intrinsic_cost: int = super_fn(
calldata=calldata,
contract_creation=contract_creation,
Expand Down
Loading
Loading