Skip to content

Commit a7d7ffd

Browse files
authored
Merge pull request #702 from Jagadeeshftw/fix/682-u256-range
fix: reject out-of-range u256 values
2 parents 3d3afaf + afdaeb7 commit a7d7ffd

2 files changed

Lines changed: 33 additions & 5 deletions

File tree

src/utils/codec.test.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
toHexString,
66
formatTokenAmount,
77
DEFAULT_TOKEN_DECIMALS,
8+
U256_MAX,
9+
U256OverflowError,
810
} from "./codec";
911

1012
describe("parseU256", () => {
@@ -20,6 +22,19 @@ describe("parseU256", () => {
2022
expect(BigInt(r.low as never)).toBe(7n);
2123
expect(BigInt(r.high as never)).toBe(1n);
2224
});
25+
26+
it("accepts the maximum representable value", () => {
27+
expect(parseU256(U256_MAX.toString())).toBeTruthy();
28+
});
29+
30+
it("throws a distinct error for values outside the u256 range", () => {
31+
expect(() => parseU256((U256_MAX + 1n).toString())).toThrow(U256OverflowError);
32+
expect(() => parseU256("-1")).toThrow(U256OverflowError);
33+
});
34+
35+
it("keeps the existing native error path for malformed values", () => {
36+
expect(() => parseU256("not-a-number")).toThrow(SyntaxError);
37+
});
2338
});
2439

2540
describe("u256ToString", () => {
@@ -103,7 +118,7 @@ describe("formatTokenAmount", () => {
103118
it("formats the full u256 max without precision loss", () => {
104119
const u256Max = (1n << 256n) - 1n;
105120
expect(formatTokenAmount(u256Max)).toBe(
106-
"115792089237316195423570985008687907853269984665640564039457584007913129.639935"
121+
"115792089237316195423570985008687907853269984665640564039457584007913129.639935",
107122
);
108123
});
109124

@@ -124,7 +139,7 @@ describe("Fuzz testing round-trip (encode/decode)", () => {
124139
const MAX_U256 = (1n << 256n) - 1n;
125140
const MAX_U128 = (1n << 128n) - 1n;
126141
const iterations = 10000;
127-
142+
128143
for (let i = 0; i < iterations; i++) {
129144
let randomBigInt = 0n;
130145
// Generate random up to 256 bits by appending 32 random bits at a time
@@ -137,11 +152,11 @@ describe("Fuzz testing round-trip (encode/decode)", () => {
137152
if (i === 1) randomBigInt = MAX_U256;
138153
if (i === 2) randomBigInt = MAX_U128;
139154
if (i === 3) randomBigInt = 1n << 128n; // min value in high part
140-
155+
141156
const str = randomBigInt.toString();
142157
const parsed = parseU256(str);
143158
const back = u256ToString(parsed);
144-
159+
145160
expect(back).toBe(str);
146161
}
147162
});

src/utils/codec.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
11
import { cairo } from "starknet";
22

3+
export const U256_MAX = (1n << 256n) - 1n;
4+
5+
/** Thrown when a value cannot be represented by a Starknet u256. */
6+
export class U256OverflowError extends RangeError {
7+
constructor(value: bigint) {
8+
super(`u256 value out of range: ${value.toString()}`);
9+
this.name = "U256OverflowError";
10+
}
11+
}
12+
313
export function parseU256(value: string) {
414
const bn = BigInt(value);
15+
if (bn < 0n || bn > U256_MAX) {
16+
throw new U256OverflowError(bn);
17+
}
518
return cairo.uint256(bn);
619
}
720

@@ -67,7 +80,7 @@ export const DEFAULT_TOKEN_DECIMALS = 6;
6780
*/
6881
export function formatTokenAmount(
6982
raw: string | bigint,
70-
decimals: number = DEFAULT_TOKEN_DECIMALS
83+
decimals: number = DEFAULT_TOKEN_DECIMALS,
7184
): string {
7285
if (!Number.isInteger(decimals) || decimals < 0) {
7386
throw new RangeError(`decimals must be a non-negative integer, got ${decimals}`);

0 commit comments

Comments
 (0)