Skip to content

Float safety: Smarkets normalizeBalance parseFloat subtraction for locked balance #671

@realfishsam

Description

@realfishsam

Risk Level

MEDIUM

Location

core/src/exchanges/smarkets/normalizer.ts:277-284

Code

const balance   = parseFloat(raw.balance || '0');
const available = parseFloat(raw.available_balance || '0');
...
return [{
    currency: 'GBP',
    total: balance,
    available,
    locked: balance - available,  // ← float subtraction
}];

Problem

Two parseFloat calls convert the Smarkets API's balance strings to IEEE 754 floats, then float subtraction derives locked = total - available. Because both balance and available were independently rounded by IEEE 754 at parse time, their difference accumulates two rounding errors. This is structurally identical to the Kalshi normalizeBalance issue (#237).

The locked value is displayed to the user and used for order-sizing decisions.

Example Failure

raw.balance           = "99.99"
raw.available_balance = "90.01"

parseFloat("99.99")  = 99.99  (exact in this case)
parseFloat("90.01")  = 90.01  (exact in this case)
locked = 99.99 - 90.01 = 9.979999999999997  (IEEE 754  should be 9.98)

// Less round numbers:
raw.balance           = "123.33"
raw.available_balance = "111.11"

parseFloat("123.33") = 123.33
parseFloat("111.11") = 111.11
locked = 123.33 - 111.11 = 12.219999999999999  (should be 12.22)

Suggested Fix

Compute locked without independent float rounding:

import Decimal from 'decimal.js';
const balance   = new Decimal(raw.balance || '0');
const available = new Decimal(raw.available_balance || '0');
return [{
    currency: 'GBP',
    total: balance.toNumber(),
    available: available.toNumber(),
    locked: balance.minus(available).toNumber(),
}];

Found by automated float safety audit

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions