Reserved reference slots in the storage grid - #4
Merged
Conversation
The storage grid silently skips the slot a mapping, dynamic array, bytes or string reserves. Record the design that gives every reserved slot a row, and the task-by-task plan that implements it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
app.css themed .cm-editor, .cm-gutters and .cm-line but nothing floating, so the lint tooltip, the diagnostic list and the Ctrl-F panel kept CodeMirror's stock light background under the app's light text — bright on bright, and effectively unreadable. A plain .cm-tooltip rule in app.css does not fix this: CodeMirror injects its base styles as a StyleModule, so an app-level rule ties on specificity and loses on order (measured: the tooltip stayed rgb(245, 245, 245)). Use the supported mechanism instead — EditorView.theme marked dark, which CodeMirror injects after its base sheet. `dark: true` also gives dark defaults to widgets not named here, so enabling one more extension cannot bring the bug back. The new e2e hovers a real lint marker, asserts the computed background, and runs axe with the tooltip open. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A mapping, dynamic array, bytes or string reserves a slot that buildRows never
emitted, because its slot list came only from materialized value cells. So
`uint256 a; mapping(...) b; uint256 c;` rendered slots 0 and 2 with nothing
between them, and the reserved slot survived only as prose under "Not expanded".
buildRows now walks a sorted list of slot claims. A reference head — always one
32-byte word, always alone in its slot — becomes its own row. An unexpanded
inplace span becomes one elided range row rather than thousands of rows, and any
unclaimed run becomes an honest `slots N-M - no declared variable` banner, so the
row list is gapless by construction rather than by assertion. A 300-run
fast-check property holds the invariant.
Each row says what its slot actually holds, which differs by encoding: a mapping
head is never written, an array head holds the length, and a bytes/string head
under 32 bytes holds the data itself — so "reserved, nothing here" would be false
for two of the three. After a run the observed word renders in amber, and only
there; a nonzero mapping head is flagged as an anomaly, since only assembly or a
slot collision can put a value in one.
Supporting changes:
- CollapsedStorageSpan carries solc's `encoding`, replacing classification by
typeLabel.startsWith('mapping'). An encoding solc does not emit now throws.
- runtimeView folds trace observations into slot -> word (reads included, so
pre-existing state is visible) plus distinct derived-entry counts per base.
- The default editor source seeds from the golden fixture, which had no
reference type, so the fixture gains balances/history/note at slots 5-7 and a
constructor that fills two of them. Declared after positionValue: the editor
virtualizes lines and relations.spec.ts clicks one inside that function.
Goldens regenerated through the reviewed --update path; every existing
variable kept its exact slot and offset.
- The "Not expanded" list is retired; its teaching copy moved onto the rows and
the per-slot record. The e2e assertion that the mapping appears only in that
list encoded the defect and is replaced deliberately.
Keccak-derived slots stay out of the grid (slotscope-xu8, v1.4): a row for one
would claim an adjacency near 2^256 that it does not have.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…gate release:verify scans for raw color literals outside tokens.css and does not exempt comments. The comment cited CodeMirror's stock background by hex as evidence; the point survives without the number. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The defect
A mapping, dynamic array,
bytesorstringreserves a slot that the grid nevershowed.
uint256 a; mapping(address => uint256) balances; uint256 b;renderedslots 0 and 2 with nothing between them — read the grid top to bottom and the
compiler appears to have skipped an address.
buildRowsbuilt its slot list onlyfrom materialized value cells, so the reserved slot survived as prose under a
"Not expanded" heading.
That was the one place the instrument was silent instead of honest, and it was
silent about the most-taught fact in Solidity storage: a mapping consumes a slot
it then never writes, because its entries live at
keccak(key ‖ slot).What this does
Every reserved slot gets its own labeled row, and the row says what the slot
actually holds — which differs by encoding, so one shared sentence would be a lie:
mappingRESERVED · mapping, no valuedynamic_arrayLENGTH · array,length = Nbytes/stringAfter a run, the observed word renders in amber — and only there, per the identity
law that amber marks measured values. A nonzero mapping head is flagged as an
anomaly rather than presented as a value, since only inline assembly or a slot
collision can put one there.
Rows are gapless by construction, not by assertion:
buildRowswalks a sortedlist of slot claims, collapses an unexpanded multi-slot span into a single elided
range instead of thousands of rows, and fills any unclaimed run with an honest
slots N–M · no declared variablebanner. A 300-run fast-check property holds theinvariant.
Notable decisions
CollapsedStorageSpancarries solc'sencoding. The UI previously decided aspan was a mapping via
typeLabel.startsWith('mapping')— a heuristic over adisplay string, which this codebase forbids. An encoding solc does not emit now
throws rather than being treated as one of the four.
alternative and the wrong trade: roving focus, pinning, React keys and
aria-rowcountall index throughrow.slot/row.segments. AddingendSlotplus two full-width segment kinds left all four untouched.
only read still shows a value, which is the only way pre-existing state becomes
visible. Documented limit: these are last-observed words, not post-revert final
state.
App.tsx:16), sothe fixture gains
balances/history/noteat slots 5–7 plus a constructorthat fills two of them. This is the one deliberate golden change, made through
the reviewed
--updatepath. Every existing variable kept its exact slot andoffset;
sourceHash,expectedLayoutHashandexpectedRelationHashmoved,expectedInstructionHashdid not.slotscope-xu8, v1.4). A row forone would claim an adjacency near 2^256 that it does not have, and the gapless
walk would need an elided range ~10^76 slots wide to reach it.
Also fixed
CodeMirror's floating widgets — the lint tooltip, the diagnostic list, the Ctrl-F
panel — were rendering with the library's light-theme defaults under the app's
light text, effectively unreadable. An app-level
.cm-tooltiprule does not fixit (CodeMirror injects its base styles as a StyleModule, so the rule ties on
specificity and loses on order — measured, the tooltip stayed
rgb(245, 245, 245)). They are themed throughEditorView.thememarkeddark.Two things the gates caught
positionValueout of CodeMirror's virtualizedviewport, so
relations.spec.tscould not find the line it clicks. The newdeclarations moved below the function; declaration order still assigns slots
5–7.
decode.test.tsasserteditems.every(kind === 'value')on the fixture, nowfalse. Replaced with a stronger check: the three reference types collapse at
slots 5–7 with the right compiler encodings.
Verification
format:check,lint,typechecktest:unitfixtures:verify--updatetest:e2ebuild+build:checkbenchmark:v0.1Design spec:
docs/design/reserved-slots-v1.2.md. Plan:docs/plans/2026-07-30-reserved-reference-slots.md. Closesslotscope-zvd(
.1–.5) andslotscope-73y.One thing worth a manual look: the Ctrl-F search panel is themed but no test
opens it.
🤖 Generated with Claude Code