Skip to content

fix: guard mul() exponent-sum overflow, surface as ExponentOverflow - #244

Open
thedavidmeister wants to merge 11 commits into
mainfrom
2026-06-17-issue-239-pow-panic-exponent-overflow
Open

fix: guard mul() exponent-sum overflow, surface as ExponentOverflow#244
thedavidmeister wants to merge 11 commits into
mainfrom
2026-06-17-issue-239-pow-panic-exponent-overflow

Conversation

@thedavidmeister

@thedavidmeisterthedavidmeister commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • pow with very large or very negative integer exponents uses exponentiation-by-squaring, which repeatedly squares the base. When the base exponent is extreme, repeated doubling drives it past int256 bounds, causing a checked addition in mul() to panic with Panic(0x11) instead of the expected ExponentOverflow.
  • Fixed by adding an exact overflow guard to mul(): checks that exponentA + exponentB itself would overflow before performing the addition. The previous approach (checking each operand against EXPONENT_MAX individually) was too broad — opposite-sign pairs can never overflow int256, but the div round-trip tests pass exponents of opposite signs (one near type(int256).max, the other type(int256).min) whose sum is safely near zero.
  • New test testPowNegativeExponentSquaringPanic pins the exact revert args for the negative-exponent squaring-loop case.
  • Updated deploy constants and ABI artifact for the implementation bytecode change. Deploy needs manual trigger before testProdDeployment* will pass: gh workflow run manual-sol-artifacts.yaml --repo rainlanguage/rain.math.float --ref 2026-06-17-issue-239-pow-panic-exponent-overflow -f suite=decimal-float

Closes#239

Test plan

  • testPowNegativeExponentSquaringPanic passes (ExponentOverflow instead of Panic)
  • testPowIntegerExponentSquaringOverflow still passes (unchanged error path)
  • All div round-trip tests pass (testDivAdjustExponent*)
  • testArtifactsCommitted passes
  • testDeployAddress / testExpectedCodeHashDecimalFloat pass
  • Full fuzz suite passes (testRoundTripFuzzPow)
  • Deploy triggered manually → testProdDeployment* passes

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

Summary by CodeRabbit

  • Bug Fixes

    • Improved decimal floating-point arithmetic validation across multiplication, division, addition, subtraction, inversion, and negation.
    • Operations now consistently reject exponent overflow and underflow, including boundary and rounding scenarios.
    • Fixed edge cases involving negative exponents, normalization, cancellation, and minimum integer values.
    • Updated deployment metadata to reflect the latest contract versions while preserving support for the previous published version.
  • Documentation

    • Updated build instructions to reflect the current script name.

QA

  • Discriminating tests: 20 exponent-domain tests across test/src/lib/LibDecimalFloat.mul.t.sol and the implementation mul/div/add/sub/inv/minus test files — a boundary pair per guard (success at EXPONENT_MAX/EXPONENT_MIN, revert one past it) — plus testPowNegativeExponentSquaringPanic and testMulRenormalizationOverflowGuard. Each fails on base by construction: base has no domain enforcement, so it returns out-of-domain Floats or Panic(0x11) exactly where these expect ExponentOverflow, and the two inverted tests (testMulPositiveExponentBoundaryNoRevert, testMulNegativeExponentBoundaryNoRevert) previously asserted the un-guarded return the rework ruling rejected. Evidence recorded by the producer run at head 85e9757; src/ is byte-identical at this head (git diff 85e9757 HEAD -- src is empty), and test/ has changed only by forge fmt reflow and one vm.skip removal in the unrelated tagged-constants test.
  • Mutations applied: enforceExponentDomain upper arm dead → 6 killers; lower arm dead → 30 killers across all five operators; zero-coefficient exemption removed → killed uniquely by testAddCancellationBelowDomainReturnsZero; the mul post-adjust guard on exponent += int256(adjustExponent) disabled → killed uniquely by testMulRenormalizationOverflowGuard (Panic(0x11) vs ExponentOverflow); add's rescale bound reverted from EXPONENT_MAX to int256.max → killed by revert-payload discrimination; minus's bound reverted → killed uniquely; both domain bounds made exclusive → killed by the boundary-success tests. Entry/exit call-site removals are pinned by the same killers via operand- vs result-payload discrimination. All applied and suite-validated at head 85e9757, whose src/ this head reproduces byte-for-byte.
  • Oracle: the human rework ruling's domain semantics ([EXPONENT_MIN, EXPONENT_MAX] enforced across the operator set) plus bounds derived independently of the implementation — 2·EXPONENT_MAX == int256.max - 1, maximization shift ≤ 76, adjustExponent ∈ [0, 76]; LibDecimalFloatSlow remains the independent value oracle for the in-domain fuzz properties.
  • Category check: pow: negative-exponent squaring loop reverts Panic(0x11) instead of ExponentOverflow #239 asks that pow's exponentiation-by-squaring surface ExponentOverflow instead of Panic(0x11) — covered; the rework ruling's wider order (enforce the domain across mul/div/add/sub/inv rather than patching mul alone, invert the 2 tests that enshrined out-of-domain returns, keep the 6 mutation-validated keepers) is covered by the same diff. Deploy: nothing here waits on a deploy — under the split release lifecycle the on-chain deploy is a decoupled manual dispatch and no merge waits on it; the "Deploy needs manual trigger" line in the Summary above is superseded (see the rework note at 9b0cc58), and CLAUDE.md's deploy-before-merge instruction is removed in 2909b39.

Previously, when `pow` used exponentiation-by-squaring with a very large
or very negative exponent, repeated squaring of the base could drive the
base exponent beyond int256 range, causing a checked addition to panic
with Panic(0x11) instead of surfacing the domain-level ExponentOverflow.
Added an exact overflow guard to `mul()`: the addition `exponentA + exponentB`
overflows int256 only when both operands share the same sign and their sum
exceeds int256 bounds. Opposite-sign pairs can never overflow, so the
previous per-operand EXPONENT_MAX check was too broad (it incorrectly
fired in div round-trip tests where exponents of opposite signs combined
safely).
Pins `testPowNegativeExponentSquaringPanic` to the exact args, updates
deploy constants and ABI artifact for the implementation change.
Closes#239
Co-Authored-By: Claude <noreply@anthropic.com>
@thedavidmeisterthedavidmeister self-assigned this Jun 17, 2026
@coderabbitai

coderabbitaiBot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Decimal-float arithmetic now enforces exponent bounds across core operations. Tests cover boundary and overflow behavior, while deployment constants, bytecode artifacts, and the build script naming are updated.

Changes

Decimal-float exponent domain

Layer / File(s)Summary
Centralized exponent-domain enforcement
src/lib/implementation/LibDecimalFloatImplementation.sol
Adds enforceExponentDomain and applies exponent validation across multiplication, division, addition, and negation paths.
Exponent boundary and arithmetic regression tests
test/src/lib/LibDecimalFloat.*.t.sol, test/src/lib/implementation/LibDecimalFloatImplementation.*.t.sol
Adds coverage for exponent overflow, underflow, normalization limits, rounding boundaries, and the negative-exponent power regression.
Deployment constants and build artifact refresh
script/Build.sol, CLAUDE.md, crates/float/abi/DecimalFloat.json, src/lib/deploy/LibDecimalFloatDeploy.sol, test/src/lib/deploy/*
Renames the build script contract, updates its documentation reference, refreshes DecimalFloat bytecode, and adds pinned 0.1.7 deployment constants.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedThe PR addresses #239 by making negative-exponent pow paths fail with ExponentOverflow and adding regression coverage.
Out of Scope Changes check✅ PassedThe broader arithmetic and deployment-artifact updates appear tied to the same overflow fix and supporting test coverage, not unrelated work.
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.
Title check✅ PassedThe title accurately describes the initial mul() overflow fix, which is a real part of the broader exponent-domain enforcement changes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 2026-06-17-issue-239-pow-panic-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 5 commits June 21, 2026 01:30
…ce (bytecode changed by mul() exponent-sum overflow fix)
Previous 3b-attempt used wrong jq filter (missing deployedBytecode, had
linkReferences). CopyArtifacts.sol uses:
{abi, bytecode: {object:.bytecode.object}, deployedBytecode: {object:.deployedBytecode.object}}
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ZOLTU_DEPLOYED_DECIMAL_FLOAT_ADDRESS and DECIMAL_FLOAT_CONTRACT_HASH are
now set to the values computed from this PR's bytecode (mul() exponent-sum
overflow fix changes source). Computed by testDeployAddress failure output:
address: 0xAEBcD4963d328b83f28c5BFDD6C3e6B1692F01fC
hash: 0x73f226457d40a7eada41cb774ac4d7736f00bd0e7cb902a6be0ff6c092ca4426
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@thedavidmeister

Copy link
Copy Markdown
ContributorAuthor

Producer note: WAITING-DEPLOY. Only red is the 5 testProdDeployment* network pins ('DecimalFloat not deployed'); deploy constants already regenerated to this PR's bytecode. Main is red on the same pins. Heads-up for triage: this PR and #242 both fix the #239 pow exponent-overflow bug — one of the two should be picked and the other closed as duplicate.

@thedavidmeister

Copy link
Copy Markdown
ContributorAuthor

🤖 ai:producer
Producer note: WAITING-DEPLOY — the only red check is rainix-sol / test / test, and every failure is a testProdDeployment* prod-pin (arbitrum, base, base_sepolia, flare, polygon: 'DecimalFloat not deployed', run 28592791294; the other 507 tests pass). This PR changes bytecode, so per the deploy-before-merge flow the red pins are the pre-merge Zoltu deploy trigger: deploy from this branch per network, pins go green, then merge. No code fix applies; leaving for the interactive deploy.

@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 312d427: ready — Guard (exponentA > int256.max - exponentB) correctly exempts opposite-sign pairs; new test pins exac
cost 200 — mul exponent-sum overflow guard correctness

@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.

…#244)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@thedavidmeisterthedavidmeister added human:needs-work Human maintainer: rework required (sacred) and removed ai:blocked-deploy labels Jul 14, 2026
@thedavidmeister

Copy link
Copy Markdown
ContributorAuthor

Rework note (human): rejecting for rework — the adversarial-mutation pass confirmed (live, in-clone) that this guard does not close #239; it converts only the one pinned negative-squaring repro.

1. The #239 Panic survives. The guard protects only the first add (exponent = exponentA + exponentB, line 184). The second add exponent += int256(adjustExponent) (line 219, adjustExponent ∈ [0,76], OUTSIDE the unchecked block) still Panics(0x11): mul(type(int256).max, EXPONENT_MAX, type(int256).max, EXPONENT_MAX)Panic(0x11), same class as #239. pow's squaring loop calls impl mul on raw unpacked values, so these accumulate and hit the line-219 Panic before any packing check.

2. Factor-of-2 domain hole. The guard is at the int256 bound, not the library's EXPONENT_MAX = int256.max/2. Two in-domain same-sign exponents sum to at most int256.max, so the guard never fires for in-domain inputs and mul silently returns out-of-domain Floats: mul(1, EXPONENT_MAX, 1, EXPONENT_MAX) → exponent 2·EXPONENT_MAX, no revert. EXPONENT_MAX/EXPONENT_MIN are dead constants — zero uses in src/ — so the nominal domain is never enforced anywhere.

3. Inconsistent siblings.div computes its exponent in an unchecked block (silently wraps); add has its own ad-hoc int256.max == exponentA guard; sub/inv inherit. One overflow surface, four different treatments.

Work order: enforce the domain [EXPONENT_MIN, EXPONENT_MAX] — guard the correct bound AND the line-219 add (plus any later adjustment), surfacing ExponentOverflow rather than Panic/out-of-domain. This is really a design decision on the intended domain-overflow semantics across mul/div/add/sub/inv (the dead EXPONENT_MAX/MIN suggest the domain was intended but never wired) — align the operator set, don't patch mul alone.

Tests already on this branch (head 3574c20): the adv-mut run pushed 8 mutation-validated tests. 6 pin correct behaviours — keep them. But 2 — testMulPositiveExponentBoundaryNoRevert and testMulNegativeExponentBoundaryNoRevert — currently ENSHRINE the out-of-domain behaviour (they assert mul returns int256.max/min without reverting); invert these to expect ExponentOverflow once the guard moves to the domain bound.

…ub/inv
Wire the previously-dead EXPONENT_MAX/EXPONENT_MIN constants into every
arithmetic operator as one shared invariant: nonzero operands outside
[EXPONENT_MIN, EXPONENT_MAX] revert ExponentOverflow, and no operation
returns a nonzero coefficient at an out-of-domain exponent.
- enforceExponentDomain: shared operand/result check, zero-coefficient
exempt (zero is zero at any exponent).
- mul: operand checks make the exponent sum panic-free; a pre-adjust
guard keeps the decimal renormalization from overflowing int256 or the
domain; a result check catches the coefficient-rounding +1 and
below-domain sums.
- div: operand checks ahead of maximization; a result check covers the
plain path and the deep-underflow truncation path, whose nonzero
int256.min-exponent returns now revert. The sub-1e75 scaling leaves
and exponent-spill machinery only engage for out-of-domain divisors,
so they are now defensively dead within the domain.
- add: operand checks; the coefficient-overflow rescale reverts at
EXPONENT_MAX instead of int256.max; a result check catches maximized
sums below the domain. Exact cancellation still returns zero at any
exponent.
- minus: the type(int256).min normalization increment reverts at
EXPONENT_MAX instead of int256.max so it cannot escape the domain.
- sub/inv inherit via minus+add and div.
pow's squaring loop now surfaces ExponentOverflow at the first
out-of-domain intermediate instead of Panic(0x11), closing #239 for the
whole class rather than the single pinned repro.
Tests: boundary pairs (success at the bound, revert one past it) for
every guard; the enshrining boundary tests inverted per the rework
ruling; div machinery tests inverted to pin operand rejection; fuzz
bounds moved to the domain where results are defined; testSubPacked now
models the packLossy-vs-packArithmeticResult underflow divergence
(pre-existing latent flake, reproduced on clean main).
Deploy constants + committed ABI regenerated for the new bytecode;
frozen 0.1.7 pins added (also red on main, fix-main PR opened
separately). REQUIRES redeploy at land.
Co-Authored-By: Claude <noreply@anthropic.com>
@thedavidmeister

Copy link
Copy Markdown
ContributorAuthor

🤖 ai:producer
Producer note: rework pushed (85e9757) implementing the rework note's ruling — the domain [EXPONENT_MIN, EXPONENT_MAX] is now enforced across the operator set, not patched into mul alone.

What changed

  1. Domain wired, all five operators: new enforceExponentDomain (zero-coefficient exempt) checks operands and results in mul/div/add; sub/inv inherit via minus+add and div. The dead EXPONENT_MAX/EXPONENT_MIN constants are now the live invariant.
  2. The surviving pow: negative-exponent squaring loop reverts Panic(0x11) instead of ExponentOverflow #239 Panic is dead: the line-219 adjust add is guarded (testMulRenormalizationOverflowGuard pins the in-domain Panic path: mul(1e75, EXPONENT_MAX, 1e75, EXPONENT_MAX)ExponentOverflow, was Panic(0x11)), and pow's squaring loop surfaces ExponentOverflow at the first out-of-domain intermediate.
  3. Factor-of-2 hole closed: in-domain operands summing past the bound revert on the result; the two enshrining boundary tests are inverted per the note, and the two opposite-sign int256-extreme tests now pin operand rejection (out-of-domain operands revert regardless of the sum).
  4. Siblings aligned: add's rescale guard moved from int256.max to EXPONENT_MAX; minus's int256.min-coefficient edge likewise; div got entry checks + a result check that also converts its former nonzero-at-int256.min truncation returns into reverts.

Consequences surfaced for review

  • div's sub-1e75 scaling leaves + exponent-spill machinery only engage for out-of-domain divisors, so they are now defensively dead within the domain (tests pin the rejection; simplifying div would be a separate issue if wanted).
  • testSubPacked had a pre-existing latent fuzz flake (reproduced on clean main): impl sub + packLossy tolerate exponent underflow (FLOAT_ZERO) while packed sub reverts ExponentUnderflow via packArithmeticResult. The test now models that documented divergence.
  • Deploy pins + committed ABI regenerated for the new bytecode; frozen _0_1_7 pins added here too (same red exists on main — minimal fix-main PR: fix(deploy): pin the 0.1.7 soldeer tag's deploy constants #256; the snapshot-canon migration in refactor(deploy): migrate deploy constants to the per-version snapshot canon #253 supersedes both flat-pin fixes when it lands).

QA

  • Discriminating tests: 20 domain tests across LibDecimalFloat.mul.t.sol + impl mul/div/add/sub/inv/minus test files — boundary pairs (success at the bound / revert one past it) per guard; each fails on base by construction (base has no domain enforcement: base returns out-of-domain values or Panics where these expect ExponentOverflow, and the inverted tests' old base expectations were the enshrined behaviour the ruling rejects).
  • Mutations applied (all killed, suite-validated): helper upper arm dead → 6 killers; helper lower arm dead → 30 killers across all five operators; zero-exemption removed → killed uniquely by testAddCancellationBelowDomainReturnsZero; mul pre-adjust guard disabled → killed uniquely by testMulRenormalizationOverflowGuard (Panic vs error); add rescale bound reverted to int256.max → killed by payload discrimination (also demonstrating the add result check); minus bound reverted → killed uniquely; both bounds made exclusive → killed by the boundary-success tests. Entry/exit call-site removals are pinned by the same killers with operand- vs result-payload discrimination observed in the arm probes.
  • Oracle: the rework note's ruling (domain semantics) + independently derived bounds (2·EXPONENT_MAX = int256.max − 1, maximization shift ≤ 76, adjust ∈ [0,76]); LibDecimalFloatSlow remains the independent value oracle for the in-domain fuzz properties.
  • Category check: the note orders the whole domain enforced across mul/div/add/sub/inv with the 2 named tests inverted and the 6 keepers kept — all covered; the 6 keepers pass unchanged (3 with identical payloads now produced by the entry checks).

WAITING-DEPLOY: local suite is 532 passed / 5 failed — the 5 are testProdDeployment* prod-pin forks, red until the new deterministic address 0xA051048f1Bc0bc59a0CAA21C642fc51BF9ae145C is deployed from this branch (deploy-before-merge).

@thedavidmeisterthedavidmeister added ai:blocked-deploy and removed human:needs-work Human maintainer: rework required (sacred) labels Jul 18, 2026
@thedavidmeister

Copy link
Copy Markdown
ContributorAuthor

🤖 ai:producer
Blocked-deploy: head 85e9757 needs a pre-merge suite=decimal-float Zoltu deploy of 0xA051048f1Bc0bc59a0CAA21C642fc51BF9ae145C, but the repo's deploy RPC has a sustained eth_estimateGas 'execution reverted, data: 0x' outage on large-calldata deploy txs — every manual-sol-artifacts run since 2026-05-10 failed with this signature (latest evidence run 29487488034, 2026-07-16, on PR #253). Dispatching now would blind-retry a diagnosed outage; needs a human RPC swap/fix, then deploy.

…lake fmt [3b-attempt]
The rainix copy-artifacts reusable requires the codegen script at
script/Build.sol (the org convention siblings already follow); rename
BuildPointers.sol accordingly and update the docs. The rs-static no-skip
gate forbids vm.skip in any form, so the tagged-constants test's
unreachable-registry branch becomes a vacuous pass (nothing to verify)
instead of a skip. Format with the rainix-pinned forge so the static fmt
check agrees.
Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/implementation/LibDecimalFloatImplementation.sol`:
- Around line 196-200: Move exponent-domain validation before zero-result fast
paths: in src/lib/implementation/LibDecimalFloatImplementation.sol lines
196-200, validate both multiplication operands before the isZero return; at
lines 328-334, after division-by-zero validation, validate the nonzero divisor
before returning for a zero dividend; and at lines 686-690, validate both
addition operands before the eitherZero return. Add regressions covering zero
multiplied by, divided by, and added to a nonzero operand with an out-of-domain
exponent, ensuring public arithmetic reverts with ExponentOverflow or
ExponentUnderflow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0fa3a2ad-95b4-450b-b3c6-a1bacbc044a6

📥 Commits

Reviewing files that changed from the base of the PR and between 774ed07 and 9b0cc58.

📒 Files selected for processing (15)
  • CLAUDE.md
  • crates/float/abi/DecimalFloat.json
  • script/Build.sol
  • src/lib/deploy/LibDecimalFloatDeploy.sol
  • src/lib/implementation/LibDecimalFloatImplementation.sol
  • test/src/lib/LibDecimalFloat.mul.t.sol
  • test/src/lib/LibDecimalFloat.pow.t.sol
  • test/src/lib/LibDecimalFloat.sub.t.sol
  • test/src/lib/deploy/LibDecimalFloatDeployTaggedConstants.t.sol
  • test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol
  • test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol
  • test/src/lib/implementation/LibDecimalFloatImplementation.inv.t.sol
  • test/src/lib/implementation/LibDecimalFloatImplementation.minus.t.sol
  • test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol
  • test/src/lib/implementation/LibDecimalFloatImplementation.sub.t.sol

Comment on lines +196 to +200
// Operands must be in the exponent domain. In-domain exponents sum
// to at most one bit past the domain bound, which cannot overflow
// int256, so the addition below is panic-free.
enforceExponentDomain(signedCoefficientA, exponentA);
enforceExponentDomain(signedCoefficientB, exponentB);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Move exponent-domain validation ahead of zero-result fast paths.

A zero operand currently suppresses validation of the other nonzero operand, contradicting the documented operand-domain contract.

  • src/lib/implementation/LibDecimalFloatImplementation.sol#L196-L200: validate both multiplication operands before the isZero return.
  • src/lib/implementation/LibDecimalFloatImplementation.sol#L328-L334: after checking division by zero, validate the nonzero divisor before returning for a zero dividend.
  • src/lib/implementation/LibDecimalFloatImplementation.sol#L686-L690: validate both addition operands before the eitherZero return.

Add regressions for mul(0, …, nonzero, outOfDomain), div(0, …, nonzero, outOfDomain), and add(0, …, nonzero, outOfDomain).

As per coding guidelines, “Public arithmetic must revert on exponent overflow and underflow using ExponentOverflow or ExponentUnderflow.”

📍 Affects 1 file
  • src/lib/implementation/LibDecimalFloatImplementation.sol#L196-L200 (this comment)
  • src/lib/implementation/LibDecimalFloatImplementation.sol#L328-L334
  • src/lib/implementation/LibDecimalFloatImplementation.sol#L686-L690
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/implementation/LibDecimalFloatImplementation.sol` around lines 196 -
200, Move exponent-domain validation before zero-result fast paths: in
src/lib/implementation/LibDecimalFloatImplementation.sol lines 196-200, validate
both multiplication operands before the isZero return; at lines 328-334, after
division-by-zero validation, validate the nonzero divisor before returning for a
zero dividend; and at lines 686-690, validate both addition operands before the
eitherZero return. Add regressions covering zero multiplied by, divided by, and
added to a nonzero operand with an out-of-domain exponent, ensuring public
arithmetic reverts with ExponentOverflow or ExponentUnderflow.

Source: Coding guidelines

@thedavidmeister

Copy link
Copy Markdown
ContributorAuthor

👤 human
Ruled 9b0cc58: 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 @9b0cc58c287f416ef04dbb5597f26256b3050112: 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
baku-ccronand others added 2 commits August 18, 2026 02:23
The deploy-before-merge choreography is superseded by the split release
lifecycle: no merge waits on a deploy, and the on-chain deploy is a manual
workflow dispatch decoupled from any PR's flow. Drop the "trigger the deploy
on the PR's branch before merge / do NOT wait for merge before deploying"
instruction from the repo's agent context. The constants paragraph below it is
left alone: it still describes what CI actually checks today.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@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.

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.

pow: negative-exponent squaring loop reverts Panic(0x11) instead of ExponentOverflow

1 participant

@thedavidmeister