Skip to content

All monetary amounts are JavaScript number — fee and routing maths accumulate floating-point error on value #225

Closed

Description

@Jagadeeshftw

Priority: High  ·  Area: Numeric correctness / money handling  ·  Est. effort: 10–14 h

📌 Problem

Every monetary field in src/models/liquidity.ts is a JavaScript number — an IEEE-754 double:

amount: number;             // :12, :30, :49, :63
remainingBalance: number;   // :32
total: number;              // :40
portion: number;            // :57

src/services/quoteService.ts does arithmetic on them:

const taken = Math.min(remaining, entry.amount);              // :49  (inside the routing loop)
const fee = Math.ceil((amount * this.feeBps) / BPS_DIVISOR);  // :54

Two distinct problems.

1. Precision loss above 2^53. Doubles represent integers exactly only up to 9,007,199,254,740,991. Stellar amounts are commonly held in stroops (1 XLM = 10,000,000 stroops), so 900,719,925 XLM exceeds the safe range. Past it, addition silently stops being exact.

2. Accumulated error in the routing loop. The service selects liquidity largest-first, subtracting taken from remaining each iteration. Repeated float subtraction accumulates representation error, so the sum of the route portions can fail to equal the requested amount by a small residue — and Math.ceil on the fee then rounds a value that is already slightly wrong.

The service's own doc comment calls the quote "deterministic". Float arithmetic across a variable number of iterations, in a variable order, is not reliably so.

🎯 Design decision required

Post your approach in a comment on this issue before writing code — this touches the models, the services and the API boundary. State and defend:

  1. Representation. bigint for integer minor units, or a decimal library? bigint has no dependency and is exact, but does not serialise to JSON natively and cannot express fractions. Argue one, and state the unit you standardise on.
  2. API compatibility. JSON has no integer type beyond double precision, so large values must serialise as strings to survive the round trip. That is a breaking API change. State it plainly and describe the migration.
  3. Rounding policy. Math.ceil on the fee rounds in the protocol's favour. Whatever representation you choose, state the rounding rule explicitly, apply it consistently, and prove the route portions sum exactly to the requested amount.

🧩 Requirements and context

  • Add tests demonstrating the current defect first — a value above Number.MAX_SAFE_INTEGER, and a multi-anchor route whose portions do not sum exactly. Those tests are the evidence the change is warranted.
  • The invariant "sum of route portions + fee equals the requested total, exactly" must hold after the change, with a property-style test over many inputs.
  • src/models/settlement.ts and the settlement path use the same amounts — check and include them, or scope explicitly and say what you left.
  • All 42 test files must pass.
  • Update src/openapi.ts to reflect any serialisation change.

🛠️ Suggested execution

  1. Write the two failing tests (large value, non-summing route).
  2. Post your representation and API-compatibility plan; wait for agreement.
  3. Convert models, then services.
  4. Add the exact-sum property test.
  5. Update the OpenAPI spec and document the breaking change.

✅ Acceptance criteria

  • Tests demonstrate precision loss and route-sum drift against the current code.
  • Monetary values use an exact representation; the unit is stated.
  • Route portions plus fee sum exactly to the requested amount, proven by a property-style test.
  • The rounding rule is documented and applied consistently.
  • Any API serialisation change is reflected in src/openapi.ts and documented as breaking.
  • All existing tests pass.
  • Anything deliberately left out of scope is listed.

🚫 Out of scope

  • The persistence layer — separate issue.
  • Changing the largest-first selection strategy.
  • Changing the fee rate.

🧪 Verification

npm ci
npm test src/services
npm run lint && npm run build && npm test

📤 What your PR must include

  1. The two failing tests and their output.
  2. A link to the agreed representation plan.
  3. Your rounding policy.
  4. The breaking-change description and migration.
  5. Closes #<n>.

🔒 Security notes

Float arithmetic on money is a correctness failure with financial consequences: a route whose portions do not sum to the requested amount means the system's accounting does not balance, and the discrepancy is small enough to escape notice while being systematic rather than random. Math.ceil on the fee compounds this by always rounding the same direction. Any residue that repeatedly favours one side of a settlement is a value leak, whether or not anyone is deliberately exploiting it.

📋 Guidelines

  • Minimum 95% test coverage on changed lines
  • Clear documentation
  • Timeframe: 96 hours from assignment
  • One logical change per commit; no merge commits

💬 Join our community

Working on this, or want to sanity-check your approach before you start? Come and ask — the maintainers are there and happy to help.

Telegram: https://t.me/Grainlify

Activity

  1. added
    bugSomething isn't working
    Maybe RewardedGrantFox: potentially rewarded contribution
    GrantFox OSSGrantFox open-source program
    priority:highHigh difficulty / architectural or cross-cutting
    Third CampaignGrantFox third campaign issue
    on Aug 20, 2026
  2. gramseostudio-dev commented on Aug 23, 2026

    @gramseostudio-dev
    Contributor

    Hi team. I would love to tackle this issue. Relying on IEEE-754 floats for financial routing is a well-known vulnerability, and I completely agree with the urgency to migrate to an exact representation.

    Here is my proposed design architecture to solve the precision loss and drift:

    1. Representation: Native bigint
      I propose using native JavaScript bigint to represent all monetary amounts internally rather than introducing a heavy third-party decimal library.
    • Unit Standard: All amounts will be strictly standardized and operated on as stroops (the integer minor unit, where 1 XLM = 10,000,000 stroops).

    • Why: Native bigint is dependency-free, memory-efficient, mathematically exact for integers, and natively supported across modern Node.js/TypeScript environments.

    1. API Compatibility & Serialization (Breaking Change)
      Since JSON does not support integers beyond Number.MAX_SAFE_INTEGER, we cannot safely transmit these values as numbers.
    • Migration: All monetary fields (amount, remainingBalance, total, portion) will be serialized to string at the API boundary before being sent out, and parsed from string to bigint on incoming requests.

    • Documentation: I will update src/openapi.ts to reflect this breaking change, documenting the fields as type: string to ensure client consumers handle the stringified large numbers correctly.

    1. Rounding Policy & Route Math Invariant
    • Rounding Rule: We will preserve the rule of rounding fees in the protocol's favor using exact BigInt math. Instead of Math.ceil(), we will use BigInt ceiling division:

    const fee = (amount * feeBps + (BPS_DIVISOR - 1n)) / BPS_DIVISOR;. This guarantees exact upward rounding without float conversion.

    • Invariant Proof: By calculating routing portions sequentially using exact BigInt subtraction (remaining -= taken), floating-point drift is mathematically eliminated. The exact residual guarantees that the sum of the route portions will perfectly match the requested amount.

    Execution Plan:
    Following your guidelines, I will first write the failing tests to demonstrate the >2^53 precision loss and the routing residue. Once proven, I will execute the refactor across the models and services, and finally add a property-style test verifying the exact-sum invariant.

    Please let me know if this architectural approach is approved so I can be assigned and begin execution!

  3. Jagadeeshftw commented on Aug 23, 2026

    @Jagadeeshftw
    ContributorAuthor

    Assignment update — exact monetary arithmetic

    Welcome, @gramseostudio-dev, and good luck with the monetary-precision issue. Please read the entire description before implementation, including the exact stroop representation, API serialization decision, rounding invariant, defect-first tests, and property-style verification. Your PR must address every requirement with a high-quality, behavior-conscious change, preserve the routing policy, document any breaking API implications, and prove that unsafe floating-point drift is gone.

    Please raise the PR within 48 hours of assignment. If no PR is opened by then, the issue may be unassigned for reassignment. Please join the Telegram group to coordinate with the maintainers: https://t.me/+u5qmu35nZ7I0OTU1

  4. grantfox-oss commented on Aug 23, 2026

    @grantfox-oss

    🦊 GrantFox — @gramseostudio-dev has been assigned to this issue as part of the Third Campaign campaign!

    Next steps:

    1. Open a Pull Request referencing this issue (e.g., Closes #225)
    2. Your PR will be reviewed by the AnchorNet-Org maintainers

    Good luck! Track your progress on GrantFox.

  5. added 2 commits that reference this issue on Aug 24, 2026
  6. added a commit that references this issue on Aug 29, 2026
  7. grantfox-oss commented on Aug 29, 2026

    @grantfox-oss

    🎉 This issue has been marked as completed on GrantFox as part of the Third Campaign campaign!

    @gramseostudio-dev's PR #236 was approved and merged by @Jagadeeshftw.

    🏆 @gramseostudio-dev: You earned 35 FoxPoints for this contribution! Your current tier: Explorer (252 total points). Track your full progress on GrantFox.

    👏 Great work, @gramseostudio-dev! Keep contributing to AnchorNet-Org.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

GrantFox OSSGrantFox open-source programMaybe RewardedGrantFox: potentially rewarded contributionThird CampaignGrantFox third campaign issuebugSomething isn't workingpriority:highHigh difficulty / architectural or cross-cutting

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions