Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

VDT — Verification Drill Tool

What this is

VDT is a read-only viewer for auditing smart contract code by drilling through pre-computed execution traces. It does not analyze contracts itself. It loads a static JSON file, called a Package, that was produced by a separate analysis process, and lets an auditor navigate that data: open a function, see what it reads, writes, and calls, follow any of those into the next function, and record notes, flags, and bookmarks as they go.

There are two builds of this app, described in full further down: a mobile build (stacked sheets) and a desktop build (side-by-side columns). They share the same data model, the same file formats, and the same entity id scheme, so a Workspace saved by one can be opened by the other.

Core concepts

Package — the input file. Immutable, produced outside this tool. Contains:

  • metadata.contract — the contract name shown in the top bar.
  • rootFunction — the function id the app opens by default.
  • functions — a map of function id to function data: its trace (a tree of reads, writes, calls, branches, and other execution steps) and its implementation (source text and language).
  • stateVariables — a map of variable id to its writers (the list of function ids that write to it).

A field can be null to mean "not yet generated by the analysis process," which the app renders as "Not yet generated," distinct from an empty array, which it renders as "Confirmed empty" or "Confirmed — no writers." That distinction is preserved everywhere and is never collapsed into a single "no data" state.

Workspace — the auditor's own state, kept separate from the Package and saved to its own file. Holds one or more investigations, and inside each: notes, chain notes, flags, and bookmarks. Never contains Package data itself.

Investigation — one root function plus everything the auditor has built around it: which sheets are drilled open, notes, flags, bookmarks, and a history of visited entities. A Workspace can hold several investigations, shown as chips in the strip under the top bar. Switching chips swaps the entire screen to that investigation's state.

Entity — anything that can be selected, noted, flagged, or bookmarked: a function, a single node in a trace tree, or a writer entry in a variable's writer list. Each entity has a stable id (for example fn:BorrowerOperations.withdrawColl or trace:BorrowerOperations.withdrawColl#n2) so annotations attach to the same thing across sessions and across the desktop build.

Screen layout: mobile

  • Top bar — contract name and a menu button.
  • Investigation strip — one chip per investigation in the current Workspace. Tap a chip to make it active.
  • Base screen — the active investigation's root function, always showing underneath everything else.
  • Sheet stack — drilled-in views (a function, a trace, or a variable's writers) stack on top of the base screen and on top of each other. Each sheet fully covers the one below it. A back arrow in the sheet header, or swiping down on the header, pops one level.
  • Action bar — appears at the bottom once something is selected: Note, Flag, Bookmark.
  • Notes tab — a small persistent handle showing the combined count of notes and flags for the active investigation. Tap it to open a sheet listing everything recorded so far.

Screen layout: desktop

The desktop build uses the same top bar and investigation strip as mobile, but replaces the sheet stack with a horizontal, side-by-side column layout — closer to a traditional multi-pane code browser than a mobile drill-down.

  • Base column — the active investigation's root function, always pinned as the leftmost column.
  • Column strip — drilling into a trace, a variable's writers, or a called/writer function appends a new column to the right of the one that opened it, instead of stacking a sheet on top. The full drill path is visible at once, left to right, rather than one screen at a time. Each column keeps the same identity (columnId) as the equivalent mobile sheet, and a drilled path is stored the same way in the Workspace (expandedColumns) — an ordered list of column ids — so switching between builds mid-investigation preserves exactly which columns were open.
  • Depth indicator — rather than mobile's per-sheet "N / depth" badge, desktop shows a single status readout for how deep the current column path runs.
  • Selection and the action bar — clicking anywhere in a column's body selects that column's function, the same "click anywhere selects the entity" rule mobile uses for its base screen and function sheets. The action bar (Note, Flag, Bookmark) behaves identically once something is selected.
  • Column reuse and dedup — opening a column that's already the rightmost one is a no-op; opening one that's already open further left truncates everything to its right and reuses it rather than opening a duplicate. This is the same rule mobile applies to its sheet stack, so a drill path never grows two branches at once in either build.

Because both builds read and write the same Workspace shape, an investigation started on desktop, with several columns open, resumes on mobile with those same views loaded as a sheet stack in the same order, and vice versa.

Navigating a function

The drilling model described here is shared by both builds; the difference is only what opening something looks like on screen — a new sheet stacked on top (mobile) or a new column appended to the right (desktop). The rest of this section uses mobile's terms, tap and sheet; read tap as click and sheet as column for desktop.

Every function is shown as two collapsible sections:

  • Trace — the recorded execution steps for that function: reads (R), writes (W), internal calls (I), external calls (X), library calls (L), events (EMIT), guards (REQUIRE, ASSERT), branches (IF, ELSE, LOOP), unresolved calls, and the return. Tapping the Trace row opens it as a new sheet showing the full tree, drawn with tree-guide characters to show nesting. Tapping a node inside that tree does one of two things: a read or write opens the target variable's writers list; a call opens the target function, in each case as a new sheet on top of the current one.
  • Implementation — the function's source, expanded inline within the same sheet rather than drilling further, since source is a leaf: there is nothing beneath it to open.

Opening a variable's writers list shows every function that writes to it. Tapping a writer opens that function as a new sheet, same as tapping a call node in a trace.

Drilling never produces duplicate sheets. Opening something that is already the top of the stack does nothing. Opening something that is already open lower in the stack truncates the stack back to that point and reuses it, rather than pushing a second copy on top.

Recording findings

Tap a function, a trace node, or a writer entry to select it. The action bar appears with three actions:

  • Note — free text attached to that exact entity. Opens a text-entry sheet in place of any native prompt.
  • Flag — cycles through five states in order: none, verified, investigate, suspicious, broken, interesting, then back to none. The glyph for the current flag is shown next to the entity everywhere it appears.
  • Bookmark — toggles a marker on the entity, shown as a star in sheet headers for bookmarked sheets.

All three are per-investigation: the same function id can carry different notes and flags in different investigations if it is reached from more than one root.

The notes tab (mobile) or notes panel (desktop) collects every note, flag, and bookmark in the active investigation into one list, so a full pass can be reviewed without re-visiting each entity individually.

File operations (menu)

  • Open Package — load a Package JSON file and start fresh from its root function. If no Workspace exists yet, one is created with a single investigation rooted there.
  • Open Workspace — load a previously saved Workspace JSON file, restoring investigations, notes, flags, bookmarks, and which sheets were drilled open in each. Requires a Package to also be loaded, since a Workspace on its own has no function or trace data to display.
  • Save Workspace — write the current Workspace to disk. Uses the File System Access API when the browser supports it, saving directly to the same file on repeat saves; otherwise falls back to a browser download.
  • Add Investigation from Selection — start a new investigation rooted at whichever function is currently selected (or, if a trace node or writer is selected, the function that sheet belongs to).
  • Delete Current Investigation — removes the active investigation after a confirmation prompt. Cannot be undone.

On load, the app also tries to fetch ../investigation_package.json relative to its own location and opens it automatically if found, so a Package can be dropped next to the app files and served without going through the menu. If that fetch fails, for example under file:// where fetch of local files is blocked, the app just waits for the menu.

Getting a Package file

This app only reads a Package; it does not produce one. Package files are generated by a separate command-line tool, solindex, which statically analyzes a Solidity codebase and writes out the exact JSON shape this app expects.

The relevant command is --closure:

solindex <project_root> --closure Contract.function -o investigation_package.json

Starting from one root function, this traces it, recursively expands every call it makes into its own full trace, and for every state variable it reads, looks up and expands every function that writes that variable too — repeating until nothing new is left to expand. The result is a single self-contained JSON file: metadata.contract, rootFunction, a functions map (each with its trace and implementation.source), and a stateVariables map (each with its writers list) — exactly the shape described under Core Concepts above, and the direct source of the Trace, Implementation, and Writers views in this app.

If -o is omitted, solindex writes investigation_package.json in the current directory, which is also the filename this app tries to auto-load on boot (see File operations, above) when placed one directory above the app's own files.

solindex has three other modes (default writer-index, --extract, --trace) for answering narrower questions from the command line, but --closure is the one that produces a file this app can open. Full usage for all four modes, including installation, lives in the solindex repo linked above.

An example Package file, investigation_package.json, is included alongside this app for reference and for trying the app out without running solindex first — it was generated with --closure rooted at BorrowerOperations.withdrawColl in the Liquity BorrowerOperations contract.

Data model notes for anyone editing this code

  • Package data is read-only and can contain null for "not analyzed yet" at the trace, implementation, or writers level. Never assume a missing field means empty; check for null explicitly and render it distinctly from an empty array.
  • Workspace data is the opposite: the app owns it fully, so a missing collection is always initialized to {} or [], never left null.
  • Entity ids follow one scheme shared with the desktop build: fn:<functionId>, trace:<functionId>#<path>, var:<variableId>, plus writer-specific keys built from the writers-list column id and an index. Do not invent a different id scheme for mobile-only code, since Workspace files move between the two builds.
  • renderer.js contains the container-agnostic logic for turning Package data into DOM (trace tree recursion, node labels, click behavior). app.js owns navigation state (the sheet stack, which investigation is active, what is selected) and wires renderer output into the sheet stack or base screen. workspace.js owns all auditor-authored state and file I/O. Keep that separation: rendering code should not reach into navigation state, and navigation code should not reach into Package data directly, it should go through Renderer.resolveFunction / Renderer.resolveStateVariable.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages