From dbcccf48732e0cb0af72745da0818863317225b9 Mon Sep 17 00:00:00 2001 From: 6figpsolseeker <6figpsolseeker@gmail.com> Date: Thu, 9 Apr 2026 17:52:45 -0400 Subject: [PATCH] fix(encode): validate string format before BigInt conversion The encode helpers (encU64, encI64, encU128, encI128) and encodePushOraclePrice all accepted `bigint | string` but converted strings via raw `BigInt(val)` without format validation. This throws an unhelpful `SyntaxError: Cannot convert X to a BigInt` on inputs containing whitespace, scientific notation (1e6), decimal points (1.5), hex prefixes (0x1A), or other non-decimal-integer strings. Since many instruction arg interfaces accept `bigint | string` (over 60 fields across the SDK), this is a systemic boundary issue. The fix adds a shared `safeBigInt()` helper in encode.ts that validates the string matches a strict decimal integer pattern before calling BigInt(). The regex is identical to the one already used in validation.ts for input validators, ensuring consistency. The helper produces a clear, actionable error message: encU64: string must be a decimal integer (got "1.5e6"). Example valid inputs: "0", "123", "-456" encodePushOraclePrice also had a direct `BigInt(args.priceE6)` call that bypassed the encode helpers. This is now inline-validated with the same regex pattern. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/abi/encode.ts | 26 ++++++++++++++++++++++---- src/abi/instructions.ts | 14 +++++++++++++- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/abi/encode.ts b/src/abi/encode.ts index 0bb3dee..acecad8 100644 --- a/src/abi/encode.ts +++ b/src/abi/encode.ts @@ -4,6 +4,24 @@ const U8_MAX = 255; const U16_MAX = 65_535; const U32_MAX = 4_294_967_295; +/** Decimal integer pattern: optional minus, then digits (no leading zeros except bare "0"). */ +const DECIMAL_INT_RE = /^-?(0|[1-9]\d*)$/; + +/** + * Convert a string to BigInt with format validation. + * Rejects whitespace, scientific notation, decimal points, hex prefixes, and + * other inputs that would cause BigInt() to throw an unhelpful SyntaxError. + */ +function safeBigInt(val: string, caller: string): bigint { + if (!DECIMAL_INT_RE.test(val)) { + throw new Error( + `${caller}: string must be a decimal integer (got ${JSON.stringify(val)}). ` + + `Example valid inputs: "0", "123", "-456"`, + ); + } + return BigInt(val); +} + /** * Encode u8 (1 byte) */ @@ -43,7 +61,7 @@ export function encU32(val: number): Uint8Array { * Input: bigint or string (decimal) */ export function encU64(val: bigint | string): Uint8Array { - const n = typeof val === "string" ? BigInt(val) : val; + const n = typeof val === "string" ? safeBigInt(val, "encU64") : val; if (n < 0n) throw new Error("encU64: value must be non-negative"); if (n > 0xffff_ffff_ffff_ffffn) throw new Error("encU64: value exceeds u64 max"); const buf = new Uint8Array(8); @@ -56,7 +74,7 @@ export function encU64(val: bigint | string): Uint8Array { * Input: bigint or string (decimal, may be negative) */ export function encI64(val: bigint | string): Uint8Array { - const n = typeof val === "string" ? BigInt(val) : val; + const n = typeof val === "string" ? safeBigInt(val, "encI64") : val; const min = -(1n << 63n); const max = (1n << 63n) - 1n; if (n < min || n > max) throw new Error("encI64: value out of range"); @@ -70,7 +88,7 @@ export function encI64(val: bigint | string): Uint8Array { * Input: bigint or string (decimal) */ export function encU128(val: bigint | string): Uint8Array { - const n = typeof val === "string" ? BigInt(val) : val; + const n = typeof val === "string" ? safeBigInt(val, "encU128") : val; if (n < 0n) throw new Error("encU128: value must be non-negative"); const max = (1n << 128n) - 1n; if (n > max) throw new Error("encU128: value exceeds u128 max"); @@ -88,7 +106,7 @@ export function encU128(val: bigint | string): Uint8Array { * Input: bigint or string (decimal, may be negative) */ export function encI128(val: bigint | string): Uint8Array { - const n = typeof val === "string" ? BigInt(val) : val; + const n = typeof val === "string" ? safeBigInt(val, "encI128") : val; const min = -(1n << 127n); const max = (1n << 127n) - 1n; if (n < min || n > max) throw new Error("encI128: value out of range"); diff --git a/src/abi/instructions.ts b/src/abi/instructions.ts index 30f0f6a..15ab6d0 100644 --- a/src/abi/instructions.ts +++ b/src/abi/instructions.ts @@ -564,7 +564,19 @@ export interface PushOraclePriceArgs { * @throws Error if price is 0 or exceeds MAX_ORACLE_PRICE */ export function encodePushOraclePrice(args: PushOraclePriceArgs): Uint8Array { - const price = typeof args.priceE6 === "string" ? BigInt(args.priceE6) : args.priceE6; + // Validate string format before BigInt conversion — rejects whitespace, + // scientific notation, decimal points, and hex prefixes that would cause + // an unhelpful SyntaxError from BigInt(). + const price = typeof args.priceE6 === "string" + ? (() => { + if (!/^-?(0|[1-9]\d*)$/.test(args.priceE6)) { + throw new Error( + `encodePushOraclePrice: priceE6 must be a decimal integer string (got ${JSON.stringify(args.priceE6)})`, + ); + } + return BigInt(args.priceE6); + })() + : args.priceE6; if (price === 0n) { throw new Error("encodePushOraclePrice: price cannot be zero (division by zero in engine)");