sc-graph
Skill1 starsChanged 5 months agoMITView on GitHub
Stage 3 of the ProofHound smart-contract audit pipeline. Builds the dependency graphs over the stage-1 chunk bank — an inheritance-resolved call graph (internal/modifier/external edges + reachability), a storage-access graph (which functions read/write which state variables, with shared-state clusters), and a value-flow graph (where tokens/ETH enter and exit). Also emits derived risk signals — CEI violations (state write after external call), cross-function and read-only reentrancy candidates, unguarded value movers — that directly seed stage 4. Repairs stage-1 inheritance blind spots. Run after sc-ingest/sc-map, before sc-spec and sc-hypothesize.
The file
We split this skill into the rules it gives an agent
1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 27
After sc-ingest (and usually sc-map).
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
sc-graph — Dependency Graphs (Pipeline Stage 3)
A single chunk cannot see that withdraw() and emergencyWithdraw() both mutate
balances, or that a low-level call in withdraw() happens before the state
update. That cross-chunk view is what catches the highest-value bugs — and it is
what this stage builds. The storage-access graph is the centerpiece: it makes
cross-function reentrancy and invariant-violation reasoning possible.
This stage also repairs a stage-1 limitation: storage accessed through inheritance (a variable declared in a base contract) is recovered here by re-scanning each function against its full inheritance closure.
When to Use
- After
sc-ingest(and usuallysc-map); orchestrator calls this third. - User says: "build the call graph", "what shares state", "find cross-function reentrancy", "trace value flow".
Announce
Begin with: "I'm using the sc-graph skill to build the call / storage / value-flow graphs..."
Workflow
1. Build the graphs (deterministic)
python skills/sc-graph/scripts/build_graphs.py <audit_dir> # default .auditOutputs into .audit/graph/:
call-graph.json— nodes + internal/modifier/external edges, entry points, per-entry-point reachability, and internal functions unreachable from any entry point.storage-graph.json— per-variablewriters/readers(qualifiedContract.var), per-functionreads/writes, shared-state clusters, andinherited_access_recovered.value-flow.json—inflows(payable / transferFrom-from-sender) andoutflows(transfer/send/call) with the accounting vars they update.derived-signals.json— the findings-precursors (see below).
2. Read the derived signals first (highest leverage)
derived-signals.json is where stage 4 starts. Review:
cei_violations— a state write occurs after the first external call in a function (the classic reentrancy fingerprint).severity_hintishighwhen unguarded,lowwhen anonReentrantguard is present,mediumif only access-controlled. This is deterministic and precise (it carries line numbers).cross_function_reentrancy—entry(an unguarded external-call function) can re-enterreenters_viabecause they shareshared_var. Whenreenters_viais a view function, that's a read-only reentrancy candidate (stale state read during the callback).unguarded_value_movers— functions that move tokens/ETH with no access control; cross-reference withreentrancy_guarded.shared_state_clusters— groups of functions touching common storage; invariant violations usually live inside one cluster.
3. Sanity-check the graphs
- Spot-check
call-graph.reachabilityfor a couple of entry points: do the reachable internal functions look right? Unreachable internal functions (internal_unreachable_from_entrypoints) are either dead code or only reached via inheritance/overrides — note them. - Confirm
storage-graph.variablesqualifies vars by declaring contract and thatinherited_access_recoveredcaptured base-contract accesses (if any). - Confirm
value-flowinflows/outflows match the money-flow narrative fromsc-map'sproject-map.md— discrepancies are interesting in themselves.
4. (Optional) semantic enrichment
The graph is heuristic (name-based storage detection, name-based internal-call
resolution that can't see function pointers or using ... for). You MAY add
edges you can prove exist (e.g. a library-bound call) by appending to the graph
JSON, but never delete deterministic edges — downstream relies on them.
How stages 4–6 consume this
- sc-spec uses the call graph to map each documented formula to the chunk(s) that implement it (formula → function via call reachability).
- sc-hypothesize seeds directly from
derived-signals.json: everycei_violationandcross_function_reentrancycandidate becomes a hypothesis;shared_state_clustersscope invariant-violation hypotheses. - sc-verify uses reachability to confirm an unprivileged actor can actually
reach the vulnerable function, and uses
value-flowto frame "can funds leave".
Why this reduces false positives
- CEI detection is line-precise and deterministic — not "looks like
reentrancy" but "writes
balancesat L65 after the external call at L62". That is verifiable, and theseverity_hintalready discounts guarded functions. - Reachability gates exploitability — a scary-looking internal function that no external entry point reaches is not a user-exploitable finding; the graph says so explicitly.
- Inheritance recovery prevents both misses and phantom edges — storage is attributed to the real declaring contract, so shared-state reasoning is correct.
Known Limitations (honest)
- Internal-call resolution is by name over the linearized inheritance chain;
function pointers,
using X for Ydispatch, and assembly calls may be missed. - Storage read/write is name-based (inherited from stage 1's heuristic); shadowed local variables with the same name as a state var can cause a false edge.
- Linearization is an approximate C3 (self-first DFS); exotic diamond hierarchies may order bases differently than solc. Edges are still sound; ordering is a hint.
- External call targets are expression strings (
token,IRouter(r)), not resolved addresses — resolution to a concrete integration is sc-map's job.
Handoff
Tell the orchestrator: counts of CEI violations, cross-function/read-only reentrancy candidates, shared-state clusters, inherited accesses recovered, and any unreachable internal code. Next stage: sc-spec (math & spec extraction).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

