Skip to content

audit: the #76 claims, measured - 23/28 killed, zero new - #115

Merged
thedavidmeister merged 2 commits into
mainfrom
hash-76-scan-record
Sep 16, 2026
Merged

thedavidmeister merged 2 commits into
mainfrom
hash-76-scan-record

Conversation

@thedavidmeister

@thedavidmeister thedavidmeister commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Closes #76.

#76 lists five documented claims that no test names: the packed-collision
motivating example, "a Foo is ALWAYS 4 words" with populated members,
deterministic boundary lengths, pointer non-determinism, and bytes1[] as a
word list.

#111 filed all five as tests and was closed unmerged: its own measurement showed
the seven tests killed no mutant the suite did not already kill, and three of
them never called the library. #76 was then reopened deliberately, undecided -
that was a verdict on the PR, not on the question of whether this repo wants a
pinning test per documented claim.

This decides it by measurement. #111 measured six mutants against tests that in
three cases could not have killed any. Here the mutant set is every behaviour
src/lib/LibHashNoAlloc.sol has, and the candidates were first rewritten to
call the library wherever the claim permits it, so the result is not an artifact
of how they were written.

A test earns its place here only by killing a mutant the suite does not already
kill, so both sides were probed with mutation-probe over the same 28 mutants:
the read offset and byte count of hashBytes and of both hashWords overloads,
the two scratch writes and the hashed range of combineHashes, the HASH_NIL
literal, and the no-allocation promise of all four.

The pre-existing 77-test suite kills 28/28. The seven tests #76 proposes,
written in their strongest library-calling form and probed alone, kill 23/28
with zero new kills
. So no test is added; the diff is the scan record, and
this is the evidence.

Claim by claim

# Claim What #76 proposed Mutants it kills New kills Already pinned by
1 "abc"+"def" and "ab"+"cdef" pack identically and the composition separates every pair (LibHashNoAlloc NatSpec) testAbcDefAndAbCdefPackAlikeAndComposeApart, testCompositionSeparatesSplitsOfOneString 5 (HB03-05, HB08, HB09) 0 testLeafNodeCollision pins combineHashes(a, b) == hashBytes(abi.encodePacked(a, b)) over fuzzed bytes, testHashBytes pins hashBytes == LibHashSlow at every fuzzed length, testFoldEqualsPackedConcatFold pins the fold against the packed-concat oracle (crossType / LibHashNoAlloc / HashPatternFold)
2 "a Foo is ALWAYS 4 words", with populated members (README) testFooIsFourWordsWithPopulatedMembers 0 0 testFooIsFourWords pins ptr == fmpBefore and a 0x80 region; testDynamicMembersArePointerWords and testOneWordReferenceMembersArePointerWords pin that each member is one word whatever it holds; testFooListIsWordList measures Foos in a list (MemoryLayout / HashPattern)
3 The word-boundary lengths reached deterministically, and hashWords([x]) being the bare word's hash testHashBytesAtWordBoundaryLengths, testSingletonWordListHashesAsItsBareWord 18 (HB01-09, HW01-06, HU01, HU02, CH02) 0 testEmptyCollision (length 0 through every entry point), testWordsOneTwoKnownAnswer (2 words / 64 bytes through every entry point against a pinned constant), testKnownAnswersAreBuiltinKeccak, testHashWordsGasOneWord and testHashWordsGasTwoWords, testHashBytesCostsTheSameAsBuiltinLong at 4096 bytes, testBytesTrueLength for 1 vs 2 bytes, the testHashBytes / testHashWords / testHashWordsUint256 fuzzers against LibHashSlow, and testFoldSingletonIsNotItem for the contrast with the seeded fold
4 "a pointer ... is not even deterministic" (README "Handling pointers") testEqualFoosDifferByRegionAndAgreeByComposition 17 (HB01-09, HU01-03, CH01-05) 0 testStructWithPointersHashesAsNestedNodes runs the README's steps A-E over a fuzzed Foo against LibFooOracle.hashFoo; testPointerOnlyStructEqualsFoldOfList and testPointerOnlyStructEqualsFoldOfListAnyLength walk the pointers of a fuzzed struct and equate the walk with the fold; testDynamicMembersArePointerWords (HashPattern / crossType / MemoryLayout)
5 bytes1[] named as a word list for hashing (README) testBytes1ArrayHashesAsItsWords 6 (HW01-06) 0 testBytes1ArrayIsWordList pins the layout - length prefix then one left-aligned word per element; testHashWordList pins the word-list Yul against keccak256(abi.encodePacked(...)); testFixedBytesIsLeftAligned; testHashWords against LibHashSlow (MemoryLayout / HashPattern / LibHashNoAlloc)

Union of the proposals: 23 of 28. Every one of those 23 already dies to the
suite, by the tests the last column names and others; the full kill list per
mutant is in the transcript linked below.

The five the proposals never reach

HB10, HW07, HU04 and CH06 replace each function's in-place hash with a copy
through a fresh allocation - the no-allocation promise, which is the whole
reason this library exists. HN01 moves HASH_NIL by one nibble. None of the
seven proposals asserts either, because none of the five documented claims is
about allocation or about the nil constant. The suite kills all five:
testHashBytesNoAlloc, testHashWordsNoAlloc, testHashWordsUint256NoAlloc,
testCombineHashesNoAlloc, the four ...TouchesNoMemory /
testCombineHashesTouchesOnlyScratch memory tests, and testHashNil with
testEmptyCollision.

The proposals were measured at their strongest, not as filed

Three of the fix texts in #76 route through a private copy of the README's
assembly or through builtins only, which is the objection that closed #111 and
which would guarantee a zero result for an uninteresting reason. Each was
rewritten to call LibHashNoAlloc wherever the claim permits it, and named in
full words rather than the single letters #111 was also faulted for, before
being probed. What is measured is the best case for adding them.

Claim 2 is the one that cannot be routed: "a Foo is ALWAYS 4 words" is a
statement about the Solidity compiler's memory layout, not about this library.
Its test calls nothing in src/, so there is no library line whose mutation it
could catch, and it kills nothing. That is a property of the claim, not a
weakness in how the test was written.

The record

The appended audit/mutation-test-scans.json entry is the scan itself: 28
behaviours, 28 killed before, 28 after, 0 candidates, 0 confirmed, 77 tests
before and after, nothing filed. It is recorded against f2a9f5f, the commit
scanned; main has since moved to 092293e by a README-only commit (#113) and
a NatSpec-only one (#114). test/ is byte-identical to the scanned tree and
src/ differs from it only in /// lines, so every mutated line is unchanged
and the 28/28 stands for main as it is now. The suite figures in this PR's QA
were re-run here, not carried over.

The 28 mutant definitions and both probe transcripts are pushed on the
evidence-only branch hash-76-measurement at a3c78d7, on top of d8fe182
which carries the seven candidate tests as they were measured. That branch is
artifact, not a proposal, and is not for merge.

QA

Suite: 77 passed / 0 failed / 0 skipped across 9 suites. forge lint -D warnings clean, forge fmt --check clean, pre-commit run --all-files passes
and rewrites nothing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01V8ViHcKLVk2YoS2joH4HdN

The five documented claims of #76 were measured rather than assumed. 28 mutants
over every behaviour of LibHashNoAlloc: the suite as it stands kills 28/28, and
the seven tests the issue proposes, written in their strongest library-calling
form and probed alone, kill 23 of those 28 and nothing the suite misses. One of
the seven kills nothing because its claim is about the compiler's memory layout
and it never calls the library.

So no test is added and the scan record is the whole change. The mutants and
both probe transcripts are on hash-76-measurement, which is evidence, not a
proposal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V8ViHcKLVk2YoS2joH4HdN
@coderabbitai

coderabbitai Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: a05f0b77-4627-4436-b0a2-e1f2e88cc397

📥 Commits

Reviewing files that changed from the base of the PR and between 092293e and c1d4f95.

📒 Files selected for processing (1)
  • audit/mutation-test-scans.json

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


Walkthrough

The audit data adds a mutation-test scan for LibHashNoAlloc.sol. It records 28 behaviours, 77 existing tests, zero surviving mutants, and no test or source changes.

Changes

Mutation Scan

Layer / File(s) Summary
Mutation-scan audit record
audit/mutation-test-scans.json
Adds a scan record for LibHashNoAlloc.sol with 28 behaviours, 77 tests before and after the scan, no test additions, and zero surviving mutants, candidates, findings, coverage PRs, or filed issues.

Priority: ⬇️ Low

Estimated code review effort: 1 (Trivial) | ~2 minutes

Change: Other

Merge Risk: ⚪ Minimal · up to c1d4f

This PR records mutation-scan results without changing production or test behavior, so it is mergeable.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Issue #111 requires seven claim-witness tests for the five claims from #76: packed-collision composition, populated Foo width, boundary lengths and singleton word-list hashing, pointer non-determini… Add the seven tests specified by #111 in the listed test files, or provide reviewable evidence that equivalent tests already exist at the reviewed head. Run the test suite and retain tests that pin all five claims.
✅ Passed checks (4 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The only changed file is audit/mutation-test-scans.json. The record documents mutation results for the five claims in #76 and the candidate tests in #111. This change supports the linked issue asses…
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…
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the audit measurement for issue #76 and accurately states the candidate-test result: 23 of 28 mutants killed with zero new kills.
Full details: Linked Issues check

Explanation

Issue #111 requires seven claim-witness tests for the five claims from #76: packed-collision composition, populated Foo width, boundary lengths and singleton word-list hashing, pointer non-determinism, and bytes1[] hashing. The whole-PR diff adds only an audit/mutation-test-scans.json record. It adds or modifies no test code. The record measures candidate tests but does not implement the required automated tests.

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch hash-76-scan-record

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.

#114 landed on main after the record was written. It changes only /// lines in
LibHashNoAlloc, so no mutated line moved, but "src/ and test/ are byte-identical
to the scanned commit" is no longer literally true and the note now says what is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V8ViHcKLVk2YoS2joH4HdN
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant