Skip to content

Transient storage as its own address space - #8

Merged
halaprix merged 3 commits into
masterfrom
feat/transient-storage
Jul 31, 2026
Merged

Transient storage as its own address space#8
halaprix merged 3 commits into
masterfrom
feat/transient-storage

Conversation

@halaprix

Copy link
Copy Markdown
Owner

Implements docs/design/transient-storage-v1.5.md. Closes slotscope-syp (.1.3).

Why it is a separate space, not a second column

Transient storage (EIP-1153) numbers from 0 in its own address space. In the
probe that shaped this design, the persistent persistent is slot 0 and the
transient lock is slot 0. A merged view would make slot 0 mean two things at
once, so transient gets its own tab and its own grid. Keeping them apart is the
lesson, not an implementation detail — there is an e2e that asserts neither grid
ever shows the other's name.

What solc actually gives us

Probed against the locked solc 0.8.36 before designing anything, because the
whole shape depended on it:

  1. transientStorageLayout already exists, same shape as storageLayout. So
    decodeStorageLayout and buildRows work on it verbatim — no new decoder. It
    simply was not in our outputSelection.
  2. Packing is identical: two uint128 transient variables shared slot 2 at offsets
    0 and 16.
  3. Value types only. mapping, structs and static arrays are rejected with
    UnimplementedFeatureError: Transient data location is only supported for value types.

Point 3 is why this phase is small: the transient grid needs none of the v1.2/v1.4
reference machinery — no reserved heads, no keccak-derived children, no elided
ranges. StorageGrid is reused unchanged behind a space prop that selects copy
and switches derived-child reconstruction off, since reconstructing a transient
slot against persistent keccaks could only invent a link.

Transient accesses are finally named

docs/limitations-v1.0.md recorded that transient accesses never resolve. That was
true only while the persistent layout was the sole index — naming them against it
would have named the wrong variable. resolveAccesses now takes an optional
transient layout and routes strictly by kind, so neither index is ever consulted
for the other space's accesses. Without this the tab could label nothing.

Honesty about lifetime

Transient state is discarded when the transaction ends, so the tab never claims a
"current" value. Two permanent text lines carry it, not styling:

  • before a run — transient storage is empty outside a transaction;
  • after one — observed during the transaction · discarded when it ended.

The tab itself appears only when the contract declares transient variables, so its
absence is compiler truth rather than a UI accident.

Note on the locked input

The Standard JSON snapshot moved deliberately to request the new output. Fixture
goldens verify unchanged, which confirms the added selection changes what solc
reports, not what it compiles — the property that matters for a locked
environment.

Verification

Gate Result
format:check, lint, typecheck clean
test:unit 247/247, including real compiled contracts through the EVM for the transient resolver
test:e2e 65/65 — 3 new transient specs, including axe and a both-spaces-have-slot-0 case
fixtures:verify no drift
release:verify passed
build:check lean main entry
benchmark:v0.1 p95 218 ms action-to-paint, 689 ms cold start — PASS

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
slotscope Ready Ready Preview Jul 31, 2026 12:05pm

halaprix and others added 2 commits July 31, 2026 09:20
Transient storage (EIP-1153) gets its own tab and its own grid. It is never
merged with persistent storage, because both spaces number from 0 — in the probe
that shaped this design, the persistent `persistent` is slot 0 and the transient
`lock` is slot 0 — so a merged view would make slot 0 mean two things at once.
Keeping them apart is the lesson, not an implementation detail.

Probed against the locked solc 0.8.36 before designing anything:

- transientStorageLayout exists with the same shape as storageLayout, so
  decodeStorageLayout and buildRows work on it verbatim;
- packing is identical (two uint128 transient vars share slot 2 at 0 and 16);
- transient is VALUE TYPES ONLY — mapping, struct and static array are rejected
  with UnimplementedFeatureError.

That last point is why this phase is small: the transient grid needs none of the
v1.2/v1.4 reference machinery. No reserved heads, no derived children, no elided
ranges. StorageGrid is reused unchanged behind a `space` prop that only selects
copy and switches derived-child reconstruction off, since reconstructing against
persistent keccaks could only invent a link.

Transient accesses are now named: resolveAccesses takes an optional transient
layout and routes strictly by kind, so neither index is ever consulted for the
other space's accesses. docs/limitations-v1.0.md is updated accordingly — that
entry was true only while the persistent layout was the sole index.

Honesty about lifetime is carried in permanent text, not styling. Before a run:
"transient storage is empty outside a transaction". After one: "observed during
the transaction, discarded when it ended". The tab itself appears only when the
contract declares transient variables, so its absence is compiler truth rather
than a UI accident.

The locked Standard JSON snapshot moved deliberately to request the new output.
Fixture goldens verify unchanged, which confirms the added selection changes what
solc reports, not what it compiles.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e leaks

Confirmed by an independent Codex + Gemini 3.6 Flash review of this PR, then
verified directly against source before fixing anything.

- useExecutionSession.ts called resolveAccesses without transientLayout, so
  the transient naming this PR added was correct and unit-tested but
  unreachable from the actual app: every tload/tstore in the UI stayed
  unresolved. Both reviewers found this independently. Pass
  artifact.transientStorageLayout through.

- resolve.ts's lastValueBySlot was one map keyed by slotHex alone, shared
  across persistent and transient accesses. Persistent and transient storage
  both number from 0, so a persistent write to slot 0 followed by a
  transient write to slot 0 — exactly what the existing test contract does
  (persistent = 1; lock = 9;) — handed the transient access the persistent
  word as its previousValueHex, producing a false byte-diff instead of the
  honest "first touch" state. Keyed the map by space + slot instead.
  Verified the new test fails without this fix (previousValueHex was the
  persistent word, not null) and passes with it.

- packages/challenges' compileArtifact()/bankRunner() test helpers built
  ContractArtifact literals missing the now-required transientStorageLayout
  field. This package isn't in the root tsconfig's project references, so
  pnpm typecheck never touched it — confirmed by reading tsconfig.json
  directly and by running tsc --noEmit on the package in isolation, on both
  this branch and master. Not a currently-failing gate, but a real type
  error; set the field to null in both helpers.

New coverage: a unit test in chains.test.ts asserting previousValueHex stays
null on first touch across a space boundary, and two e2e tests confirming a
transient write is actually named in the runtime write log (not "unresolved
slot") and that its previous-value display shows "first touch" rather than a
cross-space byte diff.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@halaprix
halaprix merged commit 26266d0 into master Jul 31, 2026
8 checks passed
@halaprix
halaprix deleted the feat/transient-storage branch July 31, 2026 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant