Act IV — Proof

Each finding, with a runnable exploit.

A detector that fires is a hypothesis. Proof is the attack contract that drains your vault on a forked chain — line, transaction, balance. The report is forensic: location, evidence, severity, and the steps to reproduce.

Critical1finding
High3findings
Medium1finding
Low1finding
Informational1note

The lattice

Seven cells out of eighty.

An eighty-cell field stands in for the contract surface — every external entry, every state-changing path, every storage slot. Most cells stay dark. The few that light up are the exploitable shape.

The deliverable

Seven findings. Each one a sentence and a path.

A finding is short on purpose. The headline is what the attacker would call it; the location is where to look; the body is why it matters; the meta is the trail back to the detector and the SWC that taught us its shape.

Finding 01 of 07Critical

Reentrancy via cross-function callback drains the vault

Vault.sol:184 — withdraw()

External low-level call{value:} returns control to the attacker before balances[msg.sender] is decremented, allowing recursive drain via the attacker's fallback(). The cross-correlate step lifted this from High to Critical when the read-only balanceOf() path on the same storage slot was confirmed.

DetectorRE-01 · classic
SWCSWC-107
Confidence0.97
Loss modelFull TVL
Reproductionforge test  ·  replay below
Finding 02 of 07High

Privileged write on transferOwnership without modifier

Access.sol:92 — transferOwnership()

The function sets owner = msg.sender's argument with no onlyOwner modifier and no internal check. Any external account can rotate the ownership pointer and unlock all admin-gated functions in one transaction.

DetectorACC-02 · privileged
SWCSWC-105
Confidence0.99
Loss modelAdmin takeover
Reproduction2-call sequence · 1 tx
Finding 03 of 07High

Read-only reentrancy in balanceOf() leaks stale state

Vault.sol:67 — balanceOf()

A view function called mid-transaction returns pre-update state when invoked from within a downstream contract's callback. Integrators reading this value to price collateral will accept inflated balances mid-withdraw.

DetectorRE-04 · readonly
SWCSWC-130
Confidence0.91
Loss modelDownstream mispricing
Reproduction2-contract harness
Finding 04 of 07High

Unchecked return on low-level call in claim()

Rewards.sol:214 — claim()

The (bool ok, ) = recipient.call{value:}(...) result is discarded. A reverting recipient — including a contract with insufficient gas in its fallback — silently fails, but state is updated as though payout succeeded. The user can be locked out of their reward indefinitely.

DetectorEXT-03 · unchecked
SWCSWC-104
Confidence0.94
Loss modelUser funds locked
Reproduction1-tx harness
Finding 05 of 07Medium

Integer truncation in mint() loses high bits silently

Token.sol:148 — mint()

A uint256 parameter is downcast to uint128 without bounds check. For mint amounts above 2¹²⁸, the value silently wraps. The accounting reads correct on the path but the supply ledger is wrong.

DetectorAR-01 · trunc
SWCSWC-101
Confidence0.86
Loss modelInflation invariant
Reproductionfuzz · 256-bit boundary
Finding 06 of 07Low

Missing zero-address check in register()

Registry.sol:19 — register()

Recovery requires a redeploy if address(0) is registered as a permitted caller. Not exploitable on its own; flagged because the recovery cost is asymmetric — operator-day vs. attacker-second.

DetectorVAL-08 · zero-addr
SWC
Confidence0.99
Loss modelOperational
Reproductionstate inspection
Finding 07 of 07Informational

SPDX identifier inconsistent across the repository

Repository-wide

Mixed MIT and Apache-2.0 markers across files. Pick one or document the deliberate split. No security impact; included so the disclosure record is complete.

DetectorMETA-01 · spdx
SWC
Confidence1.00
Loss modelNone
Reproductiongrep

The proof

Five steps. The withdraw, drained.

Scrub through the attack the way the EVM ran it. Five state transitions on a forked chain. The vault holds 100 ether at step 0 and 0 ether at step 5. The attacker contract gets there through a single re-entrant call.

REPLAY · forked mainnet PRE-STATE · vault 100 Ξ · attacker 1 Ξ
180181182183184 185186187188189 190191192193
function withdraw() external { uint256 bal = balances[msg.sender]; require(bal > 0, "empty"); (bool ok, ) = msg.sender.call{value: bal}(""); // ← external call, control yields require(ok, "send fail"); balances[msg.sender] = 0; // ← state write happens AFTER } // Attacker.sol — fallback below receive() external payable { if (vault.balance >= 1 ether) vault.withdraw(); // ← re-enters here }
Exploit chain

Anatomy

Five fields. Nothing else.

A finding card is a contract: every field has one job, and nothing speculative is allowed in. The cards below the rail show what each part of the deliverable is for, and what kind of evidence backs it.

Anatomy · ◯ severity barCritical

Reentrancy via cross-function callback drains the vault

Vault.sol:184 — withdraw()

External low-level call{value:} returns control to the attacker before balances[msg.sender] is decremented, allowing recursive drain via fallback().

DetectorRE-01
SWC107
Confidence0.97
Loss modelFull TVL
01

The bar.

Severity, encoded as a four-pixel vertical band on the leading edge. The colors are the lattice colors. There is no other use of the severity hue elsewhere in the card — the bar is the whole signal.

02

The headline.

What an attacker would call this in their writeup. Active voice. The exploit shape is named, the consequence is named. We do not say "potential" or "may" — a finding is a fired detector with sealed evidence.

03

The location.

File, line, function. Click-through goes to a deep link in the source viewer with the offending node highlighted, the AST path on the right, and the detector's predicate inline.

04

The body.

Two to three sentences explaining the shape and the consequence. No remediation, no "consider using a guard"; that lives in the detector's docs page. The finding's job is to describe, accurately.

05

The meta row.

Detector, SWC entry, confidence, loss model, reproduction. Five mono labels, no prose. The reproduction column points at a forked-chain replay; for criticals it is always runnable, single-tx.

Disclosure

Sealed. Signed. Sent.

The deliverable doesn't sit on a dashboard. Findings are sealed at the moment evidence is gathered, signed by the engine's run key, and routed to the channel the protocol team chose at access time.

01 — Sealed

Evidence frozen at first hit.

The offending node, the AST path that reached it, the detector predicate, and the fork-block height are bundled into a single finding payload before correlation runs. Nothing is rewritten downstream.

payload · sha-256 immutable
02 — Signed

One signature per run.

Each report is signed by the engine's per-run key. The signature commits to the AST hash, the detector versions, the SWC corpus revision, and the fork-block. A finding can be verified independently after the fact.

key · ed25519 · run-scoped
03 — Sent

Whichever channel you trust.

Webhook, signed email, PGP, on-chain attestation, or a private mirror — disclosure is configured at access time. Critical findings trigger the channel within the same second they are sealed.

latency · < 1s · critical

Next · Act V

The Brain. Every detector, every SWC, every past exploit.

Enter the brain