Skip to content
Closed
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
137 changes: 137 additions & 0 deletions prototypes/glamsterdam-write-prepayment/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Glamsterdam Write Prepayment — Phase 1

Base: `ethereum/execution-specs@20f7f6271a720091e5fea0a82e7bc802866ae36a` (`forks/amsterdam`, 2026-08-26).

## Decision gate

Do not change consensus semantics and do not scan mainnet until the current
execution-spec constants prove both invariants:

1. `Osaka PASS -> Amsterdam FAIL -> Amsterdam + prepayment PASS` under the
same immutable child-call gas budget.
2. Moving the repricing delta outside the child preserves the full Amsterdam
execution-resource charge.

The executable proof is:

`tests/amsterdam/eip8038_state_access_gas_cost_increase/test_write_prepayment_prototype.py`

This phase is deliberately a branch-pinned accounting/liveness proof. It does
**not** yet define a new transaction encoding or modify EVM consensus behavior.
That implementation is the next gate only if this proof survives CI.

## Minimal reproduction

Use an existing non-zero storage slot and a child that executes:

```text
PUSH value
PUSH key
SSTORE
```

Two `PUSH` instructions cost 6 execution gas. Give the child an immutable
5,006-gas budget.

### Osaka

For the first non-zero -> non-zero overwrite of a cold slot:

```text
2,100 cold storage access
+ 2,900 write component
+ 6 stack setup
= 5,006
```

Result: `PASS`.

### Amsterdam, unchanged

Current `forks/amsterdam` constants:

```text
2,100 COLD_STORAGE_ACCESS
+ 10,000 STORAGE_WRITE
+ 6 stack setup
= 12,106
```

The same immutable 5,006-gas child cannot execute it.

Result: `FAIL`.

### Amsterdam, write-prepaid

Decompose the Amsterdam write price:

```text
10,000 STORAGE_WRITE
= 2,800 historical write component
+ 7,200 repricing delta
```

Prepay the 7,200 delta outside the child and prewarm the storage key. The
child sees:

```text
100 warm access
+ 2,800 historical write component
+ 6 stack setup
= 2,906
```

Result: `PASS` under the same 5,006-gas child budget.

## Conservation proof

The mechanism is invalid if it makes Amsterdam work cheaper globally.

Ordinary cold Amsterdam storage write:

```text
2,100 cold access + 10,000 write = 12,100
```

Prepaid split:

```text
2,000 access-list storage-key prepayment
+ 100 warm access in the child
+ 7,200 write-repricing prepayment
+ 2,800 historical write component in the child
= 12,100
```

Account access conserves independently:

```text
ordinary: 3,000 cold account access
prepaid: 2,900 access-list address prepayment + 100 warm runtime access
= 3,000
```

Therefore the core state/call resource accounting is identical:

```text
ordinary Amsterdam core charge = 3,000 + 12,100 = 15,100
prepaid Amsterdam core charge = 3,000 + 12,100 = 15,100
```

The proposal changes **where** the repricing delta is charged, not whether it
is charged.

## Next gate

Only after the phase-1 test passes:

1. choose an explicit opt-in transaction representation for write vouchers;
2. implement voucher state and rollback semantics in EELS;
3. prove change -> restore -> change net-metering, nested-call revert/halt
behavior, duplicate voucher handling, and EIP-8037 state-gas interaction;
4. run the Amsterdam/Osaka test suites and independent client fixtures;
5. only then scan deployed mainnet bytecode for fixed-gas asset-release paths
and quantify value reachable only through failing paths.

A mainnet value estimate before those gates would measure a hypothesis rather
than a demonstrated compatibility failure.
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""
Executable phase-1 proof for Glamsterdam storage-write prepayment.

The proof reads the Osaka and Amsterdam gas constants directly from the
current execution-spec source tree. It must fail if those branch constants
move enough to invalidate either the liveness transition or conservation
claim.
"""

OSAKA_GAS_PATH = "src/ethereum/forks/osaka/vm/gas.py"
AMSTERDAM_GAS_PATH = "src/ethereum/forks/amsterdam/vm/gas.py"


def _uint_constant(path: str, name: str) -> int:
"""Read a direct ``Uint(...)`` gas constant from a fork gas module."""
with open(path, encoding="utf-8") as source_file:
for line in source_file:
stripped = line.strip()
if stripped.startswith(f"{name}:") and "Uint(" in stripped:
raw_value = stripped.rsplit("Uint(", 1)[1].split(")", 1)[0]
return int(raw_value)
raise AssertionError(f"direct Uint constant not found: {path}:{name}")


def _amsterdam_access_list_formulas_survive() -> None:
"""Pin the access-list prepayment formulas used by the proof."""
with open(AMSTERDAM_GAS_PATH, encoding="utf-8") as source_file:
source = source_file.read()

assert "TX_ACCESS_LIST_ADDRESS: Final[ExecutionGas] = (" in source
assert "COLD_ACCOUNT_ACCESS - WARM_ACCESS" in source
assert "TX_ACCESS_LIST_STORAGE_KEY: Final[ExecutionGas] = (" in source
assert "COLD_STORAGE_ACCESS - WARM_ACCESS" in source


def test_osaka_pass_amsterdam_fail_prepaid_pass() -> None:
"""Prove one immutable child budget flips PASS -> FAIL -> PASS."""
osaka_warm = _uint_constant(OSAKA_GAS_PATH, "WARM_ACCESS")
osaka_cold_storage = _uint_constant(OSAKA_GAS_PATH, "COLD_STORAGE_ACCESS")
osaka_cold_write = _uint_constant(OSAKA_GAS_PATH, "COLD_STORAGE_WRITE")
osaka_push = _uint_constant(OSAKA_GAS_PATH, "VERY_LOW")

amsterdam_warm = _uint_constant(AMSTERDAM_GAS_PATH, "WARM_ACCESS")
amsterdam_cold_storage = _uint_constant(
AMSTERDAM_GAS_PATH, "COLD_STORAGE_ACCESS"
)
amsterdam_write = _uint_constant(AMSTERDAM_GAS_PATH, "STORAGE_WRITE")
amsterdam_push = _uint_constant(AMSTERDAM_GAS_PATH, "VERY_LOW")

legacy_write = osaka_cold_write - osaka_cold_storage - osaka_warm
fixed_child_gas = osaka_cold_write + 2 * osaka_push

osaka_required = osaka_cold_write + 2 * osaka_push
amsterdam_required = (
amsterdam_cold_storage + amsterdam_write + 2 * amsterdam_push
)
prepaid_required = amsterdam_warm + legacy_write + 2 * amsterdam_push

assert legacy_write == 2_800
assert fixed_child_gas == 5_006
assert osaka_required == 5_006
assert amsterdam_required == 12_106
assert prepaid_required == 2_906

assert osaka_required <= fixed_child_gas
assert amsterdam_required > fixed_child_gas
assert prepaid_required <= fixed_child_gas


def test_prepayment_preserves_glamsterdam_execution_resource_charge() -> None:
"""Prove repricing relocation preserves the full Amsterdam core charge."""
_amsterdam_access_list_formulas_survive()

osaka_warm = _uint_constant(OSAKA_GAS_PATH, "WARM_ACCESS")
osaka_cold_storage = _uint_constant(OSAKA_GAS_PATH, "COLD_STORAGE_ACCESS")
osaka_cold_write = _uint_constant(OSAKA_GAS_PATH, "COLD_STORAGE_WRITE")

amsterdam_warm = _uint_constant(AMSTERDAM_GAS_PATH, "WARM_ACCESS")
amsterdam_cold_account = _uint_constant(
AMSTERDAM_GAS_PATH, "COLD_ACCOUNT_ACCESS"
)
amsterdam_cold_storage = _uint_constant(
AMSTERDAM_GAS_PATH, "COLD_STORAGE_ACCESS"
)
amsterdam_write = _uint_constant(AMSTERDAM_GAS_PATH, "STORAGE_WRITE")

legacy_write = osaka_cold_write - osaka_cold_storage - osaka_warm
write_repricing_delta = amsterdam_write - legacy_write
access_list_address = amsterdam_cold_account - amsterdam_warm
access_list_storage_key = amsterdam_cold_storage - amsterdam_warm

baseline_call_access = amsterdam_cold_account
baseline_storage = amsterdam_cold_storage + amsterdam_write
baseline_core_charge = baseline_call_access + baseline_storage

prepaid_call_access = access_list_address + amsterdam_warm
prepaid_storage = (
access_list_storage_key
+ amsterdam_warm
+ write_repricing_delta
+ legacy_write
)
prepaid_core_charge = prepaid_call_access + prepaid_storage

assert write_repricing_delta == 7_200
assert baseline_call_access == prepaid_call_access == 3_000
assert baseline_storage == prepaid_storage == 12_100
assert baseline_core_charge == prepaid_core_charge == 15_100


def test_prepayment_is_not_a_subsidy() -> None:
"""Prove the rescued local frame does not make global work cheaper."""
osaka_warm = _uint_constant(OSAKA_GAS_PATH, "WARM_ACCESS")
osaka_cold_storage = _uint_constant(OSAKA_GAS_PATH, "COLD_STORAGE_ACCESS")
osaka_cold_write = _uint_constant(OSAKA_GAS_PATH, "COLD_STORAGE_WRITE")

amsterdam_warm = _uint_constant(AMSTERDAM_GAS_PATH, "WARM_ACCESS")
amsterdam_cold_storage = _uint_constant(
AMSTERDAM_GAS_PATH, "COLD_STORAGE_ACCESS"
)
amsterdam_write = _uint_constant(AMSTERDAM_GAS_PATH, "STORAGE_WRITE")

legacy_write = osaka_cold_write - osaka_cold_storage - osaka_warm
write_repricing_delta = amsterdam_write - legacy_write
access_list_storage_key = amsterdam_cold_storage - amsterdam_warm

local_prepaid_sstore = amsterdam_warm + legacy_write
globally_paid_sstore = (
access_list_storage_key + local_prepaid_sstore + write_repricing_delta
)

assert local_prepaid_sstore == 2_900
assert globally_paid_sstore == amsterdam_cold_storage + amsterdam_write
Loading