diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a82c5fcf4..6c09fb404f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,10 +20,13 @@ and this project adheres to ([#1072]). - @cosmjs/math: Add `{Uint32,Int53,Uint53,Uint64}.toBigInt` converter methods. - @cosmjs/stargate: Add missing exports `AminoMsgTransfer`/`isAminoMsgTransfer`. +- @cosmjs/stargate: Add support for `MsgVoteWeighted` (register by default and + create Amino JSON converters) ([#1224]). [#1072]: https://github.com/cosmos/cosmjs/issues/1072 [#1154]: https://github.com/cosmos/cosmjs/issues/1154 [#1176]: https://github.com/cosmos/cosmjs/pull/1176 +[#1224]: https://github.com/cosmos/cosmjs/pull/1224 ### Fixed diff --git a/packages/stargate/src/index.ts b/packages/stargate/src/index.ts index 4869418e04..3f8748c35b 100644 --- a/packages/stargate/src/index.ts +++ b/packages/stargate/src/index.ts @@ -19,6 +19,7 @@ export { AminoMsgUnjail, AminoMsgVerifyInvariant, AminoMsgVote, + AminoMsgVoteWeighted, AminoMsgWithdrawDelegatorReward, AminoMsgWithdrawValidatorCommission, AuthExtension, @@ -44,6 +45,7 @@ export { isAminoMsgUnjail, isAminoMsgVerifyInvariant, isAminoMsgVote, + isAminoMsgVoteWeighted, isAminoMsgWithdrawDelegatorReward, isAminoMsgWithdrawValidatorCommission, isMsgDelegateEncodeObject, @@ -53,6 +55,7 @@ export { isMsgTransferEncodeObject, isMsgUndelegateEncodeObject, isMsgVoteEncodeObject, + isMsgVoteWeightedEncodeObject, isMsgWithdrawDelegatorRewardEncodeObject, MintExtension, MintParams, @@ -63,6 +66,7 @@ export { MsgTransferEncodeObject, MsgUndelegateEncodeObject, MsgVoteEncodeObject, + MsgVoteWeightedEncodeObject, MsgWithdrawDelegatorRewardEncodeObject, setupAuthExtension, setupBankExtension, diff --git a/packages/stargate/src/modules/gov/aminomessages.spec.ts b/packages/stargate/src/modules/gov/aminomessages.spec.ts index 05861f952a..2880152929 100644 --- a/packages/stargate/src/modules/gov/aminomessages.spec.ts +++ b/packages/stargate/src/modules/gov/aminomessages.spec.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { TextProposal, VoteOption } from "cosmjs-types/cosmos/gov/v1beta1/gov"; -import { MsgDeposit, MsgSubmitProposal, MsgVote } from "cosmjs-types/cosmos/gov/v1beta1/tx"; +import { MsgDeposit, MsgSubmitProposal, MsgVote, MsgVoteWeighted } from "cosmjs-types/cosmos/gov/v1beta1/tx"; import Long from "long"; import { AminoTypes } from "../../aminotypes"; @@ -8,6 +8,7 @@ import { AminoMsgDeposit, AminoMsgSubmitProposal, AminoMsgVote, + AminoMsgVoteWeighted, createGovAminoConverters, } from "./aminomessages"; @@ -90,6 +91,34 @@ describe("AminoTypes", () => { }; expect(aminoMsg).toEqual(expected); }); + + it("works for MsgVoteWeighted", () => { + const msg: MsgVoteWeighted = { + proposalId: Long.fromNumber(5), + voter: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + options: [ + { option: VoteOption.VOTE_OPTION_NO_WITH_VETO, weight: "700000000000000000" /* 0.7 */ }, + { option: VoteOption.VOTE_OPTION_NO, weight: "300000000000000000" /* 0.3 */ }, + ], + }; + const aminoTypes = new AminoTypes(createGovAminoConverters()); + const aminoMsg = aminoTypes.toAmino({ + typeUrl: "/cosmos.gov.v1beta1.MsgVoteWeighted", + value: msg, + }); + const expected: AminoMsgVoteWeighted = { + type: "cosmos-sdk/MsgVoteWeighted", + value: { + proposal_id: "5", + voter: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + options: [ + { option: VoteOption.VOTE_OPTION_NO_WITH_VETO, weight: "0.700000000000000000" }, + { option: VoteOption.VOTE_OPTION_NO, weight: "0.300000000000000000" }, + ], + }, + }; + expect(aminoMsg).toEqual(expected); + }); }); describe("fromAmino", () => { @@ -167,5 +196,32 @@ describe("AminoTypes", () => { value: expectedValue, }); }); + + it("works for MsgVoteWeighted", () => { + const aminoMsg: AminoMsgVoteWeighted = { + type: "cosmos-sdk/MsgVoteWeighted", + value: { + proposal_id: "5", + voter: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + options: [ + { option: 4, weight: "0.750000000000000000" }, + { option: 3, weight: "0.250000000000000000" }, + ], + }, + }; + const msg = new AminoTypes(createGovAminoConverters()).fromAmino(aminoMsg); + const expectedValue: MsgVoteWeighted = { + proposalId: Long.fromNumber(5), + voter: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + options: [ + { option: VoteOption.VOTE_OPTION_NO_WITH_VETO, weight: "750000000000000000" }, + { option: VoteOption.VOTE_OPTION_NO, weight: "250000000000000000" }, + ], + }; + expect(msg).toEqual({ + typeUrl: "/cosmos.gov.v1beta1.MsgVoteWeighted", + value: expectedValue, + }); + }); }); }); diff --git a/packages/stargate/src/modules/gov/aminomessages.ts b/packages/stargate/src/modules/gov/aminomessages.ts index 67ba88d276..e4de005a4a 100644 --- a/packages/stargate/src/modules/gov/aminomessages.ts +++ b/packages/stargate/src/modules/gov/aminomessages.ts @@ -1,12 +1,14 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { AminoMsg, Coin } from "@cosmjs/amino"; +import { Decimal } from "@cosmjs/math"; import { assert, assertDefinedAndNotNull, isNonNullObject } from "@cosmjs/utils"; import { TextProposal, voteOptionFromJSON } from "cosmjs-types/cosmos/gov/v1beta1/gov"; -import { MsgDeposit, MsgSubmitProposal, MsgVote } from "cosmjs-types/cosmos/gov/v1beta1/tx"; +import { MsgDeposit, MsgSubmitProposal, MsgVote, MsgVoteWeighted } from "cosmjs-types/cosmos/gov/v1beta1/tx"; import { Any } from "cosmjs-types/google/protobuf/any"; import Long from "long"; import { AminoConverters } from "../../aminotypes"; +import { decodeCosmosSdkDecFromProto } from "../../queryclient"; /** Supports submitting arbitrary proposal content. */ export interface AminoMsgSubmitProposal extends AminoMsg { @@ -59,6 +61,32 @@ export function isAminoMsgVote(msg: AminoMsg): msg is AminoMsgVote { return msg.type === "cosmos-sdk/MsgVote"; } +/** + * @see https://github.com/cosmos/cosmos-sdk/blob/v0.44.5/x/gov/types/tx.pb.go#L196-L203 + * @see https://github.com/cosmos/cosmos-sdk/blob/v0.44.5/x/gov/types/gov.pb.go#L124-L130 + */ +export interface AminoMsgVoteWeighted extends AminoMsg { + readonly type: "cosmos-sdk/MsgVoteWeighted"; + readonly value: { + readonly proposal_id: string; + /** Bech32 account address */ + readonly voter: string; + readonly options: Array<{ + /** + * VoteOption as integer from 0 to 4 🤷‍ + * + * @see https://github.com/cosmos/cosmos-sdk/blob/v0.44.5/x/gov/types/gov.pb.go#L35-L49 + */ + readonly option: number; + readonly weight: string; + }>; + }; +} + +export function isAminoMsgVoteWeighted(msg: AminoMsg): msg is AminoMsgVoteWeighted { + return (msg as AminoMsgVoteWeighted).type === "cosmos-sdk/MsgVoteWeighted"; +} + /** Submits a deposit to an existing proposal */ export interface AminoMsgDeposit extends AminoMsg { readonly type: "cosmos-sdk/MsgDeposit"; @@ -110,6 +138,31 @@ export function createGovAminoConverters(): AminoConverters { }; }, }, + "/cosmos.gov.v1beta1.MsgVoteWeighted": { + aminoType: "cosmos-sdk/MsgVoteWeighted", + toAmino: ({ options, proposalId, voter }: MsgVoteWeighted): AminoMsgVoteWeighted["value"] => { + return { + options: options.map((o) => ({ + option: o.option, + // Weight is between 0 and 1, so we always have 20 characters when printing all trailing + // zeros (e.g. "0.700000000000000000" or "1.000000000000000000") + weight: decodeCosmosSdkDecFromProto(o.weight).toString().padEnd(20, "0"), + })), + proposal_id: proposalId.toString(), + voter: voter, + }; + }, + fromAmino: ({ options, proposal_id, voter }: AminoMsgVoteWeighted["value"]): MsgVoteWeighted => { + return { + proposalId: Long.fromString(proposal_id), + voter: voter, + options: options.map((o) => ({ + option: voteOptionFromJSON(o.option), + weight: Decimal.fromUserInput(o.weight, 18).atomics, + })), + }; + }, + }, "/cosmos.gov.v1beta1.MsgSubmitProposal": { aminoType: "cosmos-sdk/MsgSubmitProposal", toAmino: ({ diff --git a/packages/stargate/src/modules/gov/messages.spec.ts b/packages/stargate/src/modules/gov/messages.spec.ts index e7165fa929..378147ea03 100644 --- a/packages/stargate/src/modules/gov/messages.spec.ts +++ b/packages/stargate/src/modules/gov/messages.spec.ts @@ -1,4 +1,4 @@ -import { coin, coins, makeCosmoshubPath } from "@cosmjs/amino"; +import { coin, coins, makeCosmoshubPath, Secp256k1HdWallet } from "@cosmjs/amino"; import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; import { assert, sleep } from "@cosmjs/utils"; import { TextProposal, VoteOption } from "cosmjs-types/cosmos/gov/v1beta1/gov"; @@ -17,6 +17,7 @@ import { validator, } from "../../testutils.spec"; import { MsgDelegateEncodeObject, MsgSubmitProposalEncodeObject, MsgVoteEncodeObject } from "../"; +import { MsgVoteWeightedEncodeObject } from "./messages"; describe("gov messages", () => { const defaultFee = { @@ -166,5 +167,72 @@ describe("gov messages", () => { client.disconnect(); }); }); + + describe("MsgVoteWeighted", () => { + it("works", async () => { + pendingWithoutSimapp(); + assert(voterWallet); + assert(proposalId, "Missing proposal ID"); + const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, voterWallet); + + const voteMsg: MsgVoteWeightedEncodeObject = { + typeUrl: "/cosmos.gov.v1beta1.MsgVoteWeighted", + value: { + proposalId: longify(proposalId), + voter: voter1Address, + options: [ + { + option: VoteOption.VOTE_OPTION_YES, + weight: "700000000000000000", // 0.7 + }, + { + option: VoteOption.VOTE_OPTION_NO, + weight: "200000000000000000", // 0.2 + }, + { + option: VoteOption.VOTE_OPTION_ABSTAIN, + weight: "100000000000000000", // 0.1 + }, + ], + }, + }; + const voteResult = await client.signAndBroadcast(voter1Address, [voteMsg], defaultFee); + assertIsDeliverTxSuccess(voteResult); + + client.disconnect(); + }); + + it("works with Amino JSON sign mode", async () => { + pendingWithoutSimapp(); + assert(voterWalletAmino); + assert(proposalId, "Missing proposal ID"); + const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, voterWalletAmino); + + const voteMsg: MsgVoteWeightedEncodeObject = { + typeUrl: "/cosmos.gov.v1beta1.MsgVoteWeighted", + value: { + proposalId: longify(proposalId), + voter: voter1Address, + options: [ + { + option: VoteOption.VOTE_OPTION_YES, + weight: "700000000000000000", // 0.7 + }, + { + option: VoteOption.VOTE_OPTION_NO, + weight: "200000000000000000", // 0.2 + }, + { + option: VoteOption.VOTE_OPTION_ABSTAIN, + weight: "100000000000000000", // 0.1 + }, + ], + }, + }; + const voteResult = await client.signAndBroadcast(voter1Address, [voteMsg], defaultFee); + assertIsDeliverTxSuccess(voteResult); + + client.disconnect(); + }); }); }); diff --git a/packages/stargate/src/modules/gov/messages.ts b/packages/stargate/src/modules/gov/messages.ts index fb1905b18b..d926e389ef 100644 --- a/packages/stargate/src/modules/gov/messages.ts +++ b/packages/stargate/src/modules/gov/messages.ts @@ -1,10 +1,11 @@ import { EncodeObject, GeneratedType } from "@cosmjs/proto-signing"; -import { MsgDeposit, MsgSubmitProposal, MsgVote } from "cosmjs-types/cosmos/gov/v1beta1/tx"; +import { MsgDeposit, MsgSubmitProposal, MsgVote, MsgVoteWeighted } from "cosmjs-types/cosmos/gov/v1beta1/tx"; export const govTypes: ReadonlyArray<[string, GeneratedType]> = [ ["/cosmos.gov.v1beta1.MsgDeposit", MsgDeposit], ["/cosmos.gov.v1beta1.MsgSubmitProposal", MsgSubmitProposal], ["/cosmos.gov.v1beta1.MsgVote", MsgVote], + ["/cosmos.gov.v1beta1.MsgVoteWeighted", MsgVoteWeighted], ]; export interface MsgDepositEncodeObject extends EncodeObject { @@ -35,3 +36,12 @@ export interface MsgVoteEncodeObject extends EncodeObject { export function isMsgVoteEncodeObject(object: EncodeObject): object is MsgVoteEncodeObject { return (object as MsgVoteEncodeObject).typeUrl === "/cosmos.gov.v1beta1.MsgVote"; } + +export interface MsgVoteWeightedEncodeObject extends EncodeObject { + readonly typeUrl: "/cosmos.gov.v1beta1.MsgVoteWeighted"; + readonly value: Partial; +} + +export function isMsgVoteWeightedEncodeObject(object: EncodeObject): object is MsgVoteWeightedEncodeObject { + return (object as MsgVoteWeightedEncodeObject).typeUrl === "/cosmos.gov.v1beta1.MsgVoteWeighted"; +} diff --git a/packages/stargate/src/modules/index.ts b/packages/stargate/src/modules/index.ts index 3358b6a6e8..70b5531def 100644 --- a/packages/stargate/src/modules/index.ts +++ b/packages/stargate/src/modules/index.ts @@ -43,19 +43,23 @@ export { AminoMsgDeposit, AminoMsgSubmitProposal, AminoMsgVote, + AminoMsgVoteWeighted, createGovAminoConverters, isAminoMsgDeposit, isAminoMsgSubmitProposal, isAminoMsgVote, + isAminoMsgVoteWeighted, } from "./gov/aminomessages"; export { govTypes, isMsgDepositEncodeObject, isMsgSubmitProposalEncodeObject, isMsgVoteEncodeObject, + isMsgVoteWeightedEncodeObject, MsgDepositEncodeObject, MsgSubmitProposalEncodeObject, MsgVoteEncodeObject, + MsgVoteWeightedEncodeObject, } from "./gov/messages"; export { GovExtension, GovParamsType, GovProposalId, setupGovExtension } from "./gov/queries"; export { AminoMsgTransfer, createIbcAminoConverters, isAminoMsgTransfer } from "./ibc/aminomessages";