You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Can invariant-only Ultrafuzz autonomously rediscover an Aave v4 benchmark bug family? #522
Can the sealed, stock Ultrafuzz invariant-only workflow—using GPT-5.5 with xhigh reasoning—autonomously rediscover at least one semantic bug family from the ScFuzzBench Aave v4 benchmark when given a genuinely stateful one-hour Recon campaign?
Controlled target
Repository: aave/aave-v4
Release: v0.5.6
Commit: 6959e3219b5506bf2acae18551cbb2a68a5b8fba
Mode: invariant-only
Model for every model-authored workflow node: GPT-5.5, xhigh reasoning
Prompts/topology/configuration: sealed stock inputs; no manual harness or property steering
Campaign requirement: Recon runs for approximately 3,600 seconds with --timeout 3600, --test-limit 18446744073709551615, and a stateful sequence length of 100
Evaluation: preserve benchmark blindness until generation, campaign, verification, dedupe, triage, severity classification, aggregation, and final reporting are complete; then compare semantic bug families rather than names or exact test shapes
Why this remains unanswered
The first otherwise-successful controlled run completed all 75 workflow nodes and executed an approximately one-hour Recon campaign with 11,183,800 calls and no property-derived findings. However, it inherited seqLen: 1 from the generated smoke configuration. Each action therefore reset to constructor state, making multi-transaction protocol states unreachable. That run is evidence about a one-step campaign, not a valid answer to the stateful research question.
The general validity defect is tracked in #520 and addressed by #521, which requires and verifies --seq-len 100 for current stateful invariant campaigns.
Completion criterion
After #521 is merged and the sealed experiment is rerun without manual steering:
Verify the immutable target, model/reasoning profile, invariant-only topology, and sealed inputs.
Verify the effective Recon command and typed artifacts prove the full timeout, unbounded test limit, and sequence length 100.
Verify the workflow and all artifact gates complete successfully.
Only then unblind against the ScFuzzBench scaffold/run and classify semantic overlap.
Record whether at least one benchmark bug family was autonomously rediscovered, including exact run IDs and reproducible evidence; a well-formed negative result also answers the question.
Status
In progress. The detached systemd supervisor is healthy and is currently validating the general sequence-length fix before the controlled stateful rerun.
Closing as the completed sequence-length experiment
The sequence-length question this issue was reopened around is answered. The benchmark-rediscovery
research question is not answered, and it moves to #531 with a matched control and a
pre-registered protocol. Correcting the record below, because an earlier reading of this evidence
was wrong in two ways.
Correction 1: there is no completed seq-len-100 Ultrafuzz run
The workflow run aave-v4-v056-gpt55-xhigh-invariant-only-20260812-seqlen100-full-timeout is canceled, 1m17s in, with project-discovery failed and 24 of 25 nodes still pending. Its
campaign prompt digest is identical to the seq-len-1 run's, so it was never configured for
sequence length 100 in the first place.
The seq-len-100 numbers come from a standalone recon fuzz invocation run by hand inside a git
worktree of the preserved stateful-invariant-campaign workspace
(.ultrafuzz/experiments/aave-v4-v056-20260812-seqlen100), between 2026-08-12T01:09:42Z and
02:09:45Z. Its argv was never recorded in any typed artifact; the effective sequence length is
provable only from the backend log:
INFO campaign::campaign: Starting fuzzing campaign with 32 workers
INFO campaign::campaign: Test limit: 18446744073709551615 (shared across all workers), Sequence length: 100
Every typed artifact under that experiment path (recon-fuzzer-results.json, campaign-plan.json, campaign-summary.json, campaign-report.md) actually describes the seq-len-1 baseline: they
carry run_id: aave-v4-v056-gpt55-xhigh-invariant-only-20260811-full-timeout, an exact_command
with no --seq-len flag, exact_config.summary.seqLen: 1, and failure_count: 0. The
experiment's echidna.yaml still says seqLen: 1 and is byte-identical to the config digest
recorded in recon-fuzzer-results.json, which is how we know the 100 arrived as a CLI override.
So: no sealed, gated, end-to-end stateful run exists for this target. Any statement that a
completed seq-len-100 Ultrafuzz campaign produced typed findings here is unsupported.
What the hand-run campaign did show
Measure
seq-len 1 (sealed run)
seq-len 100 (hand-run)
Workers
32
32
Campaign seconds
3,510.5
3,596.7
Total calls
11,183,800
85,003,705
Unique instructions
76,177
94,315
Corpus retained
63
88
Failures
0 / 58
1 / 58
Sequence length is not a marginal knob: 7.6× the calls (seq-len 1 pays a full state reset per
call), +23.8% unique instructions, and the first falsification landed 0.06 s into the campaign.
The stock campaign prompt on current origin/main does require --seq-len 100 (verified at 682d51a3, .ultrafuzz/prompts/strategies/invariants/invariant-testing-campaign.md:111); the
seq-len-1 inheritance in this project came from its pre-#521 project-local prompt copy, whose
long-campaign template omitted --seq-len entirely.
Correction 2: the counterexample is an incomplete-spec counterexample, not a rediscovery
One property broke: property_hub_spoke_aggregate_conservation()
(test/foundry/recon/Properties.sol:179), on the assertion at line 191:
This is a defect in the generated specification, not a discovery about the protocol:
The assertion is arithmetically unsatisfiable as written.Hub.getSpokeAddedAssets is
literally toAddedAssetsDown(...) (src/hub/Hub.sol:615) — a per-spoke round-down — while getAddedAssets (src/hub/Hub.sol:522) returns the untruncated total. A sum of floors is ≤ the
true total by construction, so total <= sum_of_floors fails the moment the share price is
non-integral, which one interest-accruing call guarantees. Two non-empty spokes each lost 0.5 wei
to truncation.
The benchmark states the same invariant in the opposite direction, with tolerance.
ScFuzzBench's INVARIANT_3_HUB_ADDED_ASSETS_GREATER_THAN_SPOKE_SUM asserts hub added assets ≥ the spoke sum, compares round-down against round-down
(previewRemoveByShares), and routes the delta through a relative-difference helper, so a 1-wei
delta on a 2.17e11 base integer-divides to zero and passes. The witness state does not
violate the benchmark property; family 3 refutes it.
There is no injected bug to rediscover on this target.diff -rq against scfuzzbench/aave-v4-scfuzzbench differs in exactly three files, none of them protocol logic: WETH9.sol (custom errors), AaveOracle.sol (a setSpoke test hook), and SpokeInstance.sol
(_disableInitializers() commented out for the fuzzer). Hub.sol, Spoke.sol and every
library are byte-identical to upstream Aave v4 v0.5.6.
The omission is local and traceable. Two lines above, the same generated file uses a
rounding-tolerance helper for the same class of aggregation
(_withinRoundingTolerance(reserveDrawn, aggregate.drawnDebt, aggregate.borrowerCount)).
Line 191 uses a bare strict inequality with no per-spoke tolerance term. The property faithfully
transcribes docs/overview.md:57 ("Hub assets <= sum of Spoke assets (converted from shares)")
as an exact-arithmetic assertion; ScFuzzBench's maintainers read the same doc line and
deliberately reversed it and added tolerance.
Residual value, stated plainly: the counterexample is a genuine, reproducible documentation
defect — docs/overview.md:57 states the inequality in the direction the implementation does not
satisfy, and the implementation's actual direction (hub total ≥ sum of spoke claims; the hub never
over-credits) is the protocol-safe one. Informational at most. Zero economic impact, no protocol
bug, and no semantic benchmark-family credit.
Disposition
The sequence-length experiment is complete: sequence length 100 materially changes campaign
behaviour, and the requirement is enforced by merged fix(invariants): require stateful Recon sequences #521 on current main.
Answer to this issue's research question: still unanswered. No valid, sealed, gated,
end-to-end stateful invariant-only run exists for this target, and the one counterexample
produced is an incomplete-spec counterexample rather than a ScFuzzBench family rediscovery.
Point 3 of my closing comment ("There is no injected bug to rediscover on this target") is
misleading and should not be read as written.
The ScFuzzBench Aave v4 benchmark does not inject mutations into the protocol source. Its tests/recon/Properties.sol header states its method directly: it implements a pre-vetted list of
Audit Contest invariants (Sherlock contest 1209) against upstream Aave v4. Its 10 non-canary broken
identities are therefore genuine invariant violations observable in unmutated v0.5.6, not
artificial defects planted in a fork.
Consequences for how this issue should be read:
The byte-identical diff against the fork (3 harness-compatibility files, protocol logic
unchanged) shows the two source trees are the same unit under test. It does not show there is
nothing to find on 6959e321. The benchmark families are reachable on the clean upstream commit,
given equivalent executable oracles.
What the fork supplies that the clean target does not is the hand-authored oracle. That is
exactly the capability Ultrafuzz's invariant group is supposed to produce autonomously, so
running against clean aave/aave-v4@6959e321 and letting Ultrafuzz author its own properties is
the correct experiment — not a weaker substitute for running against the fork.
The conclusion of the closing comment is unchanged, and rests on points 1, 2 and 4: the 1-wei property_hub_spoke_aggregate_conservation counterexample is an incomplete-spec counterexample
because the generated assertion compares an untruncated total against a sum of per-spoke
round-downs and is unsatisfiable as written, and because the benchmark's own family-3 property
states the inequality in the opposite direction with a relative tolerance, so the witness state
does not violate it. Neither of those arguments depends on the presence or absence of injected
bugs.
The restarted evaluation in #531 will run against aave/aave-v4@6959e3219b5506bf2acae18551cbb2a68a5b8fba
with Ultrafuzz authoring its own invariants, and will not mimic ScFuzzBench's hand-authored suite.
Activity
aviggiano commentedon Aug 12, 2026
Closing as the completed sequence-length experiment
The sequence-length question this issue was reopened around is answered. The benchmark-rediscovery
research question is not answered, and it moves to #531 with a matched control and a
pre-registered protocol. Correcting the record below, because an earlier reading of this evidence
was wrong in two ways.
Correction 1: there is no completed seq-len-100 Ultrafuzz run
The workflow run
aave-v4-v056-gpt55-xhigh-invariant-only-20260812-seqlen100-full-timeoutiscanceled, 1m17s in, with
project-discoveryfailed and 24 of 25 nodes still pending. Itscampaign prompt digest is identical to the seq-len-1 run's, so it was never configured for
sequence length 100 in the first place.
The seq-len-100 numbers come from a standalone
recon fuzzinvocation run by hand inside a gitworktree of the preserved
stateful-invariant-campaignworkspace(
.ultrafuzz/experiments/aave-v4-v056-20260812-seqlen100), between 2026-08-12T01:09:42Z and02:09:45Z. Its argv was never recorded in any typed artifact; the effective sequence length is
provable only from the backend log:
Every typed artifact under that experiment path (
recon-fuzzer-results.json,campaign-plan.json,campaign-summary.json,campaign-report.md) actually describes the seq-len-1 baseline: theycarry
run_id: aave-v4-v056-gpt55-xhigh-invariant-only-20260811-full-timeout, anexact_commandwith no
--seq-lenflag,exact_config.summary.seqLen: 1, andfailure_count: 0. Theexperiment's
echidna.yamlstill saysseqLen: 1and is byte-identical to the config digestrecorded in
recon-fuzzer-results.json, which is how we know the 100 arrived as a CLI override.So: no sealed, gated, end-to-end stateful run exists for this target. Any statement that a
completed seq-len-100 Ultrafuzz campaign produced typed findings here is unsupported.
What the hand-run campaign did show
Sequence length is not a marginal knob: 7.6× the calls (seq-len 1 pays a full state reset per
call), +23.8% unique instructions, and the first falsification landed 0.06 s into the campaign.
The stock campaign prompt on current
origin/maindoes require--seq-len 100(verified at682d51a3,.ultrafuzz/prompts/strategies/invariants/invariant-testing-campaign.md:111); theseq-len-1 inheritance in this project came from its pre-#521 project-local prompt copy, whose
long-campaign template omitted
--seq-lenentirely.Correction 2: the counterexample is an incomplete-spec counterexample, not a rediscovery
One property broke:
property_hub_spoke_aggregate_conservation()(
test/foundry/recon/Properties.sol:179), on the assertion at line 191:Witness (assetId 0, five spokes), shrunk to a single state-changing call
target_user_borrow(0):getSpokeAddedAssets=[0, 108333333333, 108333333333, 0, 0]→ sum216,666,666,666hub.getAddedAssets(0)=216,666,666,667hub.getAddedShares(0)equals the aggregate exactly — sharesconserve; only the asset conversion differs.
Reproducer:
backends/recon-fuzzer/recon-corpus/reproducers/8908329350215675565.txt.This is a defect in the generated specification, not a discovery about the protocol:
Hub.getSpokeAddedAssetsisliterally
toAddedAssetsDown(...)(src/hub/Hub.sol:615) — a per-spoke round-down — whilegetAddedAssets(src/hub/Hub.sol:522) returns the untruncated total. A sum of floors is ≤ thetrue total by construction, so
total <= sum_of_floorsfails the moment the share price isnon-integral, which one interest-accruing call guarantees. Two non-empty spokes each lost 0.5 wei
to truncation.
ScFuzzBench's
INVARIANT_3_HUB_ADDED_ASSETS_GREATER_THAN_SPOKE_SUMasserts hub added assets≥ the spoke sum, compares round-down against round-down
(
previewRemoveByShares), and routes the delta through a relative-difference helper, so a 1-weidelta on a 2.17e11 base integer-divides to zero and passes. The witness state does not
violate the benchmark property; family 3 refutes it.
diff -rqagainstscfuzzbench/aave-v4-scfuzzbenchdiffers in exactly three files, none of them protocol logic:WETH9.sol(custom errors),AaveOracle.sol(asetSpoketest hook), andSpokeInstance.sol(
_disableInitializers()commented out for the fuzzer).Hub.sol,Spoke.soland everylibrary are byte-identical to upstream Aave v4
v0.5.6.rounding-tolerance helper for the same class of aggregation
(
_withinRoundingTolerance(reserveDrawn, aggregate.drawnDebt, aggregate.borrowerCount)).Line 191 uses a bare strict inequality with no per-spoke tolerance term. The property faithfully
transcribes
docs/overview.md:57("Hub assets <= sum of Spoke assets (converted from shares)")as an exact-arithmetic assertion; ScFuzzBench's maintainers read the same doc line and
deliberately reversed it and added tolerance.
Residual value, stated plainly: the counterexample is a genuine, reproducible documentation
defect —
docs/overview.md:57states the inequality in the direction the implementation does notsatisfy, and the implementation's actual direction (hub total ≥ sum of spoke claims; the hub never
over-credits) is the protocol-safe one. Informational at most. Zero economic impact, no protocol
bug, and no semantic benchmark-family credit.
Disposition
behaviour, and the requirement is enforced by merged fix(invariants): require stateful Recon sequences #521 on current
main.end-to-end stateful invariant-only run exists for this target, and the one counterexample
produced is an incomplete-spec counterexample rather than a ScFuzzBench family rediscovery.
reachability), Require source-backed rounding tolerances in generated conservation properties #425 (source-backed rounding tolerances — the direct generic lesson of the 1-wei
assertion), Generate sequence-wide ghost and operation state in BeforeAfter scaffolds #424 (sequence-wide ghost state), and Invariant campaign can report complete when Recon smart-filter drops a generated property #519 (smart-filter omissions). Coverage
denominator provenance is now Publish the complete coverage denominator instead of a scopeless standardized score #532 and campaign comparability classification is now Classify campaign comparability when required property families are deferred or unreachable #533.
Closing per #531.
aviggiano commentedon Aug 12, 2026
Correction to the closing comment
Point 3 of my closing comment ("There is no injected bug to rediscover on this target") is
misleading and should not be read as written.
The ScFuzzBench Aave v4 benchmark does not inject mutations into the protocol source. Its
tests/recon/Properties.solheader states its method directly: it implements a pre-vetted list ofAudit Contest invariants (Sherlock contest 1209) against upstream Aave v4. Its 10 non-canary broken
identities are therefore genuine invariant violations observable in unmutated
v0.5.6, notartificial defects planted in a fork.
Consequences for how this issue should be read:
diffagainst the fork (3 harness-compatibility files, protocol logicunchanged) shows the two source trees are the same unit under test. It does not show there is
nothing to find on
6959e321. The benchmark families are reachable on the clean upstream commit,given equivalent executable oracles.
exactly the capability Ultrafuzz's invariant group is supposed to produce autonomously, so
running against clean
aave/aave-v4@6959e321and letting Ultrafuzz author its own properties isthe correct experiment — not a weaker substitute for running against the fork.
property_hub_spoke_aggregate_conservationcounterexample is an incomplete-spec counterexamplebecause the generated assertion compares an untruncated total against a sum of per-spoke
round-downs and is unsatisfiable as written, and because the benchmark's own family-3 property
states the inequality in the opposite direction with a relative tolerance, so the witness state
does not violate it. Neither of those arguments depends on the presence or absence of injected
bugs.
The restarted evaluation in #531 will run against
aave/aave-v4@6959e3219b5506bf2acae18551cbb2a68a5b8fbawith Ultrafuzz authoring its own invariants, and will not mimic ScFuzzBench's hand-authored suite.