Skip to content

SDK drift: Python's _usdc_amount rejects astronomically large deposit/withdraw amounts as "too large to encode as JSON"; TypeScript's normalizeUsdcAmount has no equivalent check for string/bigint input #2153

Description

@realfishsam

Drift

Python's escrow amount normalizer coerces every validated amount through a Python float() and rejects the call if that coercion overflows to inf. TypeScript's equivalent never coerces string or bigint input to a JS number at all — it validates decimal format/precision only and forwards the value as a string (or stringified bigint), so it has no magnitude ceiling for those input types.

TypeScript SDK

sdks/typescript/pmxt/escrow.ts:84-104 (normalizeUsdcAmount):

function normalizeUsdcAmount(value: number | string | bigint, field: string = "amount"): number | string {
    if (typeof value === "bigint") {
        if (value <= 0n) { throw new ValidationError(...); }
        return value.toString();
    }
    if (typeof value === "number") {
        if (!Number.isFinite(value)) { throw new ValidationError(...); }
        validateUsdcDecimal(String(value), field);
        return value;
    }
    if (typeof value === "string") {
        const trimmed = value.trim();
        validateUsdcDecimal(trimmed, field);   // only checks decimal format + ≤6 fractional digits
        return trimmed;                         // kept as string, never coerced to a float — no magnitude check
    }
    ...
}

Python SDK

sdks/python/pmxt/escrow.py:60-83 (_usdc_amount):

amount = Decimal(str(value))
...
scaled = amount * _USDC_SCALE
if scaled != scaled.to_integral_value():
    raise ValidationError(f"{field} precision exceeds 6 decimals...", field=field)

json_amount = float(amount)
if not math.isfinite(json_amount):
    raise ValidationError(f"{field} is too large to encode as JSON", field=field)
return json_amount

Called from both deposit_tx (escrow.py:129) and withdraw_tx's request branch (escrow.py:156).

Expected

Both languages should apply the same magnitude ceiling (or neither should). Since TypeScript's deposit_tx/withdraw_tx accept number | string | bigint and only the number path is bounded by Number.isFinite, either TypeScript should reject a string/bigint amount whose magnitude would overflow the wire representation Python uses, or Python should stop coercing through float() for the JSON body (matching TypeScript's string-passthrough for large values) so the same input is accepted or rejected consistently in both SDKs.

Impact

A 6-decimal-valid but astronomically large amount (e.g. a caller accidentally passing a value with far too many integer digits, such as through a unit-conversion bug) is silently accepted and forwarded to the server by TypeScript's depositTx/withdrawTx, while Python's deposit_tx/withdraw_tx reject the identical value outright with ValidationError. This is a narrow edge case in practice (requires a value beyond ~1.8e308) but is a real, verified behavioral divergence distinct from the already-tracked precision-loss issue (#1436, which covers float() silently losing precision on in-range values, not the overflow rejection path).


Found by automated SDK cross-language drift audit

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions