Releases: pyk/ripfuzz
Release list
v0.9.6
Added
-
[solc] show_warningcontrols whetherripfuzz compile,ripfuzz test,
ripfuzz max, andripfuzz execlog solc warnings after compilation. It
defaults tofalse, set it totrueto show warnings. -
ripfuzz compile <contract>compiles a contract through the shared solc
pipeline and logs any warnings, so diagnostics that do not fail the build are
visible without runningtest,max, orexec. Errors still fail the
command with the compiler output. A cached compilation skips solc entirely. -
ripfuzz test,ripfuzz max, andripfuzz execnow log solc warnings after
compilation, so diagnostics that do not fail the build stay visible in
campaign output. -
ripfuzz inspect storage-layout <contract>prints the storage layout of a
contract as a markdown table with each variable's name, type, slot, offset,
and byte size, read directly from thestorageLayoutof the compilation
output. The inspector reuses the shared solc pipeline, so a cached
compilation skips solc entirely. -
ripfuzz inspect function-source <contract> <selector>prints the complete
source of the function selected by its 4-byte selector together with every
symbol it references: internal functions, modifiers, structs, enums, errors,
events, state variables, and inherited declarations across the compilation
unit. Each section renders with its natspec, resolving@inheritdoc
directives from the inherited interface. The inspector reuses the shared solc
pipeline, so a cached compilation skips solc entirely. -
ripfuzz inspect external-functions <contract>prints a report of every
externally callable function of a contract as a markdown table with its name,
selector, mutability, modifiers, and source location. The report classifies
functions into mutable, view, callback, and special sections. The inspector
reuses the shared solc pipeline, so a cached compilation skips solc entirely. -
Compiled solc outputs are now cached under a hash of the solc version and the
standard JSON input at{out}/{hash}.json. Compilations with identical
sources, settings, and compiler version reuse the cached output without
running solc again. -
ripfuzz fetch <name> <url>downloads and extracts a tar.gz dependency into
.ripfuzz/dependencies/<name>, hashes the archive as a sha2-256 multihash,
and records it under[dependencies]inripfuzz.toml. Re-runningfetch
for a recorded dependency updates its URL and hash instead of erroring. -
ripfuzz test,ripfuzz max, andripfuzz execnow remap each dependency
name onto its extracted sources, so
import {InvariantTest} from "ripfuzz/std.sol"compiles without manual
remappings. See docs/dependencies.md. -
ripfuzz initnow also creates a.gitignorewith.ripfuzzand.env
entries. An existing.gitignoregets only its missing entries appended. -
ripfuzz test,ripfuzz max, andripfuzz execnow deploy contracts that
use Solidity libraries. Libraries with only internal functions are inlined by
solc and need no deployment, while libraries with external functions are
detected from the compilation link references, deployed first, and linked
into the initcode, including nested library dependencies. -
ripfuzz testnow saves a fuzzing statistics report to
.ripfuzz/stats/{unix-timestamp}-{id}.jsonat the end of each campaign. The
report groups campaign metadata (harness, chain id, seed, budgets, totals)
with per-handler and per-invariant entries carrying call counts, wall time
min/max/avg, RPC cache hits/misses/wait, and reverts grouped by decoded kind
and message. -
ripfuzz testnow names every per-run artifact after a single run id, so the
log file, execution traces, coverage report, and fuzzing statistics of one
campaign share their filename stem under.ripfuzz. The coverage report is
saved per run as.ripfuzz/coverage/{run}.infoinstead of overwriting
lcov.info, while the corpus keeps persisting across runs. and message. -
ripfuzz test --stop-on-revertstops the campaign on the first reverted
handler or invariant call and records it as aREVERT:finding that is
shrunk and traced like a broken invariant. Without a value it stops on any
revert exceptBrokenInvariantErrorreports, while
--stop-on-revert 0xaa9a98dfstops only on reverts starting with that 4-byte
selector. -
ripfuzz test --stop-on-panicstops the campaign on the first Solidity panic
and records it as aPANIC:finding carrying the panic code, shrunk and
traced like a broken invariant. Without a value it stops on any panic, while
--stop-on-panic 0x01stops only on that panic code, accepting decimal or
0x-prefixed hex codes. selector. -
ripfuzz teststatistics now group reverts as a taggedkindenum where
each kind holds only its own data:Errorwith selector and message,Panic
with selector and code,CustomErrorwith selector and the resolved error
name, andBrokenInvariantErrorwith selector and message.
Changed
-
Solc warnings are now hidden by default in
ripfuzz compile,ripfuzz test,
ripfuzz max, andripfuzz execoutput. Set[solc] show_warning = truein
ripfuzz.tomlto log them. -
Broken invariants are now reported by reverting with the
BrokenInvariantErrorcustom error instead of thervm.bail(Invariant)
cheatcode:error BrokenInvariantError(string id, string description);.
Harnesses no longer need theRVMinterface or the cheatcode address for
invariant reporting. The error must propagate to the top of the call, a
revert caught withtry/catchis treated as handled and is not reported.
Solidityassertpanics (Panic(0x01)) are not findings. -
ripfuzz testandripfuzz maxnow default--threadsto the available CPU
cores of the machine instead of a single thread. -
--max-runsis renamed to--max-fuzz-runsand now defaults to 100,000
sequences instead of 256. A new--max-shrink-runsflag decouples the
shrinker budget from the fuzzing budget and defaults to 10,000 attempts. -
Log lines that reference artifacts (corpus, coverage, trace, stats) now print
paths relative to the project root instead of rawPath::display()output,
so absolute writer paths render as e.g..ripfuzz/stats/run.json. -
ripfuzz testandripfuzz maxnow loggenerating coverage reportbefore
building the coverage report, so large codebases no longer look stalled
between the corpus save and the coverage save.
Fixed
-
Concurrent
ripfuzzinvocations compiling the identical input no longer fail
withfailed to write ./.ripfuzz/solc/<hash>.json. The compilation cache now
writes each entry through a temporary file with a unique name, so parallel
writers never rename the same temporary file twice. -
ripfuzz initnow installs stderr logging so command errors appear in the
console. It writes no log file, keeping a fresh project free of.ripfuzz
state. -
A
vm.prankcaller that pays value in the pranked call no longer panics the
fuzzer. The pranked address is now loaded into the journal before the frame
runs, so a zero-balance sponsor reverts out-of-funds cleanly instead of
hitting anunwrapon an unloaded account inside revm.
v0.9.5
Added
-
ripfuzz test <harness>finds broken invariants. It compiles a harness
through solc, deploys it on a sandbox chain, fuzzes handler sequences, checks
invariant_*functions after every call on a throwaway clone, shrinks each
reproduction, and writes traces and anlcov.infocoverage report under
.ripfuzz/. Broken invariants arervm.bail(Invariant)reports,
deduplicated by id, and argument generation is seeded from harness literals. -
ripfuzz max <harness>maximizes harnessvalue(). Coverage-guided search
uses per-call value deltas, shrinks the best sequence, persists a JSON
corpus, and writes the same traces and coverage report asripfuzz test.
Optionalsetupandsummaryfunctions run around the campaign. -
ripfuzz exec <script>compiles a Solidity script, deploys it, runsexec()
once, prints logs, and saves the execution trace. -
ripfuzz initwrites a starterripfuzz.tomland refuses to overwrite an
existing file.
Changed
-
Harnesses compile through solc from
ripfuzz.toml, not Foundry
forge build. Solc settings live under[solc], withversionrequired.
The legacy flatsolc = "0.8.36"form is rejected. Compilation resolves
remappings from the config and{root}/remappings.txt. -
Replaced
rvm.findingand theassert(false)panic workflow with
rvm.bail(Invariant).
Removed
-
ripfuzz runand the Foundry-based campaign stack. Useripfuzz testto
find broken invariants andripfuzz maxto maximizevalue(). -
The
rvm.getCodecheatcode.
Fixed
-
Tester campaigns keep the shortest reproducing sequence for each broken
invariant id, so a later one-call hit replaces a longer first hit. -
Signed-integer argument generation now uses extracted negative literals, so
gates below 256 bits are reachable. -
ripfuzz maxgenerates arguments for handlers that take struct parameters,
including arrays and nested structs. -
ripfuzzloads{cwd}/.envat startup for every command, sovm.getEnv
works intest,max, andexec.
v0.9.4
Added
- Trace decoding extracts function argument types from runtime bytecode with
evmole when no project ABI matches the selector, so unverified and forked
calls render decoded arguments instead of0xselector(...)
Changed
- Max-mode campaigns now log under a
maxxing{threads=N}span instead of
fuzz, and their progress/finished summaries include the current best max
value - The shrink progress line now logs structured fields (
runs,calls,
elapsed,call_rate,gas_rate,initial_calls,current_calls),
matching the fuzz progress summary - Max-mode campaigns no longer print the full call sequence in the log; it
stays available in the trace file - Harnesses may declare an optional
summary()function (no arguments, not
view/pure) that ripfuzz calls once after shrinking in the traced re-run, so
it can log a final summary that shows up at the end of the trace - The
Found N distinct failed assertion(s)finding now logs atinfoinstead
oferror, since the campaign still completes successfully - Shrunk invariant failures are now persisted to the corpus, so the next
campaign discovers the shortest failing sequence during replay instead of
re-fuzzing it - Campaign logs now use
shrink{threads=N}andtracespans after fuzzing, so
the whole lifecycle readsbuild→deploy→replay→fuzz→shrink→
trace; shrink progress messages also logassertion,initial_calls, and
final_callsas structured fields instead of concatenated text - The
tracespan now prints only the decoded log entries (when present) below
thetrace:line and the trace file path, instead of the compact trace and a
fulltrace:line; the campaign log path moves to its ownlogspan - Coverage report generation now logs under a
reportspan, the percentage
line is no longer indented, and the lcov path is full like the trace and log
paths - Session setup logs are now grouped under
build,deploy{contract=...}, and
replay{items=N}spans, matching thefuzz{threads=N}span - The
Loaded harness contractandDeployedmessages no longer repeat the
contract name, since thedeployspan already carries it - The corpus replay and fuzz progress/finished summaries now collapse the
edge/depth/revert/jump counters into a single compactcoveragefield (e.g.
8,407e 1,409d 17r 782j) alongside the contract count - Artifact parse warnings now render as
failed to parse artifact <path>under
thebuild:load_artifactsspan, without the repeated project path
Fixed
- Remove the redundant
contractfield from thefuzzlog span in invariant
and max-mode campaigns, so the line readsfuzz{threads=N}instead of
fuzz{contract=Name threads=N} - Invariant campaigns now report failed assertions discovered while replaying
the corpus, instead of seeding coverage and ignoring those panics
v0.9.3
Changed
- Upgraded solc dependency to v0.3.2
- ForkDB parse errors now include the raw JSON-RPC response body so provider
failures like error objects or malformed batches are visible in fuzzer logs - Automatic
.envloading uses the current working directory instead of the
project directory
v0.9.2
Added
rvm.forkresolves Flare-family network hardforks (Durango → Shanghai, Etna
→ Cancun, pre-Durango → London) from go-flare's upgrade schedule instead of
defaulting to the newest spec--max-failures Nto collect up to N distinct failed assertions (invariant
mode only) before stopping the campaign, with each one shrunk and reported
separatelymax_*harness functions: read-only, no-argumentuint256getters where
reverted or empty results score0and any value above0is the finding- Trace decoding falls back to common standard events (ERC20
Transfer/Approval, ERC721ApprovalForAll, WETH9Deposit/Withdrawal,
OwnableOwnershipTransferred) when no project artifact declares them,
rendering names and arguments instead of rawemit Log(0x...)lines
Changed
- The fuzzing lifecycle is one
fuzztracing span carrying the harness
contract and thread count, with consistentstarted,progress, and
finishedevents; the campaign log file records the fuzz-phase duration when
the span closes - Per-function statistics log
kind functionas the message (e.g.
handler deposit calls=60.2K gas=11.14 G reverts=0) instead of a generic
Function statisticsmessage with afunctionfield - Maxxing campaigns that find no improvement log a
warnnaming the objective
(e.g.
objective=max_profit No sequence improved the max value (best stayed at 0))
instead of anerror - Removed the redundant
Called setuplog line and theRipfuzz out. see ya
farewell line - Fuzzing progress lines now log structured
key=valuefields (matching the
final campaign summary) instead of·-separated prose - Terminal log lines print a simple local
HH:MM:SStimestamp without the
module target; the campaign log file keeps the full RFC 3339 timestamp with
target - Trace output hangs children, call context, logs, storage, and result lines
directly under each frame's name (aligned regardless of gas amount),
replacing the--- Call #N ---header with a[N]counter on the root frame
line --fail-on-revertis replaced by--stop-on-revert: any reverted
transaction stops the campaign (invariant and maxxing mode), writes the full
trace tofulltrace.log, dumps a compact trace (call context and storage
changes omitted) to the log and stderr, names both file paths in the error,
and exits with a failure instead of shrinking- A failed
setup()after a successful deployment stops the campaign like
--stop-on-revert: full trace tofulltrace.log, compact trace to log and
stderr, both paths named in the error - A failed harness deployment stops the campaign like
--stop-on-revert: full
trace tofulltrace.log, compact trace to log and stderr, both paths named
in the error - Failed-assertion and max-value findings now dump their traces like
--stop-on-revertwithout failing the campaign: full trace to
fulltrace.log(per-findingfulltrace-N.logorfulltrace-max-N.log),
compact trace to log and stderr, both paths named - Maxxing campaigns no longer track failed assertions or enter the shrinker on
a revert - Upgraded solc dependency to v0.1.1
- Fuzzer and shrinker progress logs one compact line every 3 seconds, with the
full statistics printed after the phase finishes - Terminal status output now goes through
tracing;--disable-logdisables
all log output (terminal and campaign log file) - Campaign mode is selected automatically: a harness with a
max_*function
runs in max mode, which supports exactly one max function and rejects
invariant_*functions - Renamed the maxxing campaign type from
MaxCampaigntoMaxxingCampaign. - Fuzzer types now live under
fuzzers. - Shrinker types now live under
shrinkers:Shrinkeris renamed to
InvariantShrinkerandMaxShrinkertoMaxxingShrinker; themaxmodule
was removed. - Campaign dispatch moved into
commands::run::run;CampaignKind::Maxis
renamed toCampaignKind::Maxxing.
Fixed
- Fork RPC batches that mix cached and missing keys no longer kill the fetcher
thread withfetcher did not receive all keys, which stalled campaigns after
new storage slots appeared - Campaign worker failures are no longer swallowed: any failed or panicked
fuzzer/shrinker thread exits the campaign after all workers settle, with the
full cause chain (e.g.
revm transaction failed: database error: RPC rate limited: …) instead of
only the outer message - Skipped build artifacts now warn with the artifact file path and full error
chain instead of a bare cause message printed twice - Build artifacts are loaded once per campaign, so trace contexts reuse them
instead of re-reading the build output directory (which duplicated artifact
parse errors in the log) --stop-on-reverttraces stop at the first reverted transaction: only the
calls up to and including it are re-run and dumped- Mid-transaction
rvm.forkswitches no longer drop or leak remote state
written earlier in the same transaction (e.g.rvm.store/rvm.dealon fork
A thenrvm.forkto B): journaled remote mutations commit to the active fork
overlay before the switch, and local harness accounts stay shared across
forks - Fork transport JSON-RPC payloads are logged at
debuginstead ofinfo, so
default runs no longer flood the terminal with full payload lines
v0.9.1
Added
-
vm.getEnvcheatcode to read environment variables as strings:function getEnv(string calldata key) external returns (string memory value); function getEnv(string calldata key, string calldata defaultValue) external returns (string memory value);
The single-argument form reverts when the key is missing:
Failed to get environment variable FOO as type string: environment variable not foundThe two-argument form returns
defaultValuewhen the key is missing. -
Automatic
.envloading from the project directory (defaults to the current
working directory). Values are available tovm.getEnv. Existing process
environment variables take precedence over.env. -
vm.forkcheatcode to create or select a remote chain fork:struct ForkConfig { uint32 retries; uint64 backoffMs; uint64 timeoutMs; uint64 rateLimit; } function fork(string calldata url, uint256 blockNumber) external; function fork(string calldata url, uint256 blockNumber, ForkConfig config) external;
Campaigns always start as an empty sandbox. Call
vm.forkinsetupor
action modifiers to opt into remote state. Multiple forks are cached and
selected by(url, block). Local accounts (harness, deployer,vm.addr
results) persist across switches. Remote state is isolated per fork, so the
same address on two chains (e.g. a bridge on Ethereum and Polygon) keeps
independent storage and balances. Coverage is keyed by bytecode hash, not
address. Single-argvm.forkdefaults: retries 3, backoff 100ms, timeout
30s, no rate limit (same as the former CLI defaults).
Changed
-
RVM address is now derived from
keccak256("ripfuzz cheatcode")instead of
Foundry'shevm cheat code:// before (Foundry HEVM) 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D // after 0x628dC59F11F72B611132eC40437F125ba1312F08Harnesses must point
rvmat the new address (ripfuzz-stdHarnessalready
does this). -
ripfuzz run <HARNESS>accepts a bare harness name (Harness) or a full
artifact id (src/Harness.sol:Harness). When multiple contracts share the
same name, the command lists the matching full ids to choose from -
Upgraded solc dependency to v0.0.14
-
Campaign directory IDs include seconds
(.ripfuzz/campaigns/YYYY-MM-DD-HHMMSS-<uuid>/) so campaigns started in the
same minute are easier to tell apart -
Fork mode is driven entirely by
vm.forkin the harness. CLI flags
--rpc-url,--rpc-block,--rpc-retries,--rpc-backoff,
--rpc-timeout, and--rpc-rate-limitare removed. Single-arg
vm.fork(url, block)uses built-in defaults (retries 3, backoff 100ms,
timeout 30s, no rate limit). Override viavm.fork(url, block, ForkConfig) -
Removed the library helper
Chain::fork_with_transport. Tests and campaigns
create an empty sandbox and opt into remote state withvm.forkonly. -
Removed the startup log for spawning the test chain (including empty-sandbox
chain id, EVM version, block number, and timestamp). Empty vs fork is decided
at runtime byvm.fork, so those defaults were misleading
Fixed
-
vm.forknow applies the forked block's EVMSpecId(and matching mainnet
gas params) to the active chain config. Previously only the former
Chain::forkpath did this, so harnesses that calledvm.forkkept the
empty-sandbox hardfork instead of the remote chain's hardfork at that height
(opcodes, gas schedule, and blob base-fee fraction). -
Traces now surface calls to empty accounts clearly. A successful call with no
bytecode shows← [stop] (no code), and a parent empty revert that follows
such a call decodes asno contract code at <address>instead of plain
reverted. This makes--fail-on-revertfailures actionable when a harness
hits remote addresses withoutrvm.fork.
v0.9.0
Initial public release
Added
ripfuzz run <target>: run a coverage-guided, mutational fuzzing campaign
against a Foundry handler contract usingFile.sol:Nameartifact ID syntax- Parallel fuzzing across configurable worker threads with a shared
coverage-guided corpus and metrics - Invariant checking: handler invariant functions are executed after each
generated call sequence - Persistent corpus support:
--corpus-dirloads and replays existing corpus
items at campaign start and saves newly discovered coverage-increasing
sequences - Automatic failure shrinking with configurable
--shrink-runs,
--shrink-timeout, and--shrink-threads - Fork mode against live networks via
--rpc-urland--rpc-block, with
retries, exponential backoff, rate limiting, request timeouts, and a local
fork state cache lcov.infocoverage reports per campaign, including function-level and
source-map-derived line coverage across all resolved build artifacts- Execution traces for deployment and setup, written to
trace.logunder
.ripfuzz/campaigns/<campaign-id>/ - Foundry cheatcodes for time and chain context (
warp,roll,prevrandao,
chain_id,coinbase,fee), accounts and balances (prank,deal,
addr,nonce,label), storage and bytecode (store,load,etch,
get_code), value encoding/decoding (parse,to_string,sign), and
opt-inffivia--ffi --external-projectto load additional Foundry project artifacts for
coverage and trace resolution, including fork-mode interactions with
separately compiled contracts- Configurable campaign limits:
--max-runs,--timeout,--max-calls,
--gas-limit, and--threads - Reproducible campaigns via
--seed; a random seed is generated and printed
when none is provided --fail-on-revertto treat any transaction revert as a failed assertion- Foundry project integration: automatic builds with storage layout, handler
deployment with configurable--deployerand--deploy-value, and library
linking - File logging to
fuzz.logper campaign with configurable--log-leveland
--disable-log - Library-first public API with
Fuzzer,Shrinker,Chain,Project,
SharedCorpus,CorpusReplayer, andCoverageReportertypes for
programmatic use