Skip to content

test: read the handle layout from LibMemoryKV, not literals - #192

Merged
thedavidmeister merged 9 commits into
mainfrom
issue-123-layout-constants
Sep 18, 2026
Merged

thedavidmeister merged 9 commits into
mainfrom
issue-123-layout-constants

Conversation

@thedavidmeister

@thedavidmeister thedavidmeister commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Closes #123

Changes

Test code reads the handle layout from LibMemoryKV (LIST_COUNT, SLOT_BITS, POINTER_MASK, COUNT_BIT_OFFSET, NODE_BYTES) and the count bound from COUNT_MAX in test/lib/LibMemoryKVHandle.sol. No test declares its own copy of a layout value, and no comment states one as a literal. Values that are derived from the layout are written as expressions over those constants, so every fixture keeps the value it had.

  • types.t.sol
    • SLOTS removed. The empty-store loop reads LibMemoryKV.LIST_COUNT and headOf.
    • Added testLayoutConstantsTileTheWord. It asserts COUNT_BIT_OFFSET == LIST_COUNT * SLOT_BITS, COUNT_BIT_OFFSET + SLOT_BITS == 256, POINTER_MASK == 2 ** SLOT_BITS - 1 and COUNT_MAX == POINTER_MASK.
    • COUNT_MAX, headOf and lengthOf are imported on one line from test/lib/LibMemoryKVHandle.sol. The header now names the layout, and the empty-store NatSpec names constants instead of "top 16 bits", "240 bits" and "Slot 14".
  • crossFunction.t.sol: the local LIST_COUNT, SLOT_BITS and NODE_SIZE are removed. The saturated handle's expected word is built from LibMemoryKV.LIST_COUNT, COUNT_BIT_OFFSET, NODE_BYTES and SLOT_BITS. In NatSpec, "fifteen" and "sixteen fields" become constant names.
  • gasClaims.t.sol: SLOTS is removed and the loops read LibMemoryKV.LIST_COUNT. SHALLOW_SLOT = LibMemoryKV.LIST_COUNT - 1. "Any of the 15" and "all 15 slots" become "Any list" and "every list".
  • wordCountOverflow.t.sol: counts and payloads are written from COUNT_MAX (COUNT_MAX - 3, - 2, - 1, COUNT_MAX, COUNT_MAX + 1). Addresses are LibMemoryKV.POINTER_MASK and POINTER_MASK + 1. NatSpec quotes the error docs as src now words them: "past POINTER_MASK" and "at a pointer above POINTER_MASK". In the testWordCountBoundIsUnreachableFromAnEmptyStore NatSpec, "carrying the count to 0x10000 needs 32768 inserts" becomes "a count past COUNT_MAX needs (COUNT_MAX + 1) / 2 inserts", which is the fill the test itself performs.
  • exportWalk.t.sol: HEAD_POINTER = LibMemoryKV.POINTER_MASK + 1 - 0x40 and CHAIN_POINTER = HEAD_POINTER - LibMemoryKV.NODE_BYTES, still 0xFFC0 and 0xFF60. The assert that HEAD_POINTER + 0x40 == POINTER_MASK + 1 is removed because it now holds by construction; NatSpec states the relation instead. The header re-quotes src.
  • getset.t.sol: 0xFFFF, 0x10000, 0x1001F and 0xFFC0 become POINTER_MASK, POINTER_MASK + 1, POINTER_MASK + 0x20 and POINTER_MASK + 1 - 0x40. "sixteen bit" and "16 bit" become SLOT_BITS. Assertion messages no longer name a literal.
  • setSlotMask.t.sol: lists 14 and 13 become LibMemoryKV.LIST_COUNT - 1 and - 2, with messages "the highest list" and "the list below it". HIGH_BIT_POINTER = 2 ** (LibMemoryKV.SLOT_BITS - 1) + 0x100, still 0x8100. "sixteen bits", "(0x8000)" and "bit 224" become constant expressions.
  • bisect.t.sol: OCCUPANCY_HIGH_HALF = OCCUPANCY_FULL ^ OCCUPANCY_LOW_HALF, still 0x7F00. Layout prose is rewritten with constant names: "16 bit length field", "Fifteen slots is 32768 combinations", "the 32768 cases", the "0xFFFF ceiling", "15 bit mask", "bit 15", "16 bits", "All fifteen slots" and "fifteen key constants". The assertion messages say "POINTER_HIGH_BIT set" and "every key in a slot of its own".
  • setInsert.t.sol: 0x10000 in the overflow-payload NatSpec becomes LibMemoryKV.POINTER_MASK + 1.
  • array.t.sol: "each of the 15 slots" becomes "each of the LibMemoryKV.LIST_COUNT slots".
  • test/lib/LibMemoryKVHandle.t.sol: testHandleWithHeadsOnlyTheNamedList fuzzes uint256 head and count bounded to POINTER_MASK and COUNT_MAX, instead of uint16 parameters that hard-code a 16 bit slot.

The issue lists some sites. The same category was also found and fixed in getset, the word-level prose in crossFunction and bisect, the derivation of setSlotMask's HIGH_BIT_POINTER and the types header.

Literals left in place:

Merge notes:

QA

  • Discriminating test: testLayoutConstantsTileTheWord. Every other change here is a refactor: each constant expression evaluates to the literal it replaces, and the whole suite passes unchanged (166 tests on the probe baseline).

  • Mutations ran on 4ec96d3 with suite --match-test testLayoutConstantsTileTheWord (baseline green, 1 passed). An earlier run on 7e43957 gave the same verdicts (head-report.txt). The head, dfaede3, adds only the merge of main's test: saturated builder refuses keys that reach past its base #187 ([A31] [INFO] buildSaturatedExternal rewinds to SATURATED_BASE without checking it clears the decoded keys #134). That merge touches crossFunction.t.sol alone: it adds testSaturatedBuilderRefusesKeysReachingPastItsBase and changes NatSpec.

    mutant result
    M01 src LIST_COUNT = 14 KILLED
    M02 src SLOT_BITS = 0x11 KILLED
    M03 src POINTER_MASK = 0x7FFF KILLED
    M04 src COUNT_BIT_OFFSET = 0xe0 KILLED
    M05 src NODE_BYTES = 0x80 SURVIVED
    M06 test/lib COUNT_MAX = type(uint256).max >> (COUNT_BIT_OFFSET - 1) KILLED
    • M06 changes no library constant, so only the COUNT_MAX == POINTER_MASK assert can fail under it.
    • M05 survives because NODE_BYTES is not part of the word's tiling. The whole suite kills it (split/123/head-4ec96d3-M05-suite-report.txt, baseline 166 passed). Killers include testCrossFrameGetReadsTheReadingFramesMemory, testEightUnrelatedWordsCostThreePairs, testOneUnrelatedWordLeavesRoomFor681Pairs, testOneUnrelatedWordMakes682PairsOverflow and testSet682PairsFitAnOtherwiseEmptyFrame; the probe lists at most five.
    • Result: 5/6 killed on the tiling test, and 1/1 killed on the whole suite.

    Reports: /home/gildlab/artifacts/memkv/audit-2026-09-18/split/123/head-4ec96d3-report.{txt,json} and head-4ec96d3-M05-suite-report.{txt,json}, with the tomls head.toml and head-M05-suite.toml alongside. The 7e43957 runs are head-report.* and head-M05-suite-report.*.

  • Oracle: testLayoutConstantsTileTheWord asserts the relations the MemoryKV NatSpec states: the lists fill the word below the count, the count is the top slot, and the pointer bound and count bound are each one slot wide. It is written independently of the values.

  • Category check: two greps over test:

    • grep -n -i -E '\b(15|14|16|0x0f|0x0e|0x60|0x10|0xf0|0xe0|0xffff|0x10000|0x8000|fifteen|sixteen|fourteen|32768|240|224)\b' -r test
    • grep -n -E '(>>|<<|&) *0x|uint16|2 \*\* *1[0-9]' -r test

    On this head they match only the forge-std-1.16.2 import paths and the sites listed above as left in place.

🤖 Generated with Claude Code

thedavidmeister and others added 4 commits September 18, 2026 15:32
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@thedavidmeister thedavidmeister self-assigned this Sep 18, 2026
@coderabbitai

coderabbitai Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 47 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: 707431ba-0689-493f-9d14-8d85d2db2acb

📥 Commits

Reviewing files that changed from the base of the PR and between 8d6eb10 and 1209bee.

📒 Files selected for processing (11)
  • test/lib/LibMemoryKVHandle.t.sol
  • test/src/lib/LibMemoryKV.array.t.sol
  • test/src/lib/LibMemoryKV.bisect.t.sol
  • test/src/lib/LibMemoryKV.crossFunction.t.sol
  • test/src/lib/LibMemoryKV.exportWalk.t.sol
  • test/src/lib/LibMemoryKV.gasClaims.t.sol
  • test/src/lib/LibMemoryKV.getset.t.sol
  • test/src/lib/LibMemoryKV.setInsert.t.sol
  • test/src/lib/LibMemoryKV.setSlotMask.t.sol
  • test/src/lib/LibMemoryKV.types.t.sol
  • test/src/lib/LibMemoryKV.wordCountOverflow.t.sol

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 and others added 2 commits September 18, 2026 15:41
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

# Conflicts:
#	test/src/lib/LibMemoryKV.types.t.sol
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
thedavidmeister and others added 3 commits September 18, 2026 17:00
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@thedavidmeister
thedavidmeister merged commit 6aa6aea into main Sep 18, 2026
4 checks passed
thedavidmeister added a commit that referenced this pull request Sep 18, 2026
Resolves the comment conflicts with #192 (#123) so each hunk is both
present tense (#138) and names layout values by LibMemoryKV constant
(#123).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

[A20] [LOW] MemoryKV handle layout is spelled as literals and per-file constants, not one definition

1 participant