diff --git a/packages/cosmwasm-stargate/src/modules/wasm/messages.spec.ts b/packages/cosmwasm-stargate/src/modules/wasm/messages.spec.ts new file mode 100644 index 0000000000..286646007c --- /dev/null +++ b/packages/cosmwasm-stargate/src/modules/wasm/messages.spec.ts @@ -0,0 +1,156 @@ +import { toUtf8 } from "@cosmjs/encoding"; +import Long from "long"; + +import { importWasmMessages } from "./messages"; + +describe("messages", () => { + describe("importWasmMessages", () => { + it("works for MsgExecuteContract", () => { + // wasmd tx wasm execute wasm1hsm76p4ahyhl5yh3ve9ur49r5kemhp2r93f89d '{"contract":"specific"}' --amount 123ucosm --from wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd --chain-id test123 --offline --generate-only | jq .body.messages -c + const msgs = importWasmMessages( + `[{"@type":"/cosmwasm.wasm.v1.MsgExecuteContract","sender":"wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd","contract":"wasm1hsm76p4ahyhl5yh3ve9ur49r5kemhp2r93f89d","msg":{"contract":"specific"},"funds":[{"denom":"ucosm","amount":"123"}]}]`, + ); + expect(msgs).toEqual([ + { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: { + sender: "wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd", + contract: "wasm1hsm76p4ahyhl5yh3ve9ur49r5kemhp2r93f89d", + msg: toUtf8(`{"contract":"specific"}`), + funds: [ + { + denom: "ucosm", + amount: "123", + }, + ], + }, + }, + ]); + }); + + it("works for two MsgExecuteContract", () => { + // same message as above, but 2x (with different amounts) + const msgs = importWasmMessages( + `[{"@type":"/cosmwasm.wasm.v1.MsgExecuteContract","sender":"wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd","contract":"wasm1hsm76p4ahyhl5yh3ve9ur49r5kemhp2r93f89d","msg":{"contract":"specific"},"funds":[{"denom":"ucosm","amount":"123"}]},{"@type":"/cosmwasm.wasm.v1.MsgExecuteContract","sender":"wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd","contract":"wasm1hsm76p4ahyhl5yh3ve9ur49r5kemhp2r93f89d","msg":{"contract":"specific"},"funds":[{"denom":"ucosm","amount":"456"}]}]`, + ); + expect(msgs).toEqual([ + { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: { + sender: "wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd", + contract: "wasm1hsm76p4ahyhl5yh3ve9ur49r5kemhp2r93f89d", + msg: toUtf8(`{"contract":"specific"}`), + funds: [ + { + denom: "ucosm", + amount: "123", + }, + ], + }, + }, + { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: { + sender: "wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd", + contract: "wasm1hsm76p4ahyhl5yh3ve9ur49r5kemhp2r93f89d", + msg: toUtf8(`{"contract":"specific"}`), + funds: [ + { + denom: "ucosm", + amount: "456", + }, + ], + }, + }, + ]); + }); + + it("works for MsgInstantiateContract", () => { + // All fields set + // wasmd tx wasm instantiate 55 '{"contract":"specific"}' --label "unique instance" --admin wasm1hhg2rlu9jscacku2wwckws7932qqqu8xm5ca8y --amount 123ucosm --from wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd --chain-id test123 --offline --generate-only | jq .body.messages -c + const msgs1 = importWasmMessages( + `[{"@type":"/cosmwasm.wasm.v1.MsgInstantiateContract","sender":"wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd","admin":"wasm1hhg2rlu9jscacku2wwckws7932qqqu8xm5ca8y","code_id":"55","label":"unique instance","msg":{"contract":"specific"},"funds":[{"denom":"ucosm","amount":"123"}]}]`, + ); + expect(msgs1).toEqual([ + { + typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract", + value: { + sender: "wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd", + codeId: Long.fromNumber(55, true), + admin: "wasm1hhg2rlu9jscacku2wwckws7932qqqu8xm5ca8y", + label: "unique instance", + msg: toUtf8(`{"contract":"specific"}`), + funds: [ + { + denom: "ucosm", + amount: "123", + }, + ], + }, + }, + ]); + + // No admin + // wasmd tx wasm instantiate 55 '{"contract":"specific"}' --label "unique instance" --no-admin --amount 123ucosm --from wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd --chain-id test123 --offline --generate-only | jq .body.messages -c + const msgs2 = importWasmMessages( + `[{"@type":"/cosmwasm.wasm.v1.MsgInstantiateContract","sender":"wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd","admin":"","code_id":"55","label":"unique instance","msg":{"contract":"specific"},"funds":[{"denom":"ucosm","amount":"123"}]}]`, + ); + expect(msgs2).toEqual([ + { + typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract", + value: { + sender: "wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd", + codeId: Long.fromNumber(55, true), + admin: "", + label: "unique instance", + msg: toUtf8(`{"contract":"specific"}`), + funds: [ + { + denom: "ucosm", + amount: "123", + }, + ], + }, + }, + ]); + }); + + it("works for mixed MsgExecuteContract and MsgInstantiateContract", () => { + const msgs = importWasmMessages( + `[{"@type":"/cosmwasm.wasm.v1.MsgExecuteContract","sender":"wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd","contract":"wasm1hsm76p4ahyhl5yh3ve9ur49r5kemhp2r93f89d","msg":{"contract":"specific"},"funds":[{"denom":"ucosm","amount":"123"}]},{"@type":"/cosmwasm.wasm.v1.MsgInstantiateContract","sender":"wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd","admin":"wasm1hhg2rlu9jscacku2wwckws7932qqqu8xm5ca8y","code_id":"55","label":"unique instance","msg":{"contract":"specific"},"funds":[{"denom":"ucosm","amount":"123"}]}]`, + ); + expect(msgs).toEqual([ + { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: { + sender: "wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd", + contract: "wasm1hsm76p4ahyhl5yh3ve9ur49r5kemhp2r93f89d", + msg: toUtf8(`{"contract":"specific"}`), + funds: [ + { + denom: "ucosm", + amount: "123", + }, + ], + }, + }, + { + typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract", + value: { + sender: "wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd", + codeId: Long.fromNumber(55, true), + admin: "wasm1hhg2rlu9jscacku2wwckws7932qqqu8xm5ca8y", + label: "unique instance", + msg: toUtf8(`{"contract":"specific"}`), + funds: [ + { + denom: "ucosm", + amount: "123", + }, + ], + }, + }, + ]); + }); + }); +}); diff --git a/packages/cosmwasm-stargate/src/modules/wasm/messages.ts b/packages/cosmwasm-stargate/src/modules/wasm/messages.ts index e9a1ea0278..b775f5b05f 100644 --- a/packages/cosmwasm-stargate/src/modules/wasm/messages.ts +++ b/packages/cosmwasm-stargate/src/modules/wasm/messages.ts @@ -1,4 +1,6 @@ +import { toUtf8 } from "@cosmjs/encoding"; import { EncodeObject, GeneratedType } from "@cosmjs/proto-signing"; +import { assert, isNonNullObject } from "@cosmjs/utils"; import { MsgClearAdmin, MsgExecuteContract, @@ -7,6 +9,7 @@ import { MsgStoreCode, MsgUpdateAdmin, } from "cosmjs-types/cosmwasm/wasm/v1/tx"; +import Long from "long"; export const wasmTypes: ReadonlyArray<[string, GeneratedType]> = [ ["/cosmwasm.wasm.v1.MsgClearAdmin", MsgClearAdmin], @@ -74,3 +77,81 @@ export interface MsgExecuteContractEncodeObject extends EncodeObject { export function isMsgExecuteEncodeObject(object: EncodeObject): object is MsgExecuteContractEncodeObject { return (object as MsgExecuteContractEncodeObject).typeUrl === "/cosmwasm.wasm.v1.MsgExecuteContract"; } + +function getField(object: any, key: string | number): any { + if (!(key in object)) throw new Error(`Missing key '${key}' in object`); + return object[key]; +} + +function getStringField(object: any, key: string | number): string { + const value = getField(object, key); + if (typeof value !== "string") { + throw new Error(`Wrong type for key '${key}' in object: ${typeof value}. Must be a string.`); + } + return value; +} + +function getArrayField(object: any, key: string | number): any[] { + const value = getField(object, key); + if (!Array.isArray(value)) { + throw new Error(`Wrong type for key '${key}' in object: ${typeof value}. Must be an array.`); + } + return value; +} + +function importMsgExecuteContractEncodeObject(object: unknown): MsgExecuteContractEncodeObject { + assert(isNonNullObject(object)); + return { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: getStringField(object, "sender"), + contract: getStringField(object, "contract"), + msg: toUtf8(JSON.stringify(getField(object, "msg"))), + funds: getArrayField(object, "funds"), + }), + }; +} + +function importMsgInstantiateContract(object: unknown): MsgInstantiateContractEncodeObject { + assert(isNonNullObject(object)); + return { + typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract", + value: MsgInstantiateContract.fromPartial({ + sender: getStringField(object, "sender"), + admin: getStringField(object, "admin"), + codeId: Long.fromString(getStringField(object, "code_id"), true, 10), + label: getStringField(object, "label"), + msg: toUtf8(JSON.stringify(getField(object, "msg"))), + funds: getArrayField(object, "funds"), + }), + }; +} + +/** + * Takes a JSON representation of the Go implementation and imports it into CosmJS types + * + * The Go JSON and the ts-proto JSON are not compatible, i.e. we have to implement every + * message individually. + */ +export function importWasmMessages( + json: string, +): Array { + const doc = JSON.parse(json); + if (!Array.isArray(doc)) throw new Error("Array expected"); + const out = []; + for (const element of doc) { + if (!isNonNullObject(element)) throw new Error("Element must be an object"); + const typeUrl = getStringField(element, "@type"); + switch (typeUrl) { + case "/cosmwasm.wasm.v1.MsgExecuteContract": + out.push(importMsgExecuteContractEncodeObject(element)); + break; + case "/cosmwasm.wasm.v1.MsgInstantiateContract": + out.push(importMsgInstantiateContract(element)); + break; + default: + throw new Error(`Unsupported message type '${typeUrl}' found.`); + } + } + return out; +}