Detect security vulnerabilities (Reentrancy, Access Control, Token Handling, etc.) in Solidity smart contracts from real Code4rena audit contests. Predict each finding's vulnerability_type, severity, and affected file. Scored on precision, recall, and F1 against auditor-labeled findings.
Hard parts:
- Noisy static analysis. ~80 pattern-based detectors fire hundreds of low-signal flags per contest. Most are false positives.
- Scope. Contests ship many
.solfiles but only a subset is in scope. Analyzing out-of-scope files wastes API budget and adds false positives. - Taxonomy drift. LLM emits free-form vulnerability labels, but the scorer matches against a fixed taxonomy.
- Duplicates. Per-file and project-level passes both surface the same bug, often with different wording.
A 9-stage pipeline that maximizes filtering before LLM calls:
- Scope filtering (
loader/scope.rs) — readsscope.txt, restricts to in-scope.solfiles - Static analysis triage (
loader/sa.rs+config.rs) — drops ~50 noise detectors, remaps surviving slugs to canonical types - SA engine (
engines/sa_engine.rs) — classifies SA findings into predictions and candidates with line numbers - IR engine (
engines/ir_engine.rs) — detects patterns from contract structure: missing access control, unprotected selfdestruct, state mutation after external calls, unchecked return values, etc. - Call graph (
engines/call_graph.rs) — builds inter-function call graph from IR, detects cross-function reentrancy and external call chains - Cross-contract (
engines/cross_contract.rs) — analyzes multi-contract interactions, shared state, delegatecall patterns - Taint analysis (
engines/taint.rs) — tracks data flow from taint sources (msg.sender, msg.value, tx.origin, block.timestamp) to sensitive sinks (selfdestruct, delegatecall, storage writes, transfers) - Symbolic verification (
engines/symbolic_engine.rs) — validates/kills candidates using pre-computed symbolic execution paths - LLM analysis (
pipeline.rs+llm.rs) — per-file and project-level prompts with full context from prior stages - Post-processing (
postprocess/) — JSON parsing, validation, taxonomy normalization, Jaccard deduplication, confidence scoring
- Tokio async runtime with semaphore-bounded concurrency for LLM calls (16 per-file, 4 per-contest in batch)
- Engine stages run synchronously per contest (fast, no I/O)
- Batch mode runs contests in parallel via
JoinSet - File-level analysis cache (SHA256-keyed) skips redundant LLM calls
- Engine cache skips stages 1-8 when input data hasn't changed
Per-file gives depth (full source in context, line-level reasoning). Project-level gives breadth (inheritance chains, shared state, multi-contract flows). They overlap on purpose: dedup collapses duplicates, and the union catches more true positives than either alone.
confidence.rs normalizes and weights engine scores:
- Each engine output carries a raw confidence score
- Configurable per-engine weights (SA: 0.3, IR: 0.35, taint: 0.4, symbolic: 0.5, call_graph: 0.25, cross_contract: 0.3, LLM: 0.7)
- Multi-engine corroboration boosts confidence
- Findings below
min_confidencethreshold get filtered
report/ module produces three output formats:
- Legacy — flat JSON array of predictions (backward-compatible)
- JSON — structured report with metadata, summary statistics, and findings
- HTML — dark-theme dashboard with severity cards, engine breakdown table, vulnerability type table, finding cards with confidence bars, evidence chains, and source code snippets
flowchart LR
A[main.rs]
L[loader/<br/>scope, SA, IR,<br/>symbolic data]
E[engines/<br/>SA, IR, call graph,<br/>cross-contract,<br/>taint, symbolic]
LLM[LLM<br/>per-file +<br/>project pass]
P[postprocess/<br/>parse, validate,<br/>dedup, confidence]
R[report/<br/>legacy/json/html]
S[score.rs]
A -->|1| L
L -->|2| E
E -->|3| LLM
LLM -->|4| P
P -->|5| R
R -->|6| S
| Module | Files | Role |
|---|---|---|
main.rs |
— | CLI entry point, arg parsing, orchestration |
pipeline.rs |
— | Contest/batch runner, stage coordination, caching |
loader/ |
mod.rs, context.rs, ir.rs, sa.rs, symbolic.rs, scope.rs, labels.rs, raw_types.rs |
Load and parse contest data (IR, SA, symbolic, source files, labels) |
engines/ |
sa_engine.rs, ir_engine.rs, call_graph.rs, cross_contract.rs, taint.rs, symbolic_engine.rs, constants.rs |
Pattern-based detection engines |
llm.rs |
— | Anthropic API client with retry and rate limiting |
prompts.rs |
— | System + per-file + project-level prompt builders |
postprocess/ |
mod.rs, similarity.rs |
JSON parsing, validation, taxonomy remap, Jaccard dedup |
confidence.rs |
— | Multi-engine confidence scoring and normalization |
config.rs |
— | Slug-to-type mapping, noise lists, detector config |
score.rs |
— | Heuristic scorer (file + type matching), per-detector/severity breakdown |
report/ |
mod.rs, stats.rs, json.rs, html.rs |
Report generation (legacy, JSON, HTML formats) |
types.rs |
— | Core types: Prediction, Candidate, VulnType, Severity, SAFinding, etc. |
cache.rs |
— | File-based caching for engine and LLM results |
error.rs |
— | Error types (LoadError, LlmError, AnalysisError) |
path_utils.rs |
— | Path normalization and matching utilities |