Skip to content

Update flake.lock - #3

Open
github-actions[bot] wants to merge 1 commit into
masterfrom
update_flake_lock_action
Open

Update flake.lock#3
github-actions[bot] wants to merge 1 commit into
masterfrom
update_flake_lock_action

Conversation

@github-actions

@github-actions github-actions Bot commented Jun 29, 2025

Copy link
Copy Markdown

Automated changes by the update-flake-lock GitHub Action.

Flake lock file updates:

• Updated input 'fenix':
    'github:nix-community/fenix/f374034' (2026-04-25)
  → 'github:nix-community/fenix/7f3ed5f' (2026-08-15)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/8954b66' (2026-04-21)
  → 'github:rust-lang/rust-analyzer/baabc58' (2026-08-13)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/01fbdee' (2026-04-23)
  → 'github:NixOS/nixpkgs/6b5e5b7' (2026-08-13)

Running GitHub Actions on this PR

GitHub Actions will not run workflows on pull requests which are opened by a GitHub Action.

To run GitHub Actions workflows on this PR, close and re-open this pull request.

@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from 276d4b9 to 6c084a5 Compare July 6, 2025 01:09
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from 6c084a5 to 4f533b5 Compare July 13, 2025 01:11
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from 4f533b5 to 813c735 Compare July 20, 2025 01:12
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from 813c735 to 8ff43d3 Compare July 27, 2025 01:12
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch 2 times, most recently from 63b22c1 to 1f5ced7 Compare August 10, 2025 01:11
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch 2 times, most recently from cbe3e8c to 0976a98 Compare August 24, 2025 01:05
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from 0976a98 to 8c629ea Compare August 31, 2025 01:01
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch 2 times, most recently from 7aed32b to 88a00d6 Compare September 14, 2025 01:00
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from 88a00d6 to 481e47d Compare September 21, 2025 01:01
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch 2 times, most recently from 309694e to c5bda1e Compare October 5, 2025 01:01
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from c5bda1e to 41c44f6 Compare October 12, 2025 00:59
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch 2 times, most recently from ab795ae to 836d148 Compare October 26, 2025 01:02
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch 2 times, most recently from 93abcaf to 8bfabe3 Compare November 9, 2025 01:03
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from 8bfabe3 to 525c669 Compare November 16, 2025 01:05
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch 2 times, most recently from 3fd5568 to ac3b447 Compare November 30, 2025 01:08
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from ac3b447 to 01dfab4 Compare December 7, 2025 01:08
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch 2 times, most recently from 7e7c6c0 to f24fbc6 Compare December 21, 2025 01:08
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from f24fbc6 to fb5ceee Compare December 28, 2025 01:12
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch 2 times, most recently from 1054a7f to 63c03cf Compare February 22, 2026 00:13
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch 2 times, most recently from 73f7460 to b295bf8 Compare March 8, 2026 00:13
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch 2 times, most recently from db11d6c to d7bd59a Compare March 22, 2026 00:14
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from d7bd59a to 22064a5 Compare March 29, 2026 00:17
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from 22064a5 to 7797be6 Compare April 5, 2026 00:17
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from 7797be6 to af1995d Compare April 12, 2026 00:19
@github-actions
github-actions Bot requested a review from mablr as a code owner April 12, 2026 00:19
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from af1995d to 31204e0 Compare April 19, 2026 00:19
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from 31204e0 to 01e5084 Compare April 26, 2026 00:22
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from 01e5084 to 0a213b3 Compare May 3, 2026 00:25
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from 0a213b3 to d4449d1 Compare May 10, 2026 00:27
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from d4449d1 to 8a199c9 Compare May 17, 2026 00:28
zerosnacks pushed a commit that referenced this pull request May 19, 2026
…icates (foundry-rs#14482)

* feat(invariant): handler-side assertion bugs decoupled from invariant predicates

Rebased on top of merged foundry-rs#12587 (assert_all). Adds handler-bug machinery:

- Decoupled handler-side assertions from invariant predicates: dedup by
  (reverter, selector) site, shortest-reproducer wins on collision.
- Dedicated 'Suite handlers:' report section with full counterexamples.
- Continuous campaign under assert_all: preflight handler bugs are
  recorded and the campaign keeps running for the full budget.
- Live pulse-stream metrics surface unique handler-bug counts alongside
  invariant unique_failures; record_new_invariant_failures emits per-tick
  events deterministically so multi-break ticks are all reported.
- Persistence + replay under failures/<contract>/handlers/<site>.json,
  keyed by keccak256(reverter || selector); replays merge with
  shortest-wins, prune non-reproducing files, leave incompatible ones.
- Post-campaign shrinking reuses the invariant shrink loop; intermediate
  calls that themselves assert are rejected to avoid promoting a
  different finding.
- Cached failure counts on InvariantFailures keep invariant_count() and
  handler_count() O(1) on the per-call hot path.
- E2E coverage in tests/cli/test_cmd/invariant/handler.rs.

* fix(invariant): clear GLOBAL_FAIL_SLOT after recording handler bug

A non-reverting `vm.assert*` (`assertions_revert = false`) writes
GLOBAL_FAIL_SLOT = 1 on the cheatcode address and the call gets
committed. Left in place, that committed slot poisons every subsequent
`is_success` / `handlers_succeeded` check for the rest of the run,
silently suppressing later `assert_invariants` and `afterInvariant`
evaluations and undercounting bugs the campaign would otherwise find.

Clear the slot via `Executor::clear_global_failure` right after
`record_handler_assertion_bug` in both call sites (the per-call
`can_continue` path and the `should_check_invariant == false` inline
path). Simplified the helper signature: dropped the unused
`Option<&mut StateChangeset>` argument and the always-discarded return
type; backend-write failures (a fundamentally broken state) are traced.

Adds a regression e2e test that asserts once on call #1, then bumps a
counter on calls #2/#3 to trip a real predicate. Without the fix the
predicate failure is silently dropped; with the fix both the handler
bug and the predicate failure are reported.

* feat(fuzz): generate msg.value for payable functions in invariant fuzzing (foundry-rs#14565)

* feat(invariant): assert all invariants

* Tests and Nits

Amp-Thread-ID: https://ampcode.com/threads/T-019dbf48-3fb0-7762-a01f-b5e966339e73
Co-authored-by: Amp <amp@ampcode.com>

* fix: check all invariants in afterInvariant gate and preflight

Amp-Thread-ID: https://ampcode.com/threads/T-019dbf48-3fb0-7762-a01f-b5e966339e73
Co-authored-by: Amp <amp@ampcode.com>

* fix: use per-invariant fail_on_revert when recording handler revert failures

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019dbf48-3fb0-7762-a01f-b5e966339e73

* fix: commit state between txs in generate_counterexample

Amp-Thread-ID: https://ampcode.com/threads/T-019dbf48-3fb0-7762-a01f-b5e966339e73
Co-authored-by: Amp <amp@ampcode.com>

* fix: preflight check all invariants, not just primary

Amp-Thread-ID: https://ampcode.com/threads/T-019dbf48-3fb0-7762-a01f-b5e966339e73
Co-authored-by: Amp <amp@ampcode.com>

* fix: exclude secondary invariants from optimization mode runs

Amp-Thread-ID: https://ampcode.com/threads/T-019dbf48-3fb0-7762-a01f-b5e966339e73
Co-authored-by: Amp <amp@ampcode.com>

* refactor: rename invariant_fn to primary_invariant_fn, deterministic preflight error, debug_assert on empty invariants

Amp-Thread-ID: https://ampcode.com/threads/T-019dbf48-3fb0-7762-a01f-b5e966339e73
Co-authored-by: Amp <amp@ampcode.com>

* feat: show broken invariant count in progress bar during continuous runs

Amp-Thread-ID: https://ampcode.com/threads/T-019dbf48-3fb0-7762-a01f-b5e966339e73
Co-authored-by: Amp <amp@ampcode.com>

* feat(invariant): rename continuous_run to assert_all and default to true

Renames the InvariantConfig field to better describe its semantics
("assert every invariant in the suite, don't stop on first failure")
and flips the default to true so multi-invariant suites report all
broken invariants by default, matching Echidna/Medusa behavior.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019dcd68-66ac-76ed-ac5c-7ea722a9c9ae

* feat(invariant): parameterize shrinker by target invariant + persisted failures footer

- Generalize shrink_sequence, shrink_sequence_value, replay_run, replay_error
  to accept target_invariant: &Function (currently always primary; unblocks
  per-secondary shrinking).
- Move reset_shrink_progress out of shrink fns; called once per invariant from
  replay_error. Progress label now 'Shrink: <invariant_name>'.
- Add TestResult.invariant_failure_dir; Display appends
  'N invariant failures persisted to <dir> — rerun to shrink' when secondary
  failures were written.

Amp-Thread-ID: https://ampcode.com/threads/T-019dcd68-66ac-76ed-ac5c-7ea722a9c9ae
Co-authored-by: Amp <amp@ampcode.com>

* feat(invariant): structured InvariantOtherFailure for assert_all secondaries

Promotes TestResult.other_failures from Vec<String> to Vec<InvariantOtherFailure>
carrying name, reason, optional counterexample, and persisted path. Display
renders each secondary symmetrically with [FAIL: reason] + [Sequence] block
when a counterexample is available, falling back to the terse 'name: reason'
one-liner otherwise.

Amp-Thread-ID: https://ampcode.com/threads/T-019dcd68-66ac-76ed-ac5c-7ea722a9c9ae
Co-authored-by: Amp <amp@ampcode.com>

* feat(invariant): serial secondary shrinking + Ctrl-C persists un-shrunk secondaries

PR-3 of the assert_all rollout. After the campaign finishes, every broken
secondary invariant is shrunk in turn via replay_error so users get a
ready-to-debug counterexample for each failure in a single run (matching
how the primary is rendered: [FAIL: reason] <name> + [Sequence] block).

On Ctrl-C, instead of dropping known secondaries (previous behavior was a
'break' before pushing them), the loop keeps recording every failure the
campaign discovered. The shrink + replay step is skipped to honor the
interrupt, but the un-shrunk sequence is persisted via
BaseCounterExample::from_invariant_call (no execution required), so a
re-run targeting that secondary picks up the saved counterexample and
shrinks from there — same UX as re-running an interrupted primary.

Output of an interrupted run now includes a terse
'<invariant>: <reason>' line for each secondary the campaign saw,
preserving visibility of all broken invariants while keeping the
interrupt fast.

Adds e2e coverage:
- assert_all: extended to verify secondary failures render symmetrically
  with shrunk sequences and that re-running skips persisted secondaries.
- assert_all_only_primary: new test confirming no secondary [FAIL] blocks
  or persisted-failures footer appear when only the primary breaks.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019dcdd3-53f5-76b6-ac36-d59f06b58280

* feat(invariant): assert_all polish — [i/N] shrink counter, suite roll-up, opt-mode warning

Three small UX wins for assert_all campaigns. No behavior change, no
new dependencies.

1. Shrink progress bar gets an [i/N] queue counter when more than one
   invariant needs shrinking, so users see how many shrinkers are
   queued behind the current one (e.g. '[2/3] Shrink: invariant_X').
   reset_shrink_progress and replay_error gain a position parameter;
   single-invariant call sites pass None.

2. Suite-level roll-up footer: when assert_all exercised >1 invariant
   and the test failed, render 'Suite assert_all: <broken>/<total>
   invariants broken' above the per-invariant blocks. Gives CI logs
   and Slack pastes a glanceable health line. New Option<usize>
   field on TestResult, populated only when meaningful.

3. Startup warning when assert_all + optimization-mode are combined.
   Optimization mode tracks one int256 return value, so any boolean
   secondary invariants in the same contract are filtered out before
   the campaign — previously silent. Now emits a once-per-suite warning
   naming the optimization invariant and every dropped boolean so users
   can move them to a separate contract.

E2E tests: extend assert_all to assert the new 4/5 roll-up;
assert_all_only_primary covers the 1/2 case; new
assert_all_optimization_mode_warning verifies the warning fires with
the dropped invariant names.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019dcdd3-53f5-76b6-ac36-d59f06b58280

* feat(invariant): warn when assert_all skips invariants with persisted failures

Symmetric with the primary's existing persisted-replay warning. Echidna
and Medusa never silently drop properties between runs — properties are
re-evaluated every campaign and a previous failure doesn't suppress
them. Foundry's per-property failure file model meant secondaries with
a stale persisted counterexample were filtered out of the campaign with
no acknowledgment, so users coming from Echidna/Medusa would see fewer
invariants in the report than their contract defines.

Now emits one stderr line listing every skipped name and the cache dir
to clean, e.g.:

  Warning: test/X.t.sol:Suite: 3 invariant(s) skipped due to persisted
  failures: invariant_a, invariant_b, invariant_c. Run `forge clean`
  or delete files in cache/invariant/failures/Suite to re-include.

E2E: extends assert_all re-run case with stderr_eq snapshot asserting
the warning fires with all 3 skipped names.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019dcdd3-53f5-76b6-ac36-d59f06b58280

* fix(invariant): gate afterInvariant per-run under assert_all

Previously afterInvariant was gated on failures.errors.is_empty()
campaign-wide. Under assert_all that gate stayed closed for the rest
of the campaign once any invariant broke, silently skipping the
afterInvariant hook on every subsequent run. Any assertions or
cleanup logic in afterInvariant therefore stopped running after the
first unrelated invariant failure.

Now snapshot failures.errors.len() at the start of each run and only
skip afterInvariant when the current run produced a new failure.
Preserves the legacy 'don't run afterInvariant on a run that already
failed' semantics while letting it run on subsequent runs once an
earlier invariant has broken.

E2E: new assert_all_after_invariant_runs_after_earlier_failure case
breaks invariant_first in run 1, keeps the campaign alive with a
second never-breaking invariant, and asserts an always-reverting
afterInvariant surfaces its marker in failure output.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019dce2d-57c7-734a-bbc6-6fa5e34b25de

* fix(invariant): re-evaluate secondary persisted failures on settings change

The secondary persisted-failure skip used a bare `.exists()` check at
two sites in runner.rs (the warning + InvariantContract::new filter,
and the post-campaign shrink loop). Under the new `assert_all = true`
default this meant any leftover failure file from a previous run was
treated as still valid even after the user changed a tracked setting
(target contracts/selectors, target/excluded senders, fail_on_revert),
silently dropping the secondary from the campaign with a misleading
'skipped due to persisted failures' warning.

Now both sites use the same settings-aware compatibility check the
primary's replay path uses (persisted_call_sequence settings.diff).
Stale caches fall back to a fresh evaluation; only secondaries whose
persisted settings still match the current run are honored.

Also hoists current_settings up so the new
secondary_has_compatible_persisted closure can reuse it across all
three call sites (warning, filter, shrink-loop skip).

E2E: new assert_all_secondary_persisted_revalidates_on_settings_change
runs once with fail_on_revert=false, flips it to true, re-runs and
asserts the suite roll-up shows 2/2 invariants broken — proving the
secondary was re-evaluated rather than silently filtered out.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019dce2d-57c7-734a-bbc6-6fa5e34b25de

* fix(invariant): drop hollow [FAIL] when only secondaries break under assert_all

When the selected invariant test passes but a secondary breaks under
assert_all, the report previously rendered a hollow '[FAIL]' header
(no reason, no counterexample) for the primary and the suite roll-up
overcounted broken invariants as '1 + other_failures.len()',
attributing a non-existent primary failure.

Now key the primary header on whether the primary actually broke
(`reason.is_some() || counterexample.is_some()`) and skip the
header when it didn't. Roll-up uses the same flag so the count
reflects only invariants that actually broke (e.g., 1/2 instead of
2/2). JSON shape is unchanged: top-level reason/counterexample stay
null when the selected primary didn't break, with full secondary
detail in other_failures.

E2E: new assert_all_secondary_only_failure_no_hollow_fail asserts a
secondary-only break renders 'Suite assert_all: 1/2 invariants broken'
followed by the secondary's '[FAIL: ...] <name>' block, with no
hollow primary header.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019dce2d-57c7-734a-bbc6-6fa5e34b25de

* fix(invariant): scope assert_all hollow [FAIL] suppression to secondary-only case

Previous commit suppressed the '[FAIL]' header whenever the primary had
no top-level reason or counterexample, which also matches DS-style
failures (they signal via the 'failed' flag and log events rather than
through TestResult.reason). That regressed failure_assertions::ds_style_test_failing
and test_cmd::core::legacy_assertions in CI.

Now the suppression is scoped strictly to the assert_all secondary-only
case: skip the primary header only when no primary failure AND
assert_all is in play AND there is at least one secondary to render.
DS-style, plain unit and single-invariant failures keep the original
'[FAIL]'/'[FAIL: ...]' rendering.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019dce2d-57c7-734a-bbc6-6fa5e34b25de

* fix(invariant): attribute failure event to first broken invariant in declaration order

The structured JSON 'failure' event emitted to stderr at campaign end
(consumed by benchmark and CI tooling) used 'errors.values().next()'
on a HashMap to pick its 'reason' field, while hardcoding the
'invariant' field to the primary's name. With HashMap RandomState,
the same broken set of invariants produced a different reason string
across runs, and the event was self-inconsistent (e.g.,
'invariant: invariant_balance, reason: fee miscalculation'). Three
sites used this pattern: in-run break path, afterInvariant break
path, and the preflight check fallback.

Now they walk 'invariant_contract.invariant_fns' in declaration
order (a Vec, deterministic) and pick the first one with a recorded
failure. Both 'invariant' and 'reason' fields refer to the same
function, and the event is stable across runs.

A new 'first_broken_event' helper centralizes the lookup.

E2E: assert_all_failure_event_uses_declaration_order declares three
invariants (a, b, c) that all break on the same call, runs with
'--mt invariant_c' (primary is the last declared) and asserts the
emitted event names invariant_a with reason 'a broken'.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019dce2d-57c7-734a-bbc6-6fa5e34b25de

* refactor(invariant): rename to InvariantSecondaryFailure / invariant_secondary_failures and serialize sparsely

Renames TestResult.other_failures -> invariant_secondary_failures and
the underlying InvariantOtherFailure struct -> InvariantSecondaryFailure.
The previous names were generic ('other relative to what?'); the new
names align with the existing 'primary/secondary' terminology used
throughout the assert_all rollout and follow the Rust Vec<Foo>/foos
plural-of-singular convention.

Also marks the field with #[serde(default, skip_serializing_if =
'Vec::is_empty')] so it is omitted from JSON output for any test that
has no secondary failure data — plain unit tests, fuzz tests, passing
tests. Pre-PR JSON consumers continue to see the same shape on those
results. invariant_failure_dir and assert_all_invariant_count already
had Option::is_none guards.

Updates the SimpleContractTest{NonVerbose,Verbose}.json fixtures to
drop the now-skipped empty field.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019dce2d-57c7-734a-bbc6-6fa5e34b25de

* test(invariant): assert_all + fail_on_revert=false attributes assert() to all live invariants

Amp-Thread-ID: https://ampcode.com/threads/T-019dd262-ed81-723c-aaaa-8e1314bed45a
Co-authored-by: Amp <amp@ampcode.com>

* feat(invariant): decouple handler-side assertions from invariant predicates

Handler-side assertion failures are now tracked in a dedicated broken_handlers
map keyed by (reverter, selector) instead of being attributed to every live
invariant. They surface in their own "Suite handlers:" report section,
keeping invariant predicate breaks rendered separately.

Under assert_all = true (the new default), the campaign continues for the
full budget after a preflight invariant failure so handler-side bugs and
still-live invariants can be discovered. The legacy abort-on-preflight
behavior is preserved when assert_all = false.

Live progress (progress bar + JSON pulse events) now surfaces unique
handler bug counts alongside invariant failure counts so both classes are
visible during the campaign.

Tests:

- New regression test assert_all_handler_assertion_routed_to_handler_section
  asserting the "handler bug != invariant break" semantics.
- Existing handler-assert tests (invariant_fail_on_assert_panic,
  invariant_fail_on_vm_assert_*, etc.) updated to expect the new
  "Suite handlers:" rendering while keeping the failure-reason line.
- should_exit_early_on_invariant_failure now sets assert_all = false
  explicitly so it continues to exercise the legacy abort-on-preflight path.

Refs: foundry-rs#14437

* fix: avoid private intra-doc link in HandlerAssertionFailure

Amp-Thread-ID: https://ampcode.com/threads/T-019dd3bb-9f77-7224-9d25-acdf2c0dd095
Co-authored-by: Amp <amp@ampcode.com>

* refactor(invariant): fold broken_handlers into InvariantFailureMetrics

Move the unique handler-bug counter into InvariantFailureMetrics alongside
failures / unique_failures so all campaign-level failure counters live in
one struct, and drop the separate parameter from build_invariant_progress_json.
Snapshot is refreshed each iteration from failures.broken_handlers.

Addresses review comment on foundry-rs#14482.

* refactor(invariant): dedup handler-side assertion bugs by edge-coverage fingerprint

- Handler-side assertion bugs are now deduped by edge-coverage fingerprint of the asserting call, not by `(reverter, selector)`. Distinct paths → distinct bugs (Medusa/Echidna semantics).
- Smaller-reproducer rule: on fingerprint collision, the entry with the shortest `call_sequence` wins.
- Graceful fallback: when edge coverage is disabled (no `corpus_dir`), fingerprint falls back to `keccak256(reverter || selector)` — preserves prior behavior.

* feat(invariant): shrink handler-side assertion bug counterexamples

- Each handler bug's call sequence is shrunk post-campaign to the minimal prefix that still triggers the asserting (anchor) call. Anchor is pinned and never dropped; intermediate calls that themselves assert are rejected so we don't promote a different finding.
- DRY refactor in `shrink.rs`: extracted `run_shrink_loop` (shared by `shrink_sequence` and `shrink_handler_sequence`) and `replay_sequence` (shared by `check_sequence` and `handler_sequence_still_asserts`). `check_sequence_simple` + `check_sequence_with_accumulation` collapsed into one `check_sequence`.
- Error policy: invariant shrink keeps legacy "keep removed on error" semantics; handler shrink restores on error so a replay failure can never silently produce a non-reproducible counterexample.
- Belt-and-suspenders: shrunk handler sequences are replayed once more before being stored; on any failure we fall back to the original.
- Renderer: `HandlerAssertionFailure` now carries `original_sequence_len` so the existing `(original: N, shrunk: M)` format works for handler bugs.

* feat(invariant): persist and replay handler-side assertion bugs

- Each broken handler is written to `<failure_dir>/handlers/<edge_fingerprint>.json` (reusing `InvariantPersistedFailure` with `assertion_failure: true`); `(reverter, selector)` is stripped off and recovered from the last call's `(target, calldata[..4])` at replay time.
- At campaign start, every file in `handlers/` is replayed via `check_sequence` with `expect_assertion_failure: true`. Bugs that still reproduce are merged into the campaign's `handler_errors` (shortest-wins on collision); files that no longer reproduce are deleted in place; settings-incompatible files are left untouched.
- DRY refactor in `runner.rs`: extracted `replay_persisted_call_sequence` (shared by primary-invariant and handler-side replay) and a small `ReplayContext<'_>` bundle so both call sites stay short. `HandlerAssertionFailure::from_replayed_sequence` constructor centralises the (reverter, selector) recovery.
- E2E coverage in `handler_assertion_persisted_to_disk`: confirms the file is written on first run, that a `runs = 0` rerun fails purely from the persisted replay (asserting the new `Replayed handler-side assertion bug` warning), and that the file is deleted when the underlying contract is patched to no longer assert.

* perf(invariant): only hash edge coverage for asserting calls

- Narrow the `pre_merge_edges_hash` snapshot gate from `call_result.reverted` to `assertion_failure`. The hash is only consumed by the handler-bug dedup path; for every other reverted call (vm.assume, MAGIC_ASSUME, plain `require` failures, fail_on_revert-ignored reverts) we were keccak-ing the edge buffer for nothing. Net effect: a `keccak256` over the per-call edge buffer is now skipped on the dominant non-asserting revert path.
- `did_fail_on_assert` is hoisted above the snapshot and reused by the non-discarded branch, so the predicate is computed once per call instead of twice. `!discarded` short-circuit also reclaims the wasted hash on MAGIC_ASSUME-discarded calls.
- Drive-by `cargo fmt` on four unrelated lines touched by earlier commits.

* feat(invariant): pre-seed campaign with persisted handler bugs for live telemetry

* test(invariant): handler-bug coverage for multi-handler, post-shrink replay, settings invalidation

- `multi_handler_bugs_each_persist_independently`: two distinct handler contracts in the same campaign, each producing its own assertion bug. Confirms both surface in `Suite handlers: 2 assertion bug(s) found` and that the `handlers/` directory holds one JSON per fingerprint.
- `handler_bug_replay_is_idempotent_after_shrink`: noop-prefixed handler so the discovered sequence has to be shrunk. Reads the persisted JSON to assert it holds the post-shrink (anchor-only) sequence, then re-runs with `runs = 0` and asserts the report renders `(original: 1, shrunk: 1)` instead of growing back.
- `handler_persisted_failure_skipped_on_settings_change`: flips a tracked `InvariantSettings` field (`fail_on_revert`) between runs. With `runs = 0` and the persisted file present, asserts the `settings have changed` warning fires, the test passes (no replay), and the file is left intact for a future run with the original settings to pick up.

* fix(invariant): strict handler-bug replay rejects stale files and divergent shrinks

- Persisted handler-side assertion bugs were replayed via `check_sequence(expect_assertion_failure=true)`, which accepted any sequence-level failure as proof the bug still reproduced. Stale files were silently kept whenever an earlier call now asserted, when only the invariant predicate now broke, or when the anchor still asserted on a different code path.
- Shrinking checked only that the anchor still asserted; dropping a setup call could push the anchor through a different branch and produce a different bug under the original fingerprint, breaking dedup semantics and persisted identity.
- Add `replay_handler_failure_sequence` as the single strict path used by both startup replay and post-shrink verification: requires no pre-anchor assertion, anchor must assert, and recomputes the normalized edge fingerprint.
- Persisted-replay keeps a file only if the recomputed fingerprint matches the filename; the shrink predicate rejects candidates whose anchor fingerprint diverges from the originally recorded one.
- Move the public `CheckSequenceOptions` and new `HandlerReplayOutcome` to the top of `shrink.rs` next to the other shared types, and split the handler-bug e2e tests into their own `invariant/handler.rs` module.

* perf(invariant): drop per-call clone in campaign loop and restore O(k) replay fast path

- Replace the per-call `BasicTxDetails::clone()` in the campaign hot loop with a one-shot extraction of `(handler_target, handler_selector)` (Address + 4-byte Selector). The clone existed solely so `&tx` could be passed to `can_continue` alongside `&mut current_run`; with `can_continue` now taking the two scalars directly, helpers (`execute_tx`, `record_metrics`, `collect_data`) re-borrow the input from `current_run.inputs.last()` via field-level split borrows. Net effect: the `Bytes` calldata buffer is no longer copied on every fuzzed call.
- Also collapses the duplicate inline `(target, selector)` extraction in the `else` branch of `should_check_invariant` — both branches now reuse the snapshot.
- Restore the O(k) fast path in `replay_sequence`: when `accumulate_warp_roll == false` we iterate only the kept indices and pass `&calls[idx]` directly, skipping both the full-length scan over `calls` and the `BasicTxDetails::clone()` per kept call. The accumulating arm still walks the full sequence so warp/roll from skipped txs lands as a concrete delta on the next kept tx. Shrinking calls this in a tight loop, so for a length-`n` candidate shrunk to `k` calls the per-iteration cost goes from O(n) + k clones back to O(k) + 0 clones.

* feat(invariant): site-granular dedup for handler bugs + live pulse-metrics fix

- Change `broken_handlers` dedup key from edge-coverage fingerprint to
  `(reverter, selector)`, matching Echidna/Medusa per-function semantics.
  Multiple paths through the same handler collapse to one bug; persisted
  reproducers stay minimal via shortest-`call_sequence`-wins on collision.
- Persist handler bugs via stable site hash `keccak256(reverter || selector)`
  in `record_handler_failure` / `replay_persisted_handler_failures` so the
  on-disk artifact name is independent of edge coverage.
- Pulse metrics: bridge any newly-recorded invariant breaks into
  `failure_metrics` regardless of the `can_continue` outcome via
  `record_new_invariant_failures`. Previously `unique_failures` stayed at
  0 until *all* invariants in an `assert_all` run broke, since
  `can_continue` only returns false at that point. Now live pulses match
  the final report. Removed `first_broken_event` (no longer needed).
- E2E tests updated to assert site-granular behavior (multiple paths
  through one selector collapse to one bug).
- TODO noted for finer-grained dedup of multiple distinct `assert(...)`
  failures within the same handler.

* fix(clippy): use values() instead of iter() with unused key

Amp-Thread-ID: https://ampcode.com/threads/T-019df149-03af-722b-abda-d8ab6332ee3b
Co-authored-by: Amp <amp@ampcode.com>

* fix(clippy): drop redundant references in trimmed_hex format args

Amp-Thread-ID: https://ampcode.com/threads/T-019df149-03af-722b-abda-d8ab6332ee3b
Co-authored-by: Amp <amp@ampcode.com>

* feat(fuzz): generate msg.value for payable functions in invariant fuzzing

Adds automatic msg.value generation for payable functions during initial
call generation and corpus mutation. Stacked on top of foundry-rs#14482.

- Add value: Option<U256> field to CallDetails and BaseCounterExample
  (serde-default for corpus back-compat)
- Add fuzz_msg_value() proptest strategy and generate_msg_value() runner
  helper, biased toward small values (85% none / 10% wei / 4% milli-eth /
  1% eth for proptest; 60/30/9/1 for runner)
- Detect payable mutability in fuzz_contract_with_calldata and compose
  the value strategy
- Forward value through execute_tx with balance-aware fallback to 0 when
  sender has insufficient balance
- Mutate value with 15% probability in abi_mutate for payable functions
- Render value in counterexamples (regular: value=X; solidity: {value: X})
- Add invariant_msg_value cli test

Excludes the all-call mutation strategy, sender-mutation, and max_deal
config from the original PR foundry-rs#13177 — those are independent concerns.

Based on foundry-rs#8644

Co-authored-by: QiuhaoLi <qiuhaoli@outlook.com>

* fix(fuzz): bound msg.value by sender balance, use UintStrategy for value generation

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: George Niculae <george@gxn3ql7y5j.tail388b2e.ts.net>
Co-authored-by: QiuhaoLi <qiuhaoli@outlook.com>

* fix(invariant): gate handler success on pending changeset only

Avoid mutating GLOBAL_FAIL_SLOT in committed backend state to clear the
poisoned flag after recording a handler bug. Instead, route the invariant
runner's view-call success checks through is_success_handler_gate, which
only counts the slot as failed when this call's own changeset writes it,
ignoring a stale committed 1 from a previously-recorded handler bug.

Preserves deterministic fuzzing paths and stops the poisoned flag from
suppressing later assert_invariants / afterInvariant evaluations.

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: George Niculae <george@gxn3ql7y5j.tail388b2e.ts.net>
Co-authored-by: QiuhaoLi <qiuhaoli@outlook.com>
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from 8a199c9 to 479afad Compare May 24, 2026 00:30
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from 479afad to 15585f9 Compare May 31, 2026 00:33
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from 15585f9 to 30565c0 Compare June 7, 2026 00:35
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from 30565c0 to b4ea824 Compare June 14, 2026 00:37
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch 2 times, most recently from 7bd90eb to f4a9ab5 Compare June 28, 2026 00:33
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from f4a9ab5 to 3f308a0 Compare July 5, 2026 00:28
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch 2 times, most recently from c39993f to e2bd775 Compare July 19, 2026 01:53
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch 2 times, most recently from cc7b911 to affb344 Compare August 2, 2026 01:57
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from affb344 to bb0fb13 Compare August 9, 2026 01:03
Flake lock file updates:

• Updated input 'fenix':
    'github:nix-community/fenix/f374034' (2026-04-25)
  → 'github:nix-community/fenix/7f3ed5f' (2026-08-15)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/8954b66' (2026-04-21)
  → 'github:rust-lang/rust-analyzer/baabc58' (2026-08-13)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/01fbdee' (2026-04-23)
  → 'github:NixOS/nixpkgs/6b5e5b7' (2026-08-13)
@github-actions
github-actions Bot force-pushed the update_flake_lock_action branch from bb0fb13 to 2095a3b Compare August 16, 2026 00:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants