Skip to content

fix(currency-math): correct within-step fractional bonding-curve pricing - #1172

Merged
bmc08gt merged 1 commit into
code/cashfrom
fix/bonding-curve-within-step-fractional
Jul 29, 2026
Merged

fix(currency-math): correct within-step fractional bonding-curve pricing#1172
bmc08gt merged 1 commit into
code/cashfrom
fix/bonding-curve-within-step-fractional

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Problem

DiscreteBondingCurve.tokensToValue returns a negative value for a within-step purchase with a fractional token amount:

(currentSupply, tokens) expected actual
(0, 12.5) 0.125 -0.750
(0, 0.0000000001) 0.000000000001 -0.999999999998

Root cause

The startStep == endStep short-circuit uses Kotlin ==BigDecimal.equals, which is scale-sensitive. For a fractional within-step purchase, startStep = "0" (scale 0) and endStep = "0.0" (scale 1) are equal in value but ==-unequal, so the code falls through to the multi-step branch and computes middleCost = cumulative[0] - cumulative[1] = -1.0.

Whole-token inputs share scale 0, so == happened to work — which is why the bug stayed hidden.

Fix

  • Compare with compareTo instead of == in the within-step check.
  • Harden the sibling tokens == BigDecimal.ZERO check to tokens.signum() == 0 (same scale-sensitivity class).

Testing

Adds DiscreteBondingCurveVectorTest (instrumented) asserting the curve against canonical vectors derived from the on-chain Rust curve (flipcash-program/api) — both apps load bit-identical pricing/cumulative tables — including the fractional cases that regressed.

Verified on device (SM-S938U1): before the fix the within-step fractional cases returned negative; after, they match the reference (0.125, 1e-12). iOS already computes these correctly, so this brings Android into agreement.

Reachability: the production sell path uses large (multi-step) new_supply, so this is unlikely to be hit today — but it's a latent money-math divergence in a shared primitive.

DiscreteBondingCurve.tokensToValue returned a negative value for a within-step
purchase with a fractional token amount — e.g. tokensToValue(0, 12.5) yielded
-0.750 instead of 0.125.

Root cause: the `startStep == endStep` short-circuit used BigDecimal `==`
(scale-sensitive equals). For a fractional within-step purchase, startStep ("0",
scale 0) and endStep ("0.0", scale 1) are value-equal but `==`-unequal, so the
code fell through to the multi-step branch and computed
middleCost = cumulative[0] - cumulative[1] = -1.0. Whole-token inputs share
scale 0, so `==` happened to work — which is why the bug stayed hidden.

Fix: compare with compareTo; also harden the sibling `tokens == ZERO` check to
signum() (same scale-sensitivity class).

Adds DiscreteBondingCurveVectorTest, asserting the curve against canonical
vectors derived from the on-chain Rust curve (both apps load identical tables),
including the fractional cases that regressed. Verified on device.
@github-actions github-actions Bot added the type: fix Bug fix label Jul 29, 2026
@bmc08gt
bmc08gt merged commit 5fa9b00 into code/cash Jul 29, 2026
3 checks passed
@bmc08gt
bmc08gt deleted the fix/bonding-curve-within-step-fractional branch July 29, 2026 16:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant