diff --git a/test/src/lib/LibCtPop.ctpop.t.sol b/test/src/lib/LibCtPop.ctpop.t.sol index 7b4d2e3..e73efb6 100644 --- a/test/src/lib/LibCtPop.ctpop.t.sol +++ b/test/src/lib/LibCtPop.ctpop.t.sol @@ -3,7 +3,18 @@ pragma solidity =0.8.25; import {Test} from "forge-std-1.16.1/src/Test.sol"; -import {LibCtPop} from "src/lib/LibCtPop.sol"; +import { + LibCtPop, + CTPOP_M1, + CTPOP_M2, + CTPOP_M4, + CTPOP_M8, + CTPOP_M16, + CTPOP_M32, + CTPOP_M64, + CTPOP_M128, + CTPOP_H01 +} from "../../../src/lib/LibCtPop.sol"; /// @title LibCtPopTest /// CTPOP (count population) is a function that counts the number of bits set in @@ -72,4 +83,42 @@ contract LibCtPopTest is Test { uint256 ctSlow = LibCtPop.ctpopSlow(x); assertEq(ct, ctSlow); } + + /// A lone bit in the highest position is counted, deterministically + /// pinning the top of the range rather than relying on fuzz coverage. + function testCtPopLoneHighBit() external pure { + uint256 x = 1 << 255; + assertEq(1, LibCtPop.ctpop(x)); + assertEq(1, LibCtPop.ctpopSlow(x)); + } + + /// Build the alternating mask with `width` bits set then `width` bits + /// clear, starting from the low bit, across the whole word. + function altMask(uint256 width) internal pure returns (uint256 mask) { + unchecked { + uint256 chunk = (1 << width) - 1; + for (uint256 i = 0; i < 256; i += 2 * width) { + mask |= chunk << i; + } + } + } + + /// Every mask constant is pinned against an independent re-derivation so + /// a corrupted constant cannot hide behind ctpop and ctpopSlow sharing it. + function testMaskConstantsRederived() external pure { + assertEq(altMask(1), CTPOP_M1); + assertEq(altMask(2), CTPOP_M2); + assertEq(altMask(4), CTPOP_M4); + assertEq(altMask(8), CTPOP_M8); + assertEq(altMask(16), CTPOP_M16); + assertEq(altMask(32), CTPOP_M32); + assertEq(altMask(64), CTPOP_M64); + assertEq(altMask(128), CTPOP_M128); + + uint256 h01 = 0; + for (uint256 i = 0; i < 256; i += 8) { + h01 |= uint256(1) << i; + } + assertEq(h01, CTPOP_H01); + } }