22pragma solidity ^ 0.8.25 ;
33
44import {Test} from "forge-std/Test.sol " ;
5- import {Quant, UintQuantizationLib, Overflow, NotAligned, BadConfig} from "src/UintQuantizationLib.sol " ;
5+ import {Quant, UintQuantizationLib, Overflow, NotAligned, BadConfig, CeilOverflow } from "src/UintQuantizationLib.sol " ;
66
77/// @notice Thin harness that exposes library functions via `using-for` so tests call them on
88/// `Quant` values rather than through the library name directly.
@@ -51,10 +51,18 @@ contract QuantHarness {
5151 return q.isAligned (value);
5252 }
5353
54+ function isValid (Quant q ) external pure returns (bool ) {
55+ return q.isValid ();
56+ }
57+
5458 function fits (Quant q , uint256 value ) external pure returns (bool ) {
5559 return q.fits (value);
5660 }
5761
62+ function fitsEncoded (Quant q , uint256 encoded ) external pure returns (bool ) {
63+ return q.fitsEncoded (encoded);
64+ }
65+
5866 function floor (Quant q , uint256 value ) external pure returns (uint256 ) {
5967 return q.floor (value);
6068 }
@@ -201,7 +209,7 @@ contract UintQuantizationLibSmokeTest is Test {
201209 }
202210
203211 // -------------------------------------------------------------------------
204- // Boundary: shift == 0 (identity / no compression)
212+ // Boundary: discardedBitWidth == 0 (identity / no compression)
205213 // -------------------------------------------------------------------------
206214
207215 function test_discardedBitWidth_zero_identity () public view {
@@ -280,6 +288,49 @@ contract UintQuantizationLibSmokeTest is Test {
280288 harness.encode (q, 2 );
281289 }
282290
291+ // -------------------------------------------------------------------------
292+ // isValid: create-produced vs hand-wrapped
293+ // -------------------------------------------------------------------------
294+
295+ function test_isValid_createProduced () public view {
296+ Quant q = harness.create (DISCARDED_8, ENCODED_8);
297+ assertTrue (harness.isValid (q));
298+ }
299+
300+ function test_isValid_handWrapped_invalid () public view {
301+ // encodedBitWidth=0 (invalid: rejected by create)
302+ assertFalse (harness.isValid (Quant.wrap (0 )));
303+ // discardedBitWidth=255, encodedBitWidth=255: sum=510 > 256
304+ assertFalse (harness.isValid (Quant.wrap (uint16 (0xFF00 | 0xFF ))));
305+ }
306+
307+ // -------------------------------------------------------------------------
308+ // fitsEncoded: decode-side range check
309+ // -------------------------------------------------------------------------
310+
311+ function test_fitsEncoded_true () public view {
312+ Quant q = harness.create (DISCARDED_8, ENCODED_8);
313+ // encodedBitWidth=8, so max encoded = 255
314+ assertTrue (harness.fitsEncoded (q, 0 ));
315+ assertTrue (harness.fitsEncoded (q, 255 ));
316+ }
317+
318+ function test_fitsEncoded_false () public view {
319+ Quant q = harness.create (DISCARDED_8, ENCODED_8);
320+ assertFalse (harness.fitsEncoded (q, 256 ));
321+ }
322+
323+ // -------------------------------------------------------------------------
324+ // ceil: overflow revert
325+ // -------------------------------------------------------------------------
326+
327+ function test_ceil_overflow_reverts () public {
328+ Quant q = harness.create (DISCARDED_8, ENCODED_8);
329+ // type(uint256).max is not aligned to 256; rounding up overflows
330+ vm.expectRevert (abi.encodeWithSelector (CeilOverflow.selector , type (uint256 ).max));
331+ harness.ceil (q, type (uint256 ).max);
332+ }
333+
283334 // -------------------------------------------------------------------------
284335 // Fuzz tests
285336 // -------------------------------------------------------------------------
@@ -303,6 +354,8 @@ contract UintQuantizationLibSmokeTest is Test {
303354 function testFuzz_decodeMax_ge_decode (uint8 discardedBitWidth_ , uint8 encodedBitWidth_ , uint256 encoded ) public view {
304355 vm.assume (encodedBitWidth_ > 0 && uint256 (discardedBitWidth_) + uint256 (encodedBitWidth_) <= 256 );
305356 Quant q = UintQuantizationLib.create (uint256 (discardedBitWidth_), uint256 (encodedBitWidth_));
357+ // Bound to valid encoded range so the test exercises the documented domain.
358+ encoded = bound (encoded, 0 , (uint256 (1 ) << harness.encodedBitWidth (q)) - 1 );
306359 assertGe (harness.decodeMax (q, encoded), harness.decode (q, encoded));
307360 }
308361
@@ -324,14 +377,18 @@ contract UintQuantizationLibSmokeTest is Test {
324377 assertEq (harness.fits (q, value), value <= harness.max (q));
325378 }
326379
327- function testFuzz_ceil_ge_value (uint8 discardedBitWidth_ , uint8 encodedBitWidth_ , uint256 value ) public view {
380+ function testFuzz_ceil_ge_value (uint8 discardedBitWidth_ , uint8 encodedBitWidth_ , uint256 value ) public {
328381 vm.assume (encodedBitWidth_ > 0 && uint256 (discardedBitWidth_) + uint256 (encodedBitWidth_) <= 256 );
329382 Quant q = UintQuantizationLib.create (uint256 (discardedBitWidth_), uint256 (encodedBitWidth_));
330383 uint256 s = uint256 (discardedBitWidth_);
331384 if (s > 0 ) {
332385 uint256 mask = (uint256 (1 ) << s) - 1 ;
333- // Exclude values where (value | mask) + 1 would overflow uint256
334- vm.assume (value < type (uint256 ).max - mask);
386+ if (value >= type (uint256 ).max - mask && value & mask != 0 ) {
387+ // Overflow region: ceil should revert
388+ vm.expectRevert (abi.encodeWithSelector (CeilOverflow.selector , value));
389+ harness.ceil (q, value);
390+ return ;
391+ }
335392 }
336393 assertGe (harness.ceil (q, value), value);
337394 }
0 commit comments