Skip to content

Latest commit

 

History

History
115 lines (96 loc) · 7.7 KB

File metadata and controls

115 lines (96 loc) · 7.7 KB

SlotScope architecture

See docs/adr/ for recorded decisions and docs/limitations-v1.0.md for the honest cut-line.

Shape

A static React/Vite application. Compilation, execution, and normalization all happen locally in the browser; the release makes no application network request (asserted in e2e). The future-facing seam is the versioned URL session schema (ADR 0002, extended to v2 by ADR 0004).

Packages and dependency rules

domain  <── compiler <── executor <── semantics
  ▲           ▲            ▲              ▲
  │           │            │              │
layout        │        lifecycle          │
  ▲           │            ▲              │
  └────────── web ─────────┴──────────────┤
  ▲       ▲        ▲            ▲         │
  │       │        │            │         │
 ui     session  challenges  fixtures ─────┘

domain imports no product package. lifecycle is the generic worker-client
base both compiler and executor subclass. fixtures imports domain/compiler
and exercises layout/session/executor in tests. compiler, layout, executor,
semantics, session, challenges import no React. web composes packages and
owns no compiler/layout/execution/resolution algorithms. ui receives
explicit view state and never invents Solidity semantics.
Package Responsibility
@slotscope/domain Locked environment constants, immutable normalized models, Utf8IndexMap (solc UTF-8 byte offsets ↔ editor UTF-16 code units), canonical JSON, deep-freeze, shared hex primitives
@slotscope/compiler Worker protocol/client/worker (ADR 0001), settings-aware Standard JSON input, instruction decoder, source-map decoder, normalize()
@slotscope/layout decodeStorageLayout: solc storageLayout → exact bigint slot/cell model with honest collapses
@slotscope/executor In-browser EVM worker (EthereumJS, Cancun), keyframe/delta trace substrate (ADR 0003), keccak/storage observations, revert and event decode
@slotscope/semantics Layer-3 resolver: pure keccak-chain grounding over the frozen trace observations → exact/inferred/unresolved
@slotscope/lifecycle Generic worker-lifecycle client (generation/job ids, cancel-by-termination, crash recovery) that compiler and executor subclass
@slotscope/session Share-URL schema v1/v2 + bounded codec, .slotscope.json (ADR 0002, ADR 0004)
@slotscope/challenges Challenge schema, runtime-derived answer validator, local-first progress store
@slotscope/ui Design tokens, focus ring, motion contract (tokens.css)
@slotscope/fixtures Public fixture corpus, goldens, manifests, verify script
@slotscope/web Composition: editor, assembly pane, probe pane, cockpit bar, execute strip, relation index, sharing

Data flow

Editor text → Compile (job id + settings + locked environment) → worker (exact-version verified soljson, settings echoed back and verified against the request) → raw Standard JSON output → pure normalize() → immutable CompilationResult → assembly view + relation index + storage decode → UI state (selection + one pin) + the deterministic call sequence + trace cursor → session schema v2 → #s= URL or .slotscope.json. Transient hover/focus/ pin state never mutates the compiled model.

Execution and trace substrate (v0.2)

Deployment and calls run in a second worker holding one long-lived EthereumJS executor per generation; the client mirrors the compiler lifecycle (generation/job ids, cancel-by-termination with verified replacement, one automatic crash recovery, then explicit retry). Termination drops executor state by design — the UI redeploys rather than pretending state survived.

Capture is opt-in per execution: full machine snapshots before each step are encoded as keyframes (512-step cadence) plus deltas, so any cursor replays in sub-millisecond time. A post-hoc pure analysis derives keccak and storage observations plus shadow-stack SlotExpr provenance, degrading to unknown wherever it cannot follow. All of these shapes are frozen — see ADR 0003.

Compiler worker lifecycle

Compilation runs in one long-lived classic web worker per generation (ADR 0001). Because solc compiles synchronously inside its worker, active cancellation is termination of that generation plus a verified replacement.

uninitialized → starting → ready → compiling → ready
                     │          │       │
                     │          │       ├─ cancel → replacing → ready
                     │          │       └─ crash  → replacing → ready
                     │          └─ fatal version mismatch → failed
                     └─ startup error → failed (Retry)

Rules (enforced by packages/compiler/src/client.ts, tested in lifecycle.test.ts and apps/web/e2e/lifecycle.spec.ts):

  • Every request carries a monotonically increasing jobId; every worker has a generationId. Only a message matching the current pair may enter app state — a terminated generation can never leak stale output, including in the completion race where a result lands just before termination.
  • Normal recompiles reuse the ready worker (workersCreated stays 1).
  • A cancelled job settles exactly once with typed cancelled; it never becomes a diagnostic.
  • A replacement worker must verify the exact solc long version before status returns to ready; a mismatch parks the client in failed (fail closed).
  • One unexpected crash earns one automatic replacement. A second consecutive crash parks the client in failed with a user-controlled Retry. A healthy compile result re-arms automatic recovery.
  • Starting a new compile while one is in flight supersedes the old job (typed superseded); stale output is never rendered for new source.
  • Unmount/disposal terminates the worker and rejects all outstanding promises (disposed); the pending-job registry must be empty afterwards.

User-visible failure modes

State UI
starting Compile disabled, "Starting compiler…"
compiling Cancel button visible
replacing "Cancelling — verifying replacement worker…"
failed Retry button plus alert text; no silent retry loops