Skip to content

[RLH-10] Cut the README's copy of the abi.encode argument - #113

Merged
thedavidmeister merged 1 commit into
mainfrom
hash-50-readme
Sep 16, 2026
Merged

thedavidmeister merged 1 commit into
mainfrom
hash-50-readme

Conversation

@thedavidmeister

Copy link
Copy Markdown
Contributor

Closes #50 (RLH-10).

#50 says the library title block and the README are two hand-maintained copies
of the same argument, and that they have already drifted (#90 landed four
corrections to the NatSpec side that the README side did not have). #79 answered
it by cutting the NatSpec and was closed unmerged: a /// block cannot point at
the README, because NatSpec is what renders in tooling and what a consumer reads
at the call site. This answers it the other way round. The NatSpec is the single
statement of the argument; the README loses its copy and points at
src/lib/LibHashNoAlloc.sol instead of restating it.

README.md only. Nothing in src/ or test/ is touched.

What was cut, and where it already lives

Line numbers are main: README at f2a9f5f, src/lib/LibHashNoAlloc.sol at the
same commit.

README (cut) NatSpec that carries it
L150-158 abi.encodePacked concatenates, "abc"+"def" / "ab"+"cdef" pack alike, packed is safe only for fixed-length data src L16-19
L160-166 the abi.encode fix, 3"abc"+3"def" / 2"ab"+4"cdef", head/tail offsets, the abi-spec URL src L19-22, L33-36
L168-172 lengths are fixed uint256; the canonical encoding of one type tuple is decodable, so injective on it src L22-23
L174-179 not injective ACROSS types; the "one hash domain, one type" restriction; the case is cost src L23-24
L190-206 an encoding must size, allocate, copy and write headers, with nonlinear expansion src L30-36
L222-224 "easily 1-10k+ gas" src L26-29
L226-231 keccak256 allocates nothing; the scratch space it hashes through src L46-56
L576-579 the induction is parity with abi.encode's decodability, reached without producing the encoding src L38-42

L222-224 is the stale side of a fact #90 corrected: the NatSpec's "easily 1k+
gas for simple structs" became measured figures ("several hundred ... even when
those fields are empty, and over 1k once they hold a handful of words"), and the
README's vaguer "easily 1-10k+ gas" was never brought along. It is deleted
rather than carried forward.

What was judged unique and kept

Nothing was cut that the NatSpec does not already state, and no prose was moved
into NatSpec to justify a cut.

  • The whole pattern walkthrough: the three memory layouts, the Foo struct, the
    pointer-word rules, hashing contiguous words / word lists / byte strings, the
    A-E pointer walkthrough, the fold and its measured gas fractions, the nil hash
    prefix.
  • The security-of-composition induction over its five cases, the "one hash
    domain, one type" restriction, and the cross-type collision list under "Across
    types nothing is unambiguous" - all of which the NatSpec defers to rather than
    duplicates ("the notes on each function below list what collides").
  • The reference implementation section and the soldeer install snippet.
  • The EIP712 quote and its key takeaways: context for the argument, with no
    NatSpec counterpart.
  • "Non goals", "Implementing and testing the pattern", "Dev stuff", the licence
    sections.
  • Inside "Gas cost of encoding": the "nobody ever got fired" framing, the
    sentence that the cost is fundamental to encoding rather than Solidity's
    fault, the bolded general claim about no-encode implementations with its
    SSTORE2 / LibDataContract measurement, and the note that Solidity gives no
    keccak256 for non-bytes types. None of these has a NatSpec counterpart.
  • The ## Problem opening sentence and the compiler transcript under it. The
    sentence overlaps src L14-17, but a section headed "Problem" has to name its
    subject for the transcript and the two questions to hang off; naming the
    subject is not the argument.
  • "Nil hash prefix". Its counterpart is the @dev on the HASH_NIL constant,
    not the title block, and the README's own fold description needs the seed
    defined where it is defined.

Three passages in the title block have no README counterpart and so had nothing
to cut against: the internal-call overhead note (src L58-62), the Header
example (src L64-88, a different struct from the README's Foo and making a
different point), and the caller memory-safety paragraph (src L90-93).

One judgment call worth naming

L174-179 carried a concrete counterexample - abi.encode of uint8(1),
uint256(1), true and address(1) are the same 32 bytes - that #90 verified
with cast abi-encode. The claim it illustrates is in the NatSpec at src
L23-24; the illustration is not. It went with the argument it illustrates rather
than being kept as a lone fragment of that argument in the README, and it is not
moved into the NatSpec. Flagging it because it is the only concrete thing that
went.

QA

  • Discriminating tests: n/a - the diff is README.md only, which nothing
    compiles, imports or reads at runtime, so no observable value can differ
    between base and branch. Inertness shown rather than asserted:
    git diff --name-only main is exactly README.md, and the suite is 77
    passed / 0 failed / 0 skipped across 9 suites on main and identical on this
    branch.
  • Mutations applied: n/a - no executable line changed, so there is no line to
    mutate. The mutation-equivalent for a deleted passage is that the passage was
    not the only statement of what it said: each row of the table above was
    checked by reading the NatSpec line independently and confirming it states the
    same thing. No surviving line depends on a cut passage for its meaning either
    • the remaining abi.encodePacked mentions in README are the ## Problem
      framing (L18), the compiler transcript (L28), the new pointer (L156) and the
      length-prefix remark at L496, which the surviving EIP712 material at L316
      already grounds; the surviving "scratch space" uses at L370-383 are introduced
      by the pointer at L172, which precedes them; and nothing in src/ or test/
      cites README prose at all.
  • Oracle: src/lib/LibHashNoAlloc.sol itself, read line by line against the
    README passages, not the audit text in the issue - the issue argues the cut in
    the other direction and its line numbers predate the src/lib/ move, so its
    evidence was re-derived here against current main.
  • Category check: the ruling asks for (a) the README's copy of the argument cut,
    (b) the README pointing at the library source instead of restating it, (c)
    nothing cut that has no NatSpec counterpart and no prose moved into NatSpec,
    (d) no stale README claim carried into the survivor. Covered: (a) the eight
    passages in the table, 62 lines deleted; (b) three pointers - one in the intro,
    one replacing the body of "Collisions with ABI encoding", one in "Gas cost of
    encoding" - each naming the topics the title block carries rather than
    restating its conclusions; (c) the kept list above, with each keep's reason,
    and src/ is untouched; (d) L222-224 deleted as the stale side of [RLH-09] Correct four claims in the LibHashNoAlloc title block #90's
    correction, and every other cut passage's surviving copy is the post-[RLH-09] Correct four claims in the LibHashNoAlloc title block #90 text.

Suite: 77 passed / 0 failed / 0 skipped across 9 suites, before and after.
forge fmt --check clean. pre-commit run --all-files passes and rewrites
nothing, run again after the edit to confirm the tree is stable.

🤖 Generated with Claude Code

https://claude.ai/code/session_01V8ViHcKLVk2YoS2joH4HdN

The library title block in src/lib/LibHashNoAlloc.sol and the README carried the
same argument against keccak256(abi.encode(...)), and they drifted: #90
corrected four claims on the NatSpec side that the README side never got.
NatSpec is the copy a consumer reads at their pinned revision, so it is the copy
that stays.

The README loses the packed-collision example, the abi.encode fix with its
decodability and cross-type claims, the encoding-overhead argument, the
"1-10k+ gas" claim #90 had already corrected on the other side, the
scratch-space mechanism and the induction-parity closer, and points at the
library source instead. Its pattern walkthrough, worked examples, security
induction, cross-type collision list and install section have no NatSpec
counterpart and are untouched.

Closes #50

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

Warning

Review limit reached

Next included review available in 31 minutes.

Check out review usage here.

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: Advanced

Run ID: e92cc3e4-fa36-47ff-93d3-dce996d474a1

📥 Commits

Reviewing files that changed from the base of the PR and between f2a9f5f and 982103e.

📒 Files selected for processing (1)
  • README.md

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.

@thedavidmeister
thedavidmeister merged commit 6afa299 into main Sep 16, 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.

[RLH-10] [LOW] Library title-block NatSpec is a second hand-maintained copy of the README rationale; the two drifted once and nothing keeps them aligned

1 participant