From 3e68ca02023df0ae417079e3e74fe23ec2476836 Mon Sep 17 00:00:00 2001 From: operagxoksana Date: Thu, 23 Jul 2026 10:34:39 +0000 Subject: [PATCH] fix: FixedPointNumber.eq() used multiplication instead of exponentiation When comparing two FixedPointNumber instances with different scales where this.scale > value.scale, eq() scaled the operand with value.value * BigInt(10) * (this.scale - value.scale) instead of value.value * BigInt(10) ** (this.scale - value.scale). lt/lte/gt/gte already use the correct ** form for both scale-mismatch branches; only this one branch of eq() used * by mistake, e.g.: new FixedPointNumber(500, 2).eq(new FixedPointNumber(5, 0)) // 5.00 vs 5 -> was false, should be true FixedPointNumber has no external consumers yet, so there is no actively exploited path in the current app - but it is a core comparison primitive in src/architecture and any future allowance/balance/permit-amount equality check built on top of it would silently misbehave for mismatched decimals. Adds src/architecture/__tests__/FixedPointNumber.test.ts covering both scale-mismatch directions symmetrically, since only one direction was broken. --- src/architecture/FixedPointNumber.ts | 2 +- .../__tests__/FixedPointNumber.test.ts | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 src/architecture/__tests__/FixedPointNumber.test.ts diff --git a/src/architecture/FixedPointNumber.ts b/src/architecture/FixedPointNumber.ts index a9a69d40d3..606fb70dd0 100644 --- a/src/architecture/FixedPointNumber.ts +++ b/src/architecture/FixedPointNumber.ts @@ -149,7 +149,7 @@ export class FixedPointNumber implements IFixedPointNumber { eq(value: FixedPointNumberValue): boolean { if (value instanceof FixedPointNumber) { if (this.scale > value.scale) { - return this.value === value.value * BigInt(10) * (this.scale - value.scale); + return this.value === value.value * BigInt(10) ** (this.scale - value.scale); } return this.value * BigInt(10) ** (value.scale - this.scale) === value.value; } diff --git a/src/architecture/__tests__/FixedPointNumber.test.ts b/src/architecture/__tests__/FixedPointNumber.test.ts new file mode 100644 index 0000000000..53bf2056d4 --- /dev/null +++ b/src/architecture/__tests__/FixedPointNumber.test.ts @@ -0,0 +1,35 @@ +import { FixedPointNumber } from '../FixedPointNumber'; + +describe('FixedPointNumber.eq', () => { + it('returns true for equal values with the same scale', () => { + expect(new FixedPointNumber(500, 2).eq(new FixedPointNumber(500, 2))).toBe(true); + }); + + it('returns true for equal values when this.scale > value.scale', () => { + expect(new FixedPointNumber(500, 2).eq(new FixedPointNumber(5, 0))).toBe(true); + }); + + it('returns true for equal values when this.scale < value.scale', () => { + expect(new FixedPointNumber(5, 0).eq(new FixedPointNumber(500, 2))).toBe(true); + }); + + it('returns false for different values when this.scale > value.scale', () => { + expect(new FixedPointNumber(501, 2).eq(new FixedPointNumber(5, 0))).toBe(false); + }); + + it('returns false for different values when this.scale < value.scale', () => { + expect(new FixedPointNumber(5, 0).eq(new FixedPointNumber(501, 2))).toBe(false); + }); + + it('stays consistent with lt/gt for the same scale-mismatched pair', () => { + const a = new FixedPointNumber(500, 2); + const b = new FixedPointNumber(5, 0); + expect(a.eq(b)).toBe(true); + expect(a.lt(b)).toBe(false); + expect(a.gt(b)).toBe(false); + }); + + it('handles a larger scale delta correctly', () => { + expect(new FixedPointNumber(5000000, 6).eq(new FixedPointNumber(5, 0))).toBe(true); + }); +});