Skip to content

feat: add configurable collision-free EVM edge coverage#14853

Merged
grandizzy merged 14 commits into
foundry-rs:masterfrom
0xalpharush:evm-edge-coverage-config
May 26, 2026
Merged

feat: add configurable collision-free EVM edge coverage#14853
grandizzy merged 14 commits into
foundry-rs:masterfrom
0xalpharush:evm-edge-coverage-config

Conversation

@0xalpharush

@0xalpharush 0xalpharush commented May 20, 2026

Copy link
Copy Markdown
Collaborator

Motivation

in the OG implementation, it is possible for edge IDs to collide which can lose some signal

OSS-249

Solution

implement and make evm_edge_coverage_collision_free the default

PR Checklist

  • Added Tests
  • Added Documentation
  • Breaking changes

Comment thread crates/config/src/fuzz.rs
/// Whether EVM edge coverage should use collision-free dense IDs.
pub evm_edge_coverage_collision_free: bool,
/// Whether EVM edge coverage IDs should include call-frame depth.
pub evm_edge_coverage_include_call_depth: bool,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why even have the configs? why not just switch it out directly?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it makes it easier to experiment. there's a couple more things to get in, and then i'd like to search for the best defaults or let some threads run with non-default features enabled

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if there's a better way to avoid these be user-facing, i suppose that'd be good to avoid churn

Comment thread crates/evm/evm/src/executors/mod.rs
Comment thread crates/evm/evm/src/inspectors/edge_cov.rs Outdated
Comment thread crates/evm/evm/src/inspectors/edge_cov.rs Outdated
Comment thread crates/evm/evm/src/inspectors/edge_cov.rs
Comment thread crates/evm/evm/src/inspectors/edge_cov.rs Outdated
@grandizzy

Copy link
Copy Markdown
Collaborator

Benchmark: 1h Aave v4 SCFuzzBench campaign (seed=42)

I ran a 4-way comparison of edge-coverage modes against the Recon-Fuzz/aave-v4-scfuzzbench suite (forge test --mc CryticToFoundry --fuzz-seed 42, FOUNDRY_INVARIANT_TIMEOUT=3600) to validate the default flip in this PR. All runs on the same host, sequentially.

Results

Config total txs tx/s (peak) edges features corpus inv breaks handler asserts total bugs peak RSS
master (collision_free=false, include_call_depth=false) 29.95M ~862 352–373 372–488 306–368 3 4 7 4.29 GB
PR ⭐ default (collision_free=true, include_call_depth=false) 30.27M ~901 376–378 442–475 322–351 4 3 7 4.45 GB
PR (collision_free=true, include_call_depth=true) 28.45M ~790 396–401 422–447 364–380 3 2 5 4.43 GB
PR (collision_free=false, include_call_depth=true) 32.16M ~1117 371–409 421–500 325–398 4 3 7 4.39 GB

Unique bugs by config

Bug master PR default PR +depth PR hash+depth
invariant_canary
invariant_shouldNotBecomeLiquidatable
invariant_totalBorrowedLessThanSupplied_v0
invariant_totalBorrowedLessThanSupplied_v1
invariant_totalBorrowedLessThanSupplied_v2
iHub_mintFeeShares_ASSERTION_MINT_FEE_SHARES_PPS_CHANGE
iSpoke_repay_ASSERTION_REPAY_DOS
iSpoke_withdraw_ASSERTION_WITHDRAW_DOS
assert_canary_ASSERTION_CANARY
Total 7 7 5 7

Key findings

  1. The new default (collision-free, no depth) is a clear improvement over master. Same bug count (7), +5% peak throughput, +1% more transactions processed, only +4% peak RSS. The collision-free signal removed two of master's bugs (v0 upgraded to also catching v1 and v2) at no measurable runtime cost.
  2. Adding include_call_depth=true on top hurt on this target. It lost 2 invariant breaks (v1/v2) and 1 handler bug (withdraw_DOS) compared to the default, with ~13% lower throughput and ~6% fewer txs in 1h. Consistent with the "context-sensitivity dilutes corpus features" worry — same code reached via different call depths shows up as multiple novel features, slowing convergence to interesting state.
  3. collision_free=false + include_call_depth=true wasn't catastrophic but is still strictly inferior to the new default (1 fewer invariant break). On this target the 64KiB map absorbed the extra dimensionality without runaway collisions, but it's a strictly worse design point and there's no reason to recommend this combination.
  4. No unbounded memory growth in 1h. Peak RSS for all 4 configs landed within 4.29–4.45 GB (≤4% spread). The EdgeIndexMap + history_map did not blow up on aave-v4 inside a 1h budget. A 3h+ run would be useful confirmation before declaring the new default safe for very long campaigns.

decofe and others added 6 commits May 26, 2026 07:22
…onfig

# Conflicts:
#	crates/evm/evm/src/executors/mod.rs
Vec::push already grows via the same amortized doubling path
(RawVec::grow_amortized) as Vec::reserve(1), so pre-reserving a
single slot adds no headroom -- only an extra capacity check.
The fixed-size hash bitmap is only used by EdgeCovKind::Hash. In
CollisionFree mode (the new default) it stayed allocated and got
memcpy'd on every per-call inspector clone for no benefit.
Previously into_coverage(self) borrowed via dense_hits(&self) and
cloned every dense entry despite consuming the inspector. Replace
with a single From impl that destructures and moves the HashMap,
and use it from both into_hitcount and into_parts.
merge_edge_coverage doesn't need ordered hits, so paying sort_by_key
on every per-call inspector drain was wasted work on the new default
path. Move the sort into snapshot_edge_fingerprint, which is gated on
handler assertion failure -- cold path -- and is the only consumer
that needs a deterministic order across HashMap iterations.
Once From<EdgeCovInspector> for EdgeCoverage landed, into_hitcount
was just a thin wrapper around self.into() with a misleading name
(it returns EdgeCoverage, not a hitcount). No callers exist.
grandizzy added 2 commits May 26, 2026 12:23
Without a tag byte, an EdgeKey with depth=None serializes identically
to depth=Some(0) when include_call_depth is toggled, so fingerprints
collide across configs sharing a corpus. Push a 1-byte is_some tag
ahead of the depth bytes. Cold path; no impact on default config.
The method took (yes: bool, kind: bool, include_call_depth: bool) and
runner.rs sourced all three from the same FuzzCorpusConfig. Replace
with collect_edge_coverage_with_config(&FuzzCorpusConfig) that derives
both the gate and EdgeCovConfig from one corpus reference, backed by
a From<&FuzzCorpusConfig> for EdgeCovConfig impl.
Comment thread crates/evm/evm/src/inspectors/stack.rs Outdated
Comment thread crates/evm/evm/src/inspectors/edge_cov.rs Outdated
Comment thread crates/evm/evm/src/inspectors/edge_cov.rs Outdated
Comment thread crates/evm/evm/src/inspectors/edge_cov.rs Outdated
Comment thread crates/evm/evm/src/executors/corpus.rs Outdated
grandizzy added 2 commits May 26, 2026 14:04
- drop stale TODO in collect_edge_coverage
- collapse with_capacity/with_capacity_and_config into with_config
- drop tests-only get_hitcount/get_dense_hits; cfg(test)-gate dense_hits
- remove vacuous hitcount==0 assertions in collision-free tests
- start WorkerCorpus history maps empty; merge paths resize on demand
- still preallocate Hash-mode history_map to keep first merge alloc-free
mablr
mablr previously approved these changes May 26, 2026

@mablr mablr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

grandizzy
grandizzy previously approved these changes May 26, 2026
@grandizzy grandizzy enabled auto-merge (squash) May 26, 2026 14:32
@decofe decofe dismissed stale reviews from mablr and grandizzy via 65e8677 May 26, 2026 14:59
@grandizzy grandizzy requested a review from mablr May 26, 2026 15:01
@grandizzy grandizzy merged commit 0659e1d into foundry-rs:master May 26, 2026
19 checks passed
@github-project-automation github-project-automation Bot moved this to Done in Foundry May 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants