Skip to content

transient storage for value types PoC - #523

Draft
mbenke wants to merge 4 commits into
mainfrom
transient
Draft

transient storage for value types PoC#523
mbenke wants to merge 4 commits into
mainfrom
transient

Conversation

@mbenke

@mbenke mbenke commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Adds support for EVM transient storage, mirroring the existing persistent-storage design.

What's included:

  • std/tstorage.solc - a new standard-library module providing the tstorage(a) type and TransientType class (backed by tload/tstore), with CanStore instances for scalars, mappings, strings, and bytes. Layout metadata (StorageSize) is reused from std, since transient and persistent storage share the same slot layout.
  • transient keyword - contract fields can now be declared name : transient T to place them in transient storage. A StorageLocation marker (Storage | Transient) threads through the AST from parser to desugarer.
  • Field lowering - FieldAccess lowers a transient field to tstorage(t) instead of storage(t). The std LVA/RVA contract-field instances were generalized over the reference type so a single instance serves both locations (a second instance would overlap on MemberAccessProxy).
  • Runtime test — transient_field.{solc,json} verifies the semantics via the testrunner: a write+read within one call returns the value, while a value written in one transaction reads back as 0 in a later one, confirming per-transaction clearing.

Fields default to persistent storage, so existing contracts are unaffected.

mbenke added 4 commits July 14, 2026 08:13
Mirror the regular contract-storage design (the parameterised pointer
type storage(a) and its classes) for EVM transient storage, using the
tload/tstore opcodes instead of sload/sstore.

The pointer type is named tstorage(a) so that `transient` stays free to
become a storage-location keyword. Layout metadata is location-independent,
so StorageSize and the generic CanStore/Assign/LVA/RVA/IdxAccess/mapping
machinery are reused from std rather than duplicated. New pieces are the
parts that actually touch the opcodes: the TransientType class and its
value-type instances, CanStore instances for tstorage value types,
mappings, and strings/bytes, the byte-array helpers, and the readTransient
/ ltidx / rtidx accessors.

Add a transient counter dispatch example and register it in the suite.

Assisted-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce a `transient` keyword and the contract-field syntax
`name : transient T`, which marks a field as living in EVM transient
storage. A StorageLocation marker (Storage | Transient) is threaded from
the surface AST through name resolution into the resolved AST and on to
the field-access desugarer.

Lowering is not yet implemented: the field-access desugarer rejects
transient fields with a clear "not implemented yet" message. So the
syntax parses and type-resolves but fails at desugaring, which is the
seam where transient lowering (tstorage-based CStructField instances)
will later plug in.

Because `transient` is now reserved, the standard-library module can no
longer be imported as `std.transient`; rename it to `std.tstorage` (the
type it provides is already `tstorage`) and update the counter example.

Parser tests cover both plain and initialized transient fields;
test/examples/dispatch/transient_field.solc demonstrates the end-to-end
syntax (intentionally unregistered, as it fails at desugaring).

Assisted-By: Claude Opus 4.8 <noreply@anthropic.com>
FieldAccess now translates a `transient` field of type t to tstorage(t)
in its generated CStructField instance (a regular field still maps to
storage(t)), replacing the previous "not implemented" desugaring error.

The storage-kind distinction is an output of the CStructField context,
not the MemberAccessProxy main type, so a second location-specific
LVA/RVA instance would overlap the storage one. Instead, generalize the
std LVA/RVA instances to abstract over the reference constructor: a
`refType` bound by CStructField(refType, off), refType:Typedef(word) and
refType:CanStore(loadType), built from the slot offset via Typedef.abs.
FieldAccess selects the location purely by the refType it emits.

test/examples/dispatch/transient_field.solc now compiles and is a
registered dispatch test; set/get on the transient field lower to
tstore/tload (verified: a write+read in one call round-trips).

Assisted-By: Claude Opus 4.8 <noreply@anthropic.com>
The dispatch test only compiles the contract; this adds an executing
end-to-end test via the testrunner/contest path that asserts EVM
behaviour.

Add a `roundtrip` method (write then read the transient field in one
call) and transient_field.json with three assertions:
  - roundtrip(77) returns 77 (tstore then tload within one tx);
  - set(99) succeeds;
  - a later get() returns 0, because transient storage is cleared at the
    end of each transaction (EIP-1153).

The last assertion is what distinguishes transient from persistent
storage: if the field were lowered to sstore/sload, get() would return
99 and the test would fail. Wired into run_contests.sh (run by
`nix flake check`).

Assisted-By: Claude Opus 4.8 <noreply@anthropic.com>
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