Skip to content

Commit bdaebb5

Browse files
authored
Merge pull request #92 from 0xferit/claude/issue-90-20260313-1816
refactor: replace encodeLossless with encode(value, precise) overload
2 parents 27e1330 + 95a09e7 commit bdaebb5

File tree

3 files changed

+25
-15
lines changed

3 files changed

+25
-15
lines changed

src/UintQuantizationLib.sol

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ type Quant is uint16;
2828
/// @notice Thrown when a value exceeds the maximum representable by the scheme.
2929
error Overflow(uint256 value, uint256 max);
3030

31-
/// @notice Thrown by `encodeLossless` when a value is not aligned to the step size.
31+
/// @notice Thrown by `encode` (precise mode) when a value is not aligned to the step size.
3232
error NotAligned(uint256 value, uint256 stepSize);
3333

3434
/// @notice Thrown by `create` when the (shift, targetBits) pair is invalid.
@@ -92,13 +92,16 @@ library UintQuantizationLib {
9292
return value >> shift(q);
9393
}
9494

95-
/// @notice Strict mode: reverts if value exceeds max(q) or is not step-aligned.
96-
function encodeLossless(Quant q, uint256 value) internal pure returns (uint256) {
95+
/// @notice Encodes `value`. When `precise` is true, reverts if value is not step-aligned.
96+
/// Always reverts if value exceeds `max(q)`.
97+
function encode(Quant q, uint256 value, bool precise) internal pure returns (uint256) {
9798
uint256 m = max(q);
9899
if (value > m) revert Overflow(value, m);
99100
uint256 s = shift(q);
100-
uint256 step = uint256(1) << s;
101-
if (value & (step - 1) != 0) revert NotAligned(value, step);
101+
if (precise) {
102+
uint256 step = uint256(1) << s;
103+
if (value & (step - 1) != 0) revert NotAligned(value, step);
104+
}
102105
return value >> s;
103106
}
104107

@@ -109,7 +112,7 @@ library UintQuantizationLib {
109112
/// @notice Left-shifts `encoded` by shift, restoring discarded bits as zeros (lower bound).
110113
/// @dev The caller must ensure `encoded < 2**targetBits(q)`. Passing a larger value
111114
/// produces a result that may silently wrap or exceed the scheme's representable range.
112-
/// Values returned by `encode` and `encodeLossless` always satisfy this constraint.
115+
/// Values returned by `encode` always satisfy this constraint.
113116
function decode(Quant q, uint256 encoded) internal pure returns (uint256) {
114117
unchecked {
115118
return encoded << shift(q);

src/showcase/ShowcaseSolidityFixtures.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ contract QuantizedETHStakingShowcase {
8080

8181
function stakeExact() external payable {
8282
if (msg.value == 0) revert QuantizedETHStakingShowcase__ZeroAmount();
83-
uint96 encoded = uint96(SCHEME.encodeLossless(msg.value));
83+
uint96 encoded = uint96(SCHEME.encode(msg.value, true));
8484
stakes[msg.sender] = UserStake({
8585
amount: encoded,
8686
stakedAt: uint64(block.timestamp),
@@ -165,7 +165,7 @@ contract QuantizedExtremePackingShowcase {
165165
function setExtremeStrict(uint256[12] calldata values) external {
166166
uint256 p;
167167
for (uint256 i; i < LANES; ++i) {
168-
uint256 lane = SCHEME.encodeLossless(values[i]);
168+
uint256 lane = SCHEME.encode(values[i], true);
169169
p |= lane << (i * 20);
170170
}
171171
packedExtreme = p;

test/UintQuantizationLib.t.sol

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ contract QuantHarness {
3131
return q.encode(value);
3232
}
3333

34-
function encodeLossless(Quant q, uint256 value) external pure returns (uint256) {
35-
return q.encodeLossless(value);
34+
function encode(Quant q, uint256 value, bool precise) external pure returns (uint256) {
35+
return q.encode(value, precise);
3636
}
3737

3838
function decode(Quant q, uint256 encoded) external pure returns (uint256) {
@@ -113,23 +113,30 @@ contract UintQuantizationLibSmokeTest is Test {
113113
}
114114

115115
// -------------------------------------------------------------------------
116-
// encodeLossless: overflow and alignment reverts
116+
// encode (precise=true): overflow and alignment reverts
117117
// -------------------------------------------------------------------------
118118

119-
function test_encodeLossless_overflow_reverts() public {
119+
function test_encodePrecise_overflow_reverts() public {
120120
Quant q = harness.create(SHIFT_8, BITS_8);
121121
uint256 m = harness.max(q);
122122
uint256 value = m + 1;
123123
vm.expectRevert(abi.encodeWithSelector(Overflow.selector, value, m));
124-
harness.encodeLossless(q, value);
124+
harness.encode(q, value, true);
125125
}
126126

127-
function test_encodeLossless_notAligned_reverts() public {
127+
function test_encodePrecise_notAligned_reverts() public {
128128
Quant q = harness.create(SHIFT_8, BITS_8);
129129
uint256 step = harness.stepSize(q); // 256
130130
uint256 value = step + 1; // 257, not aligned
131131
vm.expectRevert(abi.encodeWithSelector(NotAligned.selector, value, step));
132-
harness.encodeLossless(q, value);
132+
harness.encode(q, value, true);
133+
}
134+
135+
function test_encodePrecise_aligned_succeeds() public view {
136+
Quant q = harness.create(SHIFT_8, BITS_8);
137+
uint256 step = harness.stepSize(q); // 256
138+
// 256 is aligned: encode(256, true) == 1
139+
assertEq(harness.encode(q, step, true), 1);
133140
}
134141

135142
// -------------------------------------------------------------------------

0 commit comments

Comments
 (0)