test: mutation-harden LibDataContract coverage#22
Conversation
Add deterministic boundary tests that pin behaviors the existing fuzz suite only covered by chance, surfaced via a mutation pass over src/lib/LibDataContract.sol (write/read/readSlice). Source is unchanged. New tests: - testReadEmptyData / testReadZeroCodeReverts: pin the `read` size guard to exactly zero. Empty data deploys to a 1-byte container (the 0x00 prefix) that reads back empty WITHOUT reverting, while a no-code address reverts ReadError. Kills size==0 -> size==N mutants the fuzzer only caught when it happened to generate empty bytes. - testRoundLargestData / testContractCreationCodeLargestAccepted / testContractCreationCodeSmallestRejected: round-trip at the largest valid length (type(uint16).max - 1) and pin both sides of the GTE DataTooLarge guard. The existing revert test only covered the rejected side; the accepted boundary and the full 2-byte length-encoding path (shl 232, +1 prefix math) were never reached by `bytes` fuzzing. - testReadSliceExactEndBoundary: a slice ending exactly at the data end is valid; one byte past reverts. Pins `size < end` deterministically. - testReadSkipsPrefixExactly: a non-zero leading data byte appears at index 0 of read/readSlice, pinning the skip-one-byte read offset. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Warning Review limit reached
More reviews will be available in 58 minutes and 55 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
Reviewed 1a4a178: mutation-validated tests-only coverage for LibDataContract (read size==0 + max-size boundary, 7 tests); source byte-identical. LGTM. |
Summary
A scoped adversarial-mutation-test coverage pass over
src/lib/LibDataContract.sol(the SSTORE2-style data-contract write / read / readSlice paths). Test-only —src/is byte-for-byte unchanged (verifiedgit diff src/empty). The pass mutated each load-bearing line, ran the suite, and added discriminating tests only for behaviors that survived or were covered solely by fuzz luck.No anchoring issue exists (the only open issue, #1, is an EIP-3540 research item and is out of scope for coverage hardening), so this is standalone.
Group hardened:
LibDataContractwrite/read/readSlice boundariesThe existing suite is a strong set of fuzz round-trips, so most mutants were already killed. The gaps were edge inputs the
bytes/uint16fuzzer effectively never generates — empty data, the max valid length, and exact-end slices — which left several boundary mutants alive or killed only when the fuzzer happened to land on the edge.Mutation matrix
DataTooLarge>=→>testContractCreationCodeDataTooLargeRevert+1→+0testRoundLargestData,testReadSkipsPrefixExactlypin deterministicallyshl(232,…)→shl(233,…)size < end→<=testSameReads(fuzz)testReadSliceExactEndBoundary(deterministic)extcodecopyoffset1→0testReadSkipsPrefixExactly,testRoundLargestData(deterministic)+1→+0end = offset+length→+0testRoundSliceError(fuzz)offset→startPREFIX_BYTES_LENGTH13→12size == 0→size == 1testReadEmptyData,testReadZeroCodeRevertspin both sides of the size guardDataTooLargeguardmax→max-1(tighten accepted side)testContractCreationCodeLargestAccepted,testRoundLargestDatapin the accepted boundaryEvery new test was confirmed to pass clean and fail under its target mutation, then the source was restored.
New tests
testReadEmptyData/testReadZeroCodeReverts— writing empty data yields a 1-byte container (just the0x00prefix) thatreads back empty without reverting, while a no-code address revertsReadError. Pins thereadsize guard to exactly zero.testRoundLargestData— round-trips at the largest valid length (type(uint16).max - 1), exercising the full 2-byte length field (shl(232, data.length + 1)) and the prefix copy offset at maximum width, plus a slice of the final byte.testContractCreationCodeLargestAccepted/testContractCreationCodeSmallestRejected— pin both sides of the GTEDataTooLargeboundary (the existing revert test only covered the rejected side).testReadSliceExactEndBoundary— a slice ending exactly at the data end is valid; one byte past revertsReadError.testReadSkipsPrefixExactly— a non-zero leading data byte appears at index 0 ofread/readSlice, pinning the skip-one-byte offset.Verification
forge buildcleanforge fmt --checkcleanforge test: 13 passed, 0 failed (6 original + 7 new)git diff src/empty (source untouched)Remaining gaps / not done
readon a contract whose first byte is non-zero (i.e. NOT produced by this lib) is documented as caller responsibility (MUST have a leading byte that can be safely ignored) and intentionally not guarded; left as-is.testSameReadsrelative-cost check.