Skip to content

LibICloneableFactoryV4: the whole factory logic as a unit-tested library - #59

Merged
thedavidmeister merged 8 commits into
mainfrom
2026-08-20-lib-clone-factory
Aug 20, 2026
Merged

LibICloneableFactoryV4: the whole factory logic as a unit-tested library#59
thedavidmeister merged 8 commits into
mainfrom
2026-08-20-lib-clone-factory

Conversation

@thedavidmeister

@thedavidmeister thedavidmeister commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

The library half of the library/deploy split (#46) has been interface-only; the ruling (thedavidmeister, 2026-08-20) is that it must not be: the library repo carries interfaces plus Lib* logic, unit tested; the deploy repo keeps a thin concrete that only delegates, plus deploy pins and equivalence tests. This PR delivers the rain.factory half only; slimming rain.factory.deploy's CloneFactory to pure delegation is a follow-up PR in that repo.

Reworked on top of #58

This branch originally added the logic as a parallel src/lib/LibCloneFactory.sol. While it was open, #58 merged LibICloneableFactoryV4 — the interface-mirroring library carrying the two effective-salt derivations — and the ruling (thedavidmeister, 2026-08-20) is that #58's design wins: one lib, named for the interface it implements. So main is merged in and the factory logic is folded into LibICloneableFactoryV4:

  • Deleted: src/lib/LibCloneFactory.sol, including its duplicate derivations effectiveSaltNamespaced / effectiveSaltOpen, in favour of Extract ICloneableFactoryV4 salt-derivation math into LibICloneableFactoryV4 #58's effectiveSalt / effectiveOpenSalt (byte-identical formulas; Extract ICloneableFactoryV4 salt-derivation math into LibICloneableFactoryV4 #58's names and argument order govern).
  • Moved into LibICloneableFactoryV4: the EIP-1167 creation code and CREATE2 prediction, the implementation-code guard, the atomic clone-initialize-verify flow with its typed errors, and the four ICloneableFactoryV4 entry points — all now delegating to the merged derivations.
  • Renamed: the internal effective-salt parameter is derivedSalt so it cannot shadow the effectiveSalt function it now sits beside; the five machinery/flow test suites are LibICloneableFactoryV4.*.t.sol.
  • Pragma: the lib moves ^0.8.18^0.8.25 (qualified ICloneableFactoryV3.NewClone emission needs solc >= 0.8.21; repo convention is libs float ^0.8.25, interfaces keep ^0.8.18).

The library surface

src/lib/LibICloneableFactoryV4.sol, all internal, no external imports in published src/ (OZ is vendored test-only as the equivalence oracle; with recursive_deps = false an OZ import in src/ would land in every downstream consumer's manifest):

  • effectiveSalt(deployer, salt) / effectiveOpenSalt(salt, data) — from Extract ICloneableFactoryV4 salt-derivation math into LibICloneableFactoryV4 #58, unchanged: the two derivations exactly as ICloneableFactoryV4 pins them, a keccak256 over a 96-byte preimage led by a distinct string-derived domain tag imported from the interface.
  • cloneCreationCode(implementation) / predictCloneAddress(factory, implementation, derivedSalt) — the EIP-1167 creation code and its CREATE2 prediction built from the standard's own bytes.
  • checkImplementationCode(implementation) — the zero-code guard.
  • cloneAndInitialize(implementation, derivedSalt, data, salt) — the shared tail: guard → CREATE2 → NewClone with the RAW caller salt → initialize verified against ICLONEABLE_V2_SUCCESS, atomic per the V3/V4 spec.
  • cloneDeterministic / predictDeterministicAddress / cloneDeterministicOpenSalt / predictDeterministicAddressOpenSalt — the four entry points, whole. msg.sender and address(this) are read INSIDE the library, so a delegating concrete cannot namespace by anything else and there is no sender parameter to misroute tx.origin into.

Typed errors at file level: ZeroImplementationCodeSize, CloneDeploymentFailed, InitializationFailed. No named return values anywhere.

test/src/concrete/TestCloneFactory.sol is the executable proof of the surface: a concrete ICloneableFactoryV4 whose four functions are one delegation each; the flow tests run through it.

Divergences found

  1. The main-branch deploy concrete's _effectiveSalt is not the V4 derivation. It hashes the untagged 64-byte abi.encode(deployer, salt) — V3-era, predating the tags. The interface NatSpec is the spec and pins the tagged 96-byte preimage; the library implements the spec. Nothing is released from the new lane, so no deployed factory changes behaviour; a V4 factory's namespaced addresses differ from the V3 factory's regardless, since the factory address is inside CREATE2.
  2. The unmerged red branch rain.factory.deploy@factory/open-salt-clone-deploy is stale against the current interface. Written against rain-factory 0.1.7, it tags only the open derivation and keeps the namespaced one untagged. Main's interface has since pinned BOTH tags and derives disjointness from the two fixed words. That branch's suite was mined for intent; its untagged-squat test is reworked as testCloneDeterministicOpenSaltDisjointTagsCloseTheSquat.
  3. The occupied-salt revert type changes. Old lane: OZ's Errors.FailedDeployment. Library: typed CloneDeploymentFailed. Nothing released from the new lane depends on the OZ error.
  4. Named return values in the old deploy concrete and its harness violate the org rule and were not copied.

Tests

42 tests across 6 suites, fuzz runs 2048, one file per library concern: the merged LibICloneableFactoryV4.t.sol carries #58's derivation pins plus the six non-duplicate derivation tests from this branch (preimage shape and input sensitivity, renamed to the merged function names); the five machinery/flow suites exercise the creation code, prediction, guard, and both clone flows through TestCloneFactory.

QA

  • Oracle: the ICloneableFactoryV4 NatSpec equations restated in-test; the domain tags re-derived from their documented strings (keccak256("rain.factory.clone.namespaced") / keccak256("rain.factory.clone.opensalt")); the EIP-1167 creation and runtime bytes written literally in-test from the EIP; OZ Clones 5.6.1 as a foreign implementation of the same prediction; the raw CREATE2 formula longhand. No expected value is computed by the code under test.
  • Mutation evidence: produced with the org harness, nix run github:rainlanguage/adversarial-mutation-test#mutation-probe -- mutants.toml, over a 16-mutant matrix — one mutant per moved/kept behavior, every target in src/lib/LibICloneableFactoryV4.sol, never in test code; suite command nix develop -c forge test (fuzz 2048) per verdict. Probe output, transcribed:
baseline: running suite ...
baseline: green (42 passed)
M01 creation-code prefix corrupted (one byte of the deploy preamble): KILLED
M02 creation-code suffix corrupted (one byte of the shared runtime tail): KILLED
M03 prediction drops the 0xff CREATE2 marker: KILLED
M04 implementation-code guard never fires: KILLED
M05 create2-failure check never fires (zero child accepted): KILLED
M06 initialize return value never verified (call kept, check dead): KILLED
M07 NewClone emits the derived salt instead of the raw caller salt: KILLED
M08 cloneDeterministic skips the namespaced derivation (raw salt into CREATE2): KILLED
M09 cloneDeterministic namespaces by address(this) instead of msg.sender: KILLED
M10 predictDeterministicAddress ignores the deployer argument (uses msg.sender): KILLED
M11 predictDeterministicAddress predicts from address(0), not the factory: KILLED
M12 cloneDeterministicOpenSalt routes through the namespaced derivation: KILLED
M13 predictDeterministicAddressOpenSalt drops data from the derivation: KILLED
M14 predictDeterministicAddressOpenSalt predicts from address(0), not the factory: KILLED
M15 effectiveSalt derives under the open-salt domain tag: KILLED
M16 effectiveOpenSalt drops data from the preimage (hashes empty instead): KILLED

== 16/16 killed; survived: 0; no-run: 0; harness errors: 0
  • Gate: pre-commit run --all-files, nix develop -c forge test (42 passed, 0 failed), reuse lint — all green on the pushed head.

🤖 Generated with Claude Code

baku-ccron and others added 4 commits August 20, 2026 14:39
… library

The library half of the split carries the logic, not just the interfaces:
both V4 effective-salt derivations, the implementation-code guard, the
EIP1167 creation code and CREATE2 prediction constructed from the
standard's own bytes, and the atomic clone-initialize-verify flow with
typed errors and NewClone. msg.sender and address(this) are read inside
the library, so a concrete factory is one delegation per entry point —
TestCloneFactory in the suite is exactly that concrete, and the deploy
half's CloneFactory is meant to become it.

The published src/ imports nothing external; OZ Clones is vendored as a
test-only oracle pinning the EIP1167 construction byte for byte, next to
the raw CREATE2 formula and the EIP's literal bytes restated in-test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
README, CLAUDE.md and the release workflow all asserted the post-split
interface-only shape; LibCloneFactory makes them stale. CLAUDE.md stays
under its 4096-byte agent-context cap by cutting, not raising.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s branch

prettier-rainix, taplo and yamlfmt normalize .vscode/settings.json,
slither.config.json, REUSE.toml and .coderabbitai.yaml — all untouched by
the feature commits; main fails the all-files run the same way. Mechanical
output only, committed so the branch gate runs clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New generated file with no header capability; listed in the annotations
path array per rain.extrospection's precedent. reuse lint is compliant.

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

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@thedavidmeister, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 11 minutes

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.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6b4b3729-0bb3-4c3a-92e7-20a6b4dc6d60

📥 Commits

Reviewing files that changed from the base of the PR and between eb77616 and f05d5e7.

⛔ Files ignored due to path filters (1)
  • soldeer.lock is excluded by !**/*.lock
📒 Files selected for processing (19)
  • .coderabbitai.yaml
  • .gas-snapshot
  • .github/workflows/package-release.yaml
  • .vscode/settings.json
  • CLAUDE.md
  • README.md
  • REUSE.toml
  • foundry.toml
  • slither.config.json
  • src/lib/LibICloneableFactoryV4.sol
  • test/src/concrete/TestCloneFactory.sol
  • test/src/concrete/TestCloneable.sol
  • test/src/concrete/TestCloneableFailure.sol
  • test/src/lib/LibICloneableFactoryV4.checkImplementationCode.t.sol
  • test/src/lib/LibICloneableFactoryV4.cloneCreationCode.t.sol
  • test/src/lib/LibICloneableFactoryV4.cloneDeterministic.t.sol
  • test/src/lib/LibICloneableFactoryV4.cloneDeterministicOpenSalt.t.sol
  • test/src/lib/LibICloneableFactoryV4.predictCloneAddress.t.sol
  • test/src/lib/LibICloneableFactoryV4.t.sol

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.

baku-ccron and others added 3 commits August 20, 2026 15:09
…ranch

Conflicts resolved in CLAUDE.md, README.md and REUSE.toml by taking main's
framing — the repo is the interface surface plus the LibICloneableFactoryV4
library — extended to cover the factory logic this branch adds. The lib/test
fold onto #58's surface is the next commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ruling (thedavidmeister, 2026-08-20): #58's design wins. Its
LibICloneableFactoryV4 — the interface-mirroring lib carrying the two
effective-salt derivations — is the shape; this branch's LibCloneFactory must
not stand next to it as a parallel second lib.

So: LibCloneFactory's duplicate derivations (effectiveSaltNamespaced /
effectiveSaltOpen) are deleted in favour of the merged effectiveSalt /
effectiveOpenSalt, which stay byte-identical to #58. Everything else —
the EIP-1167 creation code and CREATE2 prediction, the implementation-code
guard, the atomic clone-initialize-verify flow with its typed errors, and the
four ICloneableFactoryV4 entry points — folds into LibICloneableFactoryV4
beneath them, delegating to the merged derivations. The internal salt
parameter is renamed derivedSalt so it cannot shadow the effectiveSalt
function it now sits beside.

The lib's pragma moves ^0.8.18 -> ^0.8.25: the flow emits the interface's
NewClone via qualified access (ICloneableFactoryV3.NewClone), which needs
solc >= 0.8.21, and this repo's convention is that libraries float ^0.8.25
while interfaces keep ^0.8.18.

Tests follow the fold: the merged LibICloneableFactoryV4.t.sol gains the six
non-duplicate derivation tests (preimage shape and input sensitivity, renamed
to the merged function names); the two LibCloneFactory derivation suites are
deleted as duplicates of what #58 already covers; the five machinery/flow
suites are renamed LibICloneableFactoryV4.*.t.sol and retargeted, as is the
TestCloneFactory pure-delegation harness. 42 tests, 6 suites, fuzz 2048, all
green; .gas-snapshot regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Line-wrapping only; no content change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thedavidmeister thedavidmeister changed the title LibCloneFactory: the ICloneableFactoryV4 logic as a unit-tested library LibICloneableFactoryV4: the whole factory logic as a unit-tested library Aug 20, 2026
CI's static gate runs forge fmt --check, which this repo's pre-commit
does not; formatted with the pinned toolchain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thedavidmeister
thedavidmeister merged commit 7d1aee7 into main Aug 20, 2026
4 checks passed
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