Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/tidy-bigints-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fleet-sdk/common": patch
---

Fix `decimalize()` and `undecimalize()` mutating the caller's options object: the resolved `decimals` and `decimalMark` defaults were assigned back into the passed object, so a caller's options came back with fields it never set (and a frozen options object threw a `TypeError`). The defaults are now resolved into local variables, leaving the caller's object untouched; outputs are unchanged.
27 changes: 27 additions & 0 deletions packages/common/src/utils/bigInt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ describe("decimalize()", () => {
const options = { decimals: 9 };
expect(decimalize(undecimalize("129.8379183", options), options)).toBe("129.8379183");
});

it("Should not mutate the caller's options object", () => {
const options = { decimals: 2 };
expect(decimalize(1763874n, options)).toBe("17638.74");
expect(options).toEqual({ decimals: 2 });
});

it("Should accept a frozen options object", () => {
const options = Object.freeze({ decimals: 2 });
expect(decimalize(1763874n, options)).toBe("17638.74");
});
});

describe("undecimalize()", () => {
Expand Down Expand Up @@ -115,6 +126,22 @@ describe("undecimalize()", () => {
undecimalize("12.23.1");
}).toThrow();
});

it("Should not mutate the caller's options object", () => {
const options = { decimals: 2 };
expect(undecimalize("1.23", options)).toBe(123n);
expect(options).toEqual({ decimals: 2 });
});

it("Should accept a frozen options object", () => {
const options = Object.freeze({ decimals: 2 });
expect(undecimalize("1.23", options)).toBe(123n);
});

it("Should accept frozen options relying on defaults", () => {
const options = Object.freeze({});
expect(undecimalize("1.23", options)).toBe(123n);
});
});

describe("ensureBigInt()", () => {
Expand Down
32 changes: 16 additions & 16 deletions packages/common/src/utils/bigInt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,11 @@ type ParsingOptions = {
export function undecimalize(input: string, options?: ParsingOptions | number): bigint {
if (!input) return _0n;

options = typeof options === "number" ? { decimals: options } : options;
if (isUndefined(options)) {
options = {};
}

options.decimals = options.decimals || 0;
options.decimalMark = options.decimalMark || ".";
const parsedOptions = typeof options === "number" ? { decimals: options } : options;
const decimals = parsedOptions?.decimals || 0;
const decimalMark = parsedOptions?.decimalMark || ".";

const fragments = input.split(options.decimalMark);
const fragments = input.split(decimalMark);
if (fragments.length > 2) {
throw new Error("Invalid numeric string.");
}
Expand All @@ -65,9 +61,9 @@ export function undecimalize(input: string, options?: ParsingOptions | number):
const negative = integer.startsWith("-") ? "-" : "";

if (!decimal) {
decimal = "0".repeat(options.decimals);
} else if (decimal.length < options.decimals) {
decimal = decimal.padEnd(options.decimals, "0");
decimal = "0".repeat(decimals);
} else if (decimal.length < decimals) {
decimal = decimal.padEnd(decimals, "0");
}

return BigInt(negative + _stripNonDigits(integer + decimal));
Expand Down Expand Up @@ -114,15 +110,19 @@ export function decimalize(value: Amount, options?: FormattingOptions | number):
return value.toString();
}

options = typeof options === "number" ? { decimals: options } : options;
options.decimals = options.decimals || 0;
options.decimalMark = options.decimalMark || ".";
const parsedOptions = typeof options === "number" ? { decimals: options } : options;
const decimals = parsedOptions.decimals || 0;
const decimalMark = parsedOptions.decimalMark || ".";

const pow = _10n ** BigInt(options.decimals);
const pow = _10n ** BigInt(decimals);
const integer = value / pow;
const decimal = value - integer * pow;

return buildFormattedDecimal(integer.toString(10), decimal.toString(10), options);
return buildFormattedDecimal(integer.toString(10), decimal.toString(10), {
...parsedOptions,
decimals,
decimalMark
});
}

/**
Expand Down