From 24e328077eb1ac8457a67845682829bacc792629 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 21 Jul 2021 09:59:38 +0200 Subject: [PATCH] Add Stargate-ready parseCoins --- CHANGELOG.md | 2 + packages/amino/src/coins.ts | 8 ++ packages/proto-signing/src/coins.spec.ts | 120 +++++++++++++++++++++++ packages/proto-signing/src/coins.ts | 24 +++++ packages/proto-signing/src/index.ts | 3 +- 5 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 packages/proto-signing/src/coins.spec.ts create mode 100644 packages/proto-signing/src/coins.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 229adeb82e..62517f86f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ and this project adheres to for Tendermint v0.33. - @cosmjs/tendermint-rpc: Exports relating to `Tendermint33Client` are now available under `tendermint33`. +- @cosmjs/proto-signing and @cosmjs/stargate: Create a Stargate-ready + `parseCoins` that replaces the `parseCoins` re-export from `@cosmjs/amino`. ### Changed diff --git a/packages/amino/src/coins.ts b/packages/amino/src/coins.ts index 1254f27f83..92123cf51e 100644 --- a/packages/amino/src/coins.ts +++ b/packages/amino/src/coins.ts @@ -21,6 +21,14 @@ export function coins(amount: number, denom: string): Coin[] { /** * Takes a coins list like "819966000ucosm,700000000ustake" and parses it. + * + * A Stargate-ready variant of this function is available via: + * + * ``` + * import { parseCoins } from "@cosmjs/proto-signing"; + * // or + * import { parseCoins } from "@cosmjs/stargate"; + * ``` */ export function parseCoins(input: string): Coin[] { return input diff --git a/packages/proto-signing/src/coins.spec.ts b/packages/proto-signing/src/coins.spec.ts new file mode 100644 index 0000000000..a41db6acdf --- /dev/null +++ b/packages/proto-signing/src/coins.spec.ts @@ -0,0 +1,120 @@ +import { parseCoins } from "./coins"; + +describe("coins", () => { + describe("parseCoins", () => { + it("works for empty", () => { + expect(parseCoins("")).toEqual([]); + }); + + it("works for one element", () => { + expect(parseCoins("7643ureef")).toEqual([ + { + amount: "7643", + denom: "ureef", + }, + ]); + }); + + it("works for various denoms", () => { + // very short (3) + expect(parseCoins("7643bar")).toEqual([ + { + amount: "7643", + denom: "bar", + }, + ]); + + // very long (128) + expect( + parseCoins( + "7643abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgh", + ), + ).toEqual([ + { + amount: "7643", + denom: + "abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgh", + }, + ]); + + // IBC denom (https://github.com/cosmos/cosmos-sdk/blob/v0.42.7/types/coin_test.go#L512-L519) + expect(parseCoins("7643ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2")).toEqual([ + { + amount: "7643", + denom: "ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2", + }, + ]); + }); + + it("works for two", () => { + expect(parseCoins("819966000ucosm,700000000ustake")).toEqual([ + { + amount: "819966000", + denom: "ucosm", + }, + { + amount: "700000000", + denom: "ustake", + }, + ]); + }); + + it("ignores empty elements", () => { + // start + expect(parseCoins(",819966000ucosm,700000000ustake")).toEqual([ + { + amount: "819966000", + denom: "ucosm", + }, + { + amount: "700000000", + denom: "ustake", + }, + ]); + // middle + expect(parseCoins("819966000ucosm,,700000000ustake")).toEqual([ + { + amount: "819966000", + denom: "ucosm", + }, + { + amount: "700000000", + denom: "ustake", + }, + ]); + // end + expect(parseCoins("819966000ucosm,700000000ustake,")).toEqual([ + { + amount: "819966000", + denom: "ucosm", + }, + { + amount: "700000000", + denom: "ustake", + }, + ]); + }); + + it("throws for invalid inputs", () => { + // denom missing + expect(() => parseCoins("3456")).toThrowError(/invalid coin string/i); + + // amount missing + expect(() => parseCoins("ucosm")).toThrowError(/invalid coin string/i); + + // denom starting with slash + expect(() => parseCoins("3456/ibc")).toThrowError(/invalid coin string/i); + + // denom too short + expect(() => parseCoins("3456a")).toThrowError(/invalid coin string/i); + expect(() => parseCoins("3456aa")).toThrowError(/invalid coin string/i); + + // denom too long + expect(() => + parseCoins( + "3456abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgha", + ), + ).toThrowError(/invalid coin string/i); + }); + }); +}); diff --git a/packages/proto-signing/src/coins.ts b/packages/proto-signing/src/coins.ts new file mode 100644 index 0000000000..f086677ec0 --- /dev/null +++ b/packages/proto-signing/src/coins.ts @@ -0,0 +1,24 @@ +import { Coin } from "@cosmjs/amino"; +import { Uint64 } from "@cosmjs/math"; + +/** + * Takes a coins list like "819966000ucosm,700000000ustake" and parses it. + * + * This is a Stargate ready version of parseCoins from @cosmjs/amino and @cosmjs/launchpad. + * It supports more denoms. + */ +export function parseCoins(input: string): Coin[] { + return input + .replace(/\s/g, "") + .split(",") + .filter(Boolean) + .map((part) => { + // Denom regex from Stargate (https://github.com/cosmos/cosmos-sdk/blob/v0.42.7/types/coin.go#L599-L601) + const match = part.match(/^([0-9]+)([a-zA-Z][a-zA-Z0-9/]{2,127})$/); + if (!match) throw new Error("Got an invalid coin string"); + return { + amount: Uint64.fromString(match[1]).toString(), + denom: match[2], + }; + }); +} diff --git a/packages/proto-signing/src/index.ts b/packages/proto-signing/src/index.ts index fe7ebe9fec..6c8bd801e7 100644 --- a/packages/proto-signing/src/index.ts +++ b/packages/proto-signing/src/index.ts @@ -1,6 +1,7 @@ // This type happens to be shared between Amino and Direct sign modes -export { Coin, coin, coins, parseCoins } from "@cosmjs/amino"; +export { Coin, coin, coins } from "@cosmjs/amino"; +export { parseCoins } from "./coins"; export { decodeTxRaw, DecodedTxRaw } from "./decode"; export { DecodeObject,