Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/primitives/Nonce/Nonce.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ describe("Nonce", () => {
});

it("creates large nonce", () => {
const large = 2n ** 200n;
// Max uint64 value
const large = 18446744073709551615n;
const nonce = Nonce.from(large);
expect(Nonce.toBigInt(nonce)).toBe(large);
});
Expand Down
65 changes: 61 additions & 4 deletions src/primitives/Nonce/from.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import * as Uint from "../Uint/index.js";
import {
IntegerOverflowError,
IntegerUnderflowError,
InvalidFormatError,
} from "../errors/index.js";

/** Maximum uint64 value (2^64 - 1) */
const MAX_UINT64 = 18446744073709551615n;

/**
* Create Nonce from number, bigint, or hex string
*
* @param {bigint | number | string} value - Value to convert
* @returns {import('./NonceType.js').NonceType} Nonce
* @throws {InvalidFormatError} If value is not a valid integer
* @throws {IntegerUnderflowError} If value is negative
* @throws {IntegerOverflowError} If value exceeds uint64 max (2^64-1)
*
* @example
* ```typescript
Expand All @@ -14,7 +24,54 @@ import * as Uint from "../Uint/index.js";
* ```
*/
export function from(value) {
return /** @type {import('./NonceType.js').NonceType} */ (
/** @type {unknown} */ (Uint.from(value))
);
/** @type {bigint} */
let bigintValue;

if (typeof value === "string") {
try {
bigintValue = BigInt(value);
} catch {
throw new InvalidFormatError(`Invalid Nonce string: ${value}`, {
code: "NONCE_INVALID_STRING",
value,
expected: "decimal or 0x-prefixed hex string",
docsPath: "/primitives/nonce#error-handling",
});
}
} else if (typeof value === "number") {
if (!Number.isInteger(value)) {
throw new InvalidFormatError(`Nonce must be an integer: ${value}`, {
code: "NONCE_NOT_INTEGER",
value,
expected: "integer value",
docsPath: "/primitives/nonce#error-handling",
});
}
bigintValue = BigInt(value);
} else {
bigintValue = value;
}

if (bigintValue < 0n) {
throw new IntegerUnderflowError(`Nonce cannot be negative: ${bigintValue}`, {
value: bigintValue,
min: 0n,
type: "nonce",
docsPath: "/primitives/nonce#error-handling",
});
}

if (bigintValue > MAX_UINT64) {
throw new IntegerOverflowError(
`Nonce exceeds maximum uint64 (2^64-1): ${bigintValue}`,
{
value: bigintValue,
max: MAX_UINT64,
type: "nonce",
docsPath: "/primitives/nonce#error-handling",
},
);
}

return /** @type {import('./NonceType.js').NonceType} */ (bigintValue);
}
49 changes: 49 additions & 0 deletions src/primitives/Nonce/from.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { describe, expect, it } from "vitest";
import { from } from "./from.js";
import {
IntegerOverflowError,
IntegerUnderflowError,
InvalidFormatError,
} from "../errors/index.js";

describe("Nonce.from", () => {
it("creates nonce from number", () => {
Expand All @@ -24,4 +29,48 @@ describe("Nonce.from", () => {
const nonce = from(0);
expect(nonce).toBe(0n);
});

it("handles max uint64 nonce", () => {
const maxUint64 = 18446744073709551615n;
const nonce = from(maxUint64);
expect(nonce).toBe(maxUint64);
});

describe("validation", () => {
it("rejects negative number", () => {
expect(() => from(-1)).toThrow(IntegerUnderflowError);
expect(() => from(-1)).toThrow("Nonce cannot be negative");
});

it("rejects negative bigint", () => {
expect(() => from(-1n)).toThrow(IntegerUnderflowError);
expect(() => from(-1n)).toThrow("Nonce cannot be negative");
});

it("rejects negative hex string", () => {
// "-0x1" is not valid BigInt syntax, throws InvalidFormat
expect(() => from("-0x1")).toThrow(InvalidFormatError);
// "-1" is valid BigInt syntax, throws Underflow
expect(() => from("-1")).toThrow(IntegerUnderflowError);
});

it("rejects large negative values", () => {
expect(() => from(-9999999999n)).toThrow(IntegerUnderflowError);
});

it("rejects value exceeding uint64 max", () => {
const overMax = 18446744073709551616n; // 2^64
expect(() => from(overMax)).toThrow(IntegerOverflowError);
expect(() => from(overMax)).toThrow("exceeds maximum uint64");
});

it("rejects non-integer number", () => {
expect(() => from(1.5)).toThrow(InvalidFormatError);
expect(() => from(1.5)).toThrow("must be an integer");
});

it("rejects invalid string", () => {
expect(() => from("not-a-number")).toThrow(InvalidFormatError);
});
});
});
3 changes: 2 additions & 1 deletion src/primitives/Nonce/toBigInt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ describe("Nonce.toBigInt", () => {
});

it("preserves large values", () => {
const original = 2n ** 200n;
// Max uint64 value
const original = 18446744073709551615n;
const nonce = Nonce.from(original);
const result = Nonce.toBigInt(nonce);
expect(result).toBe(original);
Expand Down
3 changes: 2 additions & 1 deletion src/primitives/Nonce/toNumber.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ describe("Nonce.toNumber", () => {
});

it("throws on very large value", () => {
const nonce = Nonce.from(2n ** 100n);
// Use a value larger than MAX_SAFE_INTEGER but within uint64 range
const nonce = Nonce.from(BigInt(Number.MAX_SAFE_INTEGER) + 1000n);
expect(() => Nonce.toNumber(nonce)).toThrow();
});
});
Expand Down
Loading