Skip to content

Parse representable literals past int256 - #355

Merged
thedavidmeister merged 4 commits into
mainfrom
2026-10-07-issue-341-parse-past-int256
Oct 9, 2026
Merged

thedavidmeister merged 4 commits into
mainfrom
2026-10-07-issue-341-parse-past-int256

Conversation

@thedavidmeister

Copy link
Copy Markdown
Contributor

Closes #341

Rule

Every literal whose value is a Float parses to exactly that value.

  • An integer part past int256 moves its trailing zeros into the exponent (1 followed by 77 zeros parses to 1e77). If its significant digits are still past int256, the result is ParseDecimalOverflow. A nonzero fraction digit after it is ParseDecimalPrecisionLoss. An all-zero fraction is consumed.
  • A zero coefficient accepts exponent digits of any size (0e<int256.max+1> is zero). A nonzero coefficient with exponent digits past int256 is still ParseDecimalOverflow.
  • The fallback (parseIntegerPastInt256) runs only on the integer parse's error path, so literals that parsed before take the same path and use the same gas.

The oracles follow the issue: reference.rs parse_inline applies the same rule. exact.rs check_parse now asks the Python parse_value about every literal, not only those that parse. decimal_oracle.py decides a literal past its decimal module's range by its adjusted exponent.

Overlap with #343: when a literal is no Float, its error selector (ParseDecimalOverflow, ParseDecimalPrecisionLoss, ExponentOverflow) still comes from the parser's intermediates. This PR leaves those selectors as they were, and #343 owns the error rules.

Gas

parseDecimalFloat, from LibParseDecimalFloat.gas.t.sol (new here) run on main and on this branch:

literal main PR
0 1739 1739
1 1806 1806
100 2452 2452
-42 2118 2118
1.5 3259 3259
-123.456 4553 4553
0.000001 4655 4655
1e18 3396 3396
1.5e-7 4515 4515
1000000000000000000 7620 7620
123456789012345678901234567890.123456789 15210 15210

QA

  • Discriminating tests: LibParseDecimalFloatPastInt256Test (testParseIntegerPastInt256PowersOfTen, ...Boundary, ...Int224Bounds, ...Fraction, ...Exponent, testParseZeroHugeExponent, testParseNonZeroHugeExponent, testParseIntegerTrailingZerosFuzz) and Rust exact::found::parse_past_int256. Each asserts exact coefficient, exponent, selector and cursor. All 8 Solidity tests fail on main (verified by running this file against main's source: 0 passed, 8 failed). testParseNonZeroHugeExponent fails there on the cursor, and the rest fail with ParseDecimalOverflow. Main's own tests kill 0/16 of the Solidity mutants below.
  • Mutations applied: 26, listed in the matrix below. The PR's tests kill 25. P04 is equivalent (reason below).
  • Oracle: the Python decimal module reads each literal's exact value and decides whether it packs (parse_value), independent of the parser. reference.rs is checked against it, and the Solidity parser (revm) is checked against both by exact_parse / library_parse. The Solidity tests' expected values come from the literal's text (digits, zero count), not from the code.
  • Category check: the issue asks for (a) integer parts past int256 that are Floats, (b) zero with an exponent past int256, and (c) oracles that check every literal against the general parse. Covered (a) by the PastInt256 tests and S01–S13/S16, (b) by testParseZeroHugeExponent and S14/S15, and (c) by the check_parse change and R01–R06/P01–P04.

Mutation matrix (adversarial-mutation-test 0.35.0, mutation-probe). The S columns run the full forge suite twice: main's tests over this branch's source, then this branch's tests. R/P run cargo test over crates/tests, whose build compiles the Solidity.

id mutation main tests PR tests killed by
S01 fallback off: integer past int256 is ParseDecimalOverflow survived killed testParseIntegerPastInt256*
S02 trailing zeros dropped from exponent survived killed testParseIntegerPastInt256*
S03 trailing zero count off by one survived killed testParseIntegerPastInt256*
S04 trailing zero strip disabled survived killed testParseIntegerPastInt256*
S05 strip two per zero survived killed ...Boundary, ...Fraction, ...PowersOfTen
S06 significant digits read from last digit only survived killed testParseIntegerPastInt256*
S07 significant overflow ignored survived killed ...Fraction
S08 cursor past integer off by one survived killed testParseIntegerPastInt256*
S09 fraction after past-int256 integer not consumed survived killed ...Fraction
S10 empty fraction accepted survived killed ...Fraction
S11 nonzero fraction accepted survived killed ...Fraction
S12 zero fraction rejected survived killed ...Exponent, ...Fraction
S13 nonzero fraction selector swapped survived killed ...Fraction
S14 zero with exponent past int256 rejected survived killed testParseZeroHugeExponent
S15 nonzero with exponent past int256 accepted survived killed testParseNonZeroHugeExponent
S16 nonzero fraction cursor not reported survived killed ...Fraction
R01 reference keeps trailing zeros in the coefficient n/a killed found::parse_past_int256, exact_parse, library_parse
R02 reference drops the zero count n/a killed same
R03 reference accepts a fraction after a past-int256 integer n/a killed same
R04 reference rejects zero with an exponent past int256 n/a killed found::parse_past_int256
R05 reference reads a nonzero exponent past int256 n/a killed found::parse_past_int256, exact_parse, library_parse
R06 reference reads significant digits past int256 n/a killed same
P01 python past the top is precision loss n/a killed found::parse_sheds_past_the_int256_exponent, exact_parse
P02 python range rule removed n/a killed found::parse_sheds_past_the_int256_exponent, found::parse_past_int256, exact_parse
P03 python past the bottom is overflow n/a killed exact_parse
P04 python range rule ignores the mantissa n/a equivalent —

R/P have no main column because the reference and the oracle lines they mutate are new in this PR.

P04 is equivalent in practice. Dropping m.adjusted() changes a verdict only when the mantissa's own magnitude moves the literal across RANGE (3e9) while its true value is a Float (adjusted exponent within about 2.15e9). That needs a mantissa of about 8.5e8 digits. For every other literal, the fallthrough pack returns the same ExponentOverflow / ParseDecimalPrecisionLoss.

Adversarial pass, checked against the issue's rule: no candidate. A nonzero fraction after an integer past int224 needs more than 68 significant digits, and a nonzero coefficient with exponent digits past int256 is far outside the int32 exponent range. So every rejection this PR keeps is a value that is not a Float. The scan is recorded in audit/mutation-test-scans.json.

🤖 Generated with Claude Code

baku-ccron and others added 3 commits October 7, 2026 20:50
An integer part past int256 moves its trailing zeros into the exponent,
so 1e77 written out parses to exactly 1e77. Zero takes exponent digits of
any size. The reference reads the same rule, and check_parse asks the
Python oracle for every literal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

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

Next included review available in 55 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: d172741b-4af7-4fe3-9fa2-2ab148320afa

📥 Commits

Reviewing files that changed from the base of the PR and between 5f8b01f and 80bfad9.


📒 Files selected for processing (7)
  • audit/mutation-test-scans.json
  • crates/tests/src/exact.rs
  • crates/tests/src/reference.rs
  • src/lib/parse/LibParseDecimalFloat.sol
  • test/oracle/decimal_oracle.py
  • test/src/lib/parse/LibParseDecimalFloat.gas.t.sol
  • test/src/lib/parse/LibParseDecimalFloat.pastInt256.t.sol


  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thedavidmeister
thedavidmeister merged commit b8ddd74 into main Oct 9, 2026
7 of 8 checks passed
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown

@coderabbitai assess this PR size classification for the totality of the PR with the following criterias and report it in your comment:

S/M/L PR Classification Guidelines:

This guide helps classify merged pull requests by effort and complexity rather than just line count. The goal is to assess the difficulty and scope of changes after they have been completed.

Small (S)

Characteristics:

  • Simple bug fixes, typos, or minor refactoring
  • Single-purpose changes affecting 1-2 files
  • Documentation updates
  • Configuration tweaks
  • Changes that require minimal context to review

Review Effort: Would have taken 5-10 minutes

Examples:

  • Fix typo in variable name
  • Update README with new instructions
  • Adjust configuration values
  • Simple one-line bug fixes
  • Import statement cleanup

Medium (M)

Characteristics:

  • Feature additions or enhancements
  • Refactoring that touches multiple files but maintains existing behavior
  • Breaking changes with backward compatibility
  • Changes requiring some domain knowledge to review

Review Effort: Would have taken 15-30 minutes

Examples:

  • Add new feature or component
  • Refactor common utility functions
  • Update dependencies with minor breaking changes
  • Add new component with tests
  • Performance optimizations
  • More complex bug fixes

Large (L)

Characteristics:

  • Major feature implementations
  • Breaking changes or API redesigns
  • Complex refactoring across multiple modules
  • New architectural patterns or significant design changes
  • Changes requiring deep context and multiple review rounds

Review Effort: Would have taken 45+ minutes

Examples:

  • Complete new feature with frontend/backend changes
  • Protocol upgrades or breaking changes
  • Major architectural refactoring
  • Framework or technology upgrades

Additional Factors to Consider

When deciding between sizes, also consider:

  • Test coverage impact: More comprehensive test changes lean toward larger classification
  • Risk level: Changes to critical systems bump up a size category
  • Team familiarity: Novel patterns or technologies increase complexity

Notes:

  • the assessment must be for the totality of the PR, that means comparing the base branch to the last commit of the PR
  • the assessment output must be exactly one of: S, M or L (single-line comment) in format of: SIZE={S/M/L}
  • do not include any additional text, only the size classification
  • your assessment comment must not include tips or additional sections
  • do NOT tag me or anyone else on your comment

@linear

linear Bot commented Oct 9, 2026

Copy link
Copy Markdown

RAI-3047

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.

parseDecimalFloat rejects representable literals past int256 integer part (e.g. 1e77 written out)

1 participant