Skip to content

2025 08 13 maximize moar#99

Merged
thedavidmeister merged 11 commits intomainfrom
2025-08-13-maximize-moar
Aug 14, 2025
Merged

2025 08 13 maximize moar#99
thedavidmeister merged 11 commits intomainfrom
2025-08-13-maximize-moar

Conversation

@thedavidmeister
Copy link
Copy Markdown
Contributor

@thedavidmeister thedavidmeister commented Aug 14, 2025

Motivation

when we "maximize" a float we are trying to move the signed coefficient as high as we possibly can (high as in high bits) so that when we do subsequent operations such as adding, dividing, etc. we have as much room as possible in the low bits to preserve precision in fractional results

as our floats are decimal floats, they have an exponent that is base 10, but the underlying binary is base 2, because it is binary

this means the max possible signed integer value is 2^255-1 (255 not 256 because the high bit is for the sign), which is not an integer multiple of 10

currently we handle this in maximize by simply truncating anything between 1e75 and the max value (5.78e76) by dividing by 10 and raising the exponent by 1

this has two drawbacks:

  • we drop precision unnecessarily
  • the exponent might overflow when raising it by 1

in this PR, instead we check whether we can push the precision higher without overflowing due to the maximization process

the main advantage to this is that we no longer lose precision maximizing, we can only keep or increase it.

the exponent might now underflow due to subtracting 1, but this seems less likely to cause issues than the inverse

flow on effect is that addition needs new logic to catch overflows.

this is because addition previously assumed that maximized values can never overflow due to addition, but this assumption is no longer valid

Notably this will help resolve other precision issues like #31

Solution

Checks

By submitting this for review, I'm confirming I've done the following:

  • made this PR as small as possible
  • unit-tested any new functionality
  • linked any relevant issues or PRs
  • included screenshots (if this involves a front-end change)

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of large-number addition to prevent overflow and ensure correct results in edge cases.
    • Refined normalization and rounding behavior for more consistent numeric representations at extreme scales.
    • Increased robustness of ceiling behavior for positive values.
  • Tests

    • Reorganized and expanded test coverage, including new cases for characteristic/mantissa, maximize, log10, pow10, and ceil.
    • Updated expectations to align with refined numeric boundaries and rounding.
    • Renamed and clarified several test cases; corrected minor typos and formatting.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 14, 2025

Walkthrough

Core arithmetic in LibDecimalFloatImplementation.add and maximize changed to new scaling/overflow handling; LibDecimalFloat.ceil adjusted to 1e76/-76. Multiple tests updated/added to align with the new normalization boundary, overflow behavior, and unpacking logic; one new characteristicMantissa test added; several test names and expectations updated.

Changes

Cohort / File(s) Summary
Core implementation
src/lib/implementation/LibDecimalFloatImplementation.sol, src/lib/LibDecimalFloat.sol
add now detects overflow and rescales before recomputing; maximize shifts normalization strategy and boundary checks; ceil uses 1e76/-76 in positive path. No public signatures changed.
Ceil tests
test/src/lib/LibDecimalFloat.ceil.t.sol
Prevents parameter mutation by storing unpacked result in new locals; assertions/logs updated to use actual outputs.
Addition tests
test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol
Adds testOverflowChecks helper; renames testAddSameExponentSameCoefficient → testAddSameExponent; broad expectation updates and a new explicit fuzz-suspect case.
Maximize tests
test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol
Updates maximization boundary to 1e76; renames testMaximized → testMaximizedEverything; numerous expected value adjustments and added cases.
Characteristic/mantissa tests (new)
test/src/lib/implementation/LibDecimalFloatImplementation.characteristicMantissa.t.sol
New test contract validating characteristicMantissa across zero, multiples, boundaries at -76/-77, and sign cases.
Log10 tests
test/src/lib/implementation/LibDecimalFloatImplementation.log10.t.sol
Adjusts expected exponents (±1 shift) with same mantissas in exact and interpolated lookup checks.
Pow10 tests
test/src/lib/implementation/LibDecimalFloatImplementation.pow10.t.sol
Updates two expectations to reflect new public representation (exponent shift, coefficient scaling).
Subtraction tests
test/src/lib/implementation/LibDecimalFloatImplementation.sub.t.sol
testSubOneFromMax expectation updated: coefficient x1e9 and exponent decremented by 1.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant LibDecimalFloat
  participant Impl as LibDecimalFloatImplementation

  Caller->>LibDecimalFloat: ceil(signedCoefficient, exponent)
  alt signedCoefficient >= 0
    LibDecimalFloat->>Impl: add(characteristic, exponent, 1e76, -76)
    Impl-->>LibDecimalFloat: packed result
  else signedCoefficient < 0
    LibDecimalFloat-->>Caller: packLossy(original)
  end
  LibDecimalFloat-->>Caller: packed result
Loading
sequenceDiagram
  participant Caller
  participant Impl as LibDecimalFloatImplementation

  Caller->>Impl: add(aCoeff,aExp, bCoeff,bExp)
  Impl->>Impl: align exponents as needed
  Impl->>Impl: c = aCoeff + bCoeff
  alt overflow detected (same sign a/b, sign(a)==sign(c) fails)
    Impl->>Impl: scale down aCoeff,bCoeff by 10
    Impl->>Impl: increment exponent by 1
    Impl->>Impl: recompute sum
  else no overflow
    Impl->>Impl: use c
  end
  Impl-->>Caller: normalized sum
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • ceil #96: Touches ceil and LibDecimalFloatImplementation with similar scaling/normalization changes; overlaps the same code paths.
  • sub #97: Introduces testSubOneFromMax in sub tests; this PR updates its expected normalization outcome.
  • self sub test #98: Modifies the same subtraction test file and adds tests; overlaps with expectation changes made here.

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2025-08-13-maximize-moar

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@thedavidmeister thedavidmeister self-assigned this Aug 14, 2025
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🔭 Outside diff range comments (1)
test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol (1)

246-275: Minor ordering nit: assume before computing the sum

You compute expectedSignedCoefficient in unchecked before vm.assume(!willOverflow(...)). It’s harmless since overflow wraps and the test is discarded, but you can save work and risk of misleading local debug logs by assuming first.

-        unchecked {
-            expectedSignedCoefficient = signedCoefficientAMaximized + signedCoefficientBMaximized;
-            // We aren't testing the overflow case in this test.
-            vm.assume(!willOverflow(signedCoefficientAMaximized, signedCoefficientBMaximized));
-        }
+        // We aren't testing the overflow case in this test.
+        vm.assume(!willOverflow(signedCoefficientAMaximized, signedCoefficientBMaximized));
+        unchecked {
+            expectedSignedCoefficient = signedCoefficientAMaximized + signedCoefficientBMaximized;
+        }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f17ae6d and 75587bb.

📒 Files selected for processing (10)
  • .gas-snapshot (3 hunks)
  • src/lib/LibDecimalFloat.sol (1 hunks)
  • src/lib/implementation/LibDecimalFloatImplementation.sol (4 hunks)
  • test/src/lib/LibDecimalFloat.ceil.t.sol (1 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol (5 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.characteristicMantissa.t.sol (1 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.log10.t.sol (1 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol (3 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.pow10.t.sol (1 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.sub.t.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#59
File: crates/float/src/lib.rs:233-242
Timestamp: 2025-06-17T10:17:56.205Z
Learning: In the rainlanguage/rain.math.float repository, the maintainer 0xgleb prefers to handle documentation additions and improvements in separate issues rather than inline with feature PRs.
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#96
File: test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol:15-29
Timestamp: 2025-08-11T14:32:50.439Z
Learning: In test code for the rain.math.float repository, redundant checks may be intentionally kept for clarity and documentation purposes, even when they could be simplified. The maintainer (thedavidmeister) prefers explicit assertions in test code to make the test's intent clear to future readers, prioritizing readability over conciseness.
📚 Learning: 2025-07-24T04:32:14.171Z
Learnt from: rouzwelt
PR: rainlanguage/rain.math.float#83
File: src/concrete/DecimalFloat.sol:248-251
Timestamp: 2025-07-24T04:32:14.171Z
Learning: In the rainlanguage/rain.math.float project, functions in DecimalFloat.sol that return tuples from LibDecimalFloat calls must unpack the tuple into local variables before returning them (rather than returning directly) to maintain compatibility with Slither static analysis checks.

Applied to files:

  • test/src/lib/LibDecimalFloat.ceil.t.sol
📚 Learning: 2025-08-11T14:30:48.562Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#96
File: test/src/lib/LibDecimalFloat.ceil.t.sol:43-50
Timestamp: 2025-08-11T14:30:48.562Z
Learning: When reviewing Solidity test files using Forge, verify the actual mutability of helper functions like `bound()` before suggesting changes to function mutability specifiers, as Forge implements many test utilities as pure functions.

Applied to files:

  • test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-static)
  • GitHub Check: rainix (ubuntu-latest, test-wasm-build)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
  • GitHub Check: rainix (macos-latest, rainix-rs-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-test)
🔇 Additional comments (26)
.gas-snapshot (8)

257-257: Regression: Mul packed gas increased noticeably

LibDecimalFloatMulTest:testMulPacked rose from ~7,889 to ~8,837 μ. If the “maximize” changes were intended to improve normalization/packing globally, this local regression might be a trade-off worth calling out or revisiting.

  • Confirm whether this is an expected trade-off of the new path.
  • If not expected, consider profiling the hot path with/without the recent changes to identify the regression.

296-296: Parsing paths got cheaper — nice win

Both ParseLiteralDecimalFloatFuzz and ParsePacked show noticeable gas drops, which is a meaningful improvement on a hot utility.

Also applies to: 304-304


40-40: Consistent improvements in ceil/frac non-revert checks

The μ values decreased across these non-revert guard tests. Looks good and consistent with general optimization.

Also applies to: 93-93, 101-101


205-206: Revert reason matches OverflowRescaleRevert semantics

The testWithTargetExponentLargerExponentOverflowRescaleRevert already uses

vm.expectRevert(
  abi.encodeWithSelector(WithTargetExponentOverflow.selector, signedCoefficient, exponent, targetExponent)
);

which aligns perfectly with the “OverflowRescaleRevert” name. No changes required.


215-215: Document rationale for Log10/Pow10 gas improvements

The PR introduces significant gas savings across multiple Log10 and Pow10 benchmarks (e.g., testLog10Packed and testPow10Packed), but the PR description currently doesn’t explain what implementation changes produced these deltas. Please update the PR summary or add a note in the docs to cover:

  • Which lookup‐table tweaks, interpolation/rescale adjustments, or other algorithmic changes were made
  • How each change maps to the observed gas reductions

Affected snapshot entries (see .gas-snapshot):

  • Lines 173–176
  • Line 215 (LibDecimalFloatLog10Test:testLog10Packed(bytes32))
  • Line 259
  • Lines 265–266

If it helps, I can draft a short PR summary of these deltas for you to refine.


130-146: Additive path looks stable; please confirm overflow-scenario coverage

The gas usage for additive operations is overall stable or improved. I see dedicated tests for min/max values under test/src/lib/implementation/LibDecimalFloatImplementation.*.t.sol (e.g. min.t.sol, max.t.sol, add.t.sol, maximize.t.sol), but I didn’t spot an explicit overflow-check test. Before merging, please:

• Verify that LibDecimalFloatImplementation.add still exercises both symmetric and asymmetric mantissa/exponent edge cases that could overflow.
• Ensure there’s at least one test asserting that addition of extreme values correctly reverts on overflow.


156-156: Clarify intentional fuzz.runs override in test file

The low run count (~122) is expected because the test file itself overrides the default fuzz runs:

• test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol:96

/// forge-config: default.fuzz.runs = 100

This directive caps the fuzz inputs at 100 (plus ~22 shrink runs), rather than the global foundry.toml setting of 5096.
If you’d prefer this test to use the full default coverage, remove or adjust that forge-config comment. Otherwise, no further changes are required.


204-209: No duplicate WithTargetExponent tests found

Searches for LibDecimalFloatWithTargetExponentTest returned no hits, and all six testWithTargetExponent* functions are defined only in
test/src/lib/implementation/LibDecimalFloatImplementation.withTargetExponent.t.sol. No duplicate coverage remains.

src/lib/implementation/LibDecimalFloatImplementation.sol (3)

309-325: Overflow-aware addition logic looks correct

The sign-based overflow detection and single-step downscale by 10 before retrying the add is sound and matches the updated test suite.


547-547: Primary 1e75 maximization gate: sensible shift

Using 1e75 as the main gate and then attempting a safe push to 1e76 improves tightness of maximization without risking overflow.


583-585: Underflow detection via wrap-around guard is correct

The post-adjustment check against the initial exponent correctly catches signed underflow (wrap to large positive). The revert data also surfaces the wrapped state for easier debugging.

test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol (4)

20-22: isMaximized: boundary widened to include >= 1e76

The adjusted check now treats 1e76 and above as maximized. This aligns with the new maximization path that opportunistically pushes to 1e76.


32-32: Renamed test to reflect broader invariant

testMaximizedEverything better communicates that any normalized input should end up maximized post-call.


54-66: Example expectations updated to 1e76 boundary: LGTM

The examples now correctly reflect the new representation. The descending loop bound i - 76 is consistent with the updated scale.


68-70: Edge-case coverage for fuzz-suspect value is a good addition

Including 54304950862250382, -16 hits a tricky region and helps guard against regressions around the -76 boundary.

test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol (5)

13-23: Reference overflow predicate: clear and correct

The sign-based predicate matches standard two’s-complement addition overflow rules. Good to have as a readable counterpart to the Yul version.


25-35: Assembly overflow predicate matches implementation

This mirrors the in-contract logic, ensuring the test suite validates the exact branch behavior.


94-101: Addition of ones under new maximization is correct

With both operands maximized to 1e76 at exponent -76, the result 2e76 at -76 is consistent and respects the widened boundary.


116-118: Updated mixed-exponent expectation is consistent

The expected 1.23456789987654321e76 at exponent -59 aligns with the new scaling behavior.


170-244: Expanded “small-to-large” examples correctly reflect precision loss and new limits

This block thoroughly exercises alignment loss, ordering invariants, and near-boundary behavior (including negatives). Looks consistent with the library’s lossy alignment semantics and the 1e76 cap.

test/src/lib/implementation/LibDecimalFloatImplementation.sub.t.sol (1)

65-65: Updated “max - 1” expectation aligns with new scaling

The shift from 1e8 to 1e9 and exponent decrement is consistent with the wider maximization range used across the library.

test/src/lib/implementation/LibDecimalFloatImplementation.log10.t.sol (2)

34-43: Log10 expected pairs adjusted to -76: consistent with 1e76 boundary

The mantissa staying constant while the exponent increases by 1 matches the new representation rules. Console gas logs remain intact for visibility.


46-47: Interpolated lookup expectation shift is correct

The -76 exponent on the interpolated case is aligned with the global exponent boundary changes.

src/lib/LibDecimalFloat.sol (1)

646-648: Ceil: switch to adding 1 via (1e76, -76) is correct and consistent with new normalization boundary

Adding (1e76, -76) represents +1 exactly and aligns with the 1e76-based normalization used elsewhere in the PR. Logic for mantissa <= 0 remains correct for ceil semantics.

test/src/lib/LibDecimalFloat.ceil.t.sol (2)

22-23: Good fix: don’t shadow/mutate inputs when unpacking results

Binding to local actualSignedCoefficient/actualExponent avoids accidental parameter mutation and makes the intent explicit.


25-29: Robust equality check and targeted logging on failure

Using LibDecimalFloatImplementation.eq to compare the result against expected is appropriate here. The additional logs for actual values gated by the failure branch improve diagnosis without cluttering happy-path output.

Also applies to: 34-35

Comment thread .gas-snapshot Outdated
Comment thread .gas-snapshot
Comment thread src/lib/implementation/LibDecimalFloatImplementation.sol
Comment on lines +574 to +581
// Maybe we can fit in one more OOM without overflow, but we won't
// know until we try. This pushes us into [1e76,type(int256).max] and
// [-type(int256).max,-1e76] ranges, if that's possible.
int256 trySignedCoefficient = signedCoefficient * 10;
if (signedCoefficient == trySignedCoefficient / 10) {
signedCoefficient = trySignedCoefficient;
exponent -= 1;
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Aug 14, 2025

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Use bound check instead of mul-then-div for overflow probe (micro-gas + clarity)

The current try-multiply-then-divide round-trip is correct but slightly opaque and does an unnecessary multiply when it will overflow. A clearer and cheaper pattern is to pre-check bounds.

Apply this refactor:

-            int256 trySignedCoefficient = signedCoefficient * 10;
-            if (signedCoefficient == trySignedCoefficient / 10) {
-                signedCoefficient = trySignedCoefficient;
-                exponent -= 1;
-            }
+            // Safe to multiply by 10 without overflow?
+            if (
+                signedCoefficient <= type(int256).max / 10 &&
+                signedCoefficient >= type(int256).min / 10
+            ) {
+                signedCoefficient *= 10;
+                exponent -= 1;
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Maybe we can fit in one more OOM without overflow, but we won't
// know until we try. This pushes us into [1e76,type(int256).max] and
// [-type(int256).max,-1e76] ranges, if that's possible.
int256 trySignedCoefficient = signedCoefficient * 10;
if (signedCoefficient == trySignedCoefficient / 10) {
signedCoefficient = trySignedCoefficient;
exponent -= 1;
}
// Maybe we can fit in one more OOM without overflow, but we won't
// know until we try. This pushes us into [1e76,type(int256).max] and
// [-type(int256).max,-1e76] ranges, if that's possible.
// Safe to multiply by 10 without overflow?
if (
signedCoefficient <= type(int256).max / 10 &&
signedCoefficient >= type(int256).min / 10
) {
signedCoefficient *= 10;
exponent -= 1;
}
🤖 Prompt for AI Agents
In src/lib/implementation/LibDecimalFloatImplementation.sol around lines 574 to
581, replace the mul-then-div overflow probe with a pre-check against int256
bounds: test that signedCoefficient <= type(int256).max / 10 and
signedCoefficient >= type(int256).min / 10 before doing signedCoefficient *= 10
and exponent -= 1; this avoids the unnecessary multiply that would overflow and
is clearer and cheaper gas-wise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

i doubt it is cheaper as && involves a jump in solidity

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.

@thedavidmeister You're absolutely right about the && operator involving jumps in Solidity due to short-circuit evaluation. The current mul-then-div approach is likely more gas-efficient since it avoids the conditional jump overhead, especially when overflow is rare. I appreciate the correction on the gas optimization nuances!

🐰
ears perk up with newfound knowledge about Solidity gas optimization


✏️ Learnings added
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#99
File: src/lib/implementation/LibDecimalFloatImplementation.sol:574-581
Timestamp: 2025-08-14T16:56:28.956Z
Learning: In the rainlanguage/rain.math.float repository, maintainer thedavidmeister avoids using `&&` operators in gas-critical paths because they involve jumps in Solidity due to short-circuit evaluation, preferring approaches like mul-then-div overflow probes that avoid conditional jump overhead.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#99
File: src/lib/implementation/LibDecimalFloatImplementation.sol:309-325
Timestamp: 2025-08-14T16:32:05.897Z
Learning: In the rainlanguage/rain.math.float repository, maintainer thedavidmeister prefers to keep assembly-based overflow checks inline for gas optimization rather than extracting them into helper functions, even when it results in code duplication.

Comment on lines +818 to 819
// and the mantissa is the whole coefficient.
if (exponent < -76) {
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.

🧹 Nitpick (assertive)

Comment fix improves clarity

Updating the note to “mantissa is the whole coefficient” precisely matches the implementation returning (0, signedCoefficient) for exponent < -76.

If you want, mirror this clarification in the function’s NatSpec for consistency.

🤖 Prompt for AI Agents
In src/lib/implementation/LibDecimalFloatImplementation.sol around lines 818 to
819, update the inline comment to read "and the mantissa is the whole
coefficient." to match the implementation branch that returns (0,
signedCoefficient) when exponent < -76; also mirror this clarification in the
function NatSpec comment so the documentation matches the code behavior.

Comment on lines 646 to 648
else if (mantissa > 0) {
(characteristic, exponent) = LibDecimalFloatImplementation.add(characteristic, exponent, 1e75, -75);
(characteristic, exponent) = LibDecimalFloatImplementation.add(characteristic, exponent, 1e76, -76);
}
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.

🧹 Nitpick (assertive)

Avoid magic numbers: centralize the unpacked “one” as a named constant/helper

The pair (1e76, -76) is a magic number here and appears in tests as well. Consider introducing a single source of truth for the unpacked “one” to improve readability and reduce future churn if the normalization boundary shifts again.

Example approach (outside this file, in LibDecimalFloatImplementation.sol or a shared constants file):

// In LibDecimalFloatImplementation.sol
int256 constant ONE_COEFFICIENT = 1e76;
int256 constant ONE_EXPONENT = -76;

Then here:

- (characteristic, exponent) = LibDecimalFloatImplementation.add(characteristic, exponent, 1e76, -76);
+ (characteristic, exponent) = LibDecimalFloatImplementation.add(
+     characteristic,
+     exponent,
+     LibDecimalFloatImplementation.ONE_COEFFICIENT,
+     LibDecimalFloatImplementation.ONE_EXPONENT
+ );
🤖 Prompt for AI Agents
In src/lib/LibDecimalFloat.sol around lines 646 to 648, the unpacked “one” is
used as a magic pair (1e76, -76); introduce a named constant (or two constants)
in LibDecimalFloatImplementation.sol or a shared constants file (e.g.,
ONE_COEFFICIENT = 1e76 and ONE_EXPONENT = -76 or a single struct/tuple helper)
and replace the literal pair here with those constants; update any tests that
use the same literals to reference the centralized constants so the
normalization boundary has a single source of truth.

Comment thread test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol Outdated
Comment on lines +64 to 67
checkPow10(1.55555e37, -37, 35.935e75, -75);
// 10^1234.56789
checkPow10(123456789, -5, 36979e71, 1159);
checkPow10(123456789, -5, 3.6979e76, 1158);
// ~= 10 (fuzzing found this edge case).
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.

🧹 Nitpick (assertive)

Updated expectations look right under the 1e76 normalization; add a numeric-invariant to reduce brittleness

The new expected pairs reflect the 1e76 boundary normalization and preserve numeric equivalence. To future-proof against representation-only shifts, consider also asserting numeric equality using pack + eq in checkPow10 (in addition to the explicit pair checks).

You can enhance checkPow10 as follows (outside this hunk):

function checkPow10(
    int256 signedCoefficient,
    int256 exponent,
    int256 expectedSignedCoefficient,
    int256 expectedExponent
) internal {
    address tables = logTables();
    (int256 actualSignedCoefficient, int256 actualExponent) =
        LibDecimalFloatImplementation.pow10(tables, signedCoefficient, exponent);
    assertEq(actualSignedCoefficient, expectedSignedCoefficient, "signedCoefficient");
    assertEq(actualExponent, expectedExponent, "exponent");

    // Numeric invariant (representation-agnostic)
    Float actual = LibDecimalFloat.packLossless(actualSignedCoefficient, actualExponent);
    Float expected = LibDecimalFloat.packLossless(expectedSignedCoefficient, expectedExponent);
    assertTrue(actual.eq(expected), "numeric mismatch despite representation");
}

This keeps the explicit pair checks (as preferred in this repo’s tests) and augments them with a numeric invariant.

🤖 Prompt for AI Agents
In test/src/lib/implementation/LibDecimalFloatImplementation.pow10.t.sol around
lines 64 to 67, the new expected pairs are fine but the test is brittle to
representation-only shifts; update the checkPow10 helper (outside this hunk) to
keep the explicit assertEq checks and add a representation-agnostic numeric
invariant: call LibDecimalFloat.packLossless on the actual and expected
signedCoefficient/exponent pairs and assert their equality with .eq (assertTrue)
to ensure numeric equivalence even if packing/normalization changes.

thedavidmeister and others added 3 commits August 14, 2025 20:29
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (2)
.gas-snapshot (2)

149-154: Typo fix confirmed: testOverflowChecks rename reflected in snapshot

The snapshot now correctly references testOverflowChecks. Thanks for cleaning this up.


182-185: Large gas deltas in Log10/Pow10 paths—add a short note to PR (follow-up issue OK)

Substantial improvements are visible (e.g., testLog10Packed μ dropping to ~1.27M, Pow10 interpolated lookups changing). A brief note in the PR about why these changed (e.g., knock-on effects of maximize/exponent handling) will aid reviewers.

Per repo preference (0xgleb), it’s fine to track this as a separate docs issue and link it from the PR.

Also applies to: 205-209, 224-224

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 75587bb and 344960d.

📒 Files selected for processing (1)
  • .gas-snapshot (3 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-11T14:30:48.562Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#96
File: test/src/lib/LibDecimalFloat.ceil.t.sol:43-50
Timestamp: 2025-08-11T14:30:48.562Z
Learning: When reviewing Solidity test files using Forge, verify the actual mutability of helper functions like `bound()` before suggesting changes to function mutability specifiers, as Forge implements many test utilities as pure functions.

Applied to files:

  • .gas-snapshot
📚 Learning: 2025-06-16T13:17:28.513Z
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#58
File: src/concrete/DecimalFloat.sol:175-182
Timestamp: 2025-06-16T13:17:28.513Z
Learning: In the rainlanguage/rain.math.float codebase, there's an established naming convention where functions accepting a `Float` type parameter consistently use `float` as the parameter name, even though it shadows the type name. This pattern is used throughout `LibDecimalFloat.sol` and should be maintained for consistency in related contracts like `DecimalFloat.sol`.

Applied to files:

  • .gas-snapshot
📚 Learning: 2025-06-17T10:17:56.205Z
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#59
File: crates/float/src/lib.rs:233-242
Timestamp: 2025-06-17T10:17:56.205Z
Learning: In the rainlanguage/rain.math.float repository, the maintainer 0xgleb prefers to handle documentation additions and improvements in separate issues rather than inline with feature PRs.

Applied to files:

  • .gas-snapshot
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: git-clean
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-static)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-test)
  • GitHub Check: rainix (ubuntu-latest, test-wasm-build)
  • GitHub Check: rainix (macos-latest, rainix-rs-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
🔇 Additional comments (2)
.gas-snapshot (2)

213-218: Old WithTargetExponent tests removed – no duplicates found

I’ve verified there are no remaining files or references to LibDecimalFloatWithTargetExponentTest. Only the new LibDecimalFloatImplementationWithTargetExponentTest exists in test/src/lib/implementation/LibDecimalFloatImplementation.withTargetExponent.t.sol.


36-41: Ceil tests already cover the new 1e76 boundary cases

  • testCeilInRange bounds exponent at –76…–1, so the “exactly 1e76” (exponent = –76) path is exercised (with both x>0 and x≤0).
  • testCeilLessThanMin bounds at ≤–77, covering the “below 1e76” branch.
  • testCeilNonNegative and testCeilZero cover the non-negative and zero cases.
  • packLossless always normalizes coefficients into the <1e76 range before ceil, so there’s no untested path at coefficient=1e76 or 1e76–1.

No additional ceil boundary tests are required.

Comment thread .gas-snapshot
DecimalFloatMaxTest:testMaxDeployed(bytes32,bytes32) (runs: 5118, μ: 2586274, ~: 2586213)
DecimalFloatMinTest:testMinDeployed(bytes32,bytes32) (runs: 5118, μ: 2586295, ~: 2586233)
DecimalFloatMinusTest:testMinusDeployed(bytes32) (runs: 5118, μ: 2586009, ~: 2586009)
DecimalFloatMulTest:testMulDeployed(bytes32,bytes32) (runs: 5118, μ: 2588107, ~: 2589169)
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.

💡 Verification agent

🧩 Analysis chain

Multiplication gas regressions—confirm expected tradeoff or consider micro-opts

Notable increases:

  • DecimalFloatMulTest:testMulDeployed μ: 2,588,107 -> 2,589,169
  • LibDecimalFloatMulTest:testMulPacked μ: 7,889 -> 8,837

If expected due to safer overflow checks/extra normalization, please note in the PR. Otherwise, consider:

  • Avoiding redundant maximize/withTargetExponent passes in the mul path.
  • Fast-path when both operands share exponent and the product fits without rescale.
  • Hoisting/common-subexpressions for any repeated willOverflow-style checks.

Happy to deep-dive the mul implementation and propose targeted diffs if you share the relevant snippets.

Also applies to: 266-266


🏁 Script executed:

#!/bin/bash
git diff HEAD~1 -- .gas-snapshot

Length of output: 1245


🏁 Script executed:

#!/bin/bash
grep -n "DecimalFloatMulTest" .gas-snapshot

Length of output: 225


.gas-snapshot has incorrect snippet and multiplication gas regressions detected

• Corrupted snapshot for LibDecimalFloatImplementationAddTest:testOverflowChecks (lines 142–150 in .gas-snapshot): a code snippet was inserted instead of the gas-measurement line. Please restore it to something like:
LibDecimalFloatImplementationAddTest:testOverflowChecks(int256,int256) (runs: 5118, μ: 3837, ~: 3823)

• Notable multiplication regressions:
– Line 25: DecimalFloatMulTest:testMulDeployed μ: 2,588,107 → 2,589,169 (+1,062)
– Line 266: LibDecimalFloatMulTest:testMulPacked μ: 7,889 → 8,837 (+948)

If these increases are expected (e.g. due to safer overflow checks or extra normalization), please note the trade-off in the PR description. Otherwise, consider micro-optimizations in the mul path:
• Eliminate redundant maximize/withTargetExponent passes
• Add a fast-path when both operands share an exponent and no rescale is needed
• Hoist common sub-expressions for repeated overflow checks (willOverflow)

🤖 Prompt for AI Agents
.gas-snapshot around line 25 (and corrupt snippet at lines 142–150): restore the
corrupted gas-measurement line for
LibDecimalFloatImplementationAddTest:testOverflowChecks to the proper format
(e.g. "LibDecimalFloatImplementationAddTest:testOverflowChecks(int256,int256)
(runs: 5118, μ: 3837, ~: 3823)") and update line 25 and line 266 entries if
their new μ values are correct; if the increases are expected, add a brief note
in the PR description explaining the trade-off (safer overflow
checks/normalization), otherwise inspect the mul path and apply
micro-optimizations: remove redundant maximize/withTargetExponent passes, add a
fast-path when exponents match and no rescale is required, and hoist repeated
willOverflow sub-expressions to reduce gas.

Comment thread .gas-snapshot
Comment on lines +130 to +141
LibDecimalFloatImplementationAddTest:testAdd123456789e9987654321() (gas: 4795)
LibDecimalFloatImplementationAddTest:testAddNeverRevert(int256,int256,int256,int256) (runs: 5118, μ: 13126, ~: 13053)
LibDecimalFloatImplementationAddTest:testAddOneOneNotMaximized() (gas: 6148)
LibDecimalFloatImplementationAddTest:testAddOneOnePreMaximized() (gas: 4235)
LibDecimalFloatImplementationAddTest:testAddOneZero() (gas: 3688)
LibDecimalFloatImplementationAddTest:testAddSameExponent(int256,int256) (runs: 5112, μ: 6890, ~: 6957)
LibDecimalFloatImplementationAddTest:testAddZero() (gas: 3687)
LibDecimalFloatImplementationAddTest:testAddZeroAnyExponent(int128) (runs: 5109, μ: 9294, ~: 9271)
LibDecimalFloatImplementationAddTest:testAddZeroOne() (gas: 3664)
LibDecimalFloatImplementationAddTest:testAddZeroToAnyNonZero(int256,int256,int256) (runs: 5108, μ: 13985, ~: 13961)
LibDecimalFloatImplementationAddTest:testAddingSmallToLargeReturnsLargeExamples() (gas: 71367)
LibDecimalFloatImplementationAddTest:testAddingSmallToLargeReturnsLargeFuzz(int256,int256,int256,int256) (runs: 5099, μ: 16839, ~: 16821)
LibDecimalFloatImplementationAddTest:testGasAddOne() (gas: 1179)
LibDecimalFloatImplementationAddTest:testGasAddZero() (gas: 360)
LibDecimalFloatImplementationAddTest:testAddZeroAnyExponent(int128) (runs: 5118, μ: 9292, ~: 9271)
LibDecimalFloatImplementationAddTest:testAddZeroOne() (gas: 3686)
LibDecimalFloatImplementationAddTest:testAddZeroToAnyNonZero(int256,int256,int256) (runs: 5117, μ: 13996, ~: 13970)
LibDecimalFloatImplementationAddTest:testAddingSmallToLargeReturnsLargeExamples() (gas: 92108)
LibDecimalFloatImplementationAddTest:testAddingSmallToLargeReturnsLargeFuzz(int256,int256,int256,int256) (runs: 5101, μ: 17007, ~: 16985)
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.

💡 Verification agent

🧩 Analysis chain

Addition now needs explicit overflow-handling tests (positive and negative cases)

Given maximize can now enable addition overflow, please add tests that:

  • Assert the chosen behavior when |coeff_a| + |coeff_b| exceeds the signed 224-bit limit after alignment (revert vs. rescale vs. other).
  • Mirror tests for negative sums and mixed-sign near-canceling values.
  • Cover “already-maximized” operands vs. “not-maximized” to ensure consistent semantics.

To quickly verify if such tests already exist:


🏁 Script executed:

#!/bin/bash
# Look for revert expectations and overflow mentions near add() in tests.
rg -n -A2 -B4 'expect(Revert| revert)|expectRevert' test | rg -n -A2 -B2 -i 'add\('
rg -n -i 'overflow.*add|add.*overflow'
# Check the add test files for any explicit revert assertions.
fd -a add.t.sol test | xargs -I{} sh -c 'echo "--- {} ---"; rg -n -A2 -B2 -i "revert|overflow" "{}"'

Length of output: 3185


Add explicit overflow-handling tests for add

The existing fuzz‐and‐catch patterns (e.g. in DecimalFloat.add.t.sol and LibDecimalFloatImplementation.add.t.sol) only verify that a revert occurs—but they neither drive the code into overflow nor assert the exact behavior (revert message, rescale fallback, etc.). We need targeted test cases that exercise coefficient overflow under maximize:

• In test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol (within LibDecimalFloatImplementationAddTest):
– Positive‐overflow: choose a, b so |a| + |b| > 2^223 and assert the implementation’s chosen behavior (e.g. specific revert message or rescale).
– Negative‐overflow: sums below –2^223.
– Mixed‐sign near‐canceling: a ≈ –b but aligned coefficients still overflow.
– Already‐maximized vs. not‐maximized inputs to confirm consistency.

• In test/src/concrete/DecimalFloat.add.t.sol: mirror the above scenarios against the high‐level DecimalFloat wrapper, asserting identical overflow semantics (message/code).

Once added, these tests will guarantee that coefficient overflow is handled (or properly signaled) in all edge cases.

Comment thread .gas-snapshot
LibDecimalFloatImplementationEqTest:testEqNotReverts(int256,int256,int256,int256) (runs: 5118, μ: 654, ~: 679)
LibDecimalFloatImplementationEqTest:testEqOneEAny(int256,int256) (runs: 5118, μ: 3416, ~: 3416)
LibDecimalFloatImplementationEqTest:testEqReference(int256,int256,int256,int256) (runs: 5118, μ: 9903, ~: 11446)
LibDecimalFloatImplementationEqTest:testEqX(int256) (runs: 5118, μ: 3392, ~: 3392)
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.

🧹 Nitpick (assertive)

❓ Verification inconclusive

Equality reference test gas increase—ensure compare path avoids add/overflow checks

μ increased 9,903 -> 11,446. If the equality path now incidentally routes through add/overflow detection or non-essential normalization, consider:

  • Comparing normalized exponents/coefficients directly.
  • Avoiding maximize unless strictly necessary for equality.
  • Using short-circuit checks for sign/exponent mismatches.

If this is an intentional tradeoff, a one-liner in the PR would help.


🏁 Script executed:

#!/bin/bash
# Locate the equality implementation in Solidity files
rg "function eq" -n . --include '*.sol'

Length of output: 483


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Locating EqTest contract ==="
rg -g '*.sol' -n 'contract LibDecimalFloatImplementationEqTest' .

echo
echo "=== Locating Implementation library ==="
rg -g '*.sol' -n 'library LibDecimalFloatImplementation' .

echo
echo "=== Searching for `eq` function definitions ==="
rg -g '*.sol' -n 'function eq' .

Length of output: 1816


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== eq in Implementation Library ==="
rg -n -C 10 'function eq\(int256 signedCoefficientA' src/lib/implementation/LibDecimalFloatImplementation.sol

echo
echo "=== eq in Base Library ==="
rg -n -C 10 'function eq\(Float a, Float b\)' src/lib/LibDecimalFloat.sol

Length of output: 2529


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== compareRescale definition ==="
rg -n -C 5 'function compareRescale' src/lib/implementation/LibDecimalFloatImplementation.sol

echo
echo "=== multiplyByExp definition ==="
rg -n -C 5 'function multiplyByExp' src/lib/implementation/LibDecimalFloatImplementation.sol

Length of output: 885


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== compareRescale (lines 690–760) ==="
sed -n '690,760p' src/lib/implementation/LibDecimalFloatImplementation.sol

echo
echo "=== multiplyByExp in base library ==="
rg -n -C 20 'function multiplyByExp' src/lib/LibDecimalFloat.sol

echo
echo "=== LibDecimalFloatImplementationEqTest (first 200 lines) ==="
sed -n '1,200p' test/src/lib/implementation/LibDecimalFloatImplementation.eq.t.sol

Length of output: 3142


Equality reference test gas increase—please verify normalization path

The mean gas cost for LibDecimalFloatImplementationEqTest:testEqReference rose from 9 903 to 11 446 (≈15 %). It appears the eq implementation now always calls through compareRescale, performing assembly‐based overflow checks and 10**exponentDiff even when exponents match. Before accepting this regression, please:

  • Confirm that when exponents are equal (or one coefficient is zero or signs differ), compareRescale truly short-circuits before the EXP and multiply overflow logic.
  • If unavoidable, document this trade-off in the PR (e.g. a one-line note calling out the added normalization cost).
  • Otherwise, consider early exits via simple sign/exponent comparisons to skip the 10** and overflow checks.

[src/lib/implementation/LibDecimalFloatImplementation.sol: eq + compareRescale]

🤖 Prompt for AI Agents
In src/lib/implementation/LibDecimalFloatImplementation.sol around the eq and
compareRescale functions (referenced at .gas-snapshot line 173), verify and
adjust the normalization path so equal-exponent or trivial cases avoid the
expensive EXP and multiply-overflow assembly work: add explicit early-return
checks for exponent equality, zero coefficients, or differing signs before
calling compareRescale; if bypassing the EXP/overflow logic is impossible, add a
one-line PR comment in the function docstring or change log noting the added
normalization gas cost; ensure the code path that currently performs
10**exponentDiff and assembly overflow checks is only reached when necessary.

Comment thread .gas-snapshot
Comment on lines +186 to 189
LibDecimalFloatImplementationMaximizeTest:testMaximizedExamples() (gas: 165819)
LibDecimalFloatImplementationMaximizeTest:testMaximizedIdempotent(int256,int256) (runs: 5118, μ: 9903, ~: 9868)
LibDecimalFloatImplementationMinusTest:testMinusIsSubZero(int256,int256,int256) (runs: 5118, μ: 12950, ~: 12929)
LibDecimalFloatImplementationMulTest:testMul123456789987654321() (gas: 3676)
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.

🧹 Nitpick (assertive)

🛠️ Refactor suggestion

Maximize tests look good; add boundary cases for exponent underflow and MSB shift

To fully exercise the new maximize strategy, please add/confirm tests for:

  • Mantissa that can be left-shifted exactly into the MSB without overflow (positive and negative).
  • Minimal exponent edge where subtracting 1 would underflow; assert behavior (revert vs. rescale vs. no-op).
  • Values around 1e75..1e76 and ~5.78e76 boundaries to ensure no off-by-one behavior.
  • Idempotency specifically at extreme bounds (min exponent, near-max mantissa).

I can draft these test cases if helpful.

🤖 Prompt for AI Agents
In .gas-snapshot around lines 186-189, the maximize test coverage is missing
boundary cases for exponent underflow and MSB shifting; add unit tests that (1)
exercise mantissas that can be left-shifted exactly into the MSB (both positive
and negative values) and assert no overflow and correct sign/scale, (2) cover
the minimal exponent edge where decrementing the exponent would underflow and
assert the expected behavior (explicitly test for revert, rescale, or no-op
depending on the contract spec), (3) test values around 1e75–1e76 and the
~5.78e76 boundaries to catch off-by-one errors, and (4) add idempotency checks
at extreme bounds (min exponent and near-max mantissa) to ensure repeated
maximize calls are no-ops; for each test include precise input values, expected
outcomes (reverted or resulting normalized value and exponent), and gas
assertions if relevant.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
test/src/lib/implementation/LibDecimalFloatImplementation.characteristicMantissa.t.sol (1)

9-20: Solid helper + assertions; negative-path coverage added

The helper is minimal and pure, and the negative signedCoefficient example in the examples test addresses earlier feedback about negative behavior.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 344960d and f58d8a7.

📒 Files selected for processing (1)
  • test/src/lib/implementation/LibDecimalFloatImplementation.characteristicMantissa.t.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-11T14:30:48.562Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#96
File: test/src/lib/LibDecimalFloat.ceil.t.sol:43-50
Timestamp: 2025-08-11T14:30:48.562Z
Learning: When reviewing Solidity test files using Forge, verify the actual mutability of helper functions like `bound()` before suggesting changes to function mutability specifiers, as Forge implements many test utilities as pure functions.

Applied to files:

  • test/src/lib/implementation/LibDecimalFloatImplementation.characteristicMantissa.t.sol
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-static)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-test)
  • GitHub Check: rainix (ubuntu-latest, test-wasm-build)
  • GitHub Check: rainix (macos-latest, rainix-rs-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)

Comment on lines +22 to +28
function testCharacteristicMantissaExamples() public pure {
checkCharacteristicMantissa(0, 0, 0, 0);
checkCharacteristicMantissa(0, 1, 0, 0);

checkCharacteristicMantissa(5.4304950862250382e16, -16, 5e16, 4304950862250382);
checkCharacteristicMantissa(-5.4304950862250382e16, -16, -5e16, -0.4304950862250382e16);
}
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.

🧹 Nitpick (assertive)

Add a fuzz invariant test over exponent ∈ [-77, 0]

Property tests will catch regressions without hand-computing expected values. This stays pure by using forge-std’s pure bound().

Add this function to the contract:

function testFuzzCharacteristicMantissaInvariants(int256 signedCoefficient, int256 exponent) public pure {
    // Limit to exponents where the scale is well-defined and safe to compute
    exponent = bound(exponent, -77, 0);

    (int256 c, int256 m) = LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient, exponent);

    if (exponent == -77) {
        // scale = 1e77 > int256.max, so characteristic must be 0 and mantissa equals coefficient
        assertEq(c, 0, "Characteristic at -77 must be 0");
        assertEq(m, signedCoefficient, "Mantissa at -77 must equal coefficient");
    } else {
        // For -76..0 we can safely compute scale as int256.
        uint256 scaleU = 10 ** uint256(uint256(-exponent)); // <= 1e76 fits int256
        int256 scale = int256(scaleU);

        // Decomposition invariant
        assertEq(c + m, signedCoefficient, "Decomposition invariant");

        // Characteristic is a multiple of scale
        assertEq(c % scale, 0, "Characteristic must be multiple of scale");

        // Mantissa magnitude bound: -scale < m < scale
        assertTrue(m > -scale && m < scale, "Mantissa magnitude bound");

        // Mantissa sign follows coefficient when non-zero
        if (m != 0) {
            assertEq(m > 0, signedCoefficient > 0, "Mantissa sign follows coefficient");
        }
    }
}
🤖 Prompt for AI Agents
In
test/src/lib/implementation/LibDecimalFloatImplementation.characteristicMantissa.t.sol
around lines 22 to 28, add a new pure fuzz test that bounds the exponent to
[-77,0] and verifies invariant properties of characteristicMantissa: call
LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient,
exponent) then for exponent == -77 assert characteristic == 0 and mantissa ==
signedCoefficient; otherwise compute scale = int256(10 ** uint256(-exponent))
and assert (1) decomposition c + m == signedCoefficient, (2) c is a multiple of
scale (c % scale == 0), (3) mantissa magnitude -scale < m < scale, and (4) if m
!= 0 then sign(m) == sign(signedCoefficient); use forge-std’s bound() to
constrain exponent and keep the function pure.

🛠️ Refactor suggestion

Add boundary coverage: exact multiples, off-by-one, and -76/-77 exponent edge semantics

These add high-value checks for:

  • exact multiple → zero mantissa,
  • off-by-one around the multiple,
  • exponent -76 boundary (scale = 1e76),
  • exponent -77 where scale exceeds int256.max and characteristic must be zero.

This hardens behavior around documented boundaries and negative remainders.

Apply this diff within the examples function:

     function testCharacteristicMantissaExamples() public pure {
         checkCharacteristicMantissa(0, 0, 0, 0);
         checkCharacteristicMantissa(0, 1, 0, 0);

         checkCharacteristicMantissa(5.4304950862250382e16, -16, 5e16, 4304950862250382);
-        checkCharacteristicMantissa(-5.4304950862250382e16, -16, -5e16, -0.4304950862250382e16);
+        checkCharacteristicMantissa(-5.4304950862250382e16, -16, -5e16, -4304950862250382);
+
+        // Exact multiple => zero mantissa
+        checkCharacteristicMantissa(5e16, -16, 5e16, 0);
+        checkCharacteristicMantissa(-5e16, -16, -5e16, 0);
+        // Off-by-one around the multiple
+        checkCharacteristicMantissa(5e16 + 1, -16, 5e16, 1);
+        checkCharacteristicMantissa(-5e16 - 1, -16, -5e16, -1);
+
+        // Boundary at exponent -76 (scale = 1e76)
+        checkCharacteristicMantissa(1e76, -76, 1e76, 0);
+        checkCharacteristicMantissa(1e76 + 1, -76, 1e76, 1);
+        checkCharacteristicMantissa(-1e76, -76, -1e76, 0);
+        checkCharacteristicMantissa(-1e76 - 1, -76, -1e76, -1);
+
+        // Beyond boundary at exponent -77 (scale > int256.max): characteristic must be 0
+        checkCharacteristicMantissa(1, -77, 0, 1);
+        checkCharacteristicMantissa(-1, -77, 0, -1);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function testCharacteristicMantissaExamples() public pure {
checkCharacteristicMantissa(0, 0, 0, 0);
checkCharacteristicMantissa(0, 1, 0, 0);
checkCharacteristicMantissa(5.4304950862250382e16, -16, 5e16, 4304950862250382);
checkCharacteristicMantissa(-5.4304950862250382e16, -16, -5e16, -0.4304950862250382e16);
}
function testCharacteristicMantissaExamples() public pure {
checkCharacteristicMantissa(0, 0, 0, 0);
checkCharacteristicMantissa(0, 1, 0, 0);
checkCharacteristicMantissa(5.4304950862250382e16, -16, 5e16, 4304950862250382);
checkCharacteristicMantissa(-5.4304950862250382e16, -16, -5e16, -4304950862250382);
// Exact multiple => zero mantissa
checkCharacteristicMantissa(5e16, -16, 5e16, 0);
checkCharacteristicMantissa(-5e16, -16, -5e16, 0);
// Off-by-one around the multiple
checkCharacteristicMantissa(5e16 + 1, -16, 5e16, 1);
checkCharacteristicMantissa(-5e16 - 1, -16, -5e16, -1);
// Boundary at exponent -76 (scale = 1e76)
checkCharacteristicMantissa(1e76, -76, 1e76, 0);
checkCharacteristicMantissa(1e76 + 1, -76, 1e76, 1);
checkCharacteristicMantissa(-1e76, -76, -1e76, 0);
checkCharacteristicMantissa(-1e76 - 1, -76, -1e76, -1);
// Beyond boundary at exponent -77 (scale > int256.max): characteristic must be 0
checkCharacteristicMantissa(1, -77, 0, 1);
checkCharacteristicMantissa(-1, -77, 0, -1);
}

thedavidmeister and others added 4 commits August 14, 2025 20:52
…t.sol

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔭 Outside diff range comments (1)
test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol (1)

108-110: Incorrect coefficient in test assertion

After summing 123456789 + 987654321 = 1111111110 and normalizing to eight-digit precision, the coefficient should be 1.11111111e75 (not 1.11111111e76). Please update the test accordingly:

-        assertEq(signedCoefficient, 1.11111111e76);
+        assertEq(signedCoefficient, 1.11111111e75);

If you keep the same normalization logic, also adjust the exponent assertion to:

-        assertEq(exponent, -76 + 9);
+        assertEq(exponent, -75 + 9);
♻️ Duplicate comments (3)
test/src/lib/implementation/LibDecimalFloatImplementation.characteristicMantissa.t.sol (2)

26-27: Harmonize numeric literal style for mantissa (use integer form)

Use the integer literal for the negative mantissa, mirroring the positive case and avoiding rational-literal readability/rounding pitfalls.

-        checkCharacteristicMantissa(-5.4304950862250382e16, -16, -5e16, -0.4304950862250382e16);
+        checkCharacteristicMantissa(-5.4304950862250382e16, -16, -5e16, -4304950862250382);

22-48: Add a fuzz invariant test over exponent ∈ [-77, 0]

Property-based coverage will catch regressions without having to handcraft many examples. This mirrors previous suggestions and stays pure-compatible with forge-std.

Add this function to the contract:

function testFuzzCharacteristicMantissaInvariants(int256 signedCoefficient, int256 exponent) public pure {
    // Limit to exponents where the scale is well-defined and safe to compute
    exponent = bound(exponent, -77, 0);

    (int256 c, int256 m) = LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient, exponent);

    if (exponent == -77) {
        // scale = 1e77 > int256.max, so characteristic must be 0 and mantissa equals coefficient
        assertEq(c, 0, "Characteristic at -77 must be 0");
        assertEq(m, signedCoefficient, "Mantissa at -77 must equal coefficient");
    } else {
        // For -76..0 we can safely compute scale as int256.
        uint256 scaleU = 10 ** uint256(uint256(-exponent)); // <= 1e76 fits int256
        int256 scale = int256(scaleU);

        // Decomposition invariant
        assertEq(c + m, signedCoefficient, "Decomposition invariant");

        // Characteristic is a multiple of scale
        assertEq(c % scale, 0, "Characteristic must be multiple of scale");

        // Mantissa magnitude bound: -scale < m < scale
        assertTrue(m > -scale && m < scale, "Mantissa magnitude bound");

        // Mantissa sign follows coefficient when non-zero
        if (m != 0) {
            assertEq(m > 0, signedCoefficient > 0, "Mantissa sign follows coefficient");
        }
    }
}
test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol (1)

37-41: Test function name has been corrected.

The test function name has been properly corrected from testOverflowCheks to testOverflowChecks, addressing the previous typo issue.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f58d8a7 and bc8851a.

📒 Files selected for processing (2)
  • test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol (5 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.characteristicMantissa.t.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-11T14:30:48.562Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#96
File: test/src/lib/LibDecimalFloat.ceil.t.sol:43-50
Timestamp: 2025-08-11T14:30:48.562Z
Learning: When reviewing Solidity test files using Forge, verify the actual mutability of helper functions like `bound()` before suggesting changes to function mutability specifiers, as Forge implements many test utilities as pure functions.

Applied to files:

  • test/src/lib/implementation/LibDecimalFloatImplementation.characteristicMantissa.t.sol
  • test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol
📚 Learning: 2025-06-16T13:17:28.513Z
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#58
File: src/concrete/DecimalFloat.sol:175-182
Timestamp: 2025-06-16T13:17:28.513Z
Learning: In the rainlanguage/rain.math.float codebase, there's an established naming convention where functions accepting a `Float` type parameter consistently use `float` as the parameter name, even though it shadows the type name. This pattern is used throughout `LibDecimalFloat.sol` and should be maintained for consistency in related contracts like `DecimalFloat.sol`.

Applied to files:

  • test/src/lib/implementation/LibDecimalFloatImplementation.characteristicMantissa.t.sol
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: rainix (ubuntu-latest, test-wasm-build)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-static)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
  • GitHub Check: rainix (macos-latest, rainix-rs-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-test)
  • GitHub Check: git-clean
🔇 Additional comments (9)
test/src/lib/implementation/LibDecimalFloatImplementation.characteristicMantissa.t.sol (1)

1-7: Good scaffolding and imports

License, pinned pragma, and imports are consistent with the codebase. No issues.

test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol (8)

13-35: Well-designed overflow detection utilities.

The two overflow detection functions provide good coverage for testing overflow scenarios. willOverflow uses mathematical bounds checking while willOverflow2 uses assembly-based sign comparison, providing comprehensive validation paths.


99-102: LGTM - Consistent with maximize behavior.

The test correctly verifies that adding two pre-maximized values maintains their maximized form, which aligns with the new normalization strategy.


243-243: Good addition of fuzz-discovered edge case.

Adding the specific edge case that was flagged during fuzzing (54304950862250382, -16) is excellent for regression testing and ensuring the implementation handles corner cases correctly.


248-248: Test function renamed appropriately.

The function name has been updated from testAddSameExponentSameCoefficient to testAddSameExponent, which better reflects its actual functionality of testing addition with matching exponents regardless of coefficient values.


261-266: Proper overflow handling in test logic.

The addition of overflow detection logic ensures the test focuses on non-overflow cases, which is appropriate for this specific test. The use of willOverflow function and vm.assume properly filters out problematic inputs.


94-96: Normalization via 1e76 scale is working as intended

The LibDecimalFloatImplementation.add tests now use pre-maximized inputs—e.g. (1e76, -76) to represent 1—and assert the result (2e76, -76) for 2. That aligns exactly with the maximize logic and the wrapper’s normalization in LibDecimalFloat. No further changes needed here.


116-118: Mixed exponent addition calculation is correct

The test in test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol (lines 116–118) correctly asserts:

  • signedCoefficient = 1.23456789987654321e76
  • exponent = -76 + 17 (= -59)

These values yield
1.23456789987654321e76 × 10^(–59) = 1.23456789987654321 × 10^17,
which matches 123 456 789e9 + 987 654 321. No changes needed.


172-174: Baseline test expectations are correct

The updated expected coefficients and exponents in
test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol (lines 172–174)
accurately reflect the exact sum of 1e37 and 1e0 when using maximized precision. No changes are needed.

Comment on lines +9 to +20
function checkCharacteristicMantissa(
int256 signedCoefficient,
int256 exponent,
int256 expectedCharacteristic,
int256 expectedMantissa
) internal pure {
(int256 actualCharacteristic, int256 actualMantissa) =
LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient, exponent);

assertEq(actualCharacteristic, expectedCharacteristic, "Characteristic mismatch");
assertEq(actualMantissa, expectedMantissa, "Mantissa mismatch");
}
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.

🧹 Nitpick (assertive)

Augment helper with invariants to harden correctness

On top of golden-value checks, assert structural invariants (decomposition, bounds, divisibility by scale) for exponent ranges where they’re well-defined. This catches regressions without duplicating test cases.

Apply this diff to extend the helper:

     ) internal pure {
         (int256 actualCharacteristic, int256 actualMantissa) =
             LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient, exponent);

         assertEq(actualCharacteristic, expectedCharacteristic, "Characteristic mismatch");
         assertEq(actualMantissa, expectedMantissa, "Mantissa mismatch");
+        // Invariants:
+        // - Only meaningful for exponent <= 0 (scale >= 1).
+        // - For exponent == -77, scale = 1e77 > int256.max so characteristic must be 0 and mantissa == coefficient.
+        if (exponent <= 0) {
+            if (exponent == -77) {
+                assertEq(actualCharacteristic, 0, "Characteristic at -77 must be 0");
+                assertEq(actualMantissa, signedCoefficient, "Mantissa at -77 must equal coefficient");
+            } else {
+                // For -76..0, 10**(-exponent) fits in int256.
+                uint256 scaleU = 10 ** uint256(uint256(-exponent)); // <= 1e76
+                int256 scale = int256(scaleU);
+
+                // Decomposition invariant
+                assertEq(actualCharacteristic + actualMantissa, signedCoefficient, "Decomposition invariant");
+                // Characteristic is a multiple of scale
+                assertEq(actualCharacteristic % scale, 0, "Characteristic must be multiple of scale");
+                // Mantissa magnitude bound: -scale < m < scale
+                assertTrue(actualMantissa > -scale && actualMantissa < scale, "Mantissa magnitude bound");
+                // Mantissa sign follows coefficient when non-zero
+                if (actualMantissa != 0) {
+                    assertEq(actualMantissa > 0, signedCoefficient > 0, "Mantissa sign follows coefficient");
+                }
+            }
+        }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function checkCharacteristicMantissa(
int256 signedCoefficient,
int256 exponent,
int256 expectedCharacteristic,
int256 expectedMantissa
) internal pure {
(int256 actualCharacteristic, int256 actualMantissa) =
LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient, exponent);
assertEq(actualCharacteristic, expectedCharacteristic, "Characteristic mismatch");
assertEq(actualMantissa, expectedMantissa, "Mantissa mismatch");
}
function checkCharacteristicMantissa(
int256 signedCoefficient,
int256 exponent,
int256 expectedCharacteristic,
int256 expectedMantissa
) internal pure {
(int256 actualCharacteristic, int256 actualMantissa) =
LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient, exponent);
assertEq(actualCharacteristic, expectedCharacteristic, "Characteristic mismatch");
assertEq(actualMantissa, expectedMantissa, "Mantissa mismatch");
// Invariants:
// - Only meaningful for exponent <= 0 (scale >= 1).
// - For exponent == -77, scale = 1e77 > int256.max so characteristic must be 0 and mantissa == coefficient.
if (exponent <= 0) {
if (exponent == -77) {
assertEq(actualCharacteristic, 0, "Characteristic at -77 must be 0");
assertEq(actualMantissa, signedCoefficient, "Mantissa at -77 must equal coefficient");
} else {
// For -76..0, 10**(-exponent) fits in int256.
uint256 scaleU = 10 ** uint256(uint256(-exponent)); // <= 1e76
int256 scale = int256(scaleU);
// Decomposition invariant
assertEq(actualCharacteristic + actualMantissa, signedCoefficient, "Decomposition invariant");
// Characteristic is a multiple of scale
assertEq(actualCharacteristic % scale, 0, "Characteristic must be multiple of scale");
// Mantissa magnitude bound: -scale < m < scale
assertTrue(actualMantissa > -scale && actualMantissa < scale, "Mantissa magnitude bound");
// Mantissa sign follows coefficient when non-zero
if (actualMantissa != 0) {
assertEq(actualMantissa > 0, signedCoefficient > 0, "Mantissa sign follows coefficient");
}
}
}
}

Comment on lines +45 to +47
// Beyond boundary at exponent -77 (scale > int256.max): characteristic must be 0
checkCharacteristicMantissa(1, -77, 0, 1);
checkCharacteristicMantissa(-1, -77, 0, -1);
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.

🧹 Nitpick (assertive)

Cover exponent < -77 as well (-78) to nail down “always zero characteristic” region

Given the documented semantics, exponents below -77 should behave the same as -77. Add a quick pair to make that explicit.

         // Beyond boundary at exponent -77 (scale > int256.max): characteristic must be 0
         checkCharacteristicMantissa(1, -77, 0, 1);
         checkCharacteristicMantissa(-1, -77, 0, -1);
+        // And below -77, the same semantics hold
+        checkCharacteristicMantissa(1, -78, 0, 1);
+        checkCharacteristicMantissa(-1, -78, 0, -1);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Beyond boundary at exponent -77 (scale > int256.max): characteristic must be 0
checkCharacteristicMantissa(1, -77, 0, 1);
checkCharacteristicMantissa(-1, -77, 0, -1);
// Beyond boundary at exponent -77 (scale > int256.max): characteristic must be 0
checkCharacteristicMantissa(1, -77, 0, 1);
checkCharacteristicMantissa(-1, -77, 0, -1);
// And below -77, the same semantics hold
checkCharacteristicMantissa(1, -78, 0, 1);
checkCharacteristicMantissa(-1, -78, 0, -1);
🤖 Prompt for AI Agents
In
test/src/lib/implementation/LibDecimalFloatImplementation.characteristicMantissa.t.sol
around lines 45 to 47, the test covers exponent -77 but not -78; add the same
two assertions for exponent -78 to ensure values below -77 also yield a zero
characteristic — specifically add checkCharacteristicMantissa(1, -78, 0, 1) and
checkCharacteristicMantissa(-1, -78, 0, -1) immediately after the existing -77
checks.

@thedavidmeister thedavidmeister merged commit 52e166a into main Aug 14, 2025
8 checks passed
This was referenced Aug 26, 2025
This was referenced Sep 20, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Jan 31, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants