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
25 changes: 25 additions & 0 deletions .github/workflows/glamsterdam-write-prepayment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Glamsterdam write-prepayment proof

on:
push:
branches:
- "experiment/glamsterdam-write-prepayment"
workflow_dispatch:

jobs:
proof:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/setup-uv
with:
python-version: "3.14"
- name: Fill targeted Osaka-to-Amsterdam proof
run: >
uv run fill
--skip-index
--clean
--from Osaka
--until Amsterdam
--output=.just/write-prepayment/fixtures
tests/amsterdam/eip8038_state_access_gas_cost_increase/test_write_prepayment_prototype.py
7 changes: 7 additions & 0 deletions src/ethereum/forks/amsterdam/fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
settle_transaction_gas,
)
from .vm.interpreter import TransactionOutput, process_top_level
from .write_prepayment import find_write_prepayments

BASE_FEE_MAX_CHANGE_DENOMINATOR = Uint(8)
ELASTICITY_MULTIPLIER = Uint(2)
Expand Down Expand Up @@ -591,6 +592,10 @@ def check_transaction(
for slot in access.slots:
access_list_storage_keys.add((access.account, slot))

write_prepaid_storage_keys = find_write_prepayments(
access_list_storage_keys
)

authorizations: Tuple[Authorization, ...] = ()
if isinstance(tx, SetCodeTransaction):
authorizations = tx.authorizations
Expand Down Expand Up @@ -621,6 +626,7 @@ def check_transaction(
calldata_floor=intrinsic.calldata_floor,
access_list_addresses=access_list_addresses,
access_list_storage_keys=access_list_storage_keys,
write_prepaid_storage_keys=write_prepaid_storage_keys,
accounts_with_paid_writes=accounts_with_paid_writes,
state=tx_state,
blob_versioned_hashes=blob_versioned_hashes,
Expand Down Expand Up @@ -767,6 +773,7 @@ def process_unchecked_system_transaction(
calldata_floor=Uint(0),
access_list_addresses=set(),
access_list_storage_keys=set(),
write_prepaid_storage_keys=set(),
# A system transaction charges no gas, so no write is paid for.
accounts_with_paid_writes=set(),
state=system_tx_state,
Expand Down
24 changes: 24 additions & 0 deletions src/ethereum/forks/amsterdam/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@
TransactionTypeError,
)
from .fork_types import Authorization, ExecutionGas, VersionedHash
from .write_prepayment import (
STORAGE_WRITE_REPRICING_DELTA,
WRITE_PREPAYMENT_MARKER_COUNT,
find_write_prepayments,
)


@final
Expand Down Expand Up @@ -728,6 +733,7 @@ def calculate_intrinsic_cost(
recipient_execution_gas += GasCosts.TX_VALUE_COST

access_list_cost = Uint(0)
access_list_entries: set[tuple[Address, Bytes32]] = set()
tokens_in_access_list = Uint(0)
if has_access_list(tx):
for access in tx.access_list:
Expand All @@ -739,6 +745,23 @@ def calculate_intrinsic_cost(
tokens_in_access_list += (
ulen(access.slots) * ACCESS_LIST_STORAGE_KEY_FLOOR_TOKENS
)
for slot in access.slots:
access_list_entries.add((access.account, slot))

# Experimental encoding: three ordinary access-list marker keys carry
# 6,000 gas of the 7,200 write-repricing delta. Charge the exact 1,200
# remainder here so a complete voucher has paid the full delta before
# any frame starts executing.
write_prepayments = find_write_prepayments(access_list_entries)
marker_prepayment = GasCosts.TX_ACCESS_LIST_STORAGE_KEY * Uint(
WRITE_PREPAYMENT_MARKER_COUNT
)
write_prepayment_remainder = (
STORAGE_WRITE_REPRICING_DELTA - marker_prepayment
)
write_prepayment_cost = write_prepayment_remainder * ulen(
write_prepayments
)

# Data token floor cost for access list bytes.
access_list_cost += tokens_in_access_list * GasCosts.TX_DATA_TOKEN_FLOOR
Expand Down Expand Up @@ -770,6 +793,7 @@ def calculate_intrinsic_cost(
+ init_code_gas
+ data_cost
+ access_list_cost
+ write_prepayment_cost
+ auth_cost
),
calldata_floor=ExecutionGas(data_floor_gas_cost),
Expand Down
1 change: 1 addition & 0 deletions src/ethereum/forks/amsterdam/vm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ class TransactionEnvironment:
calldata_floor: Uint
access_list_addresses: Set[Address]
access_list_storage_keys: Set[Tuple[Address, Bytes32]]
write_prepaid_storage_keys: Set[Tuple[Address, Bytes32]]
accounts_with_paid_writes: Set[Address]
state: TransactionState
blob_versioned_hashes: Tuple[VersionedHash, ...]
Expand Down
19 changes: 18 additions & 1 deletion src/ethereum/forks/amsterdam/vm/instructions/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
Implementations of the EVM storage related instructions.
"""

from ethereum_types.bytes import Bytes32
from ethereum_types.numeric import Uint

from ...fork_types import ExecutionGas, StateGas
Expand All @@ -21,6 +22,7 @@
set_storage,
set_transient_storage,
)
from ...write_prepayment import STORAGE_WRITE_REPRICING_DELTA
from .. import Evm
from ..exceptions import WriteInStaticContext
from ..gas import (
Expand All @@ -34,6 +36,18 @@
from ..stack import pop, push


def _consume_write_prepayment(evm: Evm, key: Bytes32) -> bool:
"""Consume one transaction-boundary write prepayment."""
entry = (evm.current_target, key)
if entry not in evm.tx_env.write_prepaid_storage_keys:
return False

# Consumption is transaction-wide, so a reverted or OOG child cannot
# reuse one intrinsic prepayment in another frame.
evm.tx_env.write_prepaid_storage_keys.remove(entry)
return True


def sload(evm: Evm) -> None:
"""
Loads to the stack, the value corresponding to a certain key from the
Expand Down Expand Up @@ -120,7 +134,10 @@ def sstore(evm: Evm) -> None:

# Write cost: charged on the first change to the slot this transaction.
if original_value == current_value and current_value != new_value:
gas_cost += GasCosts.STORAGE_WRITE
if _consume_write_prepayment(evm, key):
gas_cost += GasCosts.STORAGE_WRITE - STORAGE_WRITE_REPRICING_DELTA
else:
gas_cost += GasCosts.STORAGE_WRITE

# Refund Counter Calculation
if current_value != new_value:
Expand Down
42 changes: 42 additions & 0 deletions src/ethereum/forks/amsterdam/write_prepayment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Experimental transaction-boundary storage-write prepayments."""

from typing import Iterable

from ethereum_types.bytes import Bytes32
from ethereum_types.numeric import Uint

from ethereum.crypto.hash import keccak256
from ethereum.state import Address

from .fork_types import ExecutionGas

WRITE_PREPAYMENT_DOMAIN = b"glamsterdam-write-prepayment-v1"
WRITE_PREPAYMENT_MARKER_COUNT = 3

# EIP-8038 raises the existing-slot write component from 2,800 to 10,000.
# A voucher moves only that 7,200 repricing delta to transaction scope.
STORAGE_WRITE_REPRICING_DELTA = ExecutionGas(Uint(7_200))


def write_prepayment_markers(
address: Address, key: Bytes32
) -> tuple[Bytes32, ...]:
"""Return the provisional access-list markers for one storage write."""
prefix = WRITE_PREPAYMENT_DOMAIN + bytes(address) + bytes(key)
return tuple(
keccak256(prefix + bytes([index]))
for index in range(WRITE_PREPAYMENT_MARKER_COUNT)
)


def find_write_prepayments(
entries: Iterable[tuple[Address, Bytes32]],
) -> set[tuple[Address, Bytes32]]:
"""Decode complete provisional vouchers from access-list entries."""
declared = set(entries)
prepaid: set[tuple[Address, Bytes32]] = set()
for address, key in declared:
markers = write_prepayment_markers(address, key)
if all((address, marker) in declared for marker in markers):
prepaid.add((address, key))
return prepaid
Loading
Loading