Fizz
SkillDev toolsLets your agent automatically generate fuzz suite files that stress-test Ethereum smart contracts with Echidna and Medusa.
Available today. Use it from your connected AI after setup.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use the Fizz skill
About this skill
Generate Echidna/Medusa-compatible Solidity fuzz suites from Foundry or Hardhat projects. Trigger on "fizz", "generate fuzz suite", "build fuzz harness", "stateful fuzzing", "fuzzing harness", "property testing", and "invariant suite".
What this skill tells your AI
The instructions your AI receives, as published by pashov/skills in fizz/SKILL.md and read by ahel’s review.
Generate a stateful Solidity fuzz suite under {SUITE_DIR} (default: test/fizz/), with metadata and fuzzer runtime files under {META_DIR} (default: fizz_data/).
Use Echidna and Medusa for invariant campaigns. Use Foundry for compilation, smoke testing, and quick debugging.
Workflow Rules
- Follow the steps in order. Do not skip forward if a required artifact for the current step does not exist yet.
- If a step fails, stop there and report the blocker.
- If tooling is missing, say exactly what was attempted and what is missing.
- Keep the generated Solidity suite isolated under
test/fizz/and the metadata/runtime files underfizz_data/unless the user explicitly asks for different paths. - Reuse existing project setup and test logic whenever possible; do not invent a deployment flow if the repo already has one.
Parameters
PROJECT_ROOT: user-provided path, otherwise the current working directory.SKILL_PATH: the directory containing thisSKILL.md.SUITE_DIR:test/fizzrelative toPROJECT_ROOT. Pass--suite-dirto suite-generation steps.META_DIR:fizz_datarelative toPROJECT_ROOT. Pass--meta-dirto metadata steps.- Optional contract arguments narrow handler generation to specific contracts.
--no-invariantsskips Step 9 only.--max(or--opus, or "max quality") upgrades every subagent in this run from Sonnet to Opus. See "Subagent Model" below.--guided/--automaticselects the run mode. See "Run Mode" below.
Run Mode
The skill runs in one of two modes, resolved once at the start of the run and reused for every checkpoint below:
{MODE} = "guided"— the parent agent pauses for user input at key checkpoints: Step 3 (additional docs), Step 4 (interactive function picker UI), Step 4.5 (cost confirmation), Step 6 (setup review), Step 8 (per-cycle coverage decision), Step 9c (property review), Step 10 (fuzzer choice).{MODE} = "automatic"— the parent agent never pauses. Step 4 runs with--auto, Step 8 loops up to 3 coverage cycles then proceeds, Step 10 defaults to Medusa, and the cost estimate from Step 4.5 is printed but not gated on user confirmation.
Resolving {MODE}
- If the user invoked with
--guided/ "guided mode" / "walk me through" / "let me review" →{MODE} = "guided". - If the user invoked with
--automatic/--auto/ "unguided" / "run the whole thing" / "no prompts" →{MODE} = "automatic". - Otherwise, leave
{MODE}unresolved; Step 0 asks for it via the selection prompt after printing the banner.
Every subsequent instruction referencing {MODE} must substitute the resolved value. Do NOT switch modes mid-run.
Subagent Model
All subagents spawned by this skill (Step 3 fallback Protocol Analyzer, the 5 Step 9b discovery agents, the Step 9c Synthesizer, the 2 Step 9d Implementers, and the Step 11 Report Writer) default to Sonnet for cost and latency.
The parent agent orchestrating the pipeline is whatever model the user's Claude Code session is running (this skill does not control it). Only the delegated subagents are covered by {AGENT_MODEL}.
Resolving {AGENT_MODEL}
Resolve once at the start of the run and reuse it for every spawn below:
- If the user invoked with
--max/--opus/ "max quality" / "run on opus" / similar →{AGENT_MODEL} = "opus". - If the user invoked with
--sonnet/ "use sonnet" / "default model" →{AGENT_MODEL} = "sonnet". - Otherwise, leave
{AGENT_MODEL}unresolved; Step 0 asks for it via the selection prompt after printing the banner.
Every subsequent spawn instruction below references {AGENT_MODEL} — substitute the resolved value when making the actual tool call. Do NOT mix tiers within a single run.
Step 0: Print Banner
At the start of every skill run, first print this ASCII banner once before any other output — including any selection prompt:
██████╗ █████╗ ███████╗██╗ ██╗ ██████╗ ██╗ ██╗ ███████╗██╗ ██╗██╗██╗ ██╗ ███████╗
██╔══██╗██╔══██╗██╔════╝██║ ██║██╔═══██╗██║ ██║ ██╔════╝██║ ██╔╝██║██║ ██║ ██╔════╝
██████╔╝███████║███████╗███████║██║ ██║██║ ██║ ███████╗█████╔╝ ██║██║ ██║ ███████╗
██╔═══╝ ██╔══██║╚════██║██╔══██║██║ ██║╚██╗ ██╔╝ ╚════██║██╔═██╗ ██║██║ ██║ ╚════██║
██║ ██║ ██║███████║██║ ██║╚██████╔╝ ╚████╔╝ ███████║██║ ██╗██║███████╗███████╗███████║
╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚══════╝
After the banner, resolve {MODE} per the "Run Mode" section and {AGENT_MODEL} per the "Subagent Model" section. For any value still unresolved from invocation flags, ask the user via a single AskUserQuestion tool call containing only the unresolved questions (skip the call entirely if both were resolved from flags).
Output discipline (mandatory): Between the banner block and the AskUserQuestion invocation, emit no user-facing text whatsoever — no "I'll ask about…", no "loading the tool…", no acknowledgement that flags were missing. If AskUserQuestion's schema needs to be fetched via ToolSearch first, do that silently as well. The user should see banner → selection UI → resolved-values lines, with nothing in between. This overrides the default behavior of narrating intent before tool calls.
- Question for
{MODE}—header: "Run mode",question: "How should I run?", options:label: "Automatic (Recommended)",description: "Run end-to-end with no prompts."label: "Guided",description: "Pause at 7 checkpoints: extra docs, entry-point picker (browser UI), cost confirm, setup review, per-cycle coverage decision, property review, fuzzer choice."
- Question for
{AGENT_MODEL}—header: "Subagent model",question: "Which model should drive the subagents?", options:label: "Sonnet (Recommended)",description: "Default. Faster and cheaper for the 5 discovery agents, synthesizer, and 2 implementers."label: "Opus",description: "Higher quality but ~10× the cost. Equivalent to passing --max / --opus."
Map the user's selections back to {MODE} (automatic / guided) and {AGENT_MODEL} (sonnet / opus), then print these two lines so the resolved values are visible in transcript:
Mode: guidedorMode: automatic— the resolved{MODE}.Subagent model: sonnet(default) orSubagent model: opus (--max)(if--max/--opus/ "max quality" / "use opus" / Opus selection was requested).
Step 1: Verify Tooling And Environment
Run sequentially:
- Read template-map.md.
- Run
forge --version. - If
forge --versionfails, tell the user that Foundry is missing and suggest installing it using the official documentation: Foundry install guide:https://www.getfoundry.sh/introduction/installation - If
forge --versionfails, stop here. Foundry is required before proceeding with the rest of the workflow. - Run
bash {SKILL_PATH}/scripts/ensure_foundry.sh {PROJECT_ROOT}. - If
foundry.tomlis missing, allowensure_foundry.shto create one. If it fails, stop and report the error. - Run
medusa --version. - Run
echidna --version. - If either command fails, tell the user which tool is missing and suggest installing it using the official documentation:
Medusa install guide:
https://secure-contracts.com/program-analysis/medusa/docs/src/getting_started/installation.htmlEchidna install guide:https://secure-contracts.com/program-analysis/echidna/introduction/installation.html - If
medusa --versionfails, stop here. Medusa is required before proceeding with the rest of the workflow. - If
echidna --versionfails but Foundry and Medusa are installed, you may continue, but keep the installation recommendation in the user-facing summary because Echidna is still expected for the full workflow.
Step 2: Compile And Extract
Run sequentially:
- Read
{PROJECT_ROOT}/foundry.toml. - Run
cd {PROJECT_ROOT} && forge build. - Run
node {SKILL_PATH}/scripts/extract_abis.js {PROJECT_ROOT} --meta-dir {META_DIR}.
Step 3: Understand The Protocol
This step exists to drive setup, handler selection, and invariant generation quality.
If {MODE} = "guided", before touching any analysis source first ask the user: "Any additional docs, links, whitepapers, spec files, or prior-audit notes I should consider? (paste paths or URLs, or reply 'none')". If the user provides anything, write the raw list to {PROJECT_ROOT}/{META_DIR}/additional-context.md (one entry per line, include URLs verbatim). Later sub-steps of this step — and Step 9a — must read that file if it exists and fold it into the protocol-understanding context.
Start by checking whether {PROJECT_ROOT}/x-ray/ exists and contains x-ray.md. x-ray.md is REQUIRED — without it, x-ray output is considered unavailable regardless of which other files are present.
If {PROJECT_ROOT}/x-ray/x-ray.md exists, read it first as the primary project-understanding source. Then also read any of these supplementary files present in {PROJECT_ROOT}/x-ray/:
If {PROJECT_ROOT}/x-ray/x-ray.md does NOT exist, you MUST run the x-ray Acquisition Protocol below. The Protocol Analyzer fallback (Attempt 4) is FORBIDDEN until Attempts 1–3 have each been executed and their outcomes recorded in /tmp/x-ray-attempts.md. "I think x-ray isn't available" is NOT a valid skip — only the recorded output of an actual tool/command counts.
x-ray Acquisition Protocol
Before Attempt 1, delete /tmp/x-ray-attempts.md if it exists (rm -f /tmp/x-ray-attempts.md) — stale entries from a previous run would falsely satisfy the Attempt 4 gate. Then create a fresh /tmp/x-ray-attempts.md and append one entry per attempt: timestamp, attempt name, command/tool invoked, exact output (or "no output"), outcome (SUCCESS / FAILED: {reason} / SKIPPED: {reason}). Attempt 4 requires the file to contain exactly 3 entries (one per Attempt 1, 2, 3) — SKIPPED entries count toward this total.
-
Attempt 1 — invoke the skill. Call the
x-rayskill via theSkilltool withargs="{PROJECT_ROOT}". Do NOT pre-judge availability — invoke it. Only a runtime error of the form "skill not found" / "unknown skill" counts as unavailable. If it runs, wait for completion, then verify{PROJECT_ROOT}/x-ray/x-ray.mdwas written. If yes → SUCCESS, exit Protocol. -
Attempt 2 — install from the official source and re-invoke. Run:
git clone --depth 1 https://github.com/pashov/skills.git /tmp/pashov-skills-xray-install \ && mkdir -p ~/.claude/skills \ && cp -r /tmp/pashov-skills-xray-install/x-ray ~/.claude/skills/x-rayThen re-invoke
Skill('x-ray', args="{PROJECT_ROOT}"). If{PROJECT_ROOT}/x-ray/x-ray.mdis produced → SUCCESS, exit Protocol. If the re-invocation still returns "skill not found" / "unknown skill" (auto-discovery did not pick up the freshly installed skill mid-session), do NOT mark this attempt failed yet — instead read~/.claude/skills/x-ray/SKILL.md(or/tmp/pashov-skills-xray-install/x-ray/SKILL.md) and execute its instructions inline against{PROJECT_ROOT}. If that produces{PROJECT_ROOT}/x-ray/x-ray.md→ SUCCESS, exit Protocol. Only if ALL of (Skill re-invocation, inline execution) fail does this attempt count as FAILED. -
Attempt 3 — guided-mode user gate (guided only). If
{MODE} = "guided"AND Attempts 1–2 both failed, ASK the user: "Could not obtain x-ray automatically (logs in/tmp/x-ray-attempts.md). Options: (a) paste an x-ray.md path, (b) authorize Protocol Analyzer fallback, (c) abort. Choose a/b/c." Record their answer. If (a) and the file exists → copy to{PROJECT_ROOT}/x-ray/x-ray.md, SUCCESS. If (c) → halt the skill. Only (b) — explicit user authorization — permits Attempt 4. In{MODE} = "automatic", skip this attempt and recordSKIPPED: automatic mode. -
Attempt 4 — Protocol Analyzer fallback. Permitted ONLY after Attempts 1–3 are recorded in
/tmp/x-ray-attempts.md(with status FAILED, SKIPPED, or — for Attempt 3 only —(b) authorized). Before spawning, confirm the file exists and contains 3 entries; if not, GO BACK to the missing attempt — do not proceed.Fallback: Read
{SKILL_PATH}/agents/protocol-analyzer.md, replace{SKILL_PATH}with the actual{SKILL_PATH},{PROJECT_ROOT}with the actual{PROJECT_ROOT}, and{META_DIR}with the actual{META_DIR}, then spawn as ageneral-purposeagent withmodel: "{AGENT_MODEL}". The agent reads the source files, then writes the analysis to{PROJECT_ROOT}/{META_DIR}/protocol-understanding.mdso that later steps can read it back instead of relying on conversation context.
From the x-ray documentation or protocol-understanding.md infer and summarize:
- deployment order
- constructor parameter meaning
- required post-deploy initialization
- actor roles and permissioned actions
- approvals, liquidity, or other state needed before handlers will be useful
- which external functions are real fuzzing entry points versus protocol-internal plumbing
- candidate invariants to carry forward into Step 9
If something is still ambiguous after those reads, keep going with a conservative assumption and leave a targeted TODO later instead of guessing broadly.
Do not plan full ghost-variable layouts, snapshot structs, or final implementation details here. Do record the likely invariants clearly so Step 9 can reuse them from {PROJECT_ROOT}/x-ray/ or {PROJECT_ROOT}/{META_DIR}/protocol-understanding.md as its starting point.
Step 4: Select Entry Points
Read selection-policy.md.
Create {PROJECT_ROOT}/{META_DIR}/entry-point-selection.json as a filtered copy of {PROJECT_ROOT}/{META_DIR}/contracts.json that keeps the functions most likely to produce useful state transitions.
Build the preselection from the protocol understanding gathered in Step 3 — primarily the x-ray entry-point map (if available) and source-level access control observations. If Step 3 produced an entry-point map with caller or access annotations, use that as the primary filter: exclude functions marked as internal-caller-only or contract-to-contract plumbing. Use {PROJECT_ROOT}/{META_DIR}/contracts.json only as the structural template for the output JSON format, not to decide which functions to include.
Then run:
- If
{MODE} = "automatic":node {SKILL_PATH}/scripts/select_functions.js {PROJECT_ROOT} --contracts {PROJECT_ROOT}/{META_DIR}/contracts.json --selection {PROJECT_ROOT}/{META_DIR}/entry-point-selection.json --meta-dir {META_DIR} --auto - If
{MODE} = "guided":node {SKILL_PATH}/scripts/select_functions.js {PROJECT_ROOT} --contracts {PROJECT_ROOT}/{META_DIR}/contracts.json --selection {PROJECT_ROOT}/{META_DIR}/entry-point-selection.json --meta-dir {META_DIR}
The --auto flag accepts the inferred selection and exits immediately. Without it, the script opens a browser UI with the inferred selection pre-checked so the user can adjust and confirm. Both paths write entry-point-selection.json.
After the script completes, read {PROJECT_ROOT}/{META_DIR}/entry-point-selection.json.
If the script exits before writing entry-point-selection.json, stop and report that failure.
Print a short summary:
- selected contracts
- selected functions by contract
- notable excluded functions
Dispatcher for Low-Frequency Functions
After reading the selection, classify the selected functions into two tiers:
- Primary: core user flows that should be called frequently by the fuzzer (deposit, withdraw, mint, redeem, borrow, repay, swap, stake, unstake, claim, liquidate, etc.)
- Secondary: less common functions that are still useful but should be called less often (admin setters, configuration changes, pause/unpause, role grants, parameter tuning, etc.)
Write this classification to {PROJECT_ROOT}/{META_DIR}/entry-point-selection.json by adding a "tier": "primary" or "tier": "secondary" field to each function entry.
In Step 7, secondary-tier functions will be wrapped in a dispatcher handler that groups them behind a single entry point with an enum selector. This reduces call frequency naturally without excluding them entirely — the fuzzer picks a random selector value, so secondary functions get exercised occasionally but don't dominate the call sequence.
If the user already excluded a function during selection, it stays excluded. The dispatcher is only for functions the user chose to keep but that should be deprioritized.
{PROJECT_ROOT}/{META_DIR}/entry-point-selection.json limits handler generation only. It does not limit the setup dependency graph.
Step 4.5: Cost Estimate
Run:
node {SKILL_PATH}/scripts/estimate_cost.js {PROJECT_ROOT} --meta-dir {META_DIR} --model {AGENT_MODEL} --mode {MODE}
The script reads entry-point-selection.json, applies a size bucket based on selected-function count, and writes {PROJECT_ROOT}/{META_DIR}/cost-estimate.md with a per-stage breakdown plus a total and an expected range. The numbers are Anthropic list-price ballparks; actual cost varies with coverage cycles, re-runs, and prompt-cache hit rate.
Print the cost estimate table to the user. Then:
- If
{MODE} = "automatic": continue to Step 5 without pausing. - If
{MODE} = "guided": ask the user "Proceed with this estimate, or abort?" and wait for confirmation before continuing. If the user aborts, stop the run and report where the artifacts so far were written.
Step 5: Generate Scaffold
Run:
node {SKILL_PATH}/scripts/generate_suite.js {PROJECT_ROOT} --suite-dir {SUITE_DIR} --meta-dir {META_DIR}
This copies the full template scaffold into {PROJECT_ROOT}/{SUITE_DIR}/, including core harness files and utility files such as utils/MockERC20.sol. It also writes the fuzzer config files (echidna.yaml, medusa.json) into {PROJECT_ROOT}/.
It also reads {PROJECT_ROOT}/{META_DIR}/entry-point-selection.json and generates one stub handler file per selected contract under {PROJECT_ROOT}/{SUITE_DIR}/handlers/. Those stubs include the clamped and unclamped section headers but no sample handler functions. Handlers.sol is scaffolded to import and inherit from all generated handler stubs.
Treat the copied files as the starting point only. The next steps must modify them to fit the target protocol.
Step 6: Modify Core Files And Wire Setup
Read setup-playbook.md and template-map.md.
Modify the scaffolded core files under {PROJECT_ROOT}/{SUITE_DIR}/. Use template-map.md as the source of truth for the inheritance chain, file roles, and which scaffolded files are expected to be refined in this step versus later steps.
Use setup-playbook.md as the source of truth for:
- proxy and upgradeability detection
- setup requirements and good defaults
- mock-versus-real dependency choices
- signature-dependent setup guidance
Base.solintegration points and TODO/FIXME policy
When the target protocol depends on simple external ERC20s that are not part of the in-scope deployment graph, prefer the scaffolded utils/MockERC20.sol helper unless the project already includes a more faithful token mock.
The key output of this step is a compiling scaffold with a realistic Base.sol::setup() function and the rest of the core scaffold adjusted to match it.
If {MODE} = "guided", after the edits to Base.sol are complete and before running forge build, print a Setup Review block summarising what was wired:
- Contracts deployed in
setup()(name + address variable + constructor args source) - Proxies detected and which implementation each wraps
- Mocks vs real dependencies used, with the reason for each mock
- Actors configured (addresses + role), and which ones the fuzzer will impersonate via handler caller selection
- Seeded balances (token, recipient, amount)
- Roles / access-control grants (role, grantee)
- Approvals (token, owner, spender, amount)
Then ask the user: "Setup looks right? Reply 'proceed' to build, or tell me what to adjust." If the user requests adjustments, apply them and re-print the review block; loop until they approve. In {MODE} = "automatic", skip the review block and continue directly.
Run cd {PROJECT_ROOT} && forge build before moving on.
Step 7: Generate Handlers
Read handler-patterns.md.
First, run the handler generation script to produce pre-populated stubs with correct function signatures and type mappings:
node {SKILL_PATH}/scripts/generate_handlers.js {PROJECT_ROOT} --suite-dir {SUITE_DIR} --meta-dir {META_DIR}
Then read these in parallel:
{PROJECT_ROOT}/{SUITE_DIR}/handlers/Handlers.sol- each generated
{PROJECT_ROOT}/{SUITE_DIR}/handlers/<Contract>Handler.sol - each selected contract source file
Then refine the generated {PROJECT_ROOT}/{SUITE_DIR}/handlers/<Contract>Handler.sol for the selected contracts. The stubs already contain correct signatures and clamping hints — focus on wiring the actual protocol calls, adding semantic clamping, and implementing boundary-value stress variants.
Use handler-patterns.md as the source of truth for:
- clamped versus unclamped handler structure
- handler shaping and semantic action selection
- caller context
- clamping strategy
- edge-case and signature-dependent handler guidance
Update Handlers.sol to import and inherit from all generated handlers.
Run cd {PROJECT_ROOT} && forge build and fix compile issues before moving on.
Step 8: Reach Coverage With Medusa
Before generating invariants, ensure the generated harness can drive enough protocol coverage under Medusa.
Via-IR Coverage Deflation Handling
When via_ir = true is set in foundry.toml, the Yul IR optimizer aggressively merges and eliminates branches. This deflates Medusa's coverage numbers — you may be at 85% source coverage but Medusa reports 65%. This must be handled before the first Medusa run.
Step 8.0: Detect and configure fuzz profile.
- Read
{PROJECT_ROOT}/foundry.tomland check whethervia_ir = trueis set under[profile.default]or at the top level. - If
via_iris not enabled, skip this subsection — no fuzz profile is needed. - If
via_iris enabled, run:
bash {SKILL_PATH}/scripts/setup_fuzz_profile.sh {PROJECT_ROOT}
This script:
- Appends a
[profile.fuzz]section tofoundry.tomlwithvia_ir = false - Runs
FOUNDRY_PROFILE=fuzz forge build - If compilation succeeds: exits 0, prints
FUZZ_PROFILE=no-ir - If "stack too deep" error: retries with
via_ir = trueandoptimizer_runs = 0, exits 0, printsFUZZ_PROFILE=ir-no-opt - If both fail: exits 1 (use default profile, accept deflated coverage)
-
Read the script output to determine the fuzz profile mode:
FUZZ_PROFILE=no-ir→ accurate coverage, use standard targetsFUZZ_PROFILE=ir-no-opt→ reduced deflation but still some; lower coverage targets by ~10%- Script failed → fall back to default profile; lower coverage targets by ~15-20%
-
Record the profile mode in
{PROJECT_ROOT}/{META_DIR}/coverage-targets.mdat the top:no-ir: "Fuzz profile: via_ir disabled — coverage numbers are accurate"ir-no-opt: "Fuzz profile: via_ir required (stack too deep), optimizer_runs=0 — coverage deflated ~10%, targets adjusted"- default fallback: "Fuzz profile: via_ir required with optimizer — coverage deflated ~15-20%, targets adjusted"
-
For all subsequent
forge buildcommands in Steps 8–11, use:cd {PROJECT_ROOT} && FOUNDRY_PROFILE=fuzz forge build(if fuzz profile was created)cd {PROJECT_ROOT} && forge build(if no fuzz profile needed)
Store the build command in a variable {FUZZ_BUILD_CMD} for reuse in later steps.
Medusa Runs
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 1k
- Forks
- 224
- Last commit
- Sep 2026
ahel review
K6low
bundled executables the agent is told to runK1info
remote-installer-piped-to-shell (in scripts/ensure_foundry.sh)
Automated review, not a security audit. Ruleset v1+k2.
Advanced
- Item type
- skill
- Key
fizz- Source
- github.com/pashov/skills