Skip to content

Float safety: Opinion normalizer childVolume24h proportion float division-then-multiply #718

@realfishsam

Description

@realfishsam

Risk Level

LOW

Location

core/src/exchanges/opinion/normalizer.ts:63-68

Code

// In normalizeMarketsFromEvent (categorical markets):
const parentVolume24h = parseNumStr(raw.volume24h);
const totalChildVolume = children.reduce((sum, c) => sum + parseNumStr(c.volume), 0);

for (const child of children) {
    const childVolume = parseNumStr(child.volume);
    const childVolume24h = totalChildVolume > 0
        ? (childVolume / totalChildVolume) * parentVolume24h
        : 0;
    // childVolume24h is set on the UnifiedMarket
}

(parseNumStr wraps parseFloat)

Problem

childVolume24h is computed as a proportion: (childVolume / totalChildVolume) * parentVolume24h. This is a divide-then-multiply chain on three independent parseFloat values:

  1. Division childVolume / totalChildVolume can produce a repeating binary fraction
  2. Multiplication of that fraction by parentVolume24h amplifies the error

The result is used for sorting markets by volume and for the event-level volume roll-up. While this doesn't affect order building, it can:

  • Cause inconsistent market ordering across fetches (non-deterministic sort)
  • Produce child volume sums that don't equal parentVolume24h

Example Failure

parentVolume24h = 1000.0
children volumes: [300, 350, 350]
totalChildVolume = 1000.0

childVolume24h[0] = (300 / 1000) * 1000 = 300.0  ✓
childVolume24h[1] = (350 / 1000) * 1000 = 350.00000000000006  ← float error
childVolume24h[2] = (350 / 1000) * 1000 = 350.00000000000006

sum of childVolume24h = 1000.00000000000012  ≠ 1000.0

Event-level volume aggregation then double-counts 0.00000000000012 USDC per fetch.

Suggested Fix

This is display/sorting only and low severity, but can be fixed by rounding the proportion result:

const childVolume24h = totalChildVolume > 0
    ? Math.round((childVolume / totalChildVolume) * parentVolume24h * 1e6) / 1e6
    : 0;

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