Uh oh!
There was an error while loading. Please reload this page.
An empty meta file is hashed like any other, to keccak256("") - #103
Conversation
`describedByMetaHashConstantString` reverts `EmptyMeta` when the file it
reads holds no bytes. Such a file hashes to `keccak256("")`, the same
value whatever contract is being generated, so a `DESCRIBED_BY_META_HASH`
carrying it names no meta rather than the meta that describes the
contract. This is the refusal `bytecodeHashConstantString` already makes
for an account that exists and holds no code, which reaches the same
`keccak256("")` from the other side.
Contents of any length at all are hashed as they are: the refusal is on
the file holding nothing, not on the contents being something other than
meta. A file that is absent rather than empty is refused by the read.
The error carries the path rather than the name, because the path is the
file to go and fix and the name is recoverable from it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>Warning Review limit reached
Next review available in:52 minutes Limit details: You’ve used all 1 included review currently available under your plan. 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?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
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 |
…ing it
An empty `meta/<name>.rain.meta` hashes to `keccak256("")` and is emitted as
`DESCRIBED_BY_META_HASH` like any other file's hash. That is the correct hash of
the bytes that are there, so the suite states it instead of the library refusing
it.
Drops the `EmptyMeta` guard an earlier revision of this branch added, and its
NatSpec, leaving `src/lib/LibCodeGen.sol` identical to `main`. The contrast with
`bytecodeHashConstantString`, which does refuse the codeless address that
arrives at the same value, is written down in the pinning test's docstring.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>Uh oh!
There was an error while loading. Please reload this page.
Closes#70.
What an empty meta file produces
describedByMetaHashConstantStringreadsmeta/<name>.rain.metaand hasheswhatever bytes it finds there. A file that holds no bytes hashes to
keccak256(""), and that is emitted as the constant like any other hash:0xc5d2…a470iskeccak256(""). That is a measured value, printed by the suiteitself under M1 below, not an assumed one.
#70 asked whether that is intended or an oversight, and offered both branches:
pin it, or guard it. It is intended, so this is the pin.
src/lib/LibCodeGen.solis byte for byte identical tomainin this PR — thewhole change is the suite stating what the library does, and why it deliberately
differs from the sibling that refuses the same value.
Two tests carry it:
testDescribedByMetaHashConstantStringHashesEmptyMeta— writes a zero-bytefixture and pins the emitted declaration against
keccak256(""). Itsdocstring is the deliverable No test states what an empty
meta/<name>.rain.metaproduces fromdescribedByMetaHashConstantString#70 asked for: the written-down contrast withbytecodeHashConstantString's codeless refusal.testDescribedByMetaHashConstantStringHashesAnyLength— a one-byte file giveskeccak256(hex"00"), so length is not something this function inspects. Thereis no minimum, no padding, and no check that the bytes are meta. The empty
case is just the length-zero instance of the same rule.
The guard was considered and rejected
An earlier revision of this branch took #70's other route: it added
error EmptyMeta(string path)toLibCodeGenand reverted when the meta fileheld zero bytes. That is gone, along with the NatSpec paragraph that documented
the refusal and the
EmptyMetaimport in the test. Anyone arriving from #70looking for the guard should find the reason here rather than a silent omission.
The
CodelessInstanceanalogy that motivated it does not hold. Bytecodeis necessarily unique per contract, so a
codehashof nothing pinned againstan address names no deployment at all, and an address holding no code is
the address a caller lands on when they meant to deploy something — a mistake
in the call itself. Meta is documentation. Absent or shared documentation is
a normal state of a project, not a caller mistake: two contracts described
the same way legitimately carry the same meta hash.
"The same value whatever contract is being generated" is not a property of
emptiness.
keccak256(hex"00")is equally shared by every contract whosemeta file holds that one byte, and the guard accepted it — the accept-side
test on the earlier revision demonstrated exactly that, which put the guard's
stated justification in contradiction with its own second test.
keccak256("")is the correct hash of the actual bytes. A consumerchecking a blob against
DESCRIBED_BY_META_HASHand finding the blob emptygets a true answer; nothing is falsely authenticated. An honest empty state
is correct output, not a gap for library logic to route around.
The cost is real and lands on consumers. It is a hard revert in a
published library. Every consumer whose meta file is not written yet stops
being able to generate at all, turning a soft documentation gap into a build
failure. It also collides with
meta/holding committed artifacts (Hold .soldeerignore against the repo root, and stop shipping .audit/ #133): acommitted empty placeholder becomes impossible to have.
QA
Toolchain is the flake's throughout (
nix develop -c …), in a fresh clone,branch
2026-08-16-issue-70.origin/mainis merged in atd00e401— merged,never rebased — and everything below was run on the merge commit rather than a
conflict check alone.
Test counts.
mainatd00e401:Ran 19 test suites in 1.38s (14.25s CPU time): 145 tests passed, 0 failed, 0 skipped (145 total tests). This branch onthe merge commit:
Ran 19 test suites in 1.40s (15.38s CPU time): 147 tests passed, 0 failed, 0 skipped (147 total tests). 145 → 147 is the two testsabove.
forge fmt --check→ exit 0, no diff.Coverage,
forge coverage --no-match-coverage "test|script"— unchanged frommain:The pin can fail
A pin nobody proved can fail is not a pin. Both new tests were broken on purpose
from the committed tree, and the library was changed underneath them, one case
at a time with
git checkoutbetween. Each mutant is a patch applied throughgit apply --checkfirst, so one that no longer applies aborts the run insteadof reading as "survived", and
git diff --numstatis recorded after applying soan empty diff would be visible. Each row carries the suite's own totals line, so
a harness error cannot masquerade as a pass. The script and its patches live in
this clone's gitignored
cache/.Starting tree clean at the merge commit; every row restored before the next;
unmutated recheck at the end.
keccak256("")→keccak256(hex"00")1 1HashesEmptyMeta—assertion failedkeccak256(meta)→keccak256("")1 1HashesAnyLength—assertion failedbytes32(0)instead of hashing it1 1HashesEmptyMeta—assertion failed1 0HashesEmptyMeta—[FAIL: empty meta]3 0HashesAnyLength—assertion failedM1 is the direct proof asked of a pin, and it prints the real emitted value
next to the wrong expectation:
The left-hand side is what the library emits —
keccak256("")— and theright-hand side is
keccak256(hex"00"), the mutated expectation.M3 and M4 are the two ways the pinned behaviour could be walked back in the
library: routing the empty case to a different value, and refusing it outright.
Only the pin test catches either, which is what earns it its place beyond
restating the code. M5 is what earns
HashesAnyLengthits place independently:it is the sole killer of a library that folds a one-byte file into the empty
case, which the pin test alone does not see.
🤖 Generated with Claude Code