Use Decimal128 for exact monetary storage in MongoDB#6
Use Decimal128 for exact monetary storage in MongoDB#6
Conversation
- Use atomic find_one_and_update with $gte guards for tips, withdrawals, and red envelope creation to prevent spending more than available balance - Use $inc instead of $set for all balance mutations to prevent race conditions between concurrent operations - Use Decimal arithmetic for all FIRO calculations to prevent floating point precision errors (stored as float in MongoDB for backward compatibility with existing database) - Fix withdrawal fee logic: atomic deduct full amount, rollback exact amount on RPC failure - Fix catch_envelope to atomically deduct from envelope remains with $gte guard, preventing over-distribution - Fix update_balance withdrawal handler to properly handle locked amount reduction without leaving stale locked values - Store MongoClient as self.client for transaction session access - Replace print() with structured logging throughout - Replace deprecated insert() calls with insert_one() https://claude.ai/code/session_01K7FrkbpYpqJAoJfcx67osm
Replace float storage with BSON Decimal128 via a custom pymongo codec that transparently converts between Python Decimal and Decimal128. This eliminates floating-point drift from accumulated $inc operations. The codec is registered on the database connection so all collections automatically serialize Decimal values as Decimal128 and deserialize them back. The to_decimal() helper handles reading both legacy float values (pre-migration) and Decimal128 values (post-migration). Add migrate_to_decimal128.py script to batch-convert existing float Balance/Locked fields to Decimal128. Supports dry-run mode by default and logs every change. The bot works correctly both before and after migration (graceful degradation). https://claude.ai/code/session_01K7FrkbpYpqJAoJfcx67osm
|
bugbot run |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| response = self.wallet_api.spendspark( | ||
| address, | ||
| float(amount), | ||
| comment |
There was a problem hiding this comment.
Withdrawal fee no longer deducted from sent amount
High Severity
The old code sent amount - AV_FEE to spendspark and locked amount - AV_FEE, effectively collecting the 0.002 FIRO fixed withdrawal fee documented in CLAUDE.md. The new code sends float(amount) (the full amount) and locks the full amount, so the bot's withdrawal fee is no longer charged. The docstring incorrectly claims "The network receives amount - AV_FEE" but the code sends the unmodified amount. The PR test plan says "Verify withdrawal fee calculations maintain precision," implying the fee was meant to be preserved, not removed.
Additional Locations (1)
|
|
||
| def decimal_to_store(value): | ||
| """Quantize a value to 8 decimal places for MongoDB storage as Decimal128.""" | ||
| return to_decimal(value) |
There was a problem hiding this comment.
Redundant decimal_to_store wrapper duplicates to_decimal exactly
Low Severity
decimal_to_store is a pass-through that just calls return to_decimal(value) with no additional logic. Its docstring says "for MongoDB storage as Decimal128" but it returns a Python Decimal, not Decimal128 (the codec handles conversion transparently). The PR description also mentions a companion store_to_decimal() helper that was never defined. This wrapper adds indirection without value.


Summary
Test plan
Note
High Risk
Touches balance/withdrawal logic and database monetary storage, so mistakes could mis-credit users or corrupt balances. Also introduces a one-time data migration that must be applied carefully in production.
Overview
Moves monetary storage from floats to exact decimals.
tipbot.pynow uses PythonDecimalwith a MongoDBDecimal128codec, quantizes values to 8 decimal places, and stores fees/constants as decimals.Hardens money-moving operations. Deposits, tips, withdrawals, and red envelope flows are rewritten to use atomic
$inc/find_one_and_updatewith$gteguards and better idempotency (skip already-processedtxIds) to reduce race conditions and double-crediting; withdrawals add rollback on wallet send failure.Adds a one-time migration utility. New
migrate_to_decimal128.pyconverts existingusers(Balance,Locked),envelopes(amount,remains), andtip_logs(amount) fields toDecimal128, defaulting to dry-run unless--applyis provided.Written by Cursor Bugbot for commit f9cd444. This will update automatically on new commits. Configure here.