sc-map
Skill1 starsChanged 5 months agoMITView on GitHub
Stage 2 of the ProofHound smart-contract audit pipeline. Builds the shared mental model of a Solidity protocol from the stage-1 chunk bank — actors and trust model, privileged roles and their abuse surface, asset and external-integration assumptions, money flows, and the protocol invariants (each with a stable INV-N id) that later stages target. Produces .audit/project-map.md and .audit/project-map.json. Run after sc-ingest, before sc-graph and sc-hypothesize.
The file
We split this skill into the rules it gives an agent
10 rules. About 0 are common, 2 are distinctive, and 8 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 46
Read project docs: README*, docs/, NatSpec @notice/@dev on the top entry points (Grep "@notice" / read the relevant chunks' natspec).
- Very specific: line 48
Read the actual source of the highest-risk entry points (from signals.entry_points: the unprivileged, value-moving, reentrancy-surface …
- Very specific: line 50
Read each __state__ chunk to understand the storage model.
- Very specific: line 76
Write both artifacts (templates in templates/):
- Very specific: line 118
Every item in signals.json must be accounted for in the map — either incorporated or consciously dismissed with a one-line reason.
- Very specific: line 120
Every role → appears in §3.
- Very specific: line 121
Every integration → appears in §4 with assumptions.
- Very specific: line 122
Every invariant_seed → promoted to an INV-N or dismissed with reason.
- Distinctive: line 123
Every unprivileged value-moving entry point → in §7 risk register.
- Distinctive: line 124
Do not silently drop signals — a dropped signal is an unexamined surface.
≈ “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-map — Global Project Model (Pipeline Stage 2)
Turns the raw chunk bank into a mental model of intended behavior. This is the single most important input for keeping false positives low: stage 5 can only tell "this deviates from how the protocol is supposed to work" (a finding) from "this is exactly how it's designed" (not a finding) if stage 2 wrote down what "supposed to" means — the invariants, the trust model, the intended powers.
The map is injected (by reference) into every hypothesis and every verification.
When to Use
- Immediately after
sc-ingestsucceeds (.audit/manifest.jsonexists). - Orchestrator (
sc-audit) calls this second. - User says: "model the protocol", "what are the invariants", "build the trust model".
Announce
Begin with: "I'm using the sc-map skill to build the protocol's mental model and invariants..."
Workflow
1. Generate the grounded signal digest (deterministic)
python skills/sc-map/scripts/aggregate_signals.py <audit_dir> # default .auditThis writes .audit/signals.json: roles + the functions they guard, every entry
point with guard/payable/reentrancy flags, classified external integrations,
value-moving functions, the full state inventory, and invariant seeds
(SEED-N). You MUST reconcile your map against this digest — it is the
completeness backstop.
2. Read for intent (not just structure)
Signals tell you what the code does; you also need what it is meant to do.
- Read project docs:
README*,docs/, NatSpec@notice/@devon the top entry points (Grep "@notice"/ read the relevant chunks'natspec). - Read the actual
sourceof the highest-risk entry points (fromsignals.entry_points: the unprivileged, value-moving, reentrancy-surface ones). - Read each
__state__chunk to understand the storage model. - Note the
protocol_type— vault, lending, staking, amm, oracle-dependent, auction, etc. This decides whichaudit-*checklists stage 4 runs.
Scaling: inline vs fan-out (choose by contract count)
- Few contracts: inline. Synthesize the whole map yourself (step 3 below).
- Many contracts: fan-out per module, then reconcile centrally:
For eachpython skills/sc-map/scripts/batch_modules.py <audit_dir> --size 3map/batches/batch-NNN.json, spawn an sc-map-worker subagent in parallel; each writes a PARTIAL map (local invariants withlocal_id) for its contracts tomap/_partials/. Then reconcile:
The merge assigns the global, stablepython skills/sc-map/scripts/merge_maps.py <audit_dir> --name "<Protocol>"INV-Nids, dedups actors/roles/assets/ integrations, and guarantees a generic attacker. Then do one short inline pass to add cross-contract / protocol-level invariants the per-module workers couldn't see (continue theINV-Nnumbering) —merge_mapsflags this inopen_questions.
Both paths produce the same project-map.json schema and feed the same downstream
stages; fan-out only distributes the per-module modeling.
3. Synthesize the map
(inline path; for fan-out each worker does this for its contracts, then merge)
Write both artifacts (templates in templates/):
.audit/project-map.md— human-readable (followproject-map.template.md)..audit/project-map.json— structured (conform toproject-map.schema.json).
Build each section with discipline:
Actors & trust model. Enumerate privileged (owner/keeper/governance) and unprivileged (user/attacker/liquidator/MEV) actors. For each, capabilities and economic incentive. Always include a generic attacker who can: call any external function, deploy arbitrary contracts, supply malicious tokens/callbacks, and borrow unlimited flash-loan capital. State the trust assumptions explicitly — a bug that only works by violating a stated assumption (e.g. "oracle is honest") is real but must be labeled and is usually lower severity.
Roles / rug surface. For every role in signals.roles, write the worst thing
a malicious or key-compromised holder could do, and mark by_design. This is what
prevents stage 5 from reporting "owner can change the oracle" as a critical bug
when it's an intended admin power — while still capturing it if the power is
unintended or unbounded.
Assets & integrations. For each asset handle and integration in signals,
write the assumptions the code makes and what breaks if false. Be specific about
the classics:
- Tokens: fee-on-transfer, rebasing, ERC777/callback hooks, non-standard decimals, missing return values, blocklists.
- Oracles: staleness/heartbeat, manipulation (spot vs TWAP), L2 sequencer uptime, decimals, min/max bounds, multi-feed reuse.
- Routers/AMMs: slippage, deadline, manipulable quoters, fee-tier assumptions.
Invariants (⭐). Promote signals.invariant_seeds into real invariants with
stable INV-N ids, and ADD any the seeds missed (read the code — seeds are only
name-pattern heuristics). For each: statement, kind, criticality,
enforced_by (chunk IDs), and breaks_if. These IDs are referenced by stage-4
hypotheses (targets: INV-3) and broken by stage-5 PoCs. Aim for the invariants a
real auditor would write: conservation (sum of parts == total), solvency (held
assets >= liabilities), access (only role X mutates Y), pricing, monotonicity,
pause semantics.
Money flow. Narrate where value enters, accrues, and exits, with the guarding
conditions. Cross-check against signals.value_movers.
4. Reconcile (completeness gate)
Every item in signals.json must be accounted for in the map — either
incorporated or consciously dismissed with a one-line reason. Specifically:
- Every
role→ appears in §3. - Every
integration→ appears in §4 with assumptions. - Every
invariant_seed→ promoted to anINV-Nor dismissed with reason. - Every unprivileged value-moving entry point → in §7 risk register. Do not silently drop signals — a dropped signal is an unexamined surface.
5. Quality gate (see checklist.md)
Confirm before handoff. Key bars: ≥1 critical/high invariant identified per
value-handling contract; trust assumptions explicit; every role's rug surface
written; project-map.json validates against schema.
Anti-False-Positive Principles (why this stage exists)
- Write the intent down. A finding is a deviation from intent. Undocumented intent ⇒ everything looks like a bug ⇒ noise.
- Separate
by_designpowers from bugs. Admin omnipotence that's intended is not a critical finding; mark it so stage 5 doesn't re-litigate it. - Name the trust assumptions. Then "this breaks only if the oracle lies" is a calibrated, labeled finding — not a false alarm and not a missed bug.
- Invariants are falsifiable targets. Vague worries don't survive to stage 6; "violates INV-2 (solvency)" with a PoC does.
Handoff
Tell the orchestrator: protocol type(s), count of invariants by criticality,
number of by_design admin powers, and any open_questions. Next stage:
sc-graph (call / storage / value-flow graphs).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
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.

