From dbc702afe8ebb55b5a8bbec93f4b3f555ede6bab Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 15 Nov 2023 11:06:09 +0100 Subject: [PATCH 1/3] Export `omitDefault` to help build Amino JSON converters --- CHANGELOG.md | 1 + packages/amino/src/index.ts | 1 + packages/amino/src/omitdefault.spec.ts | 23 +++++++++++++++++++ packages/amino/src/omitdefault.ts | 18 +++++++++++++++ .../stargate/src/modules/ibc/aminomessages.ts | 18 +-------------- 5 files changed, 44 insertions(+), 17 deletions(-) create mode 100644 packages/amino/src/omitdefault.spec.ts create mode 100644 packages/amino/src/omitdefault.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b42fdf579..4554257a1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to `sign`/`signAndBroadcast`/`signAndBroadcastSync` and related functions now have an additional parameter to specify the timeout height. After this height, a signed transaction will be considered invalid by the chain. ([#1489]) +- @cosmjs/amino: Export `omitDefault` to help build Amino JSON converters. [#1489]: https://github.com/cosmos/cosmjs/pull/1489 diff --git a/packages/amino/src/index.ts b/packages/amino/src/index.ts index 2edbb2db94..477dc1ba19 100644 --- a/packages/amino/src/index.ts +++ b/packages/amino/src/index.ts @@ -14,6 +14,7 @@ export { encodeSecp256k1Pubkey, } from "./encoding"; export { createMultisigThresholdPubkey } from "./multisig"; +export { omitDefault } from "./omitdefault"; export { makeCosmoshubPath } from "./paths"; export { Ed25519Pubkey, diff --git a/packages/amino/src/omitdefault.spec.ts b/packages/amino/src/omitdefault.spec.ts new file mode 100644 index 0000000000..de82d76f0d --- /dev/null +++ b/packages/amino/src/omitdefault.spec.ts @@ -0,0 +1,23 @@ +import { omitDefault } from "./omitdefault"; + +describe("omitDefault", () => { + it("works for numbers", () => { + expect(omitDefault(17)).toEqual(17); + expect(omitDefault(0)).toEqual(undefined); + }); + + it("works for bigint", () => { + expect(omitDefault(17n)).toEqual(17n); + expect(omitDefault(0n)).toEqual(undefined); + }); + + it("works for boolean", () => { + expect(omitDefault(true)).toEqual(true); + expect(omitDefault(false)).toEqual(undefined); + }); + + it("works for strings", () => { + expect(omitDefault("hi")).toEqual("hi"); + expect(omitDefault("")).toEqual(undefined); + }); +}); diff --git a/packages/amino/src/omitdefault.ts b/packages/amino/src/omitdefault.ts new file mode 100644 index 0000000000..146c81713e --- /dev/null +++ b/packages/amino/src/omitdefault.ts @@ -0,0 +1,18 @@ +/** + * Returns the given input. If the input is the default value + * of protobuf, undefined is retunred. Use this when creating Amino JSON converters. + */ +export function omitDefault(input: T): T | undefined { + switch (typeof input) { + case "string": + return input === "" ? undefined : input; + case "number": + return input === 0 ? undefined : input; + case "bigint": + return input === BigInt(0) ? undefined : input; + case "boolean": + return !input ? undefined : input; + default: + throw new Error(`Got unsupported type '${typeof input}'`); + } +} diff --git a/packages/stargate/src/modules/ibc/aminomessages.ts b/packages/stargate/src/modules/ibc/aminomessages.ts index 731e926887..ee14bf722e 100644 --- a/packages/stargate/src/modules/ibc/aminomessages.ts +++ b/packages/stargate/src/modules/ibc/aminomessages.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { AminoMsg, Coin } from "@cosmjs/amino"; +import { AminoMsg, Coin, omitDefault } from "@cosmjs/amino"; import { MsgTransfer } from "cosmjs-types/ibc/applications/transfer/v1/tx"; import { AminoConverters } from "../../aminotypes"; @@ -45,22 +45,6 @@ export function isAminoMsgTransfer(msg: AminoMsg): msg is AminoMsgTransfer { return msg.type === "cosmos-sdk/MsgTransfer"; } -function omitDefault(input: T): T | undefined { - if (typeof input === "string") { - return input === "" ? undefined : input; - } - - if (typeof input === "number") { - return input === 0 ? undefined : input; - } - - if (typeof input === "bigint") { - return input === BigInt(0) ? undefined : input; - } - - throw new Error(`Got unsupported type '${typeof input}'`); -} - export function createIbcAminoConverters(): AminoConverters { return { "/ibc.applications.transfer.v1.MsgTransfer": { From 3dbc8bca9c2307771af047c52d0522277d630cc0 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 15 Nov 2023 11:11:01 +0100 Subject: [PATCH 2/3] Test instantiate2 with Amino JSON signing --- .../src/signingcosmwasmclient.spec.ts | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts index 307fc626a4..cc5fb40549 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { Secp256k1HdWallet } from "@cosmjs/amino"; -import { sha256 } from "@cosmjs/crypto"; +import { Random, sha256 } from "@cosmjs/crypto"; import { toHex, toUtf8 } from "@cosmjs/encoding"; import { decodeTxRaw, DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing"; import { @@ -343,6 +343,45 @@ describe("SigningCosmWasmClient", () => { expect(contractAddress).toEqual(expectedAddress); client.disconnect(); }); + + it("works with Amino JSON signing", async () => { + pendingWithoutWasmd(); + const aminoJsonWallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, { + prefix: wasmd.prefix, + }); + const client = await SigningCosmWasmClient.connectWithSigner( + wasmd.endpoint, + aminoJsonWallet, + defaultSigningClientOptions, + ); + const { codeId } = await client.upload(alice.address0, getHackatom().data, defaultUploadFee); + const funds = [coin(1234, "ucosm"), coin(321, "ustake")]; + const salt = Random.getBytes(64); + const msg = { + verifier: alice.address0, + beneficiary: makeRandomAddress(), + }; + + const { contractAddress } = await client.instantiate2( + alice.address0, + codeId, + salt, + msg, + "My cool label--", + defaultInstantiateFee, + { + memo: "Let's see if the memo is used", + funds: funds, + }, + ); + + const ucosmBalance = await client.getBalance(contractAddress, "ucosm"); + const ustakeBalance = await client.getBalance(contractAddress, "ustake"); + expect(ucosmBalance).toEqual(funds[0]); + expect(ustakeBalance).toEqual(funds[1]); + + client.disconnect(); + }); }); describe("updateAdmin", () => { From cf1fd0bf2d3f820c42583d2546b9b892faa87aaa Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 15 Nov 2023 11:44:40 +0100 Subject: [PATCH 3/3] Fix fix_msg in AminoMsgInstantiateContract2 --- CHANGELOG.md | 4 +++ .../src/modules/wasm/aminomessages.spec.ts | 36 +++++++++++++++++-- .../src/modules/wasm/aminomessages.ts | 16 +++++---- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4554257a1f..061f1cf88e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,8 +20,12 @@ and this project adheres to - @cosmjs/stargate: Handle key value pairs in tx search correctly if the value is a numeric value. ([#1462]) +- @cosmjs/cosmwasm-stargate: Make `fix_msg` optional in + `AminoMsgInstantiateContract2` and omit default in the Amino JSON converter to + fix Amino JSON signing for MsgInstantiateContract2. ([#1509]) [#1462]: https://github.com/cosmos/cosmjs/issues/1462 +[#1509]: https://github.com/cosmos/cosmjs/pull/1509 ### Changed diff --git a/packages/cosmwasm-stargate/src/modules/wasm/aminomessages.spec.ts b/packages/cosmwasm-stargate/src/modules/wasm/aminomessages.spec.ts index a9413a3277..66613a5380 100644 --- a/packages/cosmwasm-stargate/src/modules/wasm/aminomessages.spec.ts +++ b/packages/cosmwasm-stargate/src/modules/wasm/aminomessages.spec.ts @@ -159,7 +159,7 @@ describe("AminoTypes", () => { funds: coins(1234, "ucosm"), admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", salt: toBase64(toUtf8("salt")), - fix_msg: false, + fix_msg: undefined, }, }; expect(aminoMsg).toEqual(expected); @@ -191,7 +191,39 @@ describe("AminoTypes", () => { funds: coins(1234, "ucosm"), admin: undefined, salt: toBase64(toUtf8("salt")), - fix_msg: false, + fix_msg: undefined, + }, + }; + expect(aminoMsg).toEqual(expected); + } + + // With fixMsg=true (typically not needed) + { + const msg: MsgInstantiateContract2 = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + codeId: BigInt("12345"), + label: "sticky", + msg: toUtf8(`{"foo":"bar"}`), + funds: coins(1234, "ucosm"), + admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + salt: toUtf8("salt"), + fixMsg: true, + }; + const aminoMsg = new AminoTypes(createWasmAminoConverters()).toAmino({ + typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract2", + value: msg, + }); + const expected: AminoMsgInstantiateContract2 = { + type: "wasm/MsgInstantiateContract2", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + code_id: "12345", + label: "sticky", + msg: { foo: "bar" }, + funds: coins(1234, "ucosm"), + admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + salt: toBase64(toUtf8("salt")), + fix_msg: true, }, }; expect(aminoMsg).toEqual(expected); diff --git a/packages/cosmwasm-stargate/src/modules/wasm/aminomessages.ts b/packages/cosmwasm-stargate/src/modules/wasm/aminomessages.ts index c4e1adb89a..ec649494da 100644 --- a/packages/cosmwasm-stargate/src/modules/wasm/aminomessages.ts +++ b/packages/cosmwasm-stargate/src/modules/wasm/aminomessages.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import { omitDefault } from "@cosmjs/amino"; import { fromBase64, fromUtf8, toBase64, toUtf8 } from "@cosmjs/encoding"; import { AminoConverters, Coin } from "@cosmjs/stargate"; import { @@ -141,8 +142,11 @@ export interface AminoMsgInstantiateContract2 { readonly admin?: string; /** Arbitrary Base64-encoded value provided by the sender */ readonly salt: string; - /** Whether or not to include the msg value into the hash for the address */ - readonly fix_msg: boolean; + /** + * Whether or not to include the msg value into the hash for the address. + * Unset means false. This should always be unset/false (https://medium.com/cosmwasm/dev-note-3-limitations-of-instantiate2-and-how-to-deal-with-them-a3f946874230). + */ + readonly fix_msg?: boolean; }; } @@ -248,7 +252,7 @@ export function createWasmAminoConverters(): AminoConverters { label: label, msg: JSON.parse(fromUtf8(msg)), funds: funds, - admin: admin || undefined, + admin: omitDefault(admin), }), fromAmino: ({ sender, @@ -283,9 +287,9 @@ export function createWasmAminoConverters(): AminoConverters { label: label, msg: JSON.parse(fromUtf8(msg)), funds: funds, - admin: admin || undefined, + admin: omitDefault(admin), salt: toBase64(salt), - fix_msg: fixMsg, + fix_msg: omitDefault(fixMsg), }), fromAmino: ({ sender, @@ -304,7 +308,7 @@ export function createWasmAminoConverters(): AminoConverters { funds: [...funds], admin: admin ?? "", salt: fromBase64(salt), - fixMsg: fix_msg, + fixMsg: fix_msg ?? false, }), }, "/cosmwasm.wasm.v1.MsgUpdateAdmin": {