Skip to content
Draft
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
33 changes: 33 additions & 0 deletions .claude/commands/enhance-ported-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,13 @@ floor: pass `return_cost_deducted_prior_execution=True` to the intrinsic
calculator whenever the tx has calldata, or the derived `executed` (and
the cap) overstate. Validated on `test_refund_suicide50procent_cap`.

**Derive EIP-170 size boundaries from `fork.max_code_size()`.**
EIP-7954 raises the deployed-code ceiling (0x6000 → 0x10000 on
Amsterdam), so a pinned "one byte too big" size silently deploys on the
future fork. Compute max/toobig/huge sizes from the fork accessor and
let the created-account expectation follow. Validated on
`test_create_large_result`.

**A CREATE address collision burns the child's gas allowance** (the
EIP-684 path): the withheld child grant is consumed, nothing is created,
and under EIP-8037 the new-account state charge is refunded. Useful to
Expand All @@ -458,12 +465,38 @@ legal budget gets there. For call-loop depth tests the honest shape is a
fixed named budget with per-gas-schedule-era pinned depth counts, each
shift explained (±1 frame ≈ 64·ln(cost ratio)). Validated on
`test_loop_calls_depth_then_revert`.
**EIP-8037 corollary: pinned depths can shift by more than one frame.**
With reservoir 0, the unwind's single zero→non-zero store of a call
result (~111k with its state spill) can exceed the 63/64 retention of
the ancestor paying it, so that frame reverts too and the cascade drops
extra increments — trace the unwind before pinning. Validated on
`test_call1_mb1024_calldepth` (depth 4 → 2 on Amsterdam).

**Framework wart: the SSTORE dirty-rewrite composite prices 100 on every
fork**, but Constantinople/Petersburg charge 5,000 for a dirty re-store —
a derived budget that must survive pre-Istanbul forks needs an explicit
headroom constant for it (named, commented). Observed on
`test_revert_depth_create_address_collision`'s ConstantinopleFix sweep.
The general form of the wart: **every composite prices every fork with
the Berlin schedule** (cold 2600/warm 100, EXP 50/byte, SSTORE 20k+2100,
CALL 2600+9000). Two consequences: (a) any measurement asserting a
warm/cold composite floors at Berlin — the pre-Berlin sweep fails on the
gas value itself; (b) a *budget* derived from composites overshoots by a
few thousand on TangerineWhistle..Istanbul, so a threshold that must
hold there (e.g. "retention stays ≤ the EIP-2200 stipend") needs a named
drift-headroom constant subtracted before deriving (validated on
`test_call_goes_oog_on_second_level2`, where the surplus pushed
Istanbul's retention past 2300).

**Size a reservoir as `N * store.state_cost(fork)` to keep a frame-gas
scenario alive on EIP-8037 forks.** When a ported cascade needs several
cold first-set SSTOREs to fit inside small frame budgets (impossible
in-frame on 8037: each is ~111k), set
`state_gas_reservoir = N * entry_store.state_cost(fork)` for the N
stores: the frames then pay only `execution_cost` and the pre-8037
arithmetic carries over unchanged. The expression needs no fork branch —
`state_cost` is 0 before 8037, and a zero reservoir is accepted there.
Validated on `test_call_goes_oog_on_second_level2`.

**EIP-8037 repriced the code deposit's regular part — boundaries beware.**
On 8037 forks the deposit charges only the keccak word cost
Expand Down
193 changes: 59 additions & 134 deletions tests/ported_static/berlin/stEIP2930/test_coinbase_t01.py
Original file line number Diff line number Diff line change
@@ -1,175 +1,100 @@
"""
Ori Pomerantz qbzzt1@gmail.com.
Measure the gas cost of a value-transferring CALL to the coinbase from
type-0 and type-1 (EIP-2930 access list) transactions
(by Ori Pomerantz qbzzt1@gmail.com).

Ported from:
state_tests/stEIP2930/coinbaseT01Filler.yml

@manually-enhanced: Do not overwrite. The target contract measures, via
`Op.GAS`, the regular gas of a `CALL` that transfers value to the warm,
already-existing coinbase. EIP-8038 reprices the value-transfer
component (`CALL_VALUE` 9 000 -> 10 300), so the measurement grows by
`gas_costs.CALL_VALUE - 9000`. That delta is derived from the fork's
own gas model, so it is exactly 0 before EIP-8038 and tracks future
parameter changes; do not hardcode the Amsterdam number.
@manually-enhanced: Do not overwrite. The legacy raw GAS-delta window is
reframed as a CodeGasMeasure over the CALL, asserting the fork-derived
composite (minus the returned stipend); the coinbase is warm from an
access-list entry or EIP-3651 (Shanghai), cold otherwise.
"""

import pytest
from execution_testing import (
AccessList,
Account,
Address,
Alloc,
Bytes,
CodeGasMeasure,
Environment,
Hash,
Fork,
StateTestFiller,
Transaction,
)
from execution_testing.forks import Fork
from execution_testing.specs.static_state.expect_section import (
resolve_expect_post,
)
from execution_testing.forks import Shanghai
from execution_testing.vm import Op

REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"

GAS_SLOT = 0x0
TRANSFER_VALUE = 1_000_000


@pytest.mark.ported_from(
["state_tests/stEIP2930/coinbaseT01Filler.yml"],
)
@pytest.mark.valid_from("Cancun")
@pytest.mark.valid_from("Berlin")
@pytest.mark.parametrize(
"d, g, v",
[
pytest.param(
0,
0,
0,
id="T0",
),
pytest.param(
1,
0,
0,
id="T1baseInList",
),
pytest.param(
2,
0,
0,
id="T1baseNotInList",
),
],
"access_list_case",
["no_access_list", "coinbase_in_list", "coinbase_not_in_list"],
ids=["T0", "T1baseInList", "T1baseNotInList"],
)
@pytest.mark.pre_alloc_mutable
def test_coinbase_t01(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
d: int,
g: int,
v: int,
access_list_case: str,
) -> None:
"""Ori Pomerantz qbzzt1@gmail."""
coinbase = Address(0x7704D8A022A1BA8F3539FC82C7D7FB065ABC0DF3)
sender = pre.fund_eoa(amount=0xDE0B6B3A7640000, nonce=1)
"""Measure a value CALL to the coinbase per access-list variant."""
# The coinbase must exist and be alive, so the value transfer never
# writes a new account.
coinbase = pre.fund_eoa(amount=1)
env = Environment(fee_recipient=coinbase)

env = Environment(
fee_recipient=coinbase,
number=1,
timestamp=1000,
prev_randao=0x20000,
base_fee_per_gas=100,
)
if access_list_case == "no_access_list":
# A plain type-0 transaction.
access_list = None
elif access_list_case == "coinbase_in_list":
access_list = [AccessList(address=coinbase, storage_keys=[])]
else:
# A type-1 transaction whose access list does not warm the
# coinbase.
access_list = [
AccessList(address=pre.nonexistent_account(), storage_keys=[])
]

pre[coinbase] = Account(balance=0, nonce=1)
# Source: yul
# berlin
# {
# mstore(0, gas())
# pop(call(gas(), <eoa:0x000000000000000000000000000000000000ba5e>, 1000000, 0, 0, 0, 0)) # noqa: E501
# mstore(0x20, gas())
#
# // The 24 is the cost of twi gas(), seven pushes(), a pop(), and an mstore() # noqa: E501
# sstore(0, sub(sub(mload(0), mload(0x20)),33))
# }
target = pre.deploy_contract( # noqa: F841
code=Op.MSTORE(offset=0x0, value=Op.GAS)
+ Op.POP(
Op.CALL(
gas=Op.GAS,
address=coinbase,
value=0xF4240,
args_offset=Op.DUP1,
args_size=Op.DUP1,
ret_offset=Op.DUP1,
ret_size=0x0,
)
)
+ Op.MSTORE(offset=0x20, value=Op.GAS)
+ Op.SSTORE(
key=0x0,
value=Op.SUB(
Op.SUB(Op.MLOAD(offset=0x0), Op.MLOAD(offset=0x20)), 0x21
),
)
+ Op.STOP,
balance=0xDE0B6B3A7640000,
nonce=1,
# EIP-3651 (Shanghai) pre-warms the coinbase; before that it is only
# warm when the access list names it.
coinbase_warm = fork >= Shanghai or access_list_case == "coinbase_in_list"
call_code = Op.CALL(
address=coinbase,
value=TRANSFER_VALUE,
address_warm=coinbase_warm,
value_transfer=True,
)
target = pre.deploy_contract(
code=CodeGasMeasure(
code=call_code,
extra_stack_items=1,
sstore_key=GAS_SLOT,
),
balance=TRANSFER_VALUE,
)

# EIP-8038 reprices the value-transfer component of `CALL`; with the
# coinbase warm and already in state, the measured gas grows by the
# `CALL_VALUE` reprice alone. Derived from the fork gas model so it
# is 0 before EIP-8038.
call_value_delta = fork.gas_costs().CALL_VALUE - 9000

expect_entries_: list[dict] = [
{
"indexes": {"data": [1], "gas": -1, "value": -1},
"network": [">=Cancun"],
"result": {target: Account(storage={0: 6800 + call_value_delta})},
},
{
"indexes": {"data": [0, 2], "gas": -1, "value": -1},
"network": [">=Cancun"],
"result": {target: Account(storage={0: 6800 + call_value_delta})},
},
]

post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)

tx_data = [
Bytes("693c6139") + Hash(0x0),
Bytes("693c6139") + Hash(0x0),
Bytes("693c6139") + Hash(0x0),
]
tx_gas = [16777216]
tx_access_lists: dict[int, list] = {
1: [
AccessList(
address=coinbase,
storage_keys=[],
),
],
2: [
AccessList(
address=Address(0x000000000000000000000000000000000000BA5A),
storage_keys=[],
),
],
}

tx = Transaction(
sender=sender,
sender=pre.fund_eoa(),
to=target,
data=tx_data[d],
gas_limit=tx_gas[g],
nonce=1,
gas_price=1000,
access_list=tx_access_lists.get(d),
error=_exc,
access_list=access_list,
state_gas_reservoir=0,
)

# The coinbase consumes nothing, so the stipend handed over with the
# value comes back unused.
measured_gas = call_code.gas_cost(fork) - fork.gas_costs().CALL_STIPEND

post = {target: Account(storage={GAS_SLOT: measured_gas}, balance=0)}

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