Uh oh!
There was an error while loading. Please reload this page.
Prove the traversal property over the identifier alphabets - #101
Conversation
`testRequireContractNameAcceptedNamesCannotTraverse` asserted only on the runs where the fuzzer happened to produce an accepted name, and its untyped empty catch accepted any revert at all. Replaced with `testRequireContractNameAlphabetCannotTraverse`, which states the missing half deterministically: neither the head nor the tail alphabet contains a byte that means anything to a filesystem. `testRequireContractNameMatchesAlphabet` carries the other half, pinning an accepted name to those alphabets. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Warning Review limit reached
Next review available in:55 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 |
thedavidmeister
commented
Aug 16, 2026
@coderabbitai review |
|
Uh oh!
There was an error while loading. Please reload this page.
Closes#58
What changed
testRequireContractNameAcceptedNamesCannotTraverseis deleted and replaced bytestRequireContractNameAlphabetCannotTraverse, plus anassertNoFilesystemByteshelper alongsideassertAccepted/assertRejected.The property the old test named — an accepted name carries no byte that means
anything to a filesystem — now comes from two halves that each hold on every
run:
testRequireContractNameMatchesAlphabetpins acceptance to membership ofSLOW_HEAD_ALPHABETat the first byte andSLOW_TAIL_ALPHABETat every laterone, over arbitrary bytes.
testRequireContractNameAlphabetCannotTraverseasserts that neither of thosealphabets contains
/,\,.or nul.Together those cover the whole accepted set. The empty-name half of the old
test's assertions is already carried by
testRequireContractNameRejectsEmptyand by
isContractNameSlowreturning false on the empty name.Where the issue's proposed fix was wrong
Two corrections, both load-bearing, both with a mutant below that the proposed
version survives:
SLOW_TAIL_ALPHABETonly. An accepted name drawsits first byte from
SLOW_HEAD_ALPHABETand nothing in the suite states thatthe head alphabet is contained in the tail one, so a tail-only assertion
leaves the first byte of every accepted name unproven. Mutant M7: with
the proposed tail-only body, putting a dot in
SLOW_HEAD_ALPHABETpasses.alphabet runs the loop zero times and reports a pass.
assertGt(length, 0)is what turns that into a failure. Mutant M8: with the guard removed,
emptying
SLOW_TAIL_ALPHABETpasses.QA
testRequireContractNameAlphabetCannotTraverse- fails oneach of M1-M6 below (mutants applied in this clone and run one at a time,
Ran 1 testprinted each time); it cannot fail on base because base is thestate it asserts, so the discrimination is shown against the mutants rather
than against an unmutated base.
SLOW_HEAD_ALPHABETgains.-> killed bytestRequireContractNameAlphabetCannotTraverse;SLOW_TAIL_ALPHABETgains/,\and nul -> each killed by the same test;SLOW_HEAD_ALPHABETemptiedand
SLOW_TAIL_ALPHABETemptied -> each killed by itsassertGtguard;assert(false)at the top ofLibCodeGen.requireContractName-> survived bythe deleted
testRequireContractNameAcceptedNamesCannotTraverseand killed bythe other 14 tests in the file. Full table below.
SLOW_HEAD_ALPHABETandSLOW_TAIL_ALPHABETintest/lib/LibCodeGenSlow.sol, spelled out character by character, against thefour bytes that mean something to a filesystem written as literals in the test.
Neither side is derived from
LibCodeGen's range arithmetic, so the two canonly agree by being independently right.
testRequireContractNameAcceptedNamesCannotTraverseasserts nothing on 95% of its runs #58 asks for the traversal property to be provendeterministically and completely rather than on the 5% of fuzz runs that get a
name accepted; covered, with two corrections to the proposed fix (head alphabet
as well as tail, and a non-vacuity guard) each backed by a mutant the proposed
version survives.
Everything below was run in this clone with
nix develop -c, foundry from theflake,
[fuzz] runs = 2048.1. The finding reproduced — the old test asserts nothing on 96% of its runs
Instrumented the old test's two branches with
vm.writeLineand ran it at--fuzz-seed 1(instrumentation reverted afterwards):4.1% of the budget carried the property. (The issue measured 101/1947 on the
same seed; the
vm.writeLinecall perturbs the run, so the split moves a little.Same conclusion either way.)
2. The finding reproduced — the untyped catch hides a totally broken library
Put
assert(false)at the top ofLibCodeGen.requireContractNameso every callreverts with
Panic(0x01)and nothing is ever accepted, then ran the file:The deleted test is the one that passes. It is the only test in the file that
cannot notice the function being wholly broken, which is what
catch {}buys.The 14 reds are also the answer to "does deleting it lose coverage" — it had
none of its own.
3. Mutation matrix for the new test
Each mutant breaks exactly one thing
testRequireContractNameAlphabetCannotTraverseasserts; the test is then run on its own.
Ran 1 test/1 tests passedisprinted for every row, so a survivor is a survivor and not a filter that matched
nothing.
[PASS] ... (gas: 94674),Ran 1 test suite ... 1 tests passed.added toSLOW_HEAD_ALPHABET[FAIL: dot in the alphabet: 46 == 46]/added toSLOW_TAIL_ALPHABET[FAIL: separator in the alphabet: 47 == 47]\added toSLOW_TAIL_ALPHABET[FAIL: backslash in the alphabet: 92 == 92]SLOW_TAIL_ALPHABET[FAIL: nul in the alphabet: 0 == 0]SLOW_HEAD_ALPHABETemptied[FAIL: empty alphabet: 0 <= 0]SLOW_TAIL_ALPHABETemptied[FAIL: empty alphabet: 0 <= 0][PASS] ... (gas: 50983)assertGtguard deleted[PASS] ... (gas: 42999)M7 and M8 are the two deliberate survivors: they are the proposed fix without
the corrections above, and they are why the corrections are there.
4. Full suite and formatting after the change
The file keeps 15 tests: one deleted, one added.
Post-
Build.sol-removal sweep (2026-08-17)main(959d527) merged in, no conflict. Unaffected by #138's removal —nothing cut. This PR is one test swap inside
test/lib/LibCodeGen.requireContractName.t.soland touches nothing the removaldeleted.
Re-verified against
mainrather than restated:test/lib/LibCodeGenSlow.sol:20and:24still defineSLOW_HEAD_ALPHABETand
SLOW_TAIL_ALPHABET, andisContractNameSlowat:271/:275stilldecides membership against them — so the new test still asserts over the
alphabets the oracle actually uses. Share commentPrefix's rule in one place in LibCodeGenSlow #107 and test: parse the literal the library emitted, not one the test formatted #111 rewrote parts of that file
while this branch was open and did not move these.
testRequireContractNameMatchesAlphabet— the half this test's docstringleans on — is still present on
mainat:194.Overlap with #123, stated with both numbers.#123 renames this exact file to
test/lib/LibCodeGen.requireIdentifier.t.soland rewrites tests inside it. Thetwo will conflict as a rename-vs-modify whichever order they land in. Not
resolved here: #123 is held for sequencing against #56, which moves every
.t.solin the repo.Suite on the merge commit:
Ran 19 test suites: 145 tests passed, 0 failed, 0 skipped— unchanged frommain, which is right for a one-in-one-out swap.forge fmt --checkclean,git statusclean after the run.forge coverage --no-match-coverage "test|script"still 100% lines / statements / branches /funcs on all three
src/libfiles (79/79, 92/92, 10/10, 18/18) — the deletedfuzz test called
requireContractNameand the new one does not, and nothingdrops.