Skip to content

Guard displayExponent overflow in _toScientific (#185) - #245

Open
thedavidmeister wants to merge 7 commits into
mainfrom
2026-06-17-issue-185-scientific-exponent-overflow
Open

Guard displayExponent overflow in _toScientific (#185)#245
thedavidmeister wants to merge 7 commits into
mainfrom
2026-06-17-issue-185-scientific-exponent-overflow

Conversation

@thedavidmeister

@thedavidmeisterthedavidmeister commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • _toScientific computes displayExponent = exponent + scaleExponent where scaleExponent is 75 or 76 (from maximizeFull). For valid Float values whose exponent is within ~76 of int32.max, displayExponent overflows int32 and the formatter emits a string the parser cannot re-pack — a silent data-corruption bug.
  • Fix: guard the overflow and revert with UnformatableExponent(exponent) before emitting.
  • Two new tests: testFormatScientificDisplayExponentOverflowReverts (exact reproduction of the bug with (10, int32.max)) and testFormatScientificExponentAtMaxBoundarySucceeds (boundary: (1, int32.max) formats cleanly to "1e2147483647").

Closes#185

⚠️ REQUIRES REDEPLOY BEFORE MERGE

This changes LibFormatDecimalFloat, which is part of the deployed DecimalFloat contract. The testDeployAddress and testExpectedCodeHashDecimalFloat CI tests will fail until the Manual sol artifacts workflow is triggered on this branch:

gh workflow run manual-sol-artifacts.yaml --ref 2026-06-17-issue-185-scientific-exponent-overflow -f suite=decimal-float

Test plan

  • testFormatScientificDisplayExponentOverflowReverts — new, confirms revert for the overflow case
  • testFormatScientificExponentAtMaxBoundarySucceeds — new, confirms the boundary value still formats
  • All 25 existing LibFormatDecimalFloatToDecimalStringTest tests still pass
  • Mutation-kill verified: testFormatScientificDisplayExponentOverflowReverts uses vm.expectRevert — removing the guard causes the test to fail

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests
    • Added test coverage for scientific format exponent boundary handling.

QA

  • Discriminating tests: testFormatScientificDisplayExponentOverflowReverts, testFormatScientificExponentAtMaxBoundarySucceeds — neither fails on base, and that is the point: the _toScientific int32 guard itself already landed on main via fix: guard non-scientific formatter against positive-exp int224 overflow #243, so this PR is purely the regression coverage for it. Discrimination was verified against mutants of that guard instead (below), run in the checkout's own nix develop …#sol-shell toolchain.
  • Mutations applied: src/lib/format/LibFormatDecimalFloat.sol:108if (displayExponent > type(int32).max || displayExponent < type(int32).min)if (false) → killed by testFormatScientificDisplayExponentOverflowReverts; same line >>= → killed by testFormatScientificExponentAtMaxBoundarySucceedsalone (29 of the suite's 30 tests, including the pre-existing testFormatScientificRevertsNearPositiveInt32Limit, survive it). Unmutated baseline: 30/30 pass.
  • Oracle: issue Scientific format near int32.max exponent produces un-parseable display exponent #185's arithmetic, derived independently of the implementation — maximizeFull normalises to a 76- or 77-digit coefficient, so displayExponent = exponent + (75|76). (10, int32.max): stored exponent drops 75, display shifts +76 → int32.max + 1, out of range. (1, int32.max): drops 76, shifts +76 → int32.max exactly, in range, rendering "1e2147483647".
  • Category check: Scientific format near int32.max exponent produces un-parseable display exponent #185 asks that the scientific formatter reject a displayExponent it cannot round-trip through int32 rather than silently emit an unparseable string; covered — the exact (10, int32.max) reproduction and the in-range (1, int32.max) upper boundary, the two cases the guard's own PR left unpinned.

After maximizeFull, scaleExponent is 75 or 76. For Floats with large
positive exponents (within ~76 of int32.max), displayExponent =
exponent + scaleExponent exceeds int32.max, producing a formatted string
whose exponent the parser cannot re-pack into int32. Revert with
UnformatableExponent rather than silently emitting an un-parseable string.
The negative-overflow case is also guarded but unreachable in practice:
the minimum post-maximizeFull exponent from a valid Float is int32.min - 76,
giving displayExponent = int32.min exactly (still in range).
Two new tests pin the fix: one asserting the revert for (10, int32.max)
and one confirming (1, int32.max) still formats successfully.
Bytecode changes - requires manual-sol-artifacts redeploy before merge.
Co-Authored-By: Claude <noreply@anthropic.com>
@thedavidmeisterthedavidmeister self-assigned this Jun 17, 2026
Update DecimalFloat artifact hash after displayExponent overflow guard.
@coderabbitai

coderabbitaiBot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@thedavidmeister, you've reached your PR review limit, so we couldn't start this review.

Next review available in:54 minutes

Limit details: You’ve used all 1 included review currently available under your plan.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ecb00113-f7a7-4730-85e1-7b2ba990f57a

📥 Commits

Reviewing files that changed from the base of the PR and between 4558d2b and b99fad8.

📒 Files selected for processing (2)
  • CLAUDE.md
  • foundry.toml

Walkthrough

Two unit tests are added to LibFormatDecimalFloatToDecimalStringTest to cover the displayExponent int32 overflow fix from issue #185. One test asserts a revert with UnformatableExponent when the display exponent exceeds int32 range; the other asserts successful formatting at the exact int32.max boundary.

Changes

Scientific displayExponent overflow tests

Layer / File(s)Summary
Scientific displayExponent overflow and boundary tests
test/src/lib/format/LibFormatDecimalFloat.toDecimalString.t.sol
testFormatScientificDisplayExponentOverflowReverts constructs a lossless Float that overflows the internal displayExponent range and asserts revert with UnformatableExponent. testFormatScientificExponentAtMaxBoundarySucceeds asserts that formatting 1 with int32.max exponent succeeds and returns the canonical 1e<maxInt32> string.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly describes the main change: guarding against displayExponent overflow in _toScientific per issue #185.
Linked Issues check✅ PassedThe PR fully implements approach (1) from issue #185: detecting displayExponent overflow and reverting with UnformatableExponent, with two comprehensive tests validating overflow and boundary cases.
Out of Scope Changes check✅ PassedThe changes are narrowly scoped to adding tests for the displayExponent overflow fix; no unrelated modifications are present.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 2026-06-17-issue-185-scientific-exponent-overflow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

thedavidmeisterand others added 2 commits June 21, 2026 01:11
Accept main's originalExponent approach in UnformatableExponent revert;
update test to expect originalExponent (user-visible) instead of post-maximizeFull exponent.
Co-Authored-By: Claude <noreply@anthropic.com>
@thedavidmeister

Copy link
Copy Markdown
ContributorAuthor

Producer note: WAITING-DEPLOY (+1 inherited fuzz red). The 5 testProdDeployment* pins fail ('DecimalFloat not deployed') — same condition as main; needs the pre-merge deploy, not a code fix. The additional testRoundTripFuzzPow failure is the pre-existing #239 pow exponent-overflow bug hit by this run's fuzz seed — this PR only touches the _toScientific display path; the failure disappears once the #239 fix (#244 or #242) merges into this branch.

@thedavidmeister

Copy link
Copy Markdown
ContributorAuthor

🤖 ai:producer
Producer note: CORRECTION — my previous note said this PR carries '+1 inherited fuzz red' (testRoundTripFuzzPow). That detail was wrong: it belongs to PR #244's run, not this one. This PR's current run (28592793150) fails ONLY the 5 testProdDeployment* 'DecimalFloat not deployed' pins (508 passed), identical to main (run 28391168330). State remains WAITING-DEPLOY: greens at the interactive pre-merge deploy from this branch.

@thedavidmeister

Copy link
Copy Markdown
ContributorAuthor

🤖 ai:producer
Producer note: CORRECTION of my 2026-07-05 correction — it inverted reality. Re-read of run 28592793150 (this PR's current head 0dfd28c): it has SIX failing tests — the 5 testProdDeployment* prod pins (WAITING-DEPLOY, same as main) PLUS testRoundTripFuzzPow in LibDecimalFloat.pow.t.sol (fuzz counterexample hitting the pre-existing #239 pow exponent-overflow bug; this PR's diff is test-only in format tests and cannot cause a pow revert — the fix carriers are #242/#244). It was PR #244's run that shows only the 5 pins. State: WAITING-DEPLOY + 1 inherited fuzz red; no code action here.

@thedavidmeisterthedavidmeister added the ai:ready AI vetter: passes review, ready for human decision label Jul 6, 2026
@thedavidmeister

thedavidmeister commented Jul 7, 2026

Copy link
Copy Markdown
ContributorAuthor

🤖 ai:vetter
Reviewed 0dfd28c: ready — (10,int32.max) overflows via +76 display shift; (1,int32.max) hits int32.max exactly and succeeds; b
cost 120 — test-only boundary cases for exponent overflow

@thedavidmeisterthedavidmeister added ai:blocked-deploy and removed ai:ready AI vetter: passes review, ready for human decision labels Jul 12, 2026
@thedavidmeister

Copy link
Copy Markdown
ContributorAuthor

🤖 ai:producer
Blocked-deploy: Blocked on the same deploy-infra failure as #237 (see its ai:blocked-deploy note): red rainix-sol test is testProdDeployment* pins only; this bytecode-changing PR needs its own pre-merge suite=decimal-float deploy from this branch, and the deployer EOA 0x6698…9a88 is unfunded on flare (0 FLR) / underfunded on polygon (0.26 vs ~5 POL), so all-network deploys fail at broadcast (evidence: float run 29194586792, rainlang runs 29194199681/28649455061/28648086583). Fund the EOA, then dispatch.

@thedavidmeister

Copy link
Copy Markdown
ContributorAuthor

👤 human
Ruled 0dfd28c: reject — executing the 2026-08-06 ruling in rainlanguage/issue-pr-cron#221 — the retired ai:blocked-deploy residue (#162) migrates to ai:reject for rework to the split release lifecycle

@thedavidmeister

Copy link
Copy Markdown
ContributorAuthor

Rework note @0dfd28c7b18c14d9ccc170105f6af5f347bd774a: rework the PR to fit the split release lifecycle — deploys never gate merges (the deploy-before-merge choreography is superseded); remove or restructure anything in the PR that waits on a deploy; where deploy constants/pins are involved, follow the *.deploy repo convention (audited code only; version ↔ snapshot ↔ pins internally consistent; tag-release lifecycle). Whatever states follow the rework (including a typed blocked-on the repo's migration if one is genuinely needed) are the producer's ordinary transitions. Executes the 2026-08-06 ruling: rainlanguage/issue-pr-cron#221

@thedavidmeisterthedavidmeister added ai:needs-work Needs rework — the producer's inbox (vetter verdict or human ruling) and removed ai:blocked-deploy labels Aug 6, 2026
@thedavidmeister

Copy link
Copy Markdown
ContributorAuthor

🤖 ai:producer
QA-block repair: appended QA-GUIDE section 8's evidence block in the PR body via pr-review-report repair-qa-block. Every byte outside the ## QA section is unchanged.

baku-ccronand others added 2 commits August 17, 2026 11:39
…oy choreography
rainix's `rainix-static agent-context-cap` gate (rainlanguage/rainix#298) is red
on this repo: CLAUDE.md loaded 6593 bytes of agent context at every session
start against a 4096-byte cap. Cut to 2346 bytes by dropping what foundry.toml,
flake.nix and .github/workflows already say (build commands, layout, dependency
list, compiler settings) and keeping only the hazards and rulings that are not
recoverable from the code.
The largest section cut is the superseded deploy-before-merge choreography —
"trigger the Manual sol artifacts workflow on the PR's branch before merge", "do
NOT wait for merge before deploying", and the claim that testDeployAddress and
testExpectedCodeHashDecimalFloat gate a source-changing PR. Under the split
release lifecycle a deploy is part of a release and never gates a merge, so that
instruction is replaced by a statement of the rule it violated. The deploy
constants' own tag-pinning convention is already documented in
LibDecimalFloatDeploy.sol, where it is next to the constants it governs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rainix's `rainix-static soldeer-gate` is red on this repo: foundry.toml
[package].version was 0.1.7, which is already published to the soldeer registry
(the registry lists 0.1.1 and 0.1.7). The next-version lifecycle requires the
working version to be the next UNPUBLISHED one, so a release is a publish of
what the tree already declares rather than a bump decided at release time.
Bump only. Nothing is published by this change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai:needs-workNeeds rework — the producer's inbox (vetter verdict or human ruling)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scientific format near int32.max exponent produces un-parseable display exponent

1 participant

@thedavidmeister