test(edr_solidity): consolidate all solx fixtures onto gen-solx-fixtures; retire the sweep fixture - #1574
Draft
nebasuke wants to merge 41 commits into
Draft
Conversation
Scenarios.t.sol is spliced at test time into the committed scenarios fixture, which was compiled once (solx 0.1.4) and is not regenerable in-repo — its AST and DWARF reference byte offsets into the source as it existed at generation time. The file is therefore append-only, but nothing enforced that: an edit inside the frozen prefix (a rename, a formatter run) shifts offsets and desyncs every line assertion on the fixture, in the worst case to plausible-but-wrong locations. Pin the frozen prefix (length + keccak) with a test whose failure message states the rule. Appending new sweep scenarios keeps it green.
…scenarios Two sweep scenarios for surfaces the corpus missed, appended to Scenarios.t.sol (the frozen-fixture prefix is untouched, guarded by scenarios_source_is_append_only): - BareModifierRevertTest: a modifier's bare `revert()` compiles to a shared helper that is unmapped in the DWARF and returns no data, so every revert heuristic misses. Pinned in scenariosDivergingFromSolc at today's shape: the target contract's frames vanish and the trace collapses to the test's own call site with the raw EvmError, where solc reports the `revert()` statement with a 3-frame stack. A fix is queued in #1552 (selector-based recovery + revert walk-back); when it lands this golden breaks in the improvement direction and the pin shrinks to the one-line guard-vs-revert residual (solx line-table merge, WONTFIX solx-side). - ExternalLibraryRevertTest: `public` library function, so the call is a DELEGATECALL into a separately deployed, link-referenced library and the frame must resolve inside the library's own debug info — unlike RevertingLib, whose `internal` function inlines into the caller. At parity with solc today; joins the strict check as a regression net. Verified against solx 0.1.4 (hardhat-solx's pin): 4/4 sweep tests green.
The regeneration tool lives in #1552, which is not on main yet; state the fixture's provenance (forge-std sources scrubbed from the committed input) directly instead.
…ucibility The "not regenerable" folklore around the scenarios fixture was overstated: verified by round-trip that forge-std's v1.14.0 GitHub tag (the npm registry package is stale) + this file's frozen prefix + the solx 0.1.4 release binary reproduce all 90 committed bytecode objects byte-identically, metadata hashes included. The debugInfo payloads differ only because solx embeds the build directory in the DWARF (solx#594); until that is fixed, regeneration is machine-dependent and the fixture stays frozen with the source append-only. Ship the verification as scripts/reproduce-scenarios-fixture.sh and document the scenario-adding workflow (append-only rule + guard test, dynamic key discovery, divergence-table pinning etiquette).
…he standard flow The scenarios fixture is not a special artifact needing an out-of-band reconstruction ritual — it is this project's own build output (the build-info pair from a solx-profile hardhat compile, forge-std contents blanked before committing). What that compile produces depends on the toolchain of the day: hardhat-solx's settings defaults and the solx release selected by SOLIDITY_TO_SOLX_VERSION_MAP. The committed snapshot predated hardhat#8419's optimizer-default fix, so the standard flow no longer reproduced it. Resolve that by regenerating the fixture through the standard flow with the fixed plugin (solx 0.1.4 via the current version map, explicit -O1, now including this PR's scenario contracts) instead of enshrining the stale snapshot, and re-pin what the new codegen moved: the four PC-anchored dwarf tests (the INVALID-opcode one now finds its instruction by opcode instead of PC) and the source-prefix guard's constants. All other pins hold, including the three provider goldens. The guard's contract is restated for the regen era: append freely for sweep-only scenarios; edits inside the generation-time prefix mean regenerate + re-pin + update the constants. Drop scripts/reproduce-scenarios-fixture.sh accordingly; the README's provenance section now documents the standard flow, the version-map dependency (0.8.34 -> 0.1.4 today, 0.1.6 bump planned), and the solx#594 caveat (embedded build dir) for regenerators.
One place answering, per committed compiler artifact: what it is, what generates it, and how to regenerate it — plus the conventions that were previously only discoverable by archaeology (source splicing, the CBOR solcx version stamp, the solx#594 debugInfo path caveat). Opens with the rule the scenarios-fixture investigation earned: regenerate through the documented flow and diff; never reconstruct out-of-band.
…rgences table Deferred until #1571's table rework landed to avoid conflicting.
…ios source The tests deserialize only `sources` and `contracts` and read only `project/contracts/Scenarios.t.sol`'s section; the rest of the verbatim compiler output was ~40 MB of forge-std artifacts (safeconsole.sol's AST alone is 7.6 MB). Keep only the section the tests read: 42.6 MB -> 2.9 MB on disk, fixture parse time 6.5 s -> 0.2 s for the 78-test suite. The regeneration command in the sweep README gains the corresponding jq filter; the committed file remains byte-for-byte that command's output.
With a hand-maintained GENERATION_PREFIX_LEN, a regeneration that forgot to update the constants stayed green while the guard silently under-covered the newly frozen suffix. The generation-time length is already recorded in the fixture itself — the end of the SourceUnit's `src` span — so read it from there: a regeneration now moves the hashed prefix automatically and forces the remaining keccak constant to be re-pinned (the failing assertion prints the new value).
`pnpm regen-fixtures` replaces the copy-pasted jq commands: it wipes `artifacts/build-info` (so exactly one fresh pair exists), compiles the solx profile, and writes the extracted pair to `crates/edr_solidity/fixtures/`. Pure Node — no jq dependency; `JSON.stringify(_, null, 2)` reproduces the committed bytes exactly (verified: running the script leaves a clean tree). Deliberately NOT part of `pnpm test`: a test run that rewrites the committed fixtures would absorb toolchain drift instead of detecting it, and test runs should not dirty the tree. A compare-only sync test can gate this in CI once hardhat-solx is published and the sweep runs there.
Same rules, plainer voice: say who uses the fixtures, lead with "don't hand-edit, regenerate", and phrase the toolchain-drift advice as a troubleshooting note instead of a directive.
…tures
Fresh-eyes review of the script found three quiet failure modes and one
hedge:
- `contracts/` is gitignored AND hardhat's sources dir, so a stray
leftover .sol was silently compiled into the committed input (its
blanked content easy to miss in review). Rebuild the dir from scratch
instead of only overwriting Scenarios.t.sol.
- A missing scenarios section in the compile output was silently
dropped by JSON.stringify and committed as `{}`, deferring the
failure to the Rust suite. Throw instead.
- Junk files (.DS_Store) in build-info tripped the exactly-one-pair
check spuriously; count only .json files.
- The closing message hedged ("if the fixtures changed..."); ask git
and say "nothing to re-pin" or "re-pin now". This also makes the
script a manual drift check: run it on a clean tree — a dirty tree
means the committed fixtures were stale.
…ommand Command and re-pin instruction first for the dev who just needs the flow; the toolchain/filter/splice details move to a "when a regeneration surprises you" list below. Same facts, reordered.
The one step of a codegen-changing regeneration that was still tribal knowledge (and the one that burned the fixture-provenance session): probe with decode_deployed_for + opcode filter under --nocapture, and appends never move sibling PCs, so this only applies on toolchain changes.
…ance closure The leaf-only cut in 76588e7 broke edr_provider's solx_stack_trace tests on CI: EDR's build model resolves forge-std-inherited functions (StdInvariant's targetSelectors() etc.) through the BASE contracts' ASTs via linearizedBaseContracts, and with those stripped the selector fix-up in correct_selectors hard-errors. The fixture's real invariant is that `sources` and `contracts` cover the same inheritance closure, not just the leaf source. regen-fixtures now computes the closure from the scenarios AST's linearizedBaseContracts, keeping Scenarios.t.sol + 7 forge-std bases: 7.6 MB (vs 42.6 MB verbatim, 2.9 MB broken leaf-only), and the console/safeconsole/Vm ASTs it drops are read by nothing. The scenarios file's own section is byte-identical to the previous fixture, so every PC pin and the guard hash hold. Verified: edr_solidity 78/78; edr_provider solx_stack_trace 3/3 (the two CI failures pass). README: closure invariant documented, both consumers added to the re-pin instructions, and a note to verify the CBOR solcx stamp after regenerating — a locally linked hardhat-solx build can silently bring a different version map.
…onsumer The closure filter in the sweep's regen script encoded a Rust-domain invariant (what the build model reads) on the wrong side of the language boundary. Invert the ownership: a new test runs extract_solx_contract_metadata — the function that failed on CI — over the committed scenarios fixture, so any over-filtered regen fails `cargo test -p edr_solidity` directly, in the fixture's own crate, with the real error. The JS filter is thereby demoted to a pure size optimization; nothing re-describes the build model's reads anywhere. Red-proven against the leaf-only fixture from the pushed tip: the test reproduces CI's exact "Failed to fix up the selector for ... DirectRequireTest" error. Green on the closure fixture (79 tests).
Extends solx_stack_trace.rs from 3 to 21 tests over the existing scenarios fixture: panic sub-codes (0x01/0x12/0x21/0x31/0x32), CREATE-path reverts (plain + internal-helper constructor), modifier bodies, cross-contract and mutually-recursive CALL chains, external recursion frame counts, internal helpers/recursion, internal libraries, fallback/receive bodies, and per-statement line discrimination between requires. Assertions stay at entry-variant + source line; frame-shape parity with solc remains the JS sweep's job. One golden divergence pinned: solx attributes a multi-statement modifier revert to the function declaration line (420) instead of the failing require (415), the provider-level twin of the sweep's NestedModifierRevertTest divergence. Also adds brief_trace so assertion failures print one line per entry instead of dumping the embedded source_content per SourceReference.
…ation #1139 moved the crate from crates/tools/ to crates/tool/cli/ without updating project_root(), which has resolved to crates/ instead of the repo root since — gen-execution-api joins its paths onto the wrong directory. One ancestor further up fixes it (and the new caller in gen-solx-fixtures).
…ifacts Adds a LongTail fixture (plain contracts, no forge-std) targeting the dispatch-level and call-shape StackTraceEntry variants the solc corpus covers but no solx test reached: FunctionNotPayable, UnrecognizedFunctionWithoutFallback, MissingFallbackOrReceive, both FallbackNotPayable variants, InvalidParams (truncated calldata), plus an externally linked library exercising linkReferences substitution and DELEGATECALL decoding into the library's own debugInfo, and DirectLibraryCall. Two golden inference gaps documented: where the solc route infers ReturndataSizeError and NoncontractAccountCalledError at the call site, the DWARF route currently degrades both to OtherExecutionError attributed to the contract declaration line. The pins flip loudly if inference improves. Also adds a gen-solx-fixtures subcommand to edr_tool_cli that regenerates the self-contained fixture outputs (counter, long_tail) against a given solx binary: it splices the fixtures/sources/ contents into the committed input JSON (whose content fields are deliberately empty), runs solx --standard-json, and rewrites the output JSON with solx's verbatim (minified) output. The committed long_tail output is produced by the subcommand with solx 0.1.5. The scenarios fixture is not regenerable from the repo (forge-std contents are scrubbed) and stays hardhat-generated; decode is comp_dir-independent, verified by running the counter test against a locally regenerated blob.
…tack traces solx attributes compiler-generated helper code (calldata decoding, shared revert builders) to the *declaration* of the enclosing function or contract, where solc leaves such code unmapped. Two inference paths broke on that difference: 1. fails_right_after_call / is_call_failed_error compared every post-CALL step location to the call site by strict equality, so declaration-level padding (whose range merely contains the statement) made the heuristics bail and reverts degraded to OtherExecutionError at the contract declaration line. New TraceStrategy::locations_equivalent treats a containing (declaration-level) location as "still at the statement" for solx; solc keeps strict equality. 2. A shared revert helper flattened out of a modifier is attributed to the modified function's declaration line. SolxTraceStrategy:: revert_source_reference now walks the executed steps back from a declaration-line revert to the statement that led there (the require's message-building code keeps its own line), matching solc. Provider-path effects (goldens flipped in solx_stack_trace.rs): - ReturndataSizeError now surfaces at the call site for both a returndata-size mismatch and a typed call to a codeless account (parity with solc >= 0.8.10, which emits no EXTCODESIZE probe for returndata-expecting calls), replacing OtherExecutionError at the contract declaration. - A multi-statement modifier revert reports the failing require's line (415) instead of the function declaration line (420), removing the provider-level twin of the sweep's NestedModifierRevertTest divergence. The solc strategy is behavior-identical; edr_solidity unit tests (77) and the edr_provider integration harness (198) pass.
cargo run -p edr_tool_cli -- gen-solx-fixtures <solx-0.1.5-binary> The deployed bytecode changes, but every trace golden holds: edr_solidity unit tests (77) and the solx provider suite (31) pass unchanged. The scenarios fixture remains as generated by hardhat-solx (not regenerable from the repo).
Review feedback: 'long tail' described the coverage gap the fixture was created to fill, not its contents. StackTraceScenarios names what it is — the self-contained, regenerable counterpart to Scenarios.t.sol for provider-path stack-trace tests. Fixture regenerated via gen-solx-fixtures (the source name is embedded in the compiler output and DWARF).
Review findings on the walk in SolxTraceStrategy::revert_source_reference: 1. The walk was unbounded — the first resolvable location off the declaration line won, even if it belonged to the dispatcher or an internal function executed earlier in the message. It now only accepts statements of the failing function itself or of a modifier (flattened into its frame, possibly from another file — pure function containment would regress the NestedModifierRevertTest golden); a statement of any other function ends the walk and keeps the declaration reference. 2. Both the trigger and the skip compared bare line numbers, which don't identify a location across files. Declaration matching now requires file identity (via SourceLocation::contains, which checks the source file pointer). 3. The trigger only recognized the function declaration line, though solx also attributes helpers to the enclosing contract declaration; both lines now count as declaration-level padding. The locations_equivalent containment proxy reviewed alongside is kept as-is: it can only over-match when every instruction of a later statement is declaration-attributed, and the parity sweep guards that residual. New GuardedBareRevert scenario (a modifier's bare revert() — no message- building code keeps its own line): empirically the DWARF route attributes the whole helper to the contract declaration, whose location has no containing function, so inference degrades to OtherExecutionError at the contract-declaration line before revert_source_reference is consulted (solc reports RevertError at the revert() line). Pinned as a golden gap; fixing it needs failing-function resolution for containing-function-less locations, out of scope here. No existing golden changed.
…rary Review finding: the helper substituted every placeholder with the single provided address, silently mis-linking bytecode that references more than one distinct library. Same-library placeholders legitimately repeat, so require all placeholders to share one hash and bail otherwise.
Review finding: run_solx wrote the whole input before reading stdout, which deadlocks if the input exceeds the OS pipe buffer while the child is already filling its stdout pipe. Standard-json compilers read stdin to EOF before emitting output, so this cannot fire today — but fixtures grow and the fix is a scoped thread.
A modifier's bare revert() compiles to a shared helper that solx leaves entirely unmapped (no DWARF line at all — probing corrected the earlier contract-declaration reading), and its return data is empty, so every revert heuristic missed and inference fell through to OtherExecutionError at the contract start. check_last_instruction's revert branch now falls back to a strategy hook when the reverting instruction has no resolvable containing function: TraceStrategy::declaration_attributed_failing_function resolves the called function from the calldata selector under solx (solc returns None — behavior-identical, its reverts are mapped). The fallback only engages for REVERT opcodes and only when the calldata decodes for the resolved function, so dispatch-level reverts on undecodable calldata still reach the InvalidParamsError classification below. revert_source_reference now takes Option<&SourceLocation>: an unmapped instruction gets the same walk-back recovery as declaration-attributed padding, with the failing function's start as last resort (unmapped and contract-declaration locations can't become source references themselves). Golden flip: the GuardedBareRevert scenario now reports RevertError at the guard condition (line 67, inside the modifier) instead of OtherExecutionError at the contract declaration. solc reports the revert() statement itself (line 68); that last line is absent from the DWARF, so closing the residual delta is solx line-table fidelity, not inferrable EDR-side.
… solx 0.1.6 Deployed and creation bytecode are identical modulo the trailing metadata hash; only the DWARF debugInfo payloads change (line 0 for compiler-generated code, solx#582; Yul AST refs for inline assembly, solx#583). All 32 provider goldens and 77 edr_solidity tests pass unchanged: line-0 rows decode to `line: None`, and the decoder's Pass-3 function-AST fallback feeds the same declaration-attributed walk-back as before. The `scenarios` fixture stays on its original solx 0.1.4 (per its CBOR `solcx` stamp) — not regenerable in-repo, see `solx_fixtures.rs`.
…rtifacts The declaration-attributed walk-back trigger was expected to become legacy-only once solx emitted line 0 for compiler-generated code (solx#582). It does not: line-0 rows decode to `line: None`, Pass 2 skips them, and the decoder's Pass-3 fallback synthesizes the failing function's AST location — declaration-level — so the same trigger is the permanent consumer path for current artifacts. Prove it with a `ValidatedCounter` scenario in the regenerable fixture: the flattened `validates` modifier's revert helper walks back to the `require` that fired, matching solc, on 0.1.6 output. The pre-0.1.6 raw declaration-line attribution stays covered by `nested_modifier_revert_points_at_failing_require` (scenarios fixture, frozen on its original solx 0.1.4). Strategy comments updated to name both sources of declaration-level padding.
…declaration Since 0.1.6 solx emits DW_AT_call_line 0 for inlined subroutines whose call site is compiler-generated code — most importantly user functions inlined into the `__entry` dispatch. The decoder treated line 0 as unresolvable and dropped the range from inline_call_sites, which cost solx traces the callstack frame for the called function (solc renders that frame at the function's declaration line). Fall back to the abstract origin's decl_file/decl_line — the inlined function's own declaration — when call_file/call_line are absent or zero. DWARF gives us no decl_column, so the resolved offset sits at the line start; skip the indentation so the location lands on the declaration's first token, inside the function's AST span, where get_containing_function can name it. Proven red on the regenerated 0.1.6 fixture: the `unlucky` require of ValidatedCounter carried no inline call site for bumpIfValid at all.
…tom frame intermediate_frames seeded its duplicate-frame check with the raw failing function — the containing function of the reverting instruction's location. Under solx 0.1.6 line-0 emission that location is the decoder's Pass-3 synthesized function declaration, so for a revert inside a flattened modifier the seed named the modified function while the bottom frame actually renders under the modifier (via the revert walk-back) — and the called function's frame, just recovered from its line-0 dispatch call site, was dropped as a "duplicate". Result: solx traces lost the middle `Contract.function` frame that solc renders (sweep scenarios ModifierRevertTest / NestedModifierRevertTest went from 3 frames to 2 with solx 0.1.6). Seed the dedup with the bottom frame's already-resolved source reference instead, computing that entry before the intermediate frames at each call site. Covered by a cross-contract golden on the regenerated fixture: ValidatedCounterCaller -> ValidatedCounter keeps the bumpIfValid declaration frame between the caller frame and the modifier revert. Sweep validation (hardhat-solx, -O1): strict parity green on 0.1.6; still green on 0.1.4/0.1.5, so the change is inert for pre-line-0 artifacts.
The regenerable fixture inputs relied on solx's default optimization level. Trace shapes differ per mode (the 0.1.6 inlined-frame loss reproduced differently at -O1 vs the default), so a change to solx's default would silently change what the fixtures test on the next regeneration. Pin settings.optimizer.mode to "3" — byte-identical to today's default output, verified by a zero-diff regen.
The goldens assert entry variant + source line; frame-shape parity was delegated entirely to the JS sweep, which is not in CI and runs at a different optimizer mode and solx version — the 0.1.6 inlined-frame loss passed all 32 goldens. Add assert_trace_shape (brief_trace equality) to four representative shapes: cross-contract call, cross-contract modifier revert (the recovered middle frame), external recursion, and a linked-library DELEGATECALL. A gained frame reads as an improvement to re-pin; a lost frame is a regression.
With the bare-revert attribution fix in this PR and solx 0.1.6's statement-line mapping for assembly opcodes (solx#583), BareModifierRevertTest, InlineAssemblyRevertTest and InvalidOpcodeTest all match solc exactly; only InternalRecurseTest remains pinned. The pin set now assumes hardhat-solx maps 0.8.34 to solx 0.1.6.
…-solx-fixtures The fixtures README predates this PR's edr_tool_cli generator: the Counter row still described the manual solx --standard-json flow and the stack_trace_scenarios pair had no row at all.
…0.1.6 pnpm regen-fixtures against hardhat-solx's 0.1.6 map; the input pair is byte-identical, only the compiled output moved. Re-pins in the same commit, per the sweep README: solx 0.1.6 emits .debug_line rows for assembly opcodes (solx#583), so the inline-assembly REVERT and the invalid() INVALID now resolve to their statement lines (135, 183) instead of the function-decl fall-back. PC anchors verified unchanged via the documented decode_deployed_for probe.
…-solx's -O1 The gen-solx-fixtures inputs pinned optimizer mode "3" (solx's own default); the pipeline they stand in for compiles at hardhat-solx's -O1 default. Flip the pin to "1" and regenerate at solx 0.1.6. One golden improves: the bare-modifier-revert bottom frame lands on the revert() statement (line 68) instead of stopping at the guard condition (67) — that residual was a mode-3-only shape (shape 4 of the solx line-attribution issue). Coverage of other optimizer modes is deliberately deferred.
…shapes Plain-contract equivalents of every Scenarios.t.sol shape whose Rust tests will move off the frozen sweep fixture: the require/assert/panic family, custom error, constructor paths, recursion trio, helper chains, internal library, modifiers, fallback/receive, assembly revert and invalid(). Adds a second source file with a base contract so the multi-source inheritance resolution (linearizedBaseContracts across files) stays exercised by a regenerable fixture. Existing line pins are unshifted; all 80 + 34 tests hold on the regenerated output.
…ture Every breadth test now runs against the regenerable stack-trace fixture's plain-contract equivalents; the sweep fixture has no provider consumers left. Dropped nested_modifier_revert_points_at_failing_require — its doc already declared it superseded by nested_modifier_revert_walks_back_from_line_zero_helper on current artifacts. Added inherited_function_revert_points_into_the_base_source_file to keep cross-file inheritance resolution (linearizedBaseContracts) covered end-to-end now that the forge-std closure is gone.
All PC- and line-anchored dwarf tests now decode the regenerable stack-trace fixture; anchors re-derived via the documented probe. The two apparatus tests go with the dependency: scenarios_source_is_append_only (no frozen fixture left to splice into) and scenarios_fixture_satisfies_contract_metadata_extraction (gen-solx-fixtures writes verbatim output — there is no filter left to police). pin_invariant_failure is dropped as a duplicate of pin_direct_require: its forge invariant framing was the only difference, and that never reached the DWARF.
…apparatus With every Rust consumer on the gen-solx-fixtures pair, the committed sweep fixture had no readers left: the sweep compiles its corpus live on every run and never read it. Deleted: the 7.5MB fixture pair, scripts/regen-fixtures.js, and the pretest copy step. Scenarios.t.sol moves into the sweep project as an ordinary committed source — the frozen-prefix/append-only regime existed only because the file was spliced into a fixture compiled from an older version of itself. Docs collapse to one regen flow (gen-solx-fixtures); the PC-anchor probe how-to moves to the fixtures index next to the tests it serves.
🦋 Changeset detectedLatest commit: 4a205a8 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
nebasuke
force-pushed
the
feat/solx-stack-trace-provider-coverage
branch
11 times, most recently
from
July 28, 2026 12:22
601e5e3 to
099d9c8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Claude summary
Stacked on #1552 (review/merge that first — the first ~20 commits here are its). This PR finishes what #1552 and #1572 set up: every Rust test that pinned solx DWARF/trace behavior against the committed sweep fixture now pins it against the regenerable
stack_trace_scenariosfixture instead, and the sweep fixture — with its entire regeneration apparatus — is deleted. One regen flow remains for all committed solx fixtures:cargo run -p edr_tool_cli -- gen-solx-fixtures <solx>.Why
The repo carried two fixture regeneration flows. The
gen-solx-fixturestool (from #1552) regenerates its fixtures from committed inputs with any solx binary — no JS toolchain involved. The sweepscenariosfixture (faithful capture since #1572) needed the hardhat-solx pipeline, apnpm regen-fixturesscript, and a frozen-prefix regime: because the liveScenarios.t.solwas spliced into a fixture compiled from an older version of itself, the source was append-only, enforced by a keccak prefix-hash guard, documented in four places, with a planned CI sync test to keep source and fixture from drifting.All of that apparatus existed to serve the fixture's Rust consumers. Moving the consumers dissolves it: the sweep itself compiles its corpus live on every run and never read the committed fixture.
What moved
StackTraceScenarios.sol): ~20 new plain contracts mirroring the sweep scenarios' shapes — the require/assert/panic family (0x01/0x11/0x12/0x21/0x31/0x32), custom error, constructor reverts (direct + via internal helper), the recursion trio (external deep, internal, cross-contract mutual), internal helper chain, internal library, modifiers, fallback/receive reverts, inline-assemblyrevert,invalid(). Forge-std is not needed: every pinned DWARF shape is a plain contract construct — the forge harness framing (invariant/fuzz/expectRevert) never reached the DWARF and stays sweep-only.edr_providerbreadth tests (18): now deploy the plain equivalents from the regenerable fixture. Assertions unchanged in kind — entry variant + source line, fullassert_trace_shapepins where they existed.edr_soliditydwarf pins (20): all line- and PC-anchored tests re-anchored against the new fixture via the documented probe. Every predicted line and trace-shape pin held on the first run; only three PC anchors needed re-deriving (0x26divide REVERT,0x36assembly REVERT,0x87modifier require — the last one landed on the same PC as the old corpus).Coverage preserved deliberately
StackTraceScenariosBase.solwith a base contract, inherited by a scenario contract in the main file. The build model resolves inherited functions through base contracts' ASTs (linearizedBaseContracts) — the exact shape that broke CI during test(solx-parity-sweep): add bare-modifier-revert and linked-library scenarios #1572's fixture cut — and a new provider test asserts an inherited function's revert resolves into the base's own source file. Thegen-solx-fixturessourcestable is multi-entry now.Deleted, and why that's safe
solx_compiler_{input,output}_scenarios.json(7.5 MB)scripts/regen-fixtures.js+pnpm regen-fixturesscenarios_source_is_append_only(keccak guard)scenarios_fixture_satisfies_contract_metadata_extractiongen-solx-fixtureswrites solx's verbatim output — no filter existsmaybe-build.jsScenarios.t.solnow lives in the sweep project directlypin_invariant_failurepin_direct_requireonce the forge invariant framing (which never reached the DWARF) is gonenested_modifier_revert_points_at_failing_requirenested_modifier_revert_walks_back_from_line_zero_helperon current artifactsThis also retires the planned compare-only CI sync test from the sweep-in-CI follow-up list: there is no committed sweep artifact left to drift. The remaining Rust↔sweep coupling is intentional duplication — the same shapes exist in two independently compiled corpora, one pinned in Rust against a committed fixture, one checked live against solc for parity.
Sweep side
Scenarios.t.solmoves into the sweep as an ordinary committed source (contracts/is no longer gitignored). Adding a sweep scenario is now: edit the file, runpnpm test— no fixture regen, no frozen prefix, no cross-crate re-pin. The sweep's own behavior is untouched: same corpus, same live compile, same divergence pins (InternalRecurseTestline references unchanged).Notes
edr_solidity77/77;edr_provider --test main solx_stack_trace34/34; sweep 4/4 against solx 0.1.6; a freshgen-solx-fixturesrun after all changes is a zero-diff no-op (idempotent); provider suite at its known baseline (the 12 fork tests needALCHEMY_URL).-O1, so this rides the pendingSOLIDITY_TO_SOLX_VERSION_MAP0.8.34 → 0.1.6 bump.no changeset needed.