From 62db625dbe181cebc8e767dbe85e143bb0f7a8d2 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 10 Jun 2020 13:40:08 +0100 Subject: [PATCH 1/5] Add lint-fix script to @cosmjs/math --- packages/math/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/math/package.json b/packages/math/package.json index fb4cec28..b3125f61 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -25,6 +25,7 @@ "scripts": { "docs": "shx rm -rf docs && typedoc --options typedoc.js", "lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"", + "lint-fix": "eslint --max-warnings 0 \"**/*.{js,ts}\" --fix", "format": "prettier --write --loglevel warn \"./src/**/*.ts\"", "format-text": "prettier --write --prose-wrap always --print-width 80 \"./*.md\"", "test-node": "node jasmine-testrunner.js", From 8fbb7d4bccdc104e55d1c29293dc08ef4146ef01 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 10 Jun 2020 13:40:42 +0100 Subject: [PATCH 2/5] Add comparison methods to Decimal --- packages/math/src/decimal.ts | 25 +++++++++++++++++++++++++ packages/math/types/decimal.d.ts | 6 ++++++ 2 files changed, 31 insertions(+) diff --git a/packages/math/src/decimal.ts b/packages/math/src/decimal.ts index e126e4ed..869401fb 100644 --- a/packages/math/src/decimal.ts +++ b/packages/math/src/decimal.ts @@ -118,4 +118,29 @@ export class Decimal { const sum = this.data.atomics.add(new BN(b.atomics)); return new Decimal(sum.toString(), this.fractionalDigits); } + + public compare(b: Decimal): number { + if (this.fractionalDigits !== b.fractionalDigits) throw new Error("Fractional digits do not match"); + return this.data.atomics.cmp(new BN(b.atomics)); + } + + public equals(b: Decimal): boolean { + return this.compare(b) === 0; + } + + public isLessThan(b: Decimal): boolean { + return this.compare(b) < 0; + } + + public isLessThanOrEqual(b: Decimal): boolean { + return this.compare(b) <= 0; + } + + public isGreaterThan(b: Decimal): boolean { + return this.compare(b) > 0; + } + + public isGreaterThanOrEqual(b: Decimal): boolean { + return this.compare(b) >= 0; + } } diff --git a/packages/math/types/decimal.d.ts b/packages/math/types/decimal.d.ts index 9d65be9d..cae891b1 100644 --- a/packages/math/types/decimal.d.ts +++ b/packages/math/types/decimal.d.ts @@ -23,4 +23,10 @@ export declare class Decimal { * Both values need to have the same fractional digits. */ plus(b: Decimal): Decimal; + compare(b: Decimal): number; + equals(b: Decimal): boolean; + isLessThan(b: Decimal): boolean; + isLessThanOrEqual(b: Decimal): boolean; + isGreaterThan(b: Decimal): boolean; + isGreaterThanOrEqual(b: Decimal): boolean; } From 0ae83b065766f9f86a4591ac02f96fc62709b719 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 10 Jun 2020 13:40:57 +0100 Subject: [PATCH 3/5] Add tests for Decimal comparison methods --- packages/math/src/decimal.spec.ts | 210 ++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) diff --git a/packages/math/src/decimal.spec.ts b/packages/math/src/decimal.spec.ts index f0d6f1d3..b0e1803b 100644 --- a/packages/math/src/decimal.spec.ts +++ b/packages/math/src/decimal.spec.ts @@ -210,4 +210,214 @@ describe("Decimal", () => { expect(() => zero.plus(Decimal.fromUserInput("1", 7))).toThrowError(/do not match/i); }); }); + + describe("equals", () => { + it("returns correct values", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(zero.equals(Decimal.fromUserInput("0", 5))).toEqual(true); + expect(zero.equals(Decimal.fromUserInput("1", 5))).toEqual(false); + expect(zero.equals(Decimal.fromUserInput("2", 5))).toEqual(false); + expect(zero.equals(Decimal.fromUserInput("2.8", 5))).toEqual(false); + expect(zero.equals(Decimal.fromUserInput("0.12345", 5))).toEqual(false); + + const one = Decimal.fromUserInput("1", 5); + expect(one.equals(Decimal.fromUserInput("0", 5))).toEqual(false); + expect(one.equals(Decimal.fromUserInput("1", 5))).toEqual(true); + expect(one.equals(Decimal.fromUserInput("2", 5))).toEqual(false); + expect(one.equals(Decimal.fromUserInput("2.8", 5))).toEqual(false); + expect(one.equals(Decimal.fromUserInput("0.12345", 5))).toEqual(false); + + const oneDotFive = Decimal.fromUserInput("1.5", 5); + expect(oneDotFive.equals(Decimal.fromUserInput("0", 5))).toEqual(false); + expect(oneDotFive.equals(Decimal.fromUserInput("1", 5))).toEqual(false); + expect(oneDotFive.equals(Decimal.fromUserInput("1.5", 5))).toEqual(true); + expect(oneDotFive.equals(Decimal.fromUserInput("2", 5))).toEqual(false); + expect(oneDotFive.equals(Decimal.fromUserInput("2.8", 5))).toEqual(false); + expect(oneDotFive.equals(Decimal.fromUserInput("0.12345", 5))).toEqual(false); + + // original value remain unchanged + expect(zero.toString()).toEqual("0"); + expect(one.toString()).toEqual("1"); + expect(oneDotFive.toString()).toEqual("1.5"); + }); + + it("throws for different fractional digits", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(() => zero.equals(Decimal.fromUserInput("1", 1))).toThrowError(/do not match/i); + expect(() => zero.equals(Decimal.fromUserInput("1", 2))).toThrowError(/do not match/i); + expect(() => zero.equals(Decimal.fromUserInput("1", 3))).toThrowError(/do not match/i); + expect(() => zero.equals(Decimal.fromUserInput("1", 4))).toThrowError(/do not match/i); + + expect(() => zero.equals(Decimal.fromUserInput("1", 6))).toThrowError(/do not match/i); + expect(() => zero.equals(Decimal.fromUserInput("1", 7))).toThrowError(/do not match/i); + }); + }); + + describe("isLessThan", () => { + it("returns correct values", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(zero.isLessThan(Decimal.fromUserInput("0", 5))).toEqual(false); + expect(zero.isLessThan(Decimal.fromUserInput("1", 5))).toEqual(true); + expect(zero.isLessThan(Decimal.fromUserInput("2", 5))).toEqual(true); + expect(zero.isLessThan(Decimal.fromUserInput("2.8", 5))).toEqual(true); + expect(zero.isLessThan(Decimal.fromUserInput("0.12345", 5))).toEqual(true); + + const one = Decimal.fromUserInput("1", 5); + expect(one.isLessThan(Decimal.fromUserInput("0", 5))).toEqual(false); + expect(one.isLessThan(Decimal.fromUserInput("1", 5))).toEqual(false); + expect(one.isLessThan(Decimal.fromUserInput("2", 5))).toEqual(true); + expect(one.isLessThan(Decimal.fromUserInput("2.8", 5))).toEqual(true); + expect(one.isLessThan(Decimal.fromUserInput("0.12345", 5))).toEqual(false); + + const oneDotFive = Decimal.fromUserInput("1.5", 5); + expect(oneDotFive.isLessThan(Decimal.fromUserInput("0", 5))).toEqual(false); + expect(oneDotFive.isLessThan(Decimal.fromUserInput("1", 5))).toEqual(false); + expect(oneDotFive.isLessThan(Decimal.fromUserInput("1.5", 5))).toEqual(false); + expect(oneDotFive.isLessThan(Decimal.fromUserInput("2", 5))).toEqual(true); + expect(oneDotFive.isLessThan(Decimal.fromUserInput("2.8", 5))).toEqual(true); + expect(oneDotFive.isLessThan(Decimal.fromUserInput("0.12345", 5))).toEqual(false); + + // original value remain unchanged + expect(zero.toString()).toEqual("0"); + expect(one.toString()).toEqual("1"); + expect(oneDotFive.toString()).toEqual("1.5"); + }); + + it("throws for different fractional digits", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(() => zero.isLessThan(Decimal.fromUserInput("1", 1))).toThrowError(/do not match/i); + expect(() => zero.isLessThan(Decimal.fromUserInput("1", 2))).toThrowError(/do not match/i); + expect(() => zero.isLessThan(Decimal.fromUserInput("1", 3))).toThrowError(/do not match/i); + expect(() => zero.isLessThan(Decimal.fromUserInput("1", 4))).toThrowError(/do not match/i); + + expect(() => zero.isLessThan(Decimal.fromUserInput("1", 6))).toThrowError(/do not match/i); + expect(() => zero.isLessThan(Decimal.fromUserInput("1", 7))).toThrowError(/do not match/i); + }); + }); + + describe("isLessThanOrEqual", () => { + it("returns correct values", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(zero.isLessThanOrEqual(Decimal.fromUserInput("0", 5))).toEqual(true); + expect(zero.isLessThanOrEqual(Decimal.fromUserInput("1", 5))).toEqual(true); + expect(zero.isLessThanOrEqual(Decimal.fromUserInput("2", 5))).toEqual(true); + expect(zero.isLessThanOrEqual(Decimal.fromUserInput("2.8", 5))).toEqual(true); + expect(zero.isLessThanOrEqual(Decimal.fromUserInput("0.12345", 5))).toEqual(true); + + const one = Decimal.fromUserInput("1", 5); + expect(one.isLessThanOrEqual(Decimal.fromUserInput("0", 5))).toEqual(false); + expect(one.isLessThanOrEqual(Decimal.fromUserInput("1", 5))).toEqual(true); + expect(one.isLessThanOrEqual(Decimal.fromUserInput("2", 5))).toEqual(true); + expect(one.isLessThanOrEqual(Decimal.fromUserInput("2.8", 5))).toEqual(true); + expect(one.isLessThanOrEqual(Decimal.fromUserInput("0.12345", 5))).toEqual(false); + + const oneDotFive = Decimal.fromUserInput("1.5", 5); + expect(oneDotFive.isLessThanOrEqual(Decimal.fromUserInput("0", 5))).toEqual(false); + expect(oneDotFive.isLessThanOrEqual(Decimal.fromUserInput("1", 5))).toEqual(false); + expect(oneDotFive.isLessThanOrEqual(Decimal.fromUserInput("1.5", 5))).toEqual(true); + expect(oneDotFive.isLessThanOrEqual(Decimal.fromUserInput("2", 5))).toEqual(true); + expect(oneDotFive.isLessThanOrEqual(Decimal.fromUserInput("2.8", 5))).toEqual(true); + expect(oneDotFive.isLessThanOrEqual(Decimal.fromUserInput("0.12345", 5))).toEqual(false); + + // original value remain unchanged + expect(zero.toString()).toEqual("0"); + expect(one.toString()).toEqual("1"); + expect(oneDotFive.toString()).toEqual("1.5"); + }); + + it("throws for different fractional digits", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(() => zero.isLessThanOrEqual(Decimal.fromUserInput("1", 1))).toThrowError(/do not match/i); + expect(() => zero.isLessThanOrEqual(Decimal.fromUserInput("1", 2))).toThrowError(/do not match/i); + expect(() => zero.isLessThanOrEqual(Decimal.fromUserInput("1", 3))).toThrowError(/do not match/i); + expect(() => zero.isLessThanOrEqual(Decimal.fromUserInput("1", 4))).toThrowError(/do not match/i); + + expect(() => zero.isLessThanOrEqual(Decimal.fromUserInput("1", 6))).toThrowError(/do not match/i); + expect(() => zero.isLessThanOrEqual(Decimal.fromUserInput("1", 7))).toThrowError(/do not match/i); + }); + }); + + describe("isGreaterThan", () => { + it("returns correct values", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(zero.isGreaterThan(Decimal.fromUserInput("0", 5))).toEqual(false); + expect(zero.isGreaterThan(Decimal.fromUserInput("1", 5))).toEqual(false); + expect(zero.isGreaterThan(Decimal.fromUserInput("2", 5))).toEqual(false); + expect(zero.isGreaterThan(Decimal.fromUserInput("2.8", 5))).toEqual(false); + expect(zero.isGreaterThan(Decimal.fromUserInput("0.12345", 5))).toEqual(false); + + const one = Decimal.fromUserInput("1", 5); + expect(one.isGreaterThan(Decimal.fromUserInput("0", 5))).toEqual(true); + expect(one.isGreaterThan(Decimal.fromUserInput("1", 5))).toEqual(false); + expect(one.isGreaterThan(Decimal.fromUserInput("2", 5))).toEqual(false); + expect(one.isGreaterThan(Decimal.fromUserInput("2.8", 5))).toEqual(false); + expect(one.isGreaterThan(Decimal.fromUserInput("0.12345", 5))).toEqual(true); + + const oneDotFive = Decimal.fromUserInput("1.5", 5); + expect(oneDotFive.isGreaterThan(Decimal.fromUserInput("0", 5))).toEqual(true); + expect(oneDotFive.isGreaterThan(Decimal.fromUserInput("1", 5))).toEqual(true); + expect(oneDotFive.isGreaterThan(Decimal.fromUserInput("1.5", 5))).toEqual(false); + expect(oneDotFive.isGreaterThan(Decimal.fromUserInput("2", 5))).toEqual(false); + expect(oneDotFive.isGreaterThan(Decimal.fromUserInput("2.8", 5))).toEqual(false); + expect(oneDotFive.isGreaterThan(Decimal.fromUserInput("0.12345", 5))).toEqual(true); + + // original value remain unchanged + expect(zero.toString()).toEqual("0"); + expect(one.toString()).toEqual("1"); + expect(oneDotFive.toString()).toEqual("1.5"); + }); + + it("throws for different fractional digits", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(() => zero.isGreaterThan(Decimal.fromUserInput("1", 1))).toThrowError(/do not match/i); + expect(() => zero.isGreaterThan(Decimal.fromUserInput("1", 2))).toThrowError(/do not match/i); + expect(() => zero.isGreaterThan(Decimal.fromUserInput("1", 3))).toThrowError(/do not match/i); + expect(() => zero.isGreaterThan(Decimal.fromUserInput("1", 4))).toThrowError(/do not match/i); + + expect(() => zero.isGreaterThan(Decimal.fromUserInput("1", 6))).toThrowError(/do not match/i); + expect(() => zero.isGreaterThan(Decimal.fromUserInput("1", 7))).toThrowError(/do not match/i); + }); + }); + + describe("isGreaterThanOrEqual", () => { + it("returns correct values", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(zero.isGreaterThanOrEqual(Decimal.fromUserInput("0", 5))).toEqual(true); + expect(zero.isGreaterThanOrEqual(Decimal.fromUserInput("1", 5))).toEqual(false); + expect(zero.isGreaterThanOrEqual(Decimal.fromUserInput("2", 5))).toEqual(false); + expect(zero.isGreaterThanOrEqual(Decimal.fromUserInput("2.8", 5))).toEqual(false); + expect(zero.isGreaterThanOrEqual(Decimal.fromUserInput("0.12345", 5))).toEqual(false); + + const one = Decimal.fromUserInput("1", 5); + expect(one.isGreaterThanOrEqual(Decimal.fromUserInput("0", 5))).toEqual(true); + expect(one.isGreaterThanOrEqual(Decimal.fromUserInput("1", 5))).toEqual(true); + expect(one.isGreaterThanOrEqual(Decimal.fromUserInput("2", 5))).toEqual(false); + expect(one.isGreaterThanOrEqual(Decimal.fromUserInput("2.8", 5))).toEqual(false); + expect(one.isGreaterThanOrEqual(Decimal.fromUserInput("0.12345", 5))).toEqual(true); + + const oneDotFive = Decimal.fromUserInput("1.5", 5); + expect(oneDotFive.isGreaterThanOrEqual(Decimal.fromUserInput("0", 5))).toEqual(true); + expect(oneDotFive.isGreaterThanOrEqual(Decimal.fromUserInput("1", 5))).toEqual(true); + expect(oneDotFive.isGreaterThanOrEqual(Decimal.fromUserInput("1.5", 5))).toEqual(true); + expect(oneDotFive.isGreaterThanOrEqual(Decimal.fromUserInput("2", 5))).toEqual(false); + expect(oneDotFive.isGreaterThanOrEqual(Decimal.fromUserInput("2.8", 5))).toEqual(false); + expect(oneDotFive.isGreaterThanOrEqual(Decimal.fromUserInput("0.12345", 5))).toEqual(true); + + // original value remain unchanged + expect(zero.toString()).toEqual("0"); + expect(one.toString()).toEqual("1"); + expect(oneDotFive.toString()).toEqual("1.5"); + }); + + it("throws for different fractional digits", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(() => zero.isGreaterThanOrEqual(Decimal.fromUserInput("1", 1))).toThrowError(/do not match/i); + expect(() => zero.isGreaterThanOrEqual(Decimal.fromUserInput("1", 2))).toThrowError(/do not match/i); + expect(() => zero.isGreaterThanOrEqual(Decimal.fromUserInput("1", 3))).toThrowError(/do not match/i); + expect(() => zero.isGreaterThanOrEqual(Decimal.fromUserInput("1", 4))).toThrowError(/do not match/i); + + expect(() => zero.isGreaterThanOrEqual(Decimal.fromUserInput("1", 6))).toThrowError(/do not match/i); + expect(() => zero.isGreaterThanOrEqual(Decimal.fromUserInput("1", 7))).toThrowError(/do not match/i); + }); + }); }); From d6d6d3fee5cb84e7fba3cd2302e32eab985a3d42 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 10 Jun 2020 15:24:00 +0100 Subject: [PATCH 4/5] Use decimal comparison in faucet token manager --- packages/faucet/src/tokenmanager.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/faucet/src/tokenmanager.ts b/packages/faucet/src/tokenmanager.ts index 88996eee..9d09ad76 100644 --- a/packages/faucet/src/tokenmanager.ts +++ b/packages/faucet/src/tokenmanager.ts @@ -54,9 +54,7 @@ export class TokenManager { const thresholdAmount = this.refillThreshold(tickerSymbol); const threshold = Decimal.fromAtomics(thresholdAmount.amount, meta.fractionalDigits); - // TODO: perform < operation on Decimal type directly - // https://github.com/iov-one/iov-core/issues/1375 - return balance.toFloatApproximation() < threshold.toFloatApproximation(); + return balance.isLessThan(threshold); } private getTokenMeta(tickerSymbol: string): BankTokenMeta { From 9ac1994cb66487f8d734cc209b407a64035b1a8c Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 10 Jun 2020 15:33:42 +0100 Subject: [PATCH 5/5] Add static Decimal.compare method --- packages/math/src/decimal.ts | 20 ++++++++++---------- packages/math/types/decimal.d.ts | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/math/src/decimal.ts b/packages/math/src/decimal.ts index 869401fb..ffedd0ed 100644 --- a/packages/math/src/decimal.ts +++ b/packages/math/src/decimal.ts @@ -64,6 +64,11 @@ export class Decimal { } } + public static compare(a: Decimal, b: Decimal): number { + if (a.fractionalDigits !== b.fractionalDigits) throw new Error("Fractional digits do not match"); + return a.data.atomics.cmp(new BN(b.atomics)); + } + public get atomics(): string { return this.data.atomics.toString(); } @@ -119,28 +124,23 @@ export class Decimal { return new Decimal(sum.toString(), this.fractionalDigits); } - public compare(b: Decimal): number { - if (this.fractionalDigits !== b.fractionalDigits) throw new Error("Fractional digits do not match"); - return this.data.atomics.cmp(new BN(b.atomics)); - } - public equals(b: Decimal): boolean { - return this.compare(b) === 0; + return Decimal.compare(this, b) === 0; } public isLessThan(b: Decimal): boolean { - return this.compare(b) < 0; + return Decimal.compare(this, b) < 0; } public isLessThanOrEqual(b: Decimal): boolean { - return this.compare(b) <= 0; + return Decimal.compare(this, b) <= 0; } public isGreaterThan(b: Decimal): boolean { - return this.compare(b) > 0; + return Decimal.compare(this, b) > 0; } public isGreaterThanOrEqual(b: Decimal): boolean { - return this.compare(b) >= 0; + return Decimal.compare(this, b) >= 0; } } diff --git a/packages/math/types/decimal.d.ts b/packages/math/types/decimal.d.ts index cae891b1..0642fa07 100644 --- a/packages/math/types/decimal.d.ts +++ b/packages/math/types/decimal.d.ts @@ -7,6 +7,7 @@ export declare class Decimal { static fromUserInput(input: string, fractionalDigits: number): Decimal; static fromAtomics(atomics: string, fractionalDigits: number): Decimal; private static verifyFractionalDigits; + static compare(a: Decimal, b: Decimal): number; get atomics(): string; get fractionalDigits(): number; private readonly data; @@ -23,7 +24,6 @@ export declare class Decimal { * Both values need to have the same fractional digits. */ plus(b: Decimal): Decimal; - compare(b: Decimal): number; equals(b: Decimal): boolean; isLessThan(b: Decimal): boolean; isLessThanOrEqual(b: Decimal): boolean;