Repository navigation
Parse representable literals past int256 - #355
Conversation
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>
|
Warning Review limit reachedYou'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. View limit detailsLimit details: You’ve used the included review currently available. Review configuration: ⚙️ Run configuration
📒 Files selected for processing (7)
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. Comment |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@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:
Review Effort: Would have taken 5-10 minutes Examples:
Medium (M)Characteristics:
Review Effort: Would have taken 15-30 minutes Examples:
Large (L)Characteristics:
Review Effort: Would have taken 45+ minutes Examples:
Additional Factors to ConsiderWhen deciding between sizes, also consider:
Notes:
|
Closes #341
Rule
Every literal whose value is a Float parses to exactly that value.
1followed by 77 zeros parses to1e77). If its significant digits are still past int256, the result isParseDecimalOverflow. A nonzero fraction digit after it isParseDecimalPrecisionLoss. An all-zero fraction is consumed.0e<int256.max+1>is zero). A nonzero coefficient with exponent digits past int256 is stillParseDecimalOverflow.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.rsparse_inlineapplies the same rule.exact.rscheck_parsenow asks the Pythonparse_valueabout every literal, not only those that parse.decimal_oracle.pydecides 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, fromLibParseDecimalFloat.gas.t.sol(new here) run on main and on this branch:01100-421.5-123.4560.0000011e181.5e-71000000000000000000123456789012345678901234567890.123456789QA
LibParseDecimalFloatPastInt256Test(testParseIntegerPastInt256PowersOfTen,...Boundary,...Int224Bounds,...Fraction,...Exponent,testParseZeroHugeExponent,testParseNonZeroHugeExponent,testParseIntegerTrailingZerosFuzz) and Rustexact::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).testParseNonZeroHugeExponentfails there on the cursor, and the rest fail withParseDecimalOverflow. Main's own tests kill 0/16 of the Solidity mutants below.decimalmodule reads each literal's exact value and decides whether it packs (parse_value), independent of the parser.reference.rsis checked against it, and the Solidity parser (revm) is checked against both byexact_parse/library_parse. The Solidity tests' expected values come from the literal's text (digits, zero count), not from the code.testParseZeroHugeExponentand S14/S15, and (c) by thecheck_parsechange 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 runcargo testovercrates/tests, whose build compiles the Solidity.testParseIntegerPastInt256*testParseIntegerPastInt256*testParseIntegerPastInt256*testParseIntegerPastInt256*...Boundary,...Fraction,...PowersOfTentestParseIntegerPastInt256*...FractiontestParseIntegerPastInt256*...Fraction...Fraction...Fraction...Exponent,...Fraction...FractiontestParseZeroHugeExponenttestParseNonZeroHugeExponent...Fractionfound::parse_past_int256,exact_parse,library_parsefound::parse_past_int256found::parse_past_int256,exact_parse,library_parsefound::parse_sheds_past_the_int256_exponent,exact_parsefound::parse_sheds_past_the_int256_exponent,found::parse_past_int256,exact_parseexact_parseR/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 fallthroughpackreturns the sameExponentOverflow/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