agentleFS
WhyUSayMyNameWhyUSayMyName/skill-proofhound-audit/skills/sc-graph/SKILL.md

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
  1. Very specific:

    After sc-ingest (and usually sc-map).

    line 27

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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 usually sc-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 .audit

Outputs 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-variable writers/readers (qualified Contract.var), per-function reads/writes, shared-state clusters, and inherited_access_recovered.
  • value-flow.json — inflows (payable / transferFrom-from-sender) and outflows (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_hint is high when unguarded, low when a nonReentrant guard is present, medium if only access-controlled. This is deterministic and precise (it carries line numbers).
  • cross_function_reentrancy — entry (an unguarded external-call function) can re-enter reenters_via because they share shared_var. When reenters_via is 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 with reentrancy_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.reachability for 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.variables qualifies vars by declaring contract and that inherited_access_recovered captured base-contract accesses (if any).
  • Confirm value-flow inflows/outflows match the money-flow narrative from sc-map's project-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: every cei_violation and cross_function_reentrancy candidate becomes a hypothesis; shared_state_clusters scope invariant-violation hypotheses.
  • sc-verify uses reachability to confirm an unprivileged actor can actually reach the vulnerable function, and uses value-flow to frame "can funds leave".

Why this reduces false positives

  • CEI detection is line-precise and deterministic — not "looks like reentrancy" but "writes balances at L65 after the external call at L62". That is verifiable, and the severity_hint already 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 Y dispatch, 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).

Download now (ZIP)

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.