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
17 changes: 16 additions & 1 deletion src/ethereum_test_checklists/eip_checklist.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
Type stubs for EIP checklist - auto-generated.

DO NOT EDIT MANUALLY - This file is generated by `uv run generate-checklist-stubs`
DO NOT EDIT MANUALLY - This file is generated by `uv run generate_checklist_stubs`
"""

from typing import Any, Callable, TypeVar, overload
Expand Down Expand Up @@ -89,6 +89,21 @@ class EIPChecklist:
SecondClient: _CallableChecklistItem
TestCoverage: _CallableChecklistItem

class ModifiedTransactionValidityConstraint(_CallableChecklistItem):
class Test(_CallableChecklistItem):
class ForkTransition(_CallableChecklistItem):
AcceptedAfterFork: _CallableChecklistItem
AcceptedBeforeFork: _CallableChecklistItem
RejectedAfterFork: _CallableChecklistItem
RejectedBeforeFork: _CallableChecklistItem

class NewTransactionValidityConstraint(_CallableChecklistItem):
class Test(_CallableChecklistItem):
class ForkTransition(_CallableChecklistItem):
AcceptedAfterFork: _CallableChecklistItem
AcceptedBeforeFork: _CallableChecklistItem
RejectedAfterFork: _CallableChecklistItem

class Opcode(_CallableChecklistItem):
class Test(_CallableChecklistItem):
ExceptionalAbort: _CallableChecklistItem
Expand Down
6 changes: 6 additions & 0 deletions src/ethereum_test_forks/base_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,12 @@ def base_fee_elasticity_multiplier(cls, block_number: int = 0, timestamp: int =
"""Return the base fee elasticity multiplier at a given fork."""
pass

@classmethod
@abstractmethod
def max_refund_quotient(cls) -> int:
"""Return the max refund quotient at a given fork."""
pass

@classmethod
@abstractmethod
def transaction_data_floor_cost_calculator(
Expand Down
10 changes: 10 additions & 0 deletions src/ethereum_test_forks/forks/forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,11 @@ def create_opcodes(
(Opcodes.CREATE, EVMCodeType.LEGACY),
]

@classmethod
def max_refund_quotient(cls) -> int:
"""Return the max refund quotient at Genesis."""
return 2

@classmethod
def max_request_type(cls, block_number: int = 0, timestamp: int = 0) -> int:
"""At genesis, no request type is supported, signaled by -1."""
Expand Down Expand Up @@ -913,6 +918,11 @@ def valid_opcodes(
"""Return list of Opcodes that are valid to work on this fork."""
return [Opcodes.BASEFEE] + super(London, cls).valid_opcodes()

@classmethod
def max_refund_quotient(cls) -> int:
"""Return the max refund quotient at London."""
return 5

@classmethod
def base_fee_max_change_denominator(cls, block_number: int = 0, timestamp: int = 0) -> int:
"""Return the base fee max change denominator at London."""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
general/code_coverage/eels = Please check https://app.codecov.io/gh/ethereum/execution-specs/pull/1388/blob/src/ethereum/osaka/vm/instructions/bitwise.py#L243 for relevant test coverage
general/code_coverage/test_coverage = Please run the test with `--cov` flag for final coverage
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
system_contract = EIP does not introduce a new system contract
opcode = EIP does not introduce a new opcode
precompile = EIP does not introduce a new precompile
removed_precompile = EIP does not remove a precompile
transaction_type = EIP does not introduce a new transaction type
block_header_field = EIP does not add any new block header fields
block_body_field = EIP does not add any new block body fields
gas_cost_changes = EIP does not modify existing gas costs, only introduces new opcode with fixed cost
gas_refunds_changes = EIP does not introduce any gas refund changes
blob_count_changes = EIP does not introduce any blob count changes
execution_layer_request = EIP does not introduce an execution layer request
new_transaction_validity_constraint = EIP does not introduce a new transaction validity constraint
2 changes: 1 addition & 1 deletion tests/osaka/eip7825_transaction_gas_limit_cap/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class ReferenceSpec:


# EIP-7825 reference specification
ref_spec_7825 = ReferenceSpec("EIPS/eip-7825.md", "47cbfed315988c0bd4d10002c110ae402504cd94")
ref_spec_7825 = ReferenceSpec("EIPS/eip-7825.md", "1ed95cbac750539c2aac67c8cbbcc2d77974231c")


@dataclass(frozen=True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
AuthorizationTuple,
Block,
BlockchainTestFiller,
Bytecode,
Environment,
Hash,
StateTestFiller,
Expand Down Expand Up @@ -53,7 +54,7 @@ def tx_gas_limit_cap_tests(fork: Fork) -> List[ParameterSet]:
id="tx_gas_limit_cap_exceeds_maximum",
marks=pytest.mark.exception_test,
),
pytest.param(fork_tx_gas_limit_cap, None, id="tx_gas_limit_cap_none"),
pytest.param(fork_tx_gas_limit_cap, None, id="tx_gas_limit_cap_over"),
]


Expand Down Expand Up @@ -201,6 +202,68 @@ def test_tx_gas_larger_than_block_gas_limit(
blockchain_test(pre=pre, post={}, blocks=[block])


@pytest.mark.parametrize(
"exceed_gas_refund_limit",
[
pytest.param(True),
pytest.param(False),
],
)
@pytest.mark.valid_from("Osaka")
def test_maximum_gas_refund(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
exceed_gas_refund_limit: bool,
):
"""Test the maximum gas refund behavior according to EIP-3529."""
gas_costs = fork.gas_costs()
tx_gas_limit_cap = fork.transaction_gas_limit_cap()
assert tx_gas_limit_cap is not None, "Fork does not have a transaction gas limit cap"
max_refund_quotient = fork.max_refund_quotient()

storage = Storage()

# Base Operation: SSTORE(slot, 0)
iteration_cost = gas_costs.G_STORAGE_RESET + gas_costs.G_BASE + gas_costs.G_VERY_LOW
gas_refund = gas_costs.R_STORAGE_CLEAR

# EIP-3529: Reduction in refunds
storage_count = tx_gas_limit_cap // iteration_cost
gas_used = storage_count * iteration_cost

maximum_gas_refund = gas_used // max_refund_quotient
gas_refund_count = maximum_gas_refund // gas_refund

# Base case: operations that fit within the refund limit
iteration_count = min(storage_count, gas_refund_count + int(exceed_gas_refund_limit))

assert iteration_cost * iteration_count <= tx_gas_limit_cap, (
"Iteration cost exceeds tx gas limit cap"
)

opcode = sum(
(Op.SSTORE(storage.store_next(0), Op.PUSH0) for _ in range(iteration_count)),
Bytecode(),
)
assert len(opcode) <= fork.max_code_size(), "code size exceeds max code size"

contract = pre.deploy_contract(
code=opcode,
storage={Hash(i): Hash(1) for i in range(iteration_count)},
)

tx = Transaction(
to=contract,
sender=pre.fund_eoa(),
gas_limit=tx_gas_limit_cap,
)

post = {contract: Account(storage=storage)}

state_test(pre=pre, post=post, tx=tx)


@pytest.fixture
def total_cost_floor_per_token(fork: Fork):
"""Total cost floor per token."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import pytest

from ethereum_test_checklists import EIPChecklist
from ethereum_test_forks import Fork
from ethereum_test_tools import (
Account,
Expand All @@ -22,81 +23,98 @@
REFERENCE_SPEC_VERSION = ref_spec_7825.version


@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.AcceptedBeforeFork()
@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.RejectedBeforeFork()
@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.AcceptedAfterFork()
@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.RejectedAfterFork()
@pytest.mark.valid_at_transition_to("Osaka", subsequent_forks=True)
@pytest.mark.exception_test
@pytest.mark.parametrize(
"transaction_at_cap",
[
pytest.param(True, id="at_cap"),
pytest.param(False, marks=pytest.mark.exception_test, id="above_cap"),
],
)
def test_transaction_gas_limit_cap_at_transition(
blockchain_test: BlockchainTestFiller,
pre: Alloc,
fork: Fork,
transaction_at_cap: bool,
):
"""
Test transaction gas limit cap behavior at the Osaka transition.

Before timestamp 15000: No gas limit cap (transactions with gas > 2^24 are valid)
At/after timestamp 15000: Gas limit cap of 2^24 is enforced
"""
sender = pre.fund_eoa()
contract_address = pre.deploy_contract(
code=Op.SSTORE(0, Op.ADD(Op.SLOAD(0), 1)) + Op.STOP,
code=Op.SSTORE(Op.TIMESTAMP, Op.ADD(Op.SLOAD(Op.TIMESTAMP), 1)) + Op.STOP,
)

pre_cap = fork.transaction_gas_limit_cap()
post_cap = fork.transaction_gas_limit_cap(timestamp=15_000)
assert post_cap is not None, "Post cap should not be None"
# Get the gas limit cap at fork activation
tx_gas_cap = fork.transaction_gas_limit_cap(timestamp=15_000)
assert tx_gas_cap is not None, "Gas limit cap should not be None after fork activation"

pre_cap = pre_cap if pre_cap else post_cap + 1
# Test boundary: cap + 1 should fail after fork activation
above_cap = tx_gas_cap + 1

assert post_cap <= pre_cap, (
"Post cap should be less than or equal to pre cap, test needs update"
# Before fork activation: both cap and above_cap transactions should succeed
at_cap_tx_before_fork = Transaction(
ty=0, # Legacy transaction
to=contract_address,
gas_limit=tx_gas_cap,
sender=pre.fund_eoa(),
)

# Transaction with gas limit above the cap before transition
high_gas_tx = Transaction(
above_cap_tx_before_fork = Transaction(
ty=0, # Legacy transaction
to=contract_address,
gas_limit=pre_cap,
data=b"",
value=0,
sender=sender,
gas_limit=above_cap,
sender=pre.fund_eoa(),
)

post_cap_tx_error = TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM

# Transaction with gas limit at the cap
cap_gas_tx = Transaction(
# After fork activation: test at cap vs above cap
transition_tx = Transaction(
ty=0, # Legacy transaction
to=contract_address,
gas_limit=post_cap + 1,
data=b"",
value=0,
sender=sender,
error=post_cap_tx_error,
gas_limit=tx_gas_cap if transaction_at_cap else above_cap,
sender=pre.fund_eoa(),
error=None if transaction_at_cap else post_cap_tx_error,
)

blocks = []

# Before transition (timestamp < 15000): high gas transaction should succeed
# Before transition (timestamp < 15000): both cap and above_cap transactions should succeed
blocks.append(
Block(
timestamp=14_999,
txs=[high_gas_tx],
txs=[above_cap_tx_before_fork, at_cap_tx_before_fork],
)
)

# At transition (timestamp = 15000): high gas transaction should fail
# At transition (timestamp = 15000):
# - transaction at cap should succeed
# - transaction above cap (cap + 1) should fail
blocks.append(
Block(
timestamp=15_000,
txs=[cap_gas_tx], # Only transaction at the cap succeeds
exception=post_cap_tx_error,
txs=[transition_tx],
exception=post_cap_tx_error if not transaction_at_cap else None,
)
)

# Post state: storage should be updated by successful transactions
post = {
contract_address: Account(
storage={
0: 1, # Set by first transaction (before transition)
# Set by both transactions in first block (before transition):
14_999: 2,
# After transition:
# - Set by transaction at cap (should succeed)
# - Not set by transaction above cap (should fail)
15_000: 1 if transaction_at_cap else 0,
}
)
}
Expand Down
Loading