Skip to content
Open
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 @@ -106,6 +106,7 @@
gas_test,
generate_system_contract_deploy_test,
generate_system_contract_error_test,
oog_test,
)
from .vm import (
Bytecode,
Expand Down Expand Up @@ -226,4 +227,5 @@
"generate_system_contract_deploy_test",
"generate_system_contract_error_test",
"keccak256",
"oog_test",
)
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def calculate_genesis(self) -> FixtureHeader:
return FixtureHeader.genesis(
self.fork.transitions_from(),
self.environment,
self.pre.state_root(),
self.pre.state_root(self.fork),
)

def add_test_alloc(self, test_id: str, new_pre: Alloc) -> None:
Expand Down Expand Up @@ -272,11 +272,11 @@ class GroupPreAlloc(Alloc):
_cached_state_root: Hash | None = PrivateAttr(None)
_model_dump_cache: ModelDumpCache | None = PrivateAttr(None)

def state_root(self) -> Hash:
def state_root(self, fork: Any = None) -> Hash:
"""On pre-alloc groups, which are normally very big, always cache."""
if self._cached_state_root is not None:
return self._cached_state_root
return super().state_root()
return super().state_root(fork)

def model_dump( # type: ignore[override]
self, mode: Literal["json", "python"], **kwargs: Any
Expand Down
4 changes: 4 additions & 0 deletions packages/testing/src/execution_testing/forks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
BPO4,
BPO5,
MONAD_EIGHT,
MONAD_NEXT,
MONAD_NINE,
Amsterdam,
ArrowGlacier,
Expand Down Expand Up @@ -37,6 +38,7 @@
BPO3ToBPO4AtTime15k,
CancunToPragueAtTime15k,
MONAD_EIGHTToMONAD_NINEAtTime15k,
MONAD_NINEToMONAD_NEXTAtTime15k,
OsakaToBPO1AtTime15k,
ParisToShanghaiAtTime15k,
PragueToMONAD_EIGHTAtTime15k,
Expand Down Expand Up @@ -124,6 +126,8 @@
"MONAD_EIGHT",
"MONAD_EIGHTToMONAD_NINEAtTime15k",
"MONAD_NINE",
"MONAD_NINEToMONAD_NEXTAtTime15k",
"MONAD_NEXT",
"BPO1",
"BPO1ToBPO2AtTime15k",
"BPO2",
Expand Down
88 changes: 88 additions & 0 deletions packages/testing/src/execution_testing/forks/forks/forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -1666,6 +1666,94 @@ def c(w: int) -> int:
return fn


class MONAD_NEXT(MONAD_NINE, solc_name="cancun"): # noqa: N801
"""MONAD_NEXT fork."""

@classmethod
def gas_costs(cls) -> GasCosts:
"""Return gas costs with MIP-8 page-based storage constants."""
return replace(
MONAD_NINE.gas_costs(),
PAGE_BASE_COST=100,
PAGE_LOAD_COST=8_000,
PAGE_WRITE_COST=2_800,
PAGE_STATE_GROWTH_COST=17_000,
)

@classmethod
def opcode_gas_map(
cls,
) -> Dict[OpcodeBase, int | Callable[[OpcodeBase], int]]:
"""Override SLOAD/SSTORE gas with MIP-8 page-level rules."""
base_map = super().opcode_gas_map()
gas_costs = cls.gas_costs()
return {
**base_map,
Opcodes.SLOAD: lambda op: (
gas_costs.PAGE_BASE_COST
if op.metadata["page_load_warm"] or op.metadata["key_warm"]
else gas_costs.PAGE_LOAD_COST + gas_costs.PAGE_BASE_COST
),
Opcodes.SSTORE: lambda op: cls._calculate_sstore_gas_mip8(
op, gas_costs
),
}

@classmethod
def opcode_refund_map(
cls,
) -> Dict[OpcodeBase, int | Callable[[OpcodeBase], int]]:
"""MIP-8 removes SSTORE refunds."""
return {}

@classmethod
def _calculate_sstore_gas_mip8(
cls, opcode: OpcodeBase, gas_costs: GasCosts
) -> int:
"""
Calculate SSTORE gas cost per MIP-8.

Metadata fields used:
- current_value: value right before this SSTORE (v_original
in spec terms — `None` falls back to original_value)
- new_value: value being written
- page_load_warm: page already in read_accessed_pages
- page_write_warm: page already in write_accessed_pages
- key_warm: any prior SLOAD/SSTORE on this key — implies
page_load_warm, but for prior SSTORE will still misprice
- current_state_growth: per-page counter before this op
- net_state_growth: per-page peak before this op
"""
metadata = opcode.metadata

v_original = metadata["current_value"]
if v_original is None:
v_original = metadata["original_value"]
v_new = metadata["new_value"]

gas_cost = gas_costs.PAGE_BASE_COST

page_load_warm = metadata["page_load_warm"] or metadata["key_warm"]

if not page_load_warm:
gas_cost += gas_costs.PAGE_LOAD_COST

if v_original != v_new:
if not metadata["page_write_warm"]:
gas_cost += gas_costs.PAGE_WRITE_COST

current = metadata["current_state_growth"]
peak = metadata["net_state_growth"]
if v_original == 0 and v_new != 0:
current += 1
elif v_original != 0 and v_new == 0:
current -= 1
if current > peak:
gas_cost += gas_costs.PAGE_STATE_GROWTH_COST

return gas_cost


class BPO1(
Osaka,
bpo_fork=True,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
BPO3,
BPO4,
MONAD_EIGHT,
MONAD_NEXT,
MONAD_NINE,
Amsterdam,
Berlin,
Expand Down Expand Up @@ -71,6 +72,13 @@ class MONAD_EIGHTToMONAD_NINEAtTime15k(TransitionBaseClass): # noqa: N801
pass


@transition_fork(to_fork=MONAD_NEXT, from_fork=MONAD_NINE, at_timestamp=15_000)
class MONAD_NINEToMONAD_NEXTAtTime15k(TransitionBaseClass): # noqa: N801
"""MONAD_NINE to MONAD_NEXT transition at Timestamp 15k."""

pass


@transition_fork(to_fork=BPO1, from_fork=Osaka, at_timestamp=15_000)
class OsakaToBPO1AtTime15k(TransitionBaseClass):
"""Osaka to BPO1 transition at Timestamp 15k."""
Expand Down
6 changes: 6 additions & 0 deletions packages/testing/src/execution_testing/forks/gas_costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,9 @@ class GasCosts:
OPCODE_BLOBHASH: int = 0
OPCODE_MCOPY_BASE: int = 0
OPCODE_CLZ: int = 0

# MIP-8 page-based storage gas constants
PAGE_BASE_COST: int = 0
PAGE_LOAD_COST: int = 0
PAGE_WRITE_COST: int = 0
PAGE_STATE_GROWTH_COST: int = 0
Loading
Loading