Skip to content

Commit

Permalink
feat(forks): Add gas costs functions (#779)
Browse files Browse the repository at this point in the history
* feat(forks): Add memory expansion, calldata calculators to each fork

* fix(fw): Remove `copy_opcode_cost`, `cost_memory_bytes` and `eip_2028_transaction_data_cost`

* refactor(tests): Use fork calculator methods instead of helpers

* refactor(base_types): Move `AccessList` to base types

* fix(forks): GasCosts field description

* fix(forks): Initcode word cost

* feat(forks): Add transaction_intrinsic_cost_calculator

* refactor(tests): Use fork gas calculator methods

* refactor(forks): Add authorization to intrinsic gas cost calc

* refactor(plugins/execute): Use fork gas calc functions

* refactor(tests): Use `fork` gas calc functions

* docs: changelog
  • Loading branch information
marioevz authored Nov 4, 2024
1 parent cfbfea9 commit 4ddc4f0
Show file tree
Hide file tree
Showing 29 changed files with 667 additions and 255 deletions.
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Test fixtures for use by clients are available for each release on the [Github r
- 💥 Rename the `PragueEIP7692` fork to `Osaka` ([#869](https://github.com/ethereum/execution-spec-tests/pull/869)).
- ✨ Improve `fill` terminal output to emphasize that filling tests is not actually testing a client ([#807](https://github.com/ethereum/execution-spec-tests/pull/887)).
- ✨ Add the `BlockchainTestEngine` test spec type that only generates a fixture in the `EngineFixture` (`blockchain_test_engine`) format ([#888](https://github.com/ethereum/execution-spec-tests/pull/888)).
- 🔀 `ethereum_test_forks` forks now contain gas-calculating functions, which return the appropriate function to calculate the gas used by a transaction or memory function for the given fork ([#779](https://github.com/ethereum/execution-spec-tests/pull/779)).

### 🔧 EVM Tools

Expand Down
3 changes: 2 additions & 1 deletion src/ethereum_test_base_types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
Wei,
ZeroPaddedHexNumber,
)
from .composite_types import Account, Alloc, Storage, StorageRootType
from .composite_types import AccessList, Account, Alloc, Storage, StorageRootType
from .constants import (
AddrAA,
AddrBB,
Expand All @@ -35,6 +35,7 @@
from .reference_spec import ReferenceSpec

__all__ = (
"AccessList",
"Account",
"AddrAA",
"AddrBB",
Expand Down
17 changes: 16 additions & 1 deletion src/ethereum_test_base_types/composite_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Base composite types for Ethereum test cases.
"""
from dataclasses import dataclass
from typing import Any, ClassVar, Dict, SupportsBytes, Type, TypeAlias
from typing import Any, ClassVar, Dict, List, SupportsBytes, Type, TypeAlias

from pydantic import Field, PrivateAttr, RootModel, TypeAdapter

Expand Down Expand Up @@ -448,3 +448,18 @@ class Alloc(RootModel[Dict[Address, Account | None]]):
"""

root: Dict[Address, Account | None] = Field(default_factory=dict, validate_default=True)


class AccessList(CamelModel):
"""
Access List for transactions.
"""

address: Address
storage_keys: List[Hash]

def to_list(self) -> List[Address | List[Hash]]:
"""
Returns the access list as a list of serializable elements.
"""
return [self.address, self.storage_keys]
49 changes: 48 additions & 1 deletion src/ethereum_test_base_types/tests/test_base_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
Test suite for `ethereum_test` module base types.
"""

from typing import Any
from typing import Any, Dict

import pytest

from ..base_types import Address, Hash, Wei
from ..composite_types import AccessList
from ..json import to_json


@pytest.mark.parametrize(
Expand Down Expand Up @@ -91,3 +93,48 @@ def test_wei_parsing(s: str, expected: int):
Test the parsing of wei values.
"""
assert Wei(s) == expected


@pytest.mark.parametrize(
["can_be_deserialized", "model_instance", "json"],
[
pytest.param(
True,
AccessList(
address=0x1234,
storage_keys=[0, 1],
),
{
"address": "0x0000000000000000000000000000000000001234",
"storageKeys": [
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000000000000000000000000000000000000000000001",
],
},
id="access_list",
),
],
)
class TestPydanticModelConversion:
"""
Test that Pydantic models are converted to and from JSON correctly.
"""

def test_json_serialization(
self, can_be_deserialized: bool, model_instance: Any, json: str | Dict[str, Any]
):
"""
Test that to_json returns the expected JSON for the given object.
"""
assert to_json(model_instance) == json

def test_json_deserialization(
self, can_be_deserialized: bool, model_instance: Any, json: str | Dict[str, Any]
):
"""
Test that to_json returns the expected JSON for the given object.
"""
if not can_be_deserialized:
pytest.skip(reason="The model instance in this case can not be deserialized")
model_type = type(model_instance)
assert model_type(**json) == model_instance
3 changes: 1 addition & 2 deletions src/ethereum_test_fixtures/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@

from pydantic import BaseModel, Field

from ethereum_test_base_types import Address, Alloc, Bytes, Hash, ZeroPaddedHexNumber
from ethereum_test_base_types import AccessList, Address, Alloc, Bytes, Hash, ZeroPaddedHexNumber
from ethereum_test_exceptions import TransactionExceptionInstanceOrList
from ethereum_test_types.types import (
AccessList,
AuthorizationTupleGeneric,
CamelModel,
EnvironmentGeneric,
Expand Down
2 changes: 1 addition & 1 deletion src/ethereum_test_fixtures/tests/test_blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from pydantic import TypeAdapter

from ethereum_test_base_types import (
AccessList,
Address,
Bloom,
BLSPublicKey,
Expand All @@ -23,7 +24,6 @@
from ethereum_test_forks import Prague
from ethereum_test_types import (
EOA,
AccessList,
AuthorizationTuple,
ConsolidationRequest,
DepositRequest,
Expand Down
90 changes: 89 additions & 1 deletion src/ethereum_test_forks/base_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@

from semver import Version

from ethereum_test_base_types import Address
from ethereum_test_base_types import AccessList, Address
from ethereum_test_base_types.conversions import BytesConvertible
from ethereum_test_vm import EVMCodeType, Opcodes

from .base_decorators import prefer_transition_to_method
from .gas_costs import GasCosts


class ForkAttribute(Protocol):
Expand All @@ -25,6 +27,49 @@ def __call__(self, block_number: int = 0, timestamp: int = 0) -> Any:
pass


class MemoryExpansionGasCalculator(Protocol):
"""
A protocol to calculate the gas cost of memory expansion for a given fork.
"""

def __call__(self, *, new_bytes: int, previous_bytes: int = 0) -> int:
"""
Returns the gas cost of expanding the memory by the given length.
"""
pass


class CalldataGasCalculator(Protocol):
"""
A protocol to calculate the transaction gas cost of calldata for a given fork.
"""

def __call__(self, *, data: BytesConvertible) -> int:
"""
Returns the transaction gas cost of calldata given its contents.
"""
pass


class TransactionIntrinsicCostCalculator(Protocol):
"""
A protocol to calculate the intrinsic gas cost of a transaction for a given fork.
"""

def __call__(
self,
*,
calldata: BytesConvertible = b"",
contract_creation: bool = False,
access_list: List[AccessList] | None = None,
authorization_count: int | None = None,
) -> int:
"""
Returns the intrinsic gas cost of a transaction given its properties.
"""
pass


class BaseForkMeta(ABCMeta):
"""
Metaclass for BaseFork
Expand Down Expand Up @@ -160,6 +205,47 @@ def header_requests_required(cls, block_number: int, timestamp: int) -> bool:
"""
pass

# Gas related abstract methods

@classmethod
@abstractmethod
def gas_costs(cls, block_number: int = 0, timestamp: int = 0) -> GasCosts:
"""
Returns a dataclass with the gas costs constants for the fork.
"""
pass

@classmethod
@abstractmethod
def memory_expansion_gas_calculator(
cls, block_number: int = 0, timestamp: int = 0
) -> MemoryExpansionGasCalculator:
"""
Returns a callable that calculates the gas cost of memory expansion for the fork.
"""
pass

@classmethod
@abstractmethod
def calldata_gas_calculator(
cls, block_number: int = 0, timestamp: int = 0
) -> CalldataGasCalculator:
"""
Returns a callable that calculates the transaction gas cost for its calldata
depending on its contents.
"""
pass

@classmethod
@abstractmethod
def transaction_intrinsic_cost_calculator(
cls, block_number: int = 0, timestamp: int = 0
) -> TransactionIntrinsicCostCalculator:
"""
Returns a callable that calculates the intrinsic gas cost of a transaction for the fork.
"""
pass

@classmethod
@abstractmethod
def blob_gas_per_blob(cls, block_number: int, timestamp: int) -> int:
Expand All @@ -176,6 +262,8 @@ def get_reward(cls, block_number: int = 0, timestamp: int = 0) -> int:
"""
pass

# Transaction related abstract methods

@classmethod
@abstractmethod
def tx_types(cls, block_number: int = 0, timestamp: int = 0) -> List[int]:
Expand Down
Loading

0 comments on commit 4ddc4f0

Please sign in to comment.