diff --git a/ts/package-lock.json b/ts/package-lock.json index 8dc8b2e0..4cb479d6 100644 --- a/ts/package-lock.json +++ b/ts/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "license": "Apache-2.0", "dependencies": { + "@cosmjs/math": "^0.33.0", "rxjs": "^7.8.1" }, "devDependencies": { @@ -662,6 +663,15 @@ "node": ">=0.1.90" } }, + "node_modules/@cosmjs/math": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.33.0.tgz", + "integrity": "sha512-B2uOgM12iuIhJWzGuAxGwO6zO+cI8Q4z7mVu7HgFrGJJTM1HtPTYgb55oMOuUN0OZ352MEEm5uAt8sA9jZQqbA==", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.2.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -2749,6 +2759,12 @@ "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", "dev": true }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "license": "MIT" + }, "node_modules/bottleneck": { "version": "2.19.5", "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", diff --git a/ts/package.json b/ts/package.json index 565f94f3..4f09ffde 100644 --- a/ts/package.json +++ b/ts/package.json @@ -41,6 +41,7 @@ ] }, "dependencies": { + "@cosmjs/math": "^0.33.0", "rxjs": "^7.8.1" }, "devDependencies": { diff --git a/ts/src/patch/cosmos/base/v1beta1/coin.spec.ts b/ts/src/patch/cosmos/base/v1beta1/coin.spec.ts index 7de851e7..f36c7f42 100644 --- a/ts/src/patch/cosmos/base/v1beta1/coin.spec.ts +++ b/ts/src/patch/cosmos/base/v1beta1/coin.spec.ts @@ -3,29 +3,62 @@ import { Reader } from "protobufjs/minimal"; import * as coin from "./coin"; describe("DecCoin", () => { - describe("prototype.decode", () => { - it("should properly decode whole amount", () => { - const encodedCoin = coin.DecCoin.encode({ - $type: "cosmos.base.v1beta1.DecCoin", - denom: "", - amount: "1000", - }).finish(); - const reader = new Reader(encodedCoin); - const result = coin.DecCoin.decode(reader); + // @see https://github.com/cosmos/cosmos-sdk/blob/main/math/testdata/decimals.json + // import('@cosmjs/math').Decimal supports only non-negative decimals + it.each([ + ["0", "0"], + ["1", "1"], + ["12", "12"], + ["123", "123"], + ["1234", "1'234"], + ["01234", "1234"], + [".1234", "0.1234"], + ["0.1", "0.1"], + ["0.01", "0.01"], + ["0.001", "0.001"], + ["0.0001", "0.0001"], + ["0.00001", "0.00001"], + ["0.000001", "0.000001"], + ["0.0000001", "0.0000001"], + ["0.00000001", "0.00000001"], + ["0.000000001", "0.000000001"], + ["0.0000000001", "0.0000000001"], + ["0.00000000001", "0.00000000001"], + ["0.000000000001", "0.000000000001"], + ["0.0000000000001", "0.0000000000001"], + ["0.00000000000001", "0.00000000000001"], + ["0.000000000000001", "0.000000000000001"], + ["0.0000000000000001", "0.0000000000000001"], + ["0.00000000000000001", "0.00000000000000001"], + ["0.000000000000000001", "0.000000000000000001"], + ["0.100000000000000000", "0.1"], + ["0.010000000000000000", "0.01"], + ["0.001000000000000000", "0.001"], + ["0.000100000000000000", "0.0001"], + ["0.000010000000000000", "0.00001"], + ["0.000001000000000000", "0.000001"], + ["0.000000100000000000", "0.0000001"], + ["0.000000010000000000", "0.00000001"], + ["0.000000001000000000", "0.000000001"], + ["0.000000000100000000", "0.0000000001"], + ["0.000000000010000000", "0.00000000001"], + ["0.000000000001000000", "0.000000000001"], + ["0.000000000000100000", "0.0000000000001"], + ["0.000000000000010000", "0.00000000000001"], + ["0.000000000000001000", "0.000000000000001"], + ["0.000000000000000100", "0.0000000000000001"], + ["0.000000000000000010", "0.00000000000000001"], + ["0.000000000000000001", "0.000000000000000001"], + [Number.MAX_SAFE_INTEGER.toString(), Number.MAX_SAFE_INTEGER.toString()], + ])("should properly decode %s", (amount, expected) => { + const encodedCoin = coin.DecCoin.encode({ + $type: "cosmos.base.v1beta1.DecCoin", + denom: "", + amount, + }).finish(); + const reader = new Reader(encodedCoin); + const result = coin.DecCoin.decode(reader); - expect(result.amount).toEqual("1000.00000000000000"); - }); - - it("should properly decode amount with a floating point", () => { - const encodedCoin = coin.DecCoin.encode({ - $type: "cosmos.base.v1beta1.DecCoin", - denom: "", - amount: "1000.5", - }).finish(); - const reader = new Reader(encodedCoin); - const result = coin.DecCoin.decode(reader); - - expect(result.amount).toEqual("1000.50000000000000"); - }); + expect(result.amount).toEqual(expected.replace(/'/g, "")); }); }); diff --git a/ts/src/patch/cosmos/base/v1beta1/coin.ts b/ts/src/patch/cosmos/base/v1beta1/coin.ts index edf3cca6..54e24e25 100644 --- a/ts/src/patch/cosmos/base/v1beta1/coin.ts +++ b/ts/src/patch/cosmos/base/v1beta1/coin.ts @@ -1,3 +1,4 @@ +import { Decimal } from "@cosmjs/math"; import * as minimal from "protobufjs/minimal"; import { Reader } from "protobufjs/minimal"; @@ -5,16 +6,17 @@ import * as coin from "../../../../generated/cosmos/base/v1beta1/coin.original"; import { DecCoin } from "../../../../generated/cosmos/base/v1beta1/coin.original"; const originalEncode = coin.DecCoin.encode; +/** + * Taken from cosmos-sdk + * @see https://github.com/cosmos/cosmos-sdk/blob/25b14c3caa2ecdc99840dbb88fdb3a2d8ac02158/math/dec.go#L21 + */ +const PRECISION = 18; coin.DecCoin.encode = function encode( message: DecCoin, writer: minimal.Writer = minimal.Writer.create(), ): minimal.Writer { - const { amount } = message; - const parts = amount.includes(".") - ? message.amount.split(".") - : [message.amount, ""]; - message.amount = `${parts[0]}${parts[1].padEnd(18, "0")}`; + message.amount = Decimal.fromUserInput(message.amount, PRECISION).atomics; return originalEncode.apply(this, [message, writer]); }; @@ -26,7 +28,7 @@ coin.DecCoin.decode = function decode( length?: number, ): coin.DecCoin { const message = originalDecode.apply(this, [input, length]); - message.amount = (parseInt(message.amount) / 10 ** 18).toPrecision(18); + message.amount = Decimal.fromAtomics(message.amount, PRECISION).toString(); return message; };