Skip to content

Parallelize NDJSON analysis, Kani harnesses for vulnerable-contract, docs troubleshooting + CSS tests - #1487

Open
Smoothjane wants to merge 3 commits into
HyperSafeD:mainfrom
Smoothjane:fix/issues-1473-1472-1471-1468
Open

Parallelize NDJSON analysis, Kani harnesses for vulnerable-contract, docs troubleshooting + CSS tests#1487
Smoothjane wants to merge 3 commits into
HyperSafeD:mainfrom
Smoothjane:fix/issues-1473-1472-1471-1468

Conversation

@Smoothjane

@Smoothjane Smoothjane commented Aug 27, 2026

Copy link
Copy Markdown

Summary

Closes #1472
closes #1471
closes #1468
closes #1473.

#1472 — parallel processing. The issue pointed at tooling/sanctifier-core/src/analyzer.rs,
which doesn't exist — the batch JSON/SARIF analysis path (run_analysis in
tooling/sanctifier-cli/src/commands/analyze.rs) was already parallelized with rayon's par_iter.
The --format ndjson streaming path (stream_ndjson, same file) still used a plain sequential
for loop, so that's what this converts to par_iter().for_each(...), sharing the same
Arc<RuleRegistry> across threads. Output is still written one file's findings at a time —
Stdout::lock() from multiple threads just blocks until available — so each file's findings stay
contiguous per write, just not necessarily in directory-walk order (fine for NDJSON, since every
line already carries its own file field). Write errors from any worker thread are now captured via
a Mutex<Option<io::Error>> and surfaced after the parallel pass, since a par_iter closure can't
?-return out of the enclosing function directly.

#1471 — formal verification assertions. Extracts vulnerable-contract's balance mutation into
pure functions (credit_pure/debit_pure and checked-arithmetic counterparts), following the
"Core Logic Separation" pattern contracts/kani-poc already establishes, and adds four #[cfg(kani)]
harnesses proving the vulnerable versions can overflow/underflow and the secure versions never do,
for the invariants balance + amount <= u64::MAX / balance - amount >= 0.

Important scope note: I initially tried the #[invariant = "..."] attribute format
S011's SMT/Z3 backend (smt/invariants.rs) already parses, since the issue mentions "the Z3 solver
backend" — but that's an unregistered attribute macro from Sanctifier's own text-scanning parser,
not real Rust, and placing it on code in a compiled workspace member breaks cargo check outright
(verified locally: cannot find attribute 'invariant' in this scope). vulnerable-contract's
Cargo.toml already has unexpected_cfgs = { check-cfg = ['cfg(kani)'] } configured, which is
strong evidence this crate was meant to receive a cfg(kani) harness (Kani's default solver backend
is also Z3) rather than the S011 annotation — that's the approach here, and it compiles cleanly.

#1468 — troubleshooting docs. Adds a Troubleshooting section to docs/getting-started.md
covering the errors most likely to come up while following that exact guide: PATH issues after the
rustup install, missing wasm targets, soroban-sdk build failures from an old toolchain, "No Soroban
project found" path mistakes, stale-looking findings, and Z3/Kani solver timeouts (not a bug proof).

#1473 — CSS tests. Adds frontend/app/globals.css.test.ts. The file has no exported JS surface
and happy-dom's CSS engine doesn't reliably compute cascaded custom-property values, so these are
structural/content tests over the raw stylesheet text, matching this repo's existing vitest + RTL
convention (app/components/ThemeToggle.test.tsx). Covers Tailwind import + balanced braces, the
dark: variant wiring (a regression guard for the exact failure the file's own comment warns
about), light/dark theme custom-property definitions actually differing, color-scheme on <body>,
every high-contrast custom property, and that every @keyframes an animation class references
actually exists with both a start and end state.

Note on file pointers: two of the four issues' "File Pointer" links were stale —
frontend/styles/globals.css is actually at frontend/app/globals.css, and
tooling/sanctifier-core/src/analyzer.rs doesn't exist (the relevant loop is in
tooling/sanctifier-cli/src/commands/analyze.rs). Verified against the actual repo layout before
making changes.

Test plan

  • cargo check -p sanctifier-cli passes (rayon change)
  • cargo check -p vulnerable-contract passes (confirms the Kani module, gated behind
    #[cfg(kani)], doesn't affect normal compilation)
  • cargo kani --harness verify_credit_pure_can_overflow etc. — not run (Kani not installed in
    this environment); harnesses follow the same pattern as contracts/kani-poc's existing,
    passing harnesses
  • npx vitest run app/globals.css.test.ts — not run; npm install in frontend/ fails in this
    environment on an EBADPLATFORM error for an optional dependency
    (@commitlint/message wants linux, this environment is darwin) unrelated to this change
  • Manual review against each issue's acceptance criteria

Closes HyperSafeD#1472

The batch JSON/SARIF path (run_analysis) was already parallelized with
rayon's par_iter (Arc<RuleRegistry>/Arc<Analyzer>, one independent
registry.run_all/analyze_ledger_size/scan_storage_collisions pass per
file). stream_ndjson — the --format ndjson streaming path — still walked
rs_files with a plain sequential for loop.

Converts it to rs_files.par_iter().for_each(...), sharing the same
Arc<RuleRegistry> across threads. Output is still written one file's
findings at a time: Stdout::lock() from multiple threads simply blocks
until available, so each file's findings stay contiguous on the page,
just not necessarily emitted in directory-walk order — which is fine for
NDJSON, since every line already carries its own "file" field and a
streaming consumer doesn't depend on file ordering. Write errors from any
worker thread are captured via a Mutex<Option<io::Error>> and surfaced
after the parallel pass instead of being silently dropped (the sequential
version's `?` on writeln!/flush inside the loop can't be replicated
directly inside a par_iter closure, which can't return early from the
enclosing function).
Closes HyperSafeD#1471

Extracts the vulnerable-contract's balance mutation (previously inline
plain +/- in credit()/debit()) into pure functions (credit_pure/
debit_pure and their checked_add/checked_sub-based secure counterparts),
following the same "Core Logic Separation" pattern contracts/kani-poc
already establishes: Kani can exhaustively reason about pure u64
arithmetic with no Env/Host dependency, so the proof harnesses live in a
#[cfg(kani)] module gated the same way kani-poc's are.

Four harnesses (Z3 is Kani's default solver backend):
- verify_credit_pure_can_overflow / verify_debit_pure_can_underflow prove
  a (balance, amount) pair exists that breaks each invariant
  (balance + amount <= u64::MAX, balance - amount >= 0) for the
  vulnerable, unchecked version — the exact bug class this contract exists
  to demonstrate.
- verify_credit_pure_checked_rejects_overflow /
  verify_debit_pure_checked_rejects_underflow prove the checked_add/
  checked_sub-based secure versions can never silently violate either
  invariant, for every possible u64 pair.

vulnerable-contract's Cargo.toml already had
`unexpected_cfgs = { check-cfg = ['cfg(kani)'] }` configured — evidence
this crate was set up to receive a cfg(kani) harness that was never
added.

Note: a bare `#[invariant = "..."]` attribute (S011's SMT-backend
annotation format, tooling/sanctifier-core/src/smt/invariants.rs) is not
usable directly on this file — it's an unregistered attribute macro from
Sanctifier's own text-scanning parser, not real Rust, so it breaks
`cargo check` the moment it's placed on code that's part of the compiled
workspace (verified locally: `cargo check -p vulnerable-contract` fails
with "cannot find attribute `invariant` in this scope"). The Kani
approach here achieves the same "explicit invariants a solver backend can
analyze" goal without that conflict.
Closes HyperSafeD#1468, closes HyperSafeD#1473

HyperSafeD#1468 — adds a Troubleshooting section to docs/getting-started.md
covering the errors a new user is most likely to hit while following that
guide: cargo/rustc not on PATH after rustup install, missing
wasm32-unknown-unknown/wasm32v1-none target, soroban-sdk build failures
from an outdated toolchain, sanctifier binary not on PATH after
`cargo install`, "No Soroban project found" path mistakes, stale-looking
findings (the CLI re-reads from disk every run — a stale result almost
always means the wrong path was scanned), and Z3/Kani solver timeouts
(a timeout finding is not proof of a bug). Cross-links
docs/kani-integration.md for the solver-timeout case.

HyperSafeD#1473 — adds frontend/app/globals.css.test.ts. Since globals.css has no
exported JS surface to import and happy-dom's CSS engine doesn't reliably
compute cascaded custom-property values, these are structural/content
tests over the raw stylesheet text (matching this repo's existing vitest
+ RTL convention, see app/components/ThemeToggle.test.tsx) rather than a
rendered-DOM test. Covers: the file parses (balanced braces) and imports
Tailwind; the dark: variant is wired to .dark (a regression guard for the
exact failure the file's own leading comment warns about); light-theme
--background/--foreground are defined and mapped into Tailwind's
@theme inline block; the dark-theme override targets both
[data-theme="dark"] and .dark and actually changes both colors (not a
no-op copy) plus sets color-scheme: dark on <body>; the high-contrast
mode defines every custom property the base theme uses plus
primary/border/ring, using pure black/white/yellow; and every
@Keyframes referenced by an animation utility class (animate-in,
slide-in-from-bottom-3) is actually defined with both a start and end
state — the edge case where a class references a renamed/removed
keyframe, which CSS accepts silently with no animation rather than
erroring.

Note: could not run this suite locally to confirm a green pass —
`npm install` in frontend/ fails in this environment on an
EBADPLATFORM error for an optional dependency
(@commitlint/message wants linux, this environment is darwin) unrelated
to this change. The test follows the same vitest describe/it/expect
structure and Vite path aliasing already used throughout
frontend/app/components/*.test.tsx.
@vercel

vercel Bot commented Aug 27, 2026

Copy link
Copy Markdown

@Smoothjane is attempting to deploy a commit to the gbangbolaoluwagbemiga's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Aug 27, 2026

Copy link
Copy Markdown

@Smoothjane Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant