From f8524c661c4a982328b8bb1de4ed6d617f1c0196 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 23 Mar 2026 20:45:01 +0400 Subject: [PATCH] Add fuzz test for packLossless zero-exponent identity property packLossless(x, 0) produces the same bytes32 as the raw integer for non-negative values that fit in int224. This property is relied on by the interpreter's EVM block opcodes (block-number, timestamp, chain-id) which store raw integers directly and use packLossless in referenceFn for differential testing. Co-Authored-By: Claude Opus 4.6 (1M context) --- test/src/lib/LibDecimalFloat.pack.t.sol | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/src/lib/LibDecimalFloat.pack.t.sol b/test/src/lib/LibDecimalFloat.pack.t.sol index 026ca06..245ccaa 100644 --- a/test/src/lib/LibDecimalFloat.pack.t.sol +++ b/test/src/lib/LibDecimalFloat.pack.t.sol @@ -71,6 +71,19 @@ contract LibDecimalFloatPackTest is Test { assertEq(unpackedCoefficient, signedCoefficient / 10, "coefficient"); } + /// packLossless(x, 0) is a bitwise identity for non-negative integers + /// that fit in int224. The packed representation places the coefficient + /// in the low 224 bits and the exponent (0) in the high 32 bits, + /// producing the same bytes32 as the raw integer. + function testPackLosslessZeroExponentIdentity(uint256 value) external pure { + // Bound to non-negative values that fit in int224. + value = bound(value, 0, uint256(uint224(type(int224).max))); + // value fits in int224 so this cast is safe. + //forge-lint: disable-next-line(unsafe-typecast) + Float float = LibDecimalFloat.packLossless(int256(value), 0); + assertEq(Float.unwrap(float), bytes32(value), "packLossless(x, 0) must be bitwise identity"); + } + /// Lossy zero when exponent is negative below type(int32).min except for zero. function testPackNegativeExponentLossyZero(int256 signedCoefficient, int256 exponent) external view { exponent = bound(exponent, type(int256).min, int256(type(int32).min) - 77);