Skip to content

Hold the committed ABI directory against what alloy::sol! consumed - #283

Merged
thedavidmeister merged 1 commit into
mainfrom
2026-08-25-issue-205
Aug 25, 2026
Merged

thedavidmeister merged 1 commit into
mainfrom
2026-08-25-issue-205

Conversation

@thedavidmeister

@thedavidmeister thedavidmeister commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

Closes #205.

crates/bindings/src/lib.rs is the CONSUMER of everything LibCopyArtifacts
maintains, and nothing read it. #152 closed the sol half — contracts() and
crates/bindings/abi name each other exactly, and the copy script proves every
committed file fresh — but the consumed set is restated there as sol literals,
because the sol lane cannot read rust. So the two directions the issue's own
follow-up comment leaves open produced no signal in either lane:

  • a binding dropped from lib.rs while its committed file stays: contracts()
    still names it, the file still exists, the directory still counts, and the
    orphan is refreshed forever by a copy script for a consumer that no longer
    reads it;
  • a binding generated from anywhere but its committed copy: another name's
    file, a copy parked elsewhere, the live forge artifact.

What changed

  • crates/bindings/src/lib.rs: the sol! calls expand from one
    committed_bindings! list. The file is DERIVED from the binding's name
    (abi/<name>.json, which is LibCopyArtifacts.committedPath) rather than
    spelled per binding, so a single-binding retarget is no longer expressible.
    The same token list expands to consumed_artifacts(), so the crate reports
    what sol! consumed instead of restating it: it cannot name a binding this
    crate does not have, or miss one it does.
  • crates/bindings/tests/committed_abi.rs (new, runs in rainix-rs / rs-test):
    committed_directory_is_exactly_what_the_bindings_consume — the directory's
    entries are exactly <consumed>.json, every entry rather than every .json,
    so a file of any name that no binding reads fails.
    every_binding_is_generated_from_its_committed_artifact — each binding's
    generated ABI equals the ABI of the committed file of its own name.
  • crates/bindings/Cargo.toml, Cargo.lock: serde_json dev-dependency, to
    deserialize the committed artifact. alloy_json_abi::ContractObject::from_json
    needs an alloy-json-abi feature the alloy meta crate does not expose.

The committed directory is the oracle because it is the one thing both lanes can
read. The sol lane holds contracts() against it; this holds it against what
sol! consumed. contracts() and lib.rs then agree without either lane
restating the other.

Relation to #245 (issue #151)

Same shape, different coupling, deliberately the other lane. #245 holds
subgraph.yaml's mapping.entities against schema.graphql — a claim checked
against the file that is its other side rather than against a list restated in
the test — and it is Solidity because the things it compares against
(IMetaV1_2.MetaV1_2.selector, LibCopyArtifacts.contracts()) ARE Solidity, so
a check written elsewhere would have to re-spell them. This one is Rust for the
mirror-image reason: what it checks is what alloy::sol! consumed, which only
the rust lane can see at all. Disjoint files — #245 touches
subgraph/subgraph.yaml and test/subgraph/SubgraphManifest.t.sol, this
touches crates/bindings/** — so neither conflicts with the other.

QA

  • Discriminating tests: committed_directory_is_exactly_what_the_bindings_consume
    and every_binding_is_generated_from_its_committed_artifact, both new in
    crates/bindings/tests/committed_abi.rs. Neither can fail on base: base has no
    rust-lane check of the bindings at all, which IS the defect, so each was
    verified against the defect itself instead — M1 and M2 below re-introduce the
    two states the issue describes on top of this branch, and each test fails on
    the state it is about.
  • Mutations applied, each run as nix develop -c cargo test -p rain-metadata-bindings (baseline on this branch: 2 passed, 0 failed):
    • M1 crates/bindings/src/lib.rs: drop IDescribedByMetaV1 from the
      committed_bindings! list, leaving its committed file in place (the residue
      direction the issue's follow-up names) -> KILLED by
      committed_directory_is_exactly_what_the_bindings_consume: abi does not hold exactly the artifacts alloy::sol! consumed, left
      {IDescribedByMetaV1.json, IMetaBoardV1_2.json}, right
      {IMetaBoardV1_2.json}.
    • M2 crates/bindings/src/lib.rs: retarget every binding to
      abi/IMetaBoardV1_2.json -> KILLED by
      every_binding_is_generated_from_its_committed_artifact:
      IDescribedByMetaV1: the generated bindings are not the abi of its committed artifact. The directory test stays green, correctly: it is about names.
    • M3 crates/bindings/abi/: add an orphan IMetaBoardV1_2.alt.json -> KILLED
      by the directory test, which names the extra entry in the set diff.
    • M4 crates/bindings/src/lib.rs + tree: retarget the directory, abi/ ->
      abi_alt/ with the files moved with it -> KILLED, both tests, on the
      committed path they cannot find.
  • Oracle: the committed artifacts on disk (crates/bindings/abi/*.json), which
    forge script script/CopyArtifacts.sol writes and the sol lane already holds
    against LibCopyArtifacts.contracts(). Nothing expected is spelled in the
    test: the names come from the crate's own consumed_artifacts() and the ABIs
    are parsed out of the files. The one literal, ABI_DIR = "abi", is the test's
    own spelling of the directory rather than anything the sol! calls derive
    their paths from, which is what makes M4 a disagreement instead of something
    both sides follow together.
  • Category check: the issue asks for a cross-lane check of the coupling and, in
    the maintainer's follow-up, narrows to one direction — a rust-side retarget or
    a binding dropped from lib.rs while the file stays. Both are covered (M1, M2)
    and the sol-lane-visible directions stay covered by the sol lane. The issue's
    own suggestion, "a rust test asserting the set of files under
    crates/bindings/abi/ equals the set alloy::sol! consumes", is what
    committed_directory_is_exactly_what_the_bindings_consume is; the alternative
    it offers (a generated manifest both lanes read) is not taken, because the
    committed directory already IS a file both lanes read.

Also run: cargo fmt --check, cargo clippy -p rain-metadata-bindings --all-targets -- -D warnings, cargo check --workspace (this last is what says
crates/cli's uses of IDescribedByMetaV1 and IMetaBoardV1_2::emitMetaCall
still resolve against the regenerated bindings). The pre-commit bundle passed on
the commit. Not run locally: the sol lane and the rest of cargo test, per the
standing instruction not to run the full suite on this machine; CI on this
branch covers both.

That the sol lane cannot see M1 or M2 is not re-verified here: no .sol file in
this repo reads crates/bindings/src/lib.rs at all — CopyArtifacts.t.sol names
it only in comments and in a failure message — which is the whole reason the
consumed set is restated there.

Two things are deliberately not claimed:

  • internalType is dropped from both sides before comparison. alloy's expander
    never sets it (to_abi::ty_to_param leaves it None), so it is not a fact the
    generated bindings can carry, and comparing it would fail on every artifact.
  • The PATH sol! read is not asserted, only the content it produced. It does not
    need to be: a binding generated from a copy elsewhere is not stale until that
    copy diverges from the committed one, and the moment it does,
    every_binding_is_generated_from_its_committed_artifact fails; a path outside
    the crate directory breaks packaging loudly instead of silently.

All 11 checks pass on this branch, including rainix-rs / static / rs-static,
which had been failing repo-wide on an unrelated rainix hook bug.

🤖 Generated with Claude Code

The rust crate is the consumer of everything LibCopyArtifacts maintains, and
nothing read it: the sol lane restates the consumed set as literals, so a
binding dropped from lib.rs while its file stays, or generated from anywhere
but the committed copy, produced no signal in either lane.

The sol! path is now derived from the binding's name, and the same token list
that drives the sol! calls also expands to consumed_artifacts(), which
tests/committed_abi.rs holds against the directory on disk and against each
artifact's abi.

Closes #205

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 37 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a9ed132b-3fac-4383-97b6-58ee18b18d3f

📥 Commits

Reviewing files that changed from the base of the PR and between 4c17de0 and 0935c42.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • crates/bindings/Cargo.toml
  • crates/bindings/src/lib.rs
  • crates/bindings/tests/committed_abi.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thedavidmeister
thedavidmeister merged commit 3a2e0cf into main Aug 25, 2026
11 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

@coderabbitai assess this PR size classification for the totality of the PR with the following criterias and report it in your comment:

S/M/L PR Classification Guidelines:

This guide helps classify merged pull requests by effort and complexity rather than just line count. The goal is to assess the difficulty and scope of changes after they have been completed.

Small (S)

Characteristics:

  • Simple bug fixes, typos, or minor refactoring
  • Single-purpose changes affecting 1-2 files
  • Documentation updates
  • Configuration tweaks
  • Changes that require minimal context to review

Review Effort: Would have taken 5-10 minutes

Examples:

  • Fix typo in variable name
  • Update README with new instructions
  • Adjust configuration values
  • Simple one-line bug fixes
  • Import statement cleanup

Medium (M)

Characteristics:

  • Feature additions or enhancements
  • Refactoring that touches multiple files but maintains existing behavior
  • Breaking changes with backward compatibility
  • Changes requiring some domain knowledge to review

Review Effort: Would have taken 15-30 minutes

Examples:

  • Add new feature or component
  • Refactor common utility functions
  • Update dependencies with minor breaking changes
  • Add new component with tests
  • Performance optimizations
  • More complex bug fixes

Large (L)

Characteristics:

  • Major feature implementations
  • Breaking changes or API redesigns
  • Complex refactoring across multiple modules
  • New architectural patterns or significant design changes
  • Changes requiring deep context and multiple review rounds

Review Effort: Would have taken 45+ minutes

Examples:

  • Complete new feature with frontend/backend changes
  • Protocol upgrades or breaking changes
  • Major architectural refactoring
  • Framework or technology upgrades

Additional Factors to Consider

When deciding between sizes, also consider:

  • Test coverage impact: More comprehensive test changes lean toward larger classification
  • Risk level: Changes to critical systems bump up a size category
  • Team familiarity: Novel patterns or technologies increase complexity

Notes:

  • the assessment must be for the totality of the PR, that means comparing the base branch to the last commit of the PR
  • the assessment output must be exactly one of: S, M or L (single-line comment) in format of: SIZE={S/M/L}
  • do not include any additional text, only the size classification
  • your assessment comment must not include tips or additional sections
  • do NOT tag me or anyone else on your comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

contracts()/committedPath coupling to crates/bindings has no cross-lane oracle: dropped or retargeted binding artifacts go silently stale

1 participant