Skip to content

AMT coverage: g1-libicloneablefactoryv4-effec - #75

Open
thedavidmeister wants to merge 8 commits into
mainfrom
2026-08-24-amt-g1-libicloneablefactoryv4-effec
Open

AMT coverage: g1-libicloneablefactoryv4-effec#75
thedavidmeister wants to merge 8 commits into
mainfrom
2026-08-24-amt-g1-libicloneablefactoryv4-effec

Conversation

@thedavidmeister

@thedavidmeister thedavidmeister commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Adversarial mutation-testing pass over group g1-libicloneablefactoryv4-effec: the two effective-salt derivations, the EIP-1167 creation code and the CREATE2 prediction — 29 behaviours across four units in src/lib/LibICloneableFactoryV4.sol (plus the two domain-tag constants it imports from src/interface/ICloneableFactoryV4.sol).

58 mutants probed against the pre-existing suite before a single new test was written, so every kill below credits a named pre-existing test. 52 died to the existing suite, 3 could not be expressed as compiling code, 1 is genuinely equivalent, and 2 survived — both the same defect, in two different functions.

Do not merge; this is an audit deliverable.

What survived, and why it matters

A mutation that changes the derivation only when the salt is bytes32(type(uint256).max) survived a full run of the suite in effectiveOpenSalt and again in predictCloneAddress. The corresponding zero-salt mutants died, and the same max-salt mutation died in effectiveSalt — not because that function is tested differently, but because it is reached from more fuzz tests and therefore gets more draws. In other words the top of the salt domain was pinned nowhere, and the boundary kills the suite did score were fuzz luck rather than coverage. Both boundaries are now asserted deliberately, in the style of the existing testEffectiveOpenSaltEmptyData, and both mutants now die on the first case (runs: 0).

The seed-dependence itself is filed as #70 rather than papered over here: this PR pins the two boundaries whose mutants actually survived and deliberately does not pin the ones that happened to die, so the ledger stays honest about which is which.

The branch also carries a fix for a pre-existing test that fails on unmutated source (#68 / #64): testCheckImplementationCodeEtched feeds fuzzed bytes to vm.etch, which rejects any 0xef01-prefixed code that is not the 23-byte EIP-7702 designator. EIP-3541 forbids 0xEF-leading deployed code on chain in the first place, so the input is excluded as unrepresentable rather than by weakening the assertion. A red baseline blocks every probe in the repository, so this had to land first.

Behaviour matrix

KILLED_PREEXISTING = the mutant died to the suite as it stood at c1c2afd. KILLED_NEW = it survived that pass and dies to a test added here.

effectiveSalt(address,bytes32) — namespaced effective-CREATE2-salt derivation

behaviour mutation verdict killing test
computation: keccak256 over the abi.encode preimage M01 keccak256sha256 KILLED_PREEXISTING testEffectiveSaltMatchesFormula
constant identity: word 0 is ICLONEABLE_FACTORY_V4_NAMESPACED_DOMAIN M02 namespaced tag → open-salt tag; R01 tag literal typo; R03 namespaced tag collapsed onto the open-salt string KILLED_PREEXISTING testEffectiveSaltMatchesFormula, testEffectiveSaltPreimageShape, testDomainTagsPinned, testDomainTagsDistinct
field order: tag word 0, deployer word 1, salt word 2 M03 deployer/salt swapped; M04 tag demoted out of word 0 KILLED_PREEXISTING testEffectiveSaltMatchesFormula, testEffectiveSaltPreimageShape
encoding: abi.encode, 96-byte preimage, not abi.encodePacked M05 abi.encodeabi.encodePacked; R04 tag word dropped (64-byte preimage) KILLED_PREEXISTING testEffectiveSaltPreimageShape (length 96), testEffectiveSaltMatchesFormula
deployer left-padded into a full word by abi.encode M06 deployerbytes20(deployer) (right-padded) KILLED_PREEXISTING testEffectiveSaltPreimageShape, testCloneDeterministicOpenSaltDisjointTagsCloseTheSquat
purity: reads no state, no msg.sender — deployer is a parameter M07 deployer parameter replaced by msg.sender (function relaxed to view) EQUIVALENT no compiling mutant exists — see the equivalence notes
output sensitivity: distinct deployer ⇒ distinct salt M08 deployer zeroed, shape kept; R09 zero deployer special-cased; S02 max deployer special-cased KILLED_PREEXISTING testEffectiveSaltDeployerSensitive, testEffectiveSaltMatchesFormula
output sensitivity: distinct caller salt ⇒ distinct salt M09 salt zeroed, shape kept; R06 zero salt special-cased; S01 max salt special-cased KILLED_PREEXISTING testEffectiveSaltSaltSensitive, testEffectiveSaltMatchesFormula

effectiveOpenSalt(bytes32,bytes) — open-salt effective-CREATE2-salt derivation

behaviour mutation verdict killing test
computation: keccak256 over the abi.encode preimage N01 keccak256sha256 KILLED_PREEXISTING testEffectiveOpenSaltMatchesFormula
constant identity: word 0 is ICLONEABLE_FACTORY_V4_OPEN_SALT_DOMAIN N02 open-salt tag → namespaced tag; R02 tag literal typo KILLED_PREEXISTING testEffectiveOpenSaltMatchesFormula, testEffectiveOpenSaltPreimageShape, testDomainTagsPinned
field order: tag word 0, salt word 1, keccak256(data) word 2 N03 salt/datahash swapped; N04 tag demoted out of word 0 KILLED_PREEXISTING testEffectiveOpenSaltMatchesFormula, testEffectiveOpenSaltPreimageShape
data enters BY HASH — preimage stays 96 bytes for any data length N05 data inlined instead of hashed; R14 data inlined and packed; R16 long data special-cased; R17 single-byte data special-cased KILLED_PREEXISTING testEffectiveOpenSaltPreimageShape, testEffectiveOpenSaltMatchesFormula
encoding: abi.encode, not abi.encodePacked N06 abi.encodeabi.encodePacked EQUIVALENT all three operands are bytes32 — see the equivalence notes
empty data supported: hashes keccak256("") rather than short-circuiting N07 data.length == 0 short-circuits to the zero word KILLED_PREEXISTING testEffectiveOpenSaltEmptyData
purity: no caller-derived value in the preimage at all N08 msg.sender mixed into the hashed data (function relaxed to view) EQUIVALENT no compiling mutant exists — see the equivalence notes
output sensitivity: distinct data ⇒ distinct salt N10 data ignored, shape kept KILLED_PREEXISTING testEffectiveOpenSaltDataSensitive
output sensitivity: distinct caller salt ⇒ distinct salt N09 salt zeroed, shape kept; R07 zero salt special-cased KILLED_PREEXISTING testEffectiveOpenSaltSaltSensitive
⤷ same behaviour, at the top of the salt domain R08 max salt special-cased KILLED_NEW testEffectiveOpenSaltBoundarySalts (added here)

cloneCreationCode(address) + EIP1167_CREATION_CODE_PREFIX / _SUFFIX

behaviour mutation verdict killing test
constant bytes: prefix 3d602d80600a3d3981f3363d3d373d3d3d363d73 P01 last byte of the runtime prefix flipped (7372) KILLED_PREEXISTING testCloneCreationCodeIsEIP1167
constant bytes: suffix 5af43d82803e903d91602b57fd5bf3 (15 bytes) P04 jumpdest offset 602b602c; P05 trailing byte dropped KILLED_PREEXISTING testCloneCreationCodeIsEIP1167
concatenation order: prefix ++ implementation ++ suffix P06 prefix/suffix swapped; R18 implementation moved after the suffix KILLED_PREEXISTING testCloneCreationCodeIsEIP1167
encoding: abi.encodePacked — total length exactly 55 bytes P07 abi.encodePackedabi.encode KILLED_PREEXISTING testCloneCreationCodeIsEIP1167 (length 55)
implementation embedded as its raw 20 bytes (not padded) P08 raw 20 bytes → padded word; R11 implementation embedded twice; R10 zero implementation special-cased; S03 max implementation special-cased KILLED_PREEXISTING testCloneCreationCodeIsEIP1167, testCloneCreationCodeDeploysEIP1167Runtime
preamble SEMANTICS: deploying the 55 bytes returns the trailing 45 as runtime P02 return length 602d602c; P03 codecopy offset 600a600b KILLED_PREEXISTING testCloneCreationCodeDeploysEIP1167Runtime, testCloneCreationCodeIsEIP1167
runtime code equals suffix-side bytes with the 10-byte preamble stripped P02, P03 (same pair, observed through child.code) KILLED_PREEXISTING testCloneCreationCodeDeploysEIP1167Runtime, testCloneDeterministicMatchesPredict, testCloneDeterministicOpenSaltMatchesPredict
purity P09 implementation parameter replaced by address(this) (function relaxed to view) EQUIVALENT no compiling mutant exists — see the equivalence notes

predictCloneAddress(address,address,bytes32) — CREATE2 address formula

behaviour mutation verdict killing test
computation: keccak256(0xff ++ factory ++ derivedSalt ++ keccak256(creationCode)), low 20 bytes Q01 keccak256sha256; Q06 low 20 bytes → bytes 12..31 of the hash KILLED_PREEXISTING testPredictCloneAddressMatchesOZ, testPredictCloneAddressIsCreate2Formula, testPredictCloneAddressMatchesRealDeploy
constant: the 0xff prefix byte Q02 0xff0xfe KILLED_PREEXISTING testPredictCloneAddressMatchesOZ, testPredictCloneAddressIsCreate2Formula
field order: 0xff, factory, salt, codehash (85-byte packed preimage) Q03 factory/salt swapped; Q04 abi.encodePackedabi.encode; R12 implementation hashed in place of factory; S05/S06 max/zero factory special-cased KILLED_PREEXISTING testPredictCloneAddressMatchesOZ, testPredictCloneAddressIsCreate2Formula, testPredictCloneAddressMatchesRealDeploy
the codehash term is keccak256 of the CREATION code, not the runtime code Q05 creation code → runtime code; R13 creation code built for the factory not the implementation KILLED_PREEXISTING testPredictCloneAddressMatchesOZ, testPredictCloneAddressMatchesRealDeploy
⤷ same formula, across the salt domain R15 zero derived salt special-cased KILLED_PREEXISTING testPredictCloneAddressMatchesOZ
⤷ same formula, at the top of the salt domain S04 max derived salt special-cased KILLED_NEW testPredictCloneAddressBoundarySalts (added here)

Equivalence notes

Three behaviours have no expressible mutant, and one mutant is genuinely equivalent. None is forced.

  • N06 abi.encodeabi.encodePacked in effectiveOpenSalt is EQUIVALENT. All three operands (ICLONEABLE_FACTORY_V4_OPEN_SALT_DOMAIN, salt, keccak256(data)) are bytes32, a static 32-byte type, so packed and non-packed encoding are byte-identical 96-byte preimages. The suite itself is the proof: under the mutant, testEffectiveOpenSaltMatchesFormula — which compares against keccak256(abi.encode(...)) — passed all 2048 fuzz runs. The behaviour is not unobservable in principle, only in this function: the same mutation on effectiveSalt (M05) is killed twice over, because deployer is an address there and packs to 20 bytes instead of a padded word.

  • M07 / N08 / P09 (purity) are compiler-enforced; no compiling mutant exists. Each was probed and returned NO-RUN with solc Error 2527 — "Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires view" — raised inside the pre-existing test files, at the external pure call sites in LibICloneableFactoryV4.t.sol and LibICloneableFactoryV4.cloneCreationCode.t.sol. Those pure test functions are what makes the library's pure mutability load-bearing rather than decorative: relaxing it to view does not compile the suite.

    This was checked rather than assumed. Inline assembly does not escape the rule — a standalone probe of caller() inside a pure library function was compiled at solc 0.8.25 and rejected with the same Error 2527. No test is added for these, because a test nothing can make fail is not a discriminating test.

Adversarial pass

Filed as issues, neutrally framed, none adjudicated here:

Refuted rather than filed, recorded so the reasoning is auditable:

  • cloneCreationCode's NatSpec claim that "the tests pin it byte for byte against OZ Clones". Checked against dependencies/@openzeppelin-contracts-5.6.1/proxy/Clones.sol:119-128: OZ assembles the same 55 bytes in memory and hashes them (keccak256(add(ptr, 0x0c), 0x37)), so address equality under fuzz is byte equality up to a keccak preimage collision. The claim stands.
  • The EIP-1167 constants were re-derived opcode by opcode from the standard rather than compared to themselves. The preamble 3d 602d 80 600a 3d 39 81 f3 leaves [0, 45] on the stack and RETURNs mem[0:45] after CODECOPY(dest=0, offset=10, length=45); the runtime's 602b JUMPI target is index 43, which is the 5b JUMPDEST. Both match the implementation exactly.
  • Cross-derivation disjointness under caller-controlled inputs. The interface's claim rests entirely on the two distinct word-0 tags, and R03 — collapsing the namespaced tag onto the open-salt string — is killed by testDomainTagsDistinct and testDomainTagsPinned, with testCloneDeterministicOpenSaltDisjointTagsCloseTheSquat proving the reachable squat that the distinctness closes.

Unprobed

None of the 29 behaviours is unprobed. Three (the purity behaviours) are probed only to the point of proving no compiling mutant exists; that limit is stated above rather than scored as coverage.

QA

  • Discriminating tests: testEffectiveOpenSaltBoundarySalts(bytes), testPredictCloneAddressBoundarySalts(address,address) — each fails on base under its mutant on the very first case (runs: 0), verified two ways: by hand-applying the mutation and reading the failing test name out of forge test, and by mutation-probe --only R08 / --only S04 returning 1/1 killed where the full pass had scored both SURVIVED. Also testCheckImplementationCodeEtched(address,bytes), repaired rather than added: it fails on unmutated base whenever the fuzzer draws 0xEF-leading code.
  • Mutations applied: 58 mutants over the 29 behaviours, all against src/, never test code. src/lib/LibICloneableFactoryV4.sol:88salt == bytes32(type(uint256).max) ? bytes32(0) : salt → killed by testEffectiveOpenSaltBoundarySalts. src/lib/LibICloneableFactoryV4.sol:120derivedSalt == bytes32(type(uint256).max) ? bytes32(0) : derivedSalt → killed by testPredictCloneAddressBoundarySalts. The other 56 (lines 31, 35, 75, 88, 99, 116-124, plus src/interface/ICloneableFactoryV4.sol:15,24) are listed with their killing tests in the behaviour matrix above: 52 killed by the pre-existing suite, 1 equivalent, 3 non-compiling.
  • Oracle: never the library's own arithmetic read back. testEffectiveOpenSaltBoundarySalts recomputes keccak256(abi.encode(TAG, salt, keccak256(data))) inline from the interface constant, matching the file's existing convention; testPredictCloneAddressBoundarySalts uses OpenZeppelin Clones.predictDeterministicAddress, a foreign implementation of the same EIP-1167 CREATE2 prediction. The intent oracle for the whole pass is the ICloneableFactoryV4 NatSpec (src/interface/ICloneableFactoryV4.sol:34-48, :96-100, :172-184) together with EIP-1167 and EIP-1014, re-derived opcode by opcode rather than read off the implementation.
  • Category check: the group asks for (A) a probe of all 29 behaviours against the pre-existing suite with per-kill attribution, (B) discriminating tests for survivors, (C) an adversarial pass filed as issues. Covered A (58 mutants over 3 passes, matrix above), B (2 survivors, 2 tests, both re-probed to KILLED), C (ICloneableFactoryV3 NatSpec conditions cross-chain clone-address identity on the factory address alone, which ICloneableFactoryV4 states is not sufficient #66, testCheckImplementationCodeEtched fails on unmutated source when the fuzzer draws 0xEF-prefixed code #68, Boundary-value coverage of the salt derivations is fuzz-seed luck: max-salt mutants survive the whole suite #70, Library NatSpec claims a delegating concrete cannot misroute the msg.sender namespacing, but effectiveSalt + cloneAndInitialize compose to a factory that does #71, each labelled audit + adversarial). Nothing in the group is uncovered; the three purity behaviours are probed to the limit of what compiles and that limit is stated rather than scored as coverage.

Evidence

Harness is the CI's own, per .github/workflows/rainix-sol.yamlrainix/.github/workflows/rainix-sol-test.yaml: forge soldeer install then forge test inside nix develop .#sol-shell. Probe tool is nix run github:rainlanguage/adversarial-mutation-test#mutation-probe. The mutants files live outside the clone and are not committed.

[suite]
root = "repo"
command = ["nix", "develop", ".#sol-shell", "-c", "forge", "test"]
proof = '(\d+) tests passed, (\d+) failed'
fail-pattern = '\[FAIL[^\]]*\] (\S+)'

Pass 1 — 34 mutants, entire behaviour list, zero new tests present, baseline 42 passed:

baseline: green (42 passed)
M01..M06, M08, M09        KILLED
M07                       NO-RUN (solc Error 2527 at test/src/lib/LibICloneableFactoryV4.t.sol:95)
N01..N05, N07, N09, N10   KILLED
N06                       SURVIVED
N08                       NO-RUN (solc Error 2527 at test/src/lib/LibICloneableFactoryV4.t.sol:125)
P01..P08                  KILLED
P09                       NO-RUN (solc Error 2527 at .../cloneCreationCode.t.sol:21)
Q01..Q06                  KILLED

== 30/34 killed; survived: 1; no-run: 3; harness errors: 0

Pass 2 — 18 further mutants (tag literals, preimage shape, extremes, data-length special cases), baseline 42 passed:

R01 namespaced tag literal: string typo: KILLED — killed by: testDomainTagsPinned()
R02 open-salt tag literal: string typo: KILLED — killed by: testDomainTagsPinned()
R03 tag disjointness: namespaced tag collapsed onto the open-salt string: KILLED — killed by: testDomainTagsDistinct(), testDomainTagsPinned()
R04..R07, R09..R18: KILLED
R08 effectiveOpenSalt extreme: max salt special-cased: SURVIVED

== 17/18 killed; survived: 1; no-run: 0; harness errors: 0

Pass 3 — 6 symmetric extremes, baseline 42 passed:

S01 effectiveSalt extreme: max salt special-cased: KILLED
S02 effectiveSalt extreme: max deployer special-cased: KILLED
S03 cloneCreationCode extreme: max implementation special-cased: KILLED
S04 predictCloneAddress extreme: max derived salt special-cased: SURVIVED
S05 predictCloneAddress extreme: max factory special-cased: KILLED
S06 predictCloneAddress extreme: zero factory special-cased: KILLED

== 5/6 killed; survived: 1; no-run: 0; harness errors: 0

Re-probe of the two survivors with the new tests in place, --only:

baseline: green (44 passed)
R08 effectiveOpenSalt extreme: max salt special-cased: KILLED
== 1/1 killed; survived: 0; no-run: 0; harness errors: 0

baseline: green (44 passed)
S04 predictCloneAddress extreme: max derived salt special-cased: KILLED
== 1/1 killed; survived: 0; no-run: 0; harness errors: 0

Attribution of those two kills, from a hand-applied mutation so the failing test names are visible:

R08 → [FAIL: assertion failed: 0x74e2585d… != 0xfe66701c…]
      testEffectiveOpenSaltBoundarySalts(bytes) (runs: 0)
S04 → [FAIL: assertion failed: 0x3E6c3241… != 0xA0BaFBc0…]
      testPredictCloneAddressBoundarySalts(address,address) (runs: 0)

Both fail on the first case, not on a lucky draw. In those same runs other fuzz tests also failed — which is exactly #70's point: the mutant a full pass scored SURVIVED is reachable by the existing tests, just not reliably.

Branch head: forge fmt --check clean, Ran 6 test suites: 44 tests passed, 0 failed, 0 skipped.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests
    • Expanded validation for deterministic clone address predictions using zero and maximum salt values.
    • Added boundary coverage for effective salt calculations across varied input data.
    • Improved code validation tests to account for bytecode formats that cannot be represented at target addresses.
    • These checks strengthen confidence in clone deployment behavior and edge-case handling.

thedavidmeister and others added 2 commits August 24, 2026 07:47
… code

`vm.etch` rejects any code whose first bytes are `0xef01` unless the code is
exactly the 23-byte EIP-7702 delegation designator, so the fuzzer eventually
produces a `code` the cheatcode cannot write and the test fails on unmutated
source (counterexample `code = 0xef0150ab5cd0906843c517`). EIP-3541 forbids
deployed code beginning `0xEF` on a real chain in the first place, so the
input is excluded as unrepresentable rather than by weakening what the
code-size guard is asserted to do.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mutation probing found two derivation behaviours the suite covered only by
fuzz luck. A mutant that special-cases `bytes32(type(uint256).max)` survived a
full run of the suite in both `effectiveOpenSalt` and `predictCloneAddress`:
2048 fuzz runs per test reach `bytes32(0)` reliably but do not reach the
maximum word reliably, so the top of the salt domain was not pinned anywhere.

Both boundaries are now asserted deliberately, in the style of the existing
`testEffectiveOpenSaltEmptyData`, which pins the empty-data boundary for the
same reason. The prediction boundary is checked against OZ `Clones` rather
than against this library's own arithmetic.

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

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

Next included review available in 21 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: 535d812f-93de-4a83-bbca-2e8c8a3e21e6

📥 Commits

Reviewing files that changed from the base of the PR and between 99b1ef2 and 219778a.

📒 Files selected for processing (8)
  • .gas-snapshot
  • test/concrete/TestCloneFactory.sol
  • test/concrete/TestCloneable.sol
  • test/concrete/TestCloneableFailure.sol
  • test/src/concrete/TestCloneable.sol
  • test/src/lib/LibICloneableFactoryV4.checkImplementationCode.t.sol
  • test/src/lib/LibICloneableFactoryV4.cloneDeterministic.t.sol
  • test/src/lib/LibICloneableFactoryV4.cloneDeterministicOpenSalt.t.sol

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: 514a0e74-63e5-4d1a-8a39-cdf0b563adad

📥 Commits

Reviewing files that changed from the base of the PR and between c1c2afd and 99b1ef2.

📒 Files selected for processing (3)
  • test/src/lib/LibICloneableFactoryV4.checkImplementationCode.t.sol
  • test/src/lib/LibICloneableFactoryV4.predictCloneAddress.t.sol
  • test/src/lib/LibICloneableFactoryV4.t.sol

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


Walkthrough

The test suite adds boundary checks for zero and maximum salts, and excludes 0xef-prefixed bytecode from etched-code property inputs that vm.etch cannot represent.

Changes

Library test coverage

Layer / File(s) Summary
Boundary and representability tests
test/src/lib/LibICloneableFactoryV4.t.sol, test/src/lib/LibICloneableFactoryV4.predictCloneAddress.t.sol, test/src/lib/LibICloneableFactoryV4.checkImplementationCode.t.sol
Tests verify open-salt derivation and clone address prediction for zero and maximum salts. The etched-code property test excludes 0xef-prefixed bytecode.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 99b1e

The PR only adds boundary coverage and adjusts test inputs without changing production behavior. No actionable merge-blocking risk remains beyond normal checks and review.

🚥 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 identifies the mutation-testing coverage work for the LibICloneableFactoryV4 effective-salt unit, which matches the primary objective.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0 files. (3 skipped: 3 unsupported.)
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-24-amt-g1-libicloneablefactoryv4-effec

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 6 commits August 24, 2026 12:17
`test/src/**` mirrors `src/**` and holds the `.t.sol` suites. This repo is
the library half of the split — there is no `src/concrete/`, the concrete
lives in rain.factory.deploy — so `test/src/concrete/` mirrored nothing.
Test SUPPORT code (harnesses, mocks, fixtures) belongs outside the mirror,
in `test/concrete/`, `test/lib/`, `test/abstract/`, as in rain.deploy and
rain.math.float.

Pure move plus the import paths that follow it. No behaviour change.

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

Three parallel AMT branches (#76, #77, #78) each grew their own variant of
`TestCloneable` because it satisfies neither of `ICloneableV2`'s normative
MUSTs and moves in lockstep with the constant the library compares against.
Fix it once, here, so the branches converge on one fixture instead of four:

- `initialize` can NOT be called more than once. That is the interface's
  first MUST and no fixture honoured it.
- The RECOMMENDED typed overload is present and reverts
  `InitializeSignatureFn` always, as the interface requires.
- The success sentinel is written out from the LITERAL string the interface
  names, not imported from `ICLONEABLE_V2_SUCCESS`. Importing it put both
  sides of the library's comparison in lockstep: the constant could drift and
  every flow test would still pass, because the fixture drifted with it. A
  third party hard-codes `keccak256("ICloneableV2.initialize")`, so the
  fixture does too, and every existing flow test now discriminates a drift.

Separately, `testCheckImplementationCodeEtched` could fail for a harness
reason: `vm.etch` parses a `0xef01` prefix as an EIP-7702 delegation
designator and rejects anything that is not exactly the 23-byte designator.
EIP-3541 forbids deploying any `0xef`-leading code at all, so such code cannot
exist at an implementation address on any chain and the guard is not specified
over it; the fuzz domain is narrowed to code that could actually exist. The
guard only ever reads code LENGTH, so nothing about the property changes.

Gas snapshot regenerated for the extra `SSTORE` the initialization guard costs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	test/src/lib/LibICloneableFactoryV4.checkImplementationCode.t.sol
The merge from `2026-08-24-test-fixtures-out-of-src-mirror` gives
`TestCloneable` a one-shot initialization guard, so every clone in the suite
now costs an extra `SSTORE`, and this branch's two new boundary-salt tests had
no entries at all.

The `0xef` fuzz-domain narrowing this branch carried as its own commit is
resolved onto the base branch's version of the same line, which now holds it
once for all four AMT branches instead of four times.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Per CodeRabbit on this PR. The finding is correct and the comment on
`vm.assume(code[0] != 0xef)` was overclaiming.

The comment said no implementation on any chain can have `0xef`-leading code.
EIP-3541 does forbid DEPLOYING it, so no CREATE or CREATE2 can produce it — but
EIP-7702 leaves exactly one way an account can hold it anyway: a delegation
designator, `0xef0100 || address`, exactly 23 bytes. `EXTCODESIZE` on a
delegated EOA returns 23, not zero. The assume was silently excluding a case
that is real, behind a comment saying it was not.

`testCheckImplementationCodeEip7702Designator` pins it as a fixed case, since
the fuzz test cannot reach it. It PASSES the guard, which is the part worth
having on the record: a code-SIZE check cannot tell an implementation contract
from an EOA that has delegated, and a delegation is REVOCABLE by the account
holder where deployed code is not. Callers wanting an immutable implementation
do not get that from this guard.

Mutation-checked, not assumed: with the guard mutated to
`code.length == 0 || code[0] == 0xef`, this is the ONLY test in the suite that
fails (42 pass, 1 fail). The fuzz test cannot kill that mutant by construction,
because its assume excludes the input that would.

Scoped honestly in the NatSpec: `foundry.toml` pins `evm_version = "cancun"`,
which predates EIP-7702, so the test asserts that the 23-byte designator is
storable and passes the SIZE check. It does not exercise, and does not claim,
the execution semantics of delegation.

The rest of the assume stands: `0xef`-leading blobs of any OTHER length cannot
exist on any chain, and `vm.etch` rejects them outright ("Eip7702 is not 23
bytes long"), which is what was breaking the fuzz test 8 runs in 8.

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

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Queuing a review — the original attempt hit the OSS rate limit before reading the diff.

Orientation: nothing under src/ is touched. The net diff over the base branch is two tests, testEffectiveOpenSaltBoundarySalts and testPredictCloneAddressBoundarySalts, plus a gas snapshot regeneration. The 0xef fuzz-domain narrowing this branch originally carried as its own commit now lives once on the base branch instead of four times across four AMT branches.

@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

@thedavidmeister I will review pull request #75 with the stated scope in mind.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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