Skip to content

Float safety: Opinion normalizer normalizePosition currentValue / sharesOwned float division #713

@realfishsam

Description

@realfishsam

Risk Level

HIGH

Location

core/src/exchanges/opinion/normalizer.ts:195-198

Code

normalizePosition(raw: OpinionRawPosition): Position {
    const sharesOwned = parseNumStr(raw.sharesOwned);
    const currentValue = parseNumStr(raw.currentValueInQuoteToken);
    const currentPrice = sharesOwned > 0 ? currentValue / sharesOwned : 0;

    return {
        ...
        entryPrice: parseNumStr(raw.avgEntryPrice),
        currentPrice,
        unrealizedPnL: parseNumStr(raw.unrealizedPnl),
    };
}

(parseNumStr wraps parseFloat)

Problem

currentPrice is derived by dividing two parseFloat values. Division of two IEEE 754 floats produces a result that can differ from the true rational quotient by up to 1 ULP. Since prediction market prices must live in [0, 1], errors around common rational values like 1/3, 2/3, 0.1, 0.4 can produce prices that differ by more than the market's minimum tick (0.001).

Example Failure

raw.currentValueInQuoteToken = "66.67"  // USDC value of position
raw.sharesOwned = "100"                  // 100 shares

parseNumStr("66.67") = 66.67000000000000284
parseNumStr("100")   = 100

currentPrice = 66.67000000000000284 / 100 = 0.6667000000000000284
// Correct: 0.6667
// Returned: 0.6667000000000000284
// Displayed or compared: may show as 0.6667000000000000 (OK) or 0.6667000000000001 (wrong tick)

For smaller share sizes (e.g. 3 shares), division produces irrational decimals that diverge from any valid market tick.

Suggested Fix

Use Decimal.js for the division, or round to the market's tick size after computing:

const currentPrice = sharesOwned > 0
    ? new Decimal(currentValue).dividedBy(sharesOwned).toNumber()
    : 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