Risk Level
MEDIUM
Location
core/src/exchanges/polymarket_us/price.ts:52-55
Code
export function roundToTickSize(price: number, tickSize: number = POLYMARKET_US_TICK_SIZE): number {
const ticks = Math.round(price / tickSize);
const rounded = ticks * tickSize;
const scale = Math.pow(10, POLYMARKET_US_PRICE_DECIMALS);
return Math.round(rounded * scale) / scale;
}
Problem
This function applies two separate Math.round calls — the second one exists specifically to clean up float drift introduced by the first. The comment in the source even says "Re-rounds to avoid floating-point drift." This is a documented band-aid acknowledging the underlying problem.
price / tickSize is a float division. ticks * tickSize reconstructs the rounded price but via float multiplication, which can produce a dirty result like 0.5499999999999999 instead of 0.55. The second Math.round(rounded * scale) / scale corrects most cases but fails if the dirty intermediate happens to round the wrong way.
This function is on the critical path for every Polymarket US order — it produces the price sent to the order intent API.
Example Failure
price = 0.5501, tickSize = 0.001, POLYMARKET_US_PRICE_DECIMALS = 3
ticks = Math.round(0.5501 / 0.001) = Math.round(550.0999...) = 550
rounded = 550 * 0.001 = 0.5499999999999999 (IEEE 754 drift!)
scale = 1000
Math.round(0.5499999999999999 * 1000) / 1000
= Math.round(549.9999999999999) / 1000
= 549 / 1000
= 0.549 ← WRONG, should be 0.550
Suggested Fix
Use decimal.js to avoid the need for double-rounding entirely:
import Decimal from 'decimal.js';
export function roundToTickSize(price: number, tickSize: number = POLYMARKET_US_TICK_SIZE): number {
return new Decimal(price)
.dividedBy(tickSize)
.toDecimalPlaces(0, Decimal.ROUND_HALF_UP)
.times(tickSize)
.toDecimalPlaces(POLYMARKET_US_PRICE_DECIMALS)
.toNumber();
}
Found by automated float safety audit
Risk Level
MEDIUM
Location
core/src/exchanges/polymarket_us/price.ts:52-55Code
Problem
This function applies two separate
Math.roundcalls — the second one exists specifically to clean up float drift introduced by the first. The comment in the source even says "Re-rounds to avoid floating-point drift." This is a documented band-aid acknowledging the underlying problem.price / tickSizeis a float division.ticks * tickSizereconstructs the rounded price but via float multiplication, which can produce a dirty result like0.5499999999999999instead of0.55. The secondMath.round(rounded * scale) / scalecorrects most cases but fails if the dirty intermediate happens to round the wrong way.This function is on the critical path for every Polymarket US order — it produces the price sent to the order intent API.
Example Failure
Suggested Fix
Use
decimal.jsto avoid the need for double-rounding entirely:Found by automated float safety audit