Skip to content

CloneFactory: concrete only delegates to LibICloneableFactoryV4 - #21

Merged
thedavidmeister merged 5 commits into
mainfrom
2026-08-20-delegate-to-lib
Aug 20, 2026
Merged

CloneFactory: concrete only delegates to LibICloneableFactoryV4#21
thedavidmeister merged 5 commits into
mainfrom
2026-08-20-delegate-to-lib

Conversation

@thedavidmeister

@thedavidmeister thedavidmeister commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Reworks the concrete CloneFactory to use rain.factory properly: the concrete is now nothing but one delegation per entry point into LibICloneableFactoryV4, per the model that the logic lives in the library repo and the deploy repo ships only the concrete and its pins.

What changed

  • rain-factory 0.1.5 → 0.1.9, the release that carries src/lib/LibICloneableFactoryV4.sol: both V4 domain-tagged salt derivations, EIP-1167 construction and prediction, the implementation-code guard, the atomic clone-initialize-verify flow, four entry points, and the typed errors (ZeroImplementationCodeSize, CloneDeploymentFailed, InitializationFailed).
  • src/concrete/CloneFactory.sol now implements ICloneableFactoryV4 as four single-line delegations. The local _effectiveSalt (untagged keccak256(abi.encode(deployer, salt))), the local errors, the OZ Clones dependency and the inline initialize check are all gone — superseded by the library, not preserved. The namespaced derivation is therefore now the V4 domain-tagged one; the concrete's bytecode, address and codehash all move, which is the point of releasing through the candidate.
  • test/src/concrete/TestLibCloneFactory.sol + test/src/concrete/CloneFactoryLibEquivalence.t.sol: per-function equivalence holding concrete == library (see QA).
  • The existing CloneFactoryCloneDeterministic suite keeps OZ Clones as a foreign EIP-1167 oracle and pins the new domain-tagged derivation.
  • src/generated/candidate/CloneFactory.sol, src/lib/LibCloneFactoryDeploy.sol regenerated by script/Build.sol; .gas-snapshot regenerated (it was stale on main — it still named LibCloneFactoryDeployTaggedConstantsTest, deleted in the abstracts migration).
  • test/src/lib/LibCloneFactoryDeployProd.t.sol deleted. It asserted the CANDIDATE pins are live on five networks, which rain-deploy's own model doc calls out as wrong by construction: "the candidate is meant to be ahead of the chain … Demanding the candidate be live asserts something false by design" (RainDeployVerifyChain NatSpec). It only ever passed because the candidate happened to equal the deployed 0.1.5 bytecode; any source change reds it until a human broadcasts. The model-correct chain anchor — every RELEASED suite live on every network — already exists here as CloneFactoryDeployChainTest, and the candidate stays anchored to source by CloneFactoryDeploySnapshotTest + LibCloneFactoryDeployCandidateTest.
  • CLAUDE.md / README: the two sentences the change falsifies (concrete "clones via OpenZeppelin Clones"; library half is "the ICloneable* interfaces") updated to the delegation model.

QA

Discriminating tests

CloneFactoryLibEquivalenceTest holds each entry point to the library's behaviour two ways at once: against the library's own pure derivation (effectiveSalt / effectiveOpenSalt / predictCloneAddress) applied at the concrete's address, and against TestLibCloneFactory — the library run bare behind an independent delegating surface — exercised from identical chain state via vm.snapshotState / vm.revertToState, comparing child address, deployed code, initialized state, and NewClone field for field. Revert equivalence covers ZeroImplementationCodeSize, InitializationFailed, and CloneDeploymentFailed (salt reuse: different data on the namespaced path, different sender on the open-salt path) on both surfaces. testEquivalenceRuntimeBytecode pins the strongest form: with metadata stripped, the concrete and the bare-library surface compile to byte-identical runtime code — any behaviour added to the concrete breaks this before it breaks anything behavioural.

Mutations applied

nix run github:rainlanguage/adversarial-mutation-test#mutation-probe -- mutants.toml, suite scoped to test/src/concrete/* so kills come from the concrete's own tests, never from the pin-currency tests (which any bytecode change trips trivially). Six mutants over the four delegations in src/concrete/CloneFactory.sol, baseline green (18 passed):

Mutant Verdict Killed by (behavioural pass, bytecode-identity test excluded)
M1 cloneDeterministic cross-wired to the open-salt derivation KILLED testCloneDeterministicMatchesPredict, testCloneDeterministicSenderScoped, testEquivalenceCloneDeterministic, testEquivalenceCloneDeterministicSaltTaken
M2 cloneDeterministic drops the caller salt (bytes32(0)) KILLED testCloneDeterministicEvent, testCloneDeterministicManyClonesPerImpl, testCloneDeterministicMatchesPredict, testCloneDeterministicSenderScoped, testEquivalenceCloneDeterministic
M3 predictDeterministicAddress ignores the deployer argument (msg.sender) KILLED testCloneDeterministicSaltIsDomainTaggedHash, testCloneDeterministicSenderScoped, testEquivalencePredictDeterministicAddress
M4 predictDeterministicAddress predicts from the wrong factory address KILLED testEquivalencePredictDeterministicAddress, testCloneDeterministicMatchesPredict, testCloneDeterministicSaltIsDomainTaggedHash, testCloneDeterministicSenderScoped
M5 cloneDeterministicOpenSalt cross-wired to the namespaced derivation KILLED testEquivalenceCloneDeterministicOpenSalt, testEquivalenceCloneDeterministicOpenSaltSaltTaken
M6 predictDeterministicAddressOpenSalt ignores the data argument KILLED testEquivalencePredictDeterministicAddressOpenSalt

Two passes, both 6/6 killed, 0 survived, 0 no-run, 0 harness errors: the full concrete suite (where testEquivalenceRuntimeBytecode additionally kills every mutant), and a second pass with that test excluded (--no-match-test) proving the behavioural tests discriminate on their own — the killer names above are from the behavioural pass.

Oracle

ICloneableFactoryV4's NatSpec pins both effective-salt derivations to exact bytes; the tests recompute them inline from the pinned formulas and from the library's pure functions, never read back from the contract under test. OZ Clones serves as a foreign implementation of the same EIP-1167 standard for address prediction, so the derivation is checked against code that shares none of the library's arithmetic.

Category check

  • Wrong derivation routed (M1, M5): killed by the child-address-vs-derivation assertions in the clone equivalence tests.
  • Input dropped from the derivation (M2, M6): killed by the prediction/clone equivalence over fuzzed salt and data.
  • Wrong account in the derivation (M3): killed by prediction equivalence over fuzzed deployer.
  • Wrong factory address in the prediction (M4): killed by prediction equivalence pinning address(this) of the concrete.
  • Behaviour added to the concrete (any of the above): killed by testEquivalenceRuntimeBytecode before behaviour is even observed.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Upgraded the clone factory to the latest V4 interface and behavior.
    • Added open-salt deterministic clone deployment and address prediction.
    • Improved deterministic salt handling with domain-tagged hashing.
  • Bug Fixes

    • Strengthened consistency between predicted and deployed clone addresses, initialization, events, and error behavior.
  • Documentation

    • Clarified the roles of the clone factory, shared library, interfaces, and deployment artifacts.

thedavidmeister and others added 5 commits August 20, 2026 16:29
0.1.9 is the release that carries LibICloneableFactoryV4 — the whole of an
ICloneableFactoryV4 factory as internal library logic. This commit only moves
the dependency and every import path; the concrete still carries its own
logic, replaced in the next commits. CloneFactory's bytecode is unchanged so
the candidate pins hold.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
rain-deploy's RainDeployVerifyChain NatSpec scopes chain verification to
released suites only: "the candidate is meant to be ahead of the chain", and
"Demanding the candidate be live asserts something false by design". This
test asserted exactly that over the candidate alias pins, so any
bytecode-moving change reds five fork suites until a human broadcasts the new
candidate. It predates the migration onto the rain-deploy abstracts and only
ever passed because the candidate happened to equal the deployed 0.1.5
bytecode.

The model-correct anchors already exist here: CloneFactoryDeployChainTest
holds every RELEASED suite live on every supported network, and
CloneFactoryDeploySnapshotTest / LibCloneFactoryDeployCandidateTest hold the
candidate anchored to source.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The concrete is now nothing but one delegation per entry point into the
library, implementing ICloneableFactoryV4 — the namespaced pair inherited
from V3 plus the open-salt pair. The local _effectiveSalt (untagged
keccak256(abi.encode(deployer, salt))), the local errors, the OZ Clones
dependency and the inline initialize check are superseded by the library, not
preserved: both derivations are now domain-tagged per the V4 spec, and the
typed errors (ZeroImplementationCodeSize, CloneDeploymentFailed,
InitializationFailed) live in LibICloneableFactoryV4.

The behavior suite pins the new namespaced derivation against OZ Clones as a
foreign EIP-1167 oracle and imports the errors from the library.

script/Build.sol regenerated the candidate snapshot; the bytecode, address
and codehash all move, which is what releasing through the rolling candidate
is for. OZ Clones remains a test-only dependency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TestLibCloneFactory is the library run bare behind an independent delegating
surface. CloneFactoryLibEquivalenceTest holds each entry point to the
library's behaviour two ways at once: against the library's own pure
derivation (effectiveSalt / effectiveOpenSalt / predictCloneAddress) applied
at the concrete's address, and against the bare surface exercised from
identical chain state via snapshotState/revertToState — child address,
deployed code, initialized state and NewClone field for field, plus revert
equivalence for ZeroImplementationCodeSize, InitializationFailed and
CloneDeploymentFailed (salt reuse with different data on the namespaced path,
a different sender on the open-salt path).

testEquivalenceRuntimeBytecode pins the strongest form: metadata is stripped,
so the concrete and the bare surface compile to byte-identical runtime code —
behaviour added to the concrete breaks this before it breaks anything
behavioural.

.gas-snapshot regenerated; it was stale on main (it still named
LibCloneFactoryDeployTaggedConstantsTest, deleted in the abstracts
migration).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CLAUDE.md and README each carried a sentence the rework falsifies — the
concrete "clones via OpenZeppelin Clones", and the library half being only
the ICloneable* interfaces. Both now state the delegation model:
LibICloneableFactoryV4 carries the factory logic and the concrete adds no
behaviour of its own.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1ca073dc-084f-414e-ac1a-4f8cb94fecf7

📥 Commits

Reviewing files that changed from the base of the PR and between 6deef89 and 5792a23.

⛔ Files ignored due to path filters (2)
  • soldeer.lock is excluded by !**/*.lock
  • src/generated/candidate/CloneFactory.sol is excluded by !**/generated/**
📒 Files selected for processing (11)
  • .gas-snapshot
  • CLAUDE.md
  • README.md
  • foundry.toml
  • src/concrete/CloneFactory.sol
  • test/src/concrete/CloneFactoryCloneDeterministic.t.sol
  • test/src/concrete/CloneFactoryLibEquivalence.t.sol
  • test/src/concrete/TestCloneable.sol
  • test/src/concrete/TestCloneableFailure.sol
  • test/src/concrete/TestLibCloneFactory.sol
  • test/src/lib/LibCloneFactoryDeployProd.t.sol
💤 Files with no reviewable changes (1)
  • test/src/lib/LibCloneFactoryDeployProd.t.sol

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


Walkthrough

CloneFactory now implements the V4 factory interface and delegates deterministic clone operations to LibICloneableFactoryV4. The change adds open-salt methods, updates dependency documentation, and adds equivalence and deployment validation coverage.

Changes

V4 clone factory delegation

Layer / File(s) Summary
V4 delegation and public entry points
foundry.toml, src/concrete/CloneFactory.sol, CLAUDE.md, README.md
The factory uses ICloneableFactoryV4 and delegates clone deployment and prediction to LibICloneableFactoryV4. Open-salt methods are added. Documentation and the rain-factory dependency are updated.
Library-backed test surface
test/src/concrete/TestLibCloneFactory.sol, test/src/concrete/CloneFactoryCloneDeterministic.t.sol, test/src/concrete/TestCloneable.sol, test/src/concrete/TestCloneableFailure.sol
Tests add a library-backed V4 factory and validate domain-tagged salt derivation with updated dependency imports.
Equivalence and deployment validation
test/src/concrete/CloneFactoryLibEquivalence.t.sol, .gas-snapshot
Tests compare predictions, clone results, initialization, events, and revert data between the concrete factory and library-backed factory. Gas snapshots are updated for the revised coverage and deployment measurements.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to 5792a

The delegation change is merge-ready after normal checks and review; no actionable merge-blocking risk remains.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant CloneFactory
  participant LibICloneableFactoryV4
  Caller->>CloneFactory: Request deterministic clone
  CloneFactory->>LibICloneableFactoryV4: Delegate deployment or prediction
  LibICloneableFactoryV4-->>CloneFactory: Return clone address or revert
  CloneFactory-->>Caller: Return result
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: CloneFactory delegates its implementation to LibICloneableFactoryV4.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 2026-08-20-delegate-to-lib

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.

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.

1 participant