Skip to content

feat: add E2E tests for EIP-1153 (TLOAD/TSTORE transient storage)#9

Merged
libotony merged 10 commits intomainfrom
feat/eip1153-tload-tstore
Apr 15, 2026
Merged

feat: add E2E tests for EIP-1153 (TLOAD/TSTORE transient storage)#9
libotony merged 10 commits intomainfrom
feat/eip1153-tload-tstore

Conversation

@libotony
Copy link
Copy Markdown
Member

Summary

  • Add tests/eip1153 package with full E2E coverage for EIP-1153 transient storage opcodes (TSTORE/TLOAD)
  • Tests verify pre-fork revert behavior and post-fork correct execution
  • Gas cost assertions validate opcode pricing per EIP-1153 spec

Tests Added

  • TestTSTORE_TLOAD_Roundtrip — stores a value with TSTORE and reads it back with TLOAD; asserts revert pre-fork, correct value post-fork
  • TestTLOAD_UninitializedSlot — reads an unwritten slot; asserts revert pre-fork, zero value post-fork
  • TestTSTORE_TLOAD_GasCost — validates TSTORE costs 100 gas and TLOAD costs 103 gas (deployed contract call path, STOP terminator to avoid code-deposit charge)
  • TestTransientStorage_IsolatedFromSstore — verifies TSTORE/TLOAD and SSTORE/SLOAD operate on separate namespaces
  • TestTransientStorage_ClearedBetweenTransactions — verifies transient slots written in tx N are zero in tx N+1

Test Plan

  • All tests pass via make test
  • Pre-fork cases revert as expected
  • Post-fork cases return correct values
  • Gas assertions match opcode schedule
  • go vet passes, no linter issues

Introduces TestTSTORE_TLOAD_Roundtrip which stores 0xDEADBEEF into
transient slot 0 via TSTORE and retrieves it via TLOAD, asserting
revert pre-INTERSTELLAR (invalid opcode) and correct output post-fork.
…ng tests

Adds TestTransientStorage_DoesNotPersistToSLOAD (5a) to verify that TSTORE
and SLOAD operate on separate namespaces, and TestTransientStorage_ClearedBetweenTransactions
(5b) to verify transient slots are zeroed at transaction boundaries.
Copy link
Copy Markdown
Collaborator

@BenderVeChain BenderVeChain left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Retracted

After discussion with Tony Li — finding #1 is withdrawn. Init-code execution via nil-To InspectClauses is correct: TLOAD/TSTORE are valid within init-code post-fork, and the gas measurements are accurate. No issue there.


Request Changes

TestTransientStorage_ClearedBetweenTransactions — use receipt block ID instead of "best"

// Current (racy)
results, err := client.InspectClauses(tloadCallData, thorclient.Revision("best"))

Using "best" is racy in a 3-node network. Between WaitForReceipt returning and InspectClauses executing, the best block on the queried node may have advanced (or not yet caught up). The simulation should be pinned to the exact block in which the TSTORE tx was mined.

The receipt already carries Meta.BlockID:

// Fix
results, err := client.InspectClauses(tloadCallData, thorclient.Revision(tstoreReceipt.Meta.BlockID.String()))

This guarantees the simulation runs against the state at the block immediately after the TSTORE tx, which is the correct semantics: verify that transient storage written in that tx is not visible to a new execution context at the same block height.


Confirmed Good

  • Bytecode correctness: ✅ TSTORE/TLOAD opcodes, stack order, MSTORE layout, RETURN sizes all accurate
  • STOP terminator in gas tests to avoid code-deposit charge: ✅ correct and well-commented
  • Pre/post-fork revert pattern: ✅ consistent across all tests
  • Namespace isolation test (DoesNotPersistToSLOAD): ✅ most valuable test in the set
  • RunTestMain 2-arg signature: ✅ matches current helper/testmain.go
  • Inline bytecode annotations: ✅ excellent clarity

Using "best" is racy on a multi-node network — the simulated tx must
run against the state at the block where the TSTORE tx was mined, not
whatever block happens to be best at query time.
@libotony libotony requested a review from BenderVeChain April 13, 2026 08:38
Copy link
Copy Markdown
Collaborator

@BenderVeChain BenderVeChain left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The requested change is in — tstoreReceipt.Meta.BlockID.String() pins the simulation to the exact block the TSTORE tx landed in. No further issues.

Full verification:

  • Bytecode: all opcodes, stack order, byte offsets, and JUMPDEST target verified by hand — correct throughout
  • transientIsolationRuntime dispatcher: CALLDATASIZE → ISZERO → JUMPI correctly routes empty calldata to TSTORE handler (byte 16) and non-empty to TLOAD handler (byte 5)
  • TSTORE stack order: PUSH2 0xCAFE then PUSH1 0x00 → TSTORE pops key=0, value=0xCAFE ✅
  • initcode header: 12 bytes, runtime offset 0x0C matches PUSH1 0x0C
  • Gas arithmetic: TSTORE=106, TLOAD=103 both correct (STOP avoids code-deposit charge) ✅
  • RunTestMain 2-arg signature matches current helper ✅

LGTM.

@libotony libotony merged commit a6eee6b into main Apr 15, 2026
5 checks passed
@libotony libotony deleted the feat/eip1153-tload-tstore branch April 15, 2026 08:23
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.

3 participants