fix(M2): Decimal-based amount parsing for /utxo/transfer (precision + overflow)#2814
Merged
fix(M2): Decimal-based amount parsing for /utxo/transfer (precision + overflow)#2814
Conversation
…nd overflowed (closes #2867 M2) The /utxo/transfer endpoint parsed amounts via float(), which has two bugs: 1. Precision loss: float(0.29) * 100_000_000 = 28999999.999... → int() = 28999999. User sent 0.29 RTC, system credited 0.28999999 RTC (1 nrtc lost per tx). 2. OverflowError on large input: float('1e308') = inf → int(inf * UNIT) raises. Crashes the request handler instead of returning a clean 400. Replaced with _parse_rtc_amount() helper using Decimal: - Decimal(str(0.29)) is exact: 29000000 nrtc as expected - Bounds check (<= 2^53 RTC) catches overflow before int() conversion - Rejects Infinity, NaN, negative - Returns clean 400 with error message instead of 500 Verified all 8 edge cases pass including the headline 0.29 precision bug. Audit credit: BossChaos #2744 M2 (paid 25 RTC for the find) Bounty: #2867 fix-bounty Medium-tier
Contributor
✅ BCOS v2 Scan Results
What does this mean?The BCOS (Beacon Certified Open Source) engine scans for:
BCOS v2 Engine - Free & Open Source (MIT) - Elyan Labs |
Scottcjn
added a commit
that referenced
this pull request
May 1, 2026
… C1 PoC, RC_P2P_SECRET) (#2859) Five tests on main were broken by yesterday's audit fixes (#2812-#2816 + #2800): 1. test_mempool_add_manage_tx_undefined (#2812 follow-up) - Was asserting the BUG exists (manage_tx undefined). After fix, manage_tx IS defined. Updated to assert FIX is in place + smoke-test no NameError. 2. test_pncounter_max_merge_inflation - Imports rustchain_p2p_gossip which raises SystemExit if RC_P2P_SECRET not set. CI workflow didn't set it. Added RC_P2P_SECRET to ci.yml env. 3. test_bounty_lifecycle_workflow (#2800 follow-up) - haoyousun60-create's #2800 added admin auth on /api/bounties/<id>/claim. Test was sending request without X-Admin-Key. Added the header. 4. test_utxo_transfer_rejects_duplicate_nonce (#2814 M2 follow-up) 5. test_utxo_transfer_failed_attempt_does_not_burn_nonce (#2814 M2 follow-up) - M2 fix made amount_rtc / fee_rtc Decimal types internally for precision. Decimal isn't JSON-serializable, so signed-payload construction (json.dumps) and response jsonify both broke. - Cast to float for the signed payload (preserves byte-identical signature bytes vs what wallets compute) and for the response jsonify. - Decimal arithmetic still happens internally for the int(amount * UNIT) conversion, so the precision-loss + overflow guards from M2 are intact. All 6 tests pass locally with the env vars set. Co-authored-by: Scottcjn <scottbphone12@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes BossChaos audit finding M2 (Bounty #2867).
/utxo/transferparsed amounts viafloat()which has two bugs:float(0.29) * 100_000_000 = 28999999.999...→int()truncates to 28999999. User sent 0.29 RTC, system credited 0.28999999 RTC.float('1e308') = inf→int(inf * UNIT)raises 500 instead of clean 400.Replaced with
_parse_rtc_amount()helper using Decimal:Decimal(str(0.29))→ 29000000 nrtc exactly<= 2^53 RTC) catches overflow before int conversionVerified all 8 edge cases pass.
+55/-2 in
node/utxo_endpoints.py. Audit credit @BossChaos #2744 M2 (paid 25 RTC). Fix-bounty: 25 RTC Medium-tier.