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
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,13 @@ def test_contract_creation_with_access_list(
id="value_non_zero_insufficient_balance",
marks=[
pytest.mark.exception_test,
# Monad includes a transaction once the sender's balance
# covers its gas fees; the value it cannot cover fails the
# top-level frame instead, which the reserve balance tests
# cover.
pytest.mark.not_valid_for(
"MONAD_EIGHT", subsequent_forks=True
),
EIPChecklist.TransactionType.Test.IntrinsicValidity.ValueNonZeroInsufficientBalance(),
],
),
Expand Down
26 changes: 24 additions & 2 deletions tests/monad_eight/reserve_balance/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
"""

from enum import Enum, auto, unique
from typing import List
from typing import Any, List

from execution_testing import EOA, Address, AuthorizationTuple, Op
from execution_testing import EOA, Address, AuthorizationTuple, Op, Transaction
from execution_testing.forks.helpers import Fork
from execution_testing.test_types.block_types import DEFAULT_BASE_FEE

from tests.prague.eip7702_set_code_tx.spec import Spec as Spec7702

Expand All @@ -32,6 +33,27 @@ def generous_gas(fork: Fork) -> int:
)


def priced_transaction(
tx_type: int, gas_price: int, **fields: Any
) -> Transaction:
"""
Build a transaction of `tx_type` charged at `gas_price`.

A type-2 transaction caps its fee at twice that price, with the tip
chosen so that the effective price stays at `gas_price`.
"""
if tx_type in (0, 1):
return Transaction(ty=tx_type, gas_price=gas_price, **fields)
if tx_type == 2:
return Transaction(
ty=2,
max_fee_per_gas=2 * gas_price,
max_priority_fee_per_gas=gas_price - DEFAULT_BASE_FEE,
**fields,
)
raise ValueError(f"unsupported transaction type {tx_type}")


@unique
class Stage1Balance(Enum):
"""Initial balance states for Stage 1."""
Expand Down
24 changes: 1 addition & 23 deletions tests/monad_eight/reserve_balance/test_gas_fees_vs_reserve.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"""

from enum import Enum, auto, unique
from typing import Any

import pytest
from execution_testing import (
Expand All @@ -15,12 +14,12 @@
BlockchainTestFiller,
Header,
Op,
Transaction,
TransactionException,
)
from execution_testing.forks.helpers import Fork
from execution_testing.test_types.block_types import DEFAULT_BASE_FEE

from .helpers import priced_transaction
from .spec import Spec, ref_spec_7702

REFERENCE_SPEC_GIT_PATH = ref_spec_7702.git_path
Expand All @@ -42,27 +41,6 @@
]


def priced_transaction(
tx_type: int, gas_price: int, **fields: Any
) -> Transaction:
"""
Build a transaction of `tx_type` charged at `gas_price`.

A type-2 transaction caps its fee at twice that price, with the tip
chosen so that the effective price stays at `gas_price`.
"""
if tx_type == 0:
return Transaction(ty=0, gas_price=gas_price, **fields)
if tx_type == 2:
return Transaction(
ty=2,
max_fee_per_gas=2 * gas_price,
max_priority_fee_per_gas=gas_price - DEFAULT_BASE_FEE,
**fields,
)
raise ValueError(f"unsupported transaction type {tx_type}")


@unique
class GasFees(Enum):
"""Gas fees of a max-gas transaction, relative to the reserve."""
Expand Down
1 change: 1 addition & 0 deletions tests/monad_eleven/eip7981_fee_solvency/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for the EIP-7981 surcharge against Monad's sender solvency rule."""
119 changes: 119 additions & 0 deletions tests/monad_eleven/eip7981_fee_solvency/test_surcharged_fee.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""
Tests for the EIP-7981 access list surcharge against the sender's balance.

Monad includes a transaction once the sender covers its gas fees at the
effective gas price and bills the whole gas limit. MONAD_TEN fills the
same boundaries without the surcharge, for comparison.
"""

import pytest
from execution_testing import (
AccessList,
Account,
Address,
Alloc,
EIPChecklist,
Fork,
Hash,
RecipientType,
StateTestFiller,
TransactionException,
TransactionReceipt,
)

from ...amsterdam.eip7981_increase_access_list_cost.helpers import (
calculate_access_list_data_cost,
)
from ...amsterdam.eip7981_increase_access_list_cost.spec import ref_spec_7981
from ...monad_eight.reserve_balance.helpers import priced_transaction

REFERENCE_SPEC_GIT_PATH = ref_spec_7981.git_path
REFERENCE_SPEC_VERSION = ref_spec_7981.version

GAS_PRICE = 10
ACCESS_LIST = [AccessList(address=Address(1), storage_keys=[Hash(0)])]

pytestmark = pytest.mark.valid_from("MONAD_TEN")


@pytest.mark.parametrize(
"value,balance_delta",
[
pytest.param(
1,
-1,
id="fee_short_by_one",
marks=[
pytest.mark.exception_test,
EIPChecklist.TransactionType.Test.IntrinsicValidity.ValueNonZeroInsufficientBalance(),
],
),
pytest.param(
1,
0,
id="fee_exact",
marks=EIPChecklist.TransactionType.Test.IntrinsicValidity.ValueNonZeroInsufficientBalance(),
),
pytest.param(2, 1, id="value_short_by_one"),
],
)
@pytest.mark.parametrize(
"tx_type",
[pytest.param(1, id="type_1"), pytest.param(2, id="type_2")],
)
def test_surcharged_fee_inclusion(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
tx_type: int,
value: int,
balance_delta: int,
) -> None:
"""
Fund the surcharged fee one wei short, exactly, or with part of the
value on top; the value stays out of the inclusion check.
"""
recipient = pre.fund_eoa(amount=0)
intrinsic_cost = fork.transaction_intrinsic_cost_calculator()
gas_limit = intrinsic_cost(
access_list=ACCESS_LIST,
sends_value=True,
recipient_type=RecipientType.EMPTY_ACCOUNT,
)
# Pin where the fee boundary sits on either side of EIP-7981.
costs = fork.gas_costs()
surcharge = (
calculate_access_list_data_cost(ACCESS_LIST, fork)
if fork.is_eip_enabled(7981)
else 0
)
assert gas_limit == (
intrinsic_cost(
sends_value=True, recipient_type=RecipientType.EMPTY_ACCOUNT
)
+ costs.TX_ACCESS_LIST_ADDRESS
+ costs.TX_ACCESS_LIST_STORAGE_KEY
+ surcharge
)
sender = pre.fund_eoa(amount=gas_limit * GAS_PRICE + balance_delta)
sender_nonce = sender.nonce
rejected = balance_delta < 0
tx = priced_transaction(
tx_type,
GAS_PRICE,
sender=sender,
to=recipient,
value=value,
gas_limit=gas_limit,
access_list=ACCESS_LIST,
error=TransactionException.INSUFFICIENT_ACCOUNT_FUNDS
if rejected
else None,
expected_receipt=None
if rejected
else TransactionReceipt(status=0, gas_used=gas_limit),
)
post: dict[Address, Account | None] = {recipient: Account.NONEXISTENT}
if not rejected:
post[sender] = Account(nonce=sender_nonce + 1, balance=balance_delta)
state_test(pre=pre, post=post, tx=tx)
Loading