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)");