diff --git a/packages/launchpad/src/encoding.spec.ts b/packages/launchpad/src/encoding.spec.ts index ccdb8a4a..1ac93c9c 100644 --- a/packages/launchpad/src/encoding.spec.ts +++ b/packages/launchpad/src/encoding.spec.ts @@ -1 +1,50 @@ -describe("encoding", () => {}); +import { sortedJsonStringify } from "./encoding"; + +describe("encoding", () => { + describe("sortedJsonStringify", () => { + it("leaves non-objects unchanged", () => { + expect(sortedJsonStringify(true)).toEqual(`true`); + expect(sortedJsonStringify(false)).toEqual(`false`); + expect(sortedJsonStringify("aabbccdd")).toEqual(`"aabbccdd"`); + expect(sortedJsonStringify(75)).toEqual(`75`); + expect(sortedJsonStringify(null)).toEqual(`null`); + expect(sortedJsonStringify([5, 6, 7, 1])).toEqual(`[5,6,7,1]`); + expect(sortedJsonStringify([5, ["a", "b"], true, null, 1])).toEqual(`[5,["a","b"],true,null,1]`); + }); + + it("sorts objects by key", () => { + // already sorted + expect(sortedJsonStringify({})).toEqual(`{}`); + expect(sortedJsonStringify({ a: 3 })).toEqual(`{"a":3}`); + expect(sortedJsonStringify({ a: 3, b: 2, c: 1 })).toEqual(`{"a":3,"b":2,"c":1}`); + + // not yet sorted + expect(sortedJsonStringify({ b: 2, a: 3, c: 1 })).toEqual(`{"a":3,"b":2,"c":1}`); + expect(sortedJsonStringify({ aaa: true, aa: true, a: true })).toEqual( + `{"a":true,"aa":true,"aaa":true}`, + ); + }); + + it("sorts nested objects", () => { + // already sorted + expect(sortedJsonStringify({ x: { y: { z: null } } })).toEqual(`{"x":{"y":{"z":null}}}`); + + // not yet sorted + expect(sortedJsonStringify({ b: { z: true, x: true, y: true }, a: true, c: true })).toEqual( + `{"a":true,"b":{"x":true,"y":true,"z":true},"c":true}`, + ); + }); + + it("sorts objects in arrays", () => { + // already sorted + expect(sortedJsonStringify([1, 2, { x: { y: { z: null } } }, 4])).toEqual( + `[1,2,{"x":{"y":{"z":null}}},4]`, + ); + + // not yet sorted + expect(sortedJsonStringify([1, 2, { b: { z: true, x: true, y: true }, a: true, c: true }, 4])).toEqual( + `[1,2,{"a":true,"b":{"x":true,"y":true,"z":true},"c":true},4]`, + ); + }); + }); +}); diff --git a/packages/launchpad/src/encoding.ts b/packages/launchpad/src/encoding.ts index 81bafa2f..6090cc79 100644 --- a/packages/launchpad/src/encoding.ts +++ b/packages/launchpad/src/encoding.ts @@ -5,24 +5,30 @@ import { Uint53 } from "@cosmjs/math"; import { Msg } from "./msgs"; import { StdFee } from "./types"; -function sortJson(json: any): any { - if (typeof json !== "object" || json === null) { - return json; +function sortedObject(obj: any): any { + if (typeof obj !== "object" || obj === null) { + return obj; } - if (Array.isArray(json)) { - return json.map(sortJson); + if (Array.isArray(obj)) { + return obj.map(sortedObject); } - const sortedKeys = Object.keys(json).sort(); + const sortedKeys = Object.keys(obj).sort(); const result = sortedKeys.reduce( (accumulator, key) => ({ ...accumulator, - [key]: sortJson(json[key]), + [key]: sortedObject(obj[key]), }), {}, ); return result; } +/** Returns a JSON string with objects sorted by key */ +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function sortedJsonStringify(obj: any): string { + return JSON.stringify(sortedObject(obj)); +} + /** * The document to be signed * @@ -56,6 +62,5 @@ export function makeSignDoc( } export function serializeSignDoc(signDoc: StdSignDoc): Uint8Array { - const sortedSignDoc = sortJson(signDoc); - return toUtf8(JSON.stringify(sortedSignDoc)); + return toUtf8(sortedJsonStringify(signDoc)); } diff --git a/packages/launchpad/types/encoding.d.ts b/packages/launchpad/types/encoding.d.ts index 05f8d8ce..0cad6f04 100644 --- a/packages/launchpad/types/encoding.d.ts +++ b/packages/launchpad/types/encoding.d.ts @@ -1,5 +1,7 @@ import { Msg } from "./msgs"; import { StdFee } from "./types"; +/** Returns a JSON string with objects sorted by key */ +export declare function sortedJsonStringify(obj: any): string; /** * The document to be signed *