Audit reference
Protofire, March 2026, finding H03 — Silent Precision Loss in Packed Arithmetic Operations. Severity: High. Status at audit (19a65ffa): New.
What
Every public arithmetic function in LibDecimalFloat finalizes with (Float c,) = packLossy(...), discarding the bool lossless flag. On extreme exponent underflow (e.g. mul where expA + expB < int32.min), packLossy returns (FLOAT_ZERO, false) to indicate the value rounded to zero. The discarded flag means the library silently propagates zero in place of the true (tiny but non-zero) result.
Affected callers (every packLossy use site in LibDecimalFloat.sol):
add (line 397), sub (line 415), minus (line 430), negate path (line 454)
mul (line 486), div (line 505), inv (line 518)
intFrac calls (lines 602, 613)
floor (line 632), ceil (line 659)
pow10 (line 677), log10 (line 693), pow (line 772)
fromFixedDecimalLossyPacked (line 134) — only legitimate lossy: true propagation in the public surface
Impact
Math library composition breaks: f(x) * 0 shapes appear from values that should be small but non-zero. Triggers downstream "x must be non-zero" reverts in caller protocols, or worse, silent acceptance of zero where a small non-zero was expected. Reaching exponent ≈ int32.min requires adversarial input construction; not reachable from typical arithmetic on parsed values.
Recommendation
Two options, not mutually exclusive:
- Revert at every caller when
lossless = false for the underflow case (returns FLOAT_ZERO). Distinguishes the coefficient too big for int224 form of lossless = false from the exponent underflow → zero form so the latter reverts.
- Expose lossy variants of each op that surface the flag to the caller.
Verification
Per-op tests that hand-construct exponent inputs near int32.min and assert either revert (option 1) or lossless = false propagation (option 2).
Audit reference
Protofire, March 2026, finding H03 — Silent Precision Loss in Packed Arithmetic Operations. Severity: High. Status at audit (
19a65ffa): New.What
Every public arithmetic function in
LibDecimalFloatfinalizes with(Float c,) = packLossy(...), discarding thebool losslessflag. On extreme exponent underflow (e.g.mulwhereexpA + expB < int32.min),packLossyreturns(FLOAT_ZERO, false)to indicate the value rounded to zero. The discarded flag means the library silently propagates zero in place of the true (tiny but non-zero) result.Affected callers (every
packLossyuse site inLibDecimalFloat.sol):add(line 397),sub(line 415),minus(line 430),negatepath (line 454)mul(line 486),div(line 505),inv(line 518)intFraccalls (lines 602, 613)floor(line 632),ceil(line 659)pow10(line 677),log10(line 693),pow(line 772)fromFixedDecimalLossyPacked(line 134) — only legitimatelossy: truepropagation in the public surfaceImpact
Math library composition breaks:
f(x) * 0shapes appear from values that should be small but non-zero. Triggers downstream "x must be non-zero" reverts in caller protocols, or worse, silent acceptance of zero where a small non-zero was expected. Reaching exponent ≈int32.minrequires adversarial input construction; not reachable from typical arithmetic on parsed values.Recommendation
Two options, not mutually exclusive:
lossless = falsefor the underflow case (returnsFLOAT_ZERO). Distinguishes thecoefficient too big for int224form oflossless = falsefrom theexponent underflow → zeroform so the latter reverts.Verification
Per-op tests that hand-construct exponent inputs near
int32.minand assert either revert (option 1) orlossless = falsepropagation (option 2).