From c113d7d6ffe9d9b9f2b313e388736fe61ed4fe8f Mon Sep 17 00:00:00 2001 From: leesj9476 Date: Mon, 1 Nov 2021 17:43:11 +0900 Subject: [PATCH 1/2] feat: add gov/weighted-vote --- client/docs/swagger-ui/swagger.yaml | 167 ++++++++- docs/core/proto-docs.md | 50 ++- proto/lbm/gov/v1/gov.proto | 20 +- proto/lbm/gov/v1/tx.proto | 18 + simapp/params/weights.go | 1 + types/tx_msg.go | 5 + x/auth/legacy/legacytx/stdsign.go | 18 + x/gov/abci_test.go | 4 +- x/gov/client/cli/cli_test.go | 46 ++- x/gov/client/cli/tx.go | 54 +++ x/gov/client/rest/grpc_query_test.go | 44 ++- x/gov/client/rest/rest_test.go | 9 +- x/gov/client/testutil/helpers.go | 2 +- x/gov/client/utils/query.go | 70 +++- x/gov/client/utils/query_test.go | 44 ++- x/gov/client/utils/utils.go | 20 +- x/gov/client/utils/utils_test.go | 49 +++ x/gov/handler.go | 4 + x/gov/keeper/grpc_query.go | 1 + x/gov/keeper/grpc_query_test.go | 30 +- x/gov/keeper/msg_server.go | 29 +- x/gov/keeper/proposal_test.go | 2 +- x/gov/keeper/querier_test.go | 25 +- x/gov/keeper/tally.go | 16 +- x/gov/keeper/tally_test.go | 78 ++--- x/gov/keeper/vote.go | 31 +- x/gov/keeper/vote_test.go | 42 ++- x/gov/simulation/decoder_test.go | 2 +- x/gov/simulation/operations.go | 117 ++++++- x/gov/simulation/operations_test.go | 43 +++ x/gov/spec/04_events.md | 10 + x/gov/types/codec.go | 3 +- x/gov/types/gov.pb.go | 454 +++++++++++++++++++------ x/gov/types/msgs.go | 66 +++- x/gov/types/msgs_test.go | 44 +++ x/gov/types/tally.go | 12 +- x/gov/types/tx.pb.go | 488 ++++++++++++++++++++++++--- x/gov/types/vote.go | 59 +++- x/simulation/expected_keepers.go | 16 + 39 files changed, 1904 insertions(+), 289 deletions(-) create mode 100644 x/simulation/expected_keepers.go diff --git a/client/docs/swagger-ui/swagger.yaml b/client/docs/swagger-ui/swagger.yaml index 735a807e0d..323868add8 100644 --- a/client/docs/swagger-ui/swagger.yaml +++ b/client/docs/swagger-ui/swagger.yaml @@ -13467,6 +13467,29 @@ paths: voter: type: string option: + description: >- + Deprecated: Prefer to use `options` instead. This field + is set in queries + + if and only if `len(options) == 1` and that option has + weight 1. In all + + other cases, this field will default to + VOTE_OPTION_UNSPECIFIED. + type: string + enum: + - VOTE_OPTION_UNSPECIFIED + - VOTE_OPTION_YES + - VOTE_OPTION_ABSTAIN + - VOTE_OPTION_NO + - VOTE_OPTION_NO_WITH_VETO + default: VOTE_OPTION_UNSPECIFIED + options: + type: array + items: + type: object + properties: + option: type: string enum: - VOTE_OPTION_UNSPECIFIED @@ -13476,14 +13499,19 @@ paths: - VOTE_OPTION_NO_WITH_VETO default: VOTE_OPTION_UNSPECIFIED description: >- - VoteOption enumerates the valid vote options for a given - governance proposal. + VoteOption enumerates the valid vote options for a + given governance proposal. - VOTE_OPTION_UNSPECIFIED: VOTE_OPTION_UNSPECIFIED defines a no-op vote option. - VOTE_OPTION_YES: VOTE_OPTION_YES defines a yes vote option. - VOTE_OPTION_ABSTAIN: VOTE_OPTION_ABSTAIN defines an abstain vote option. - VOTE_OPTION_NO: VOTE_OPTION_NO defines a no vote option. - VOTE_OPTION_NO_WITH_VETO: VOTE_OPTION_NO_WITH_VETO defines a no with veto vote option. + weight: + type: string + description: >- + WeightedVoteOption defines a unit of vote for vote + split. description: >- Vote defines a vote on a governance proposal. @@ -13775,6 +13803,29 @@ paths: voter: type: string option: + description: >- + Deprecated: Prefer to use `options` instead. This field is + set in queries + + if and only if `len(options) == 1` and that option has + weight 1. In all + + other cases, this field will default to + VOTE_OPTION_UNSPECIFIED. + type: string + enum: + - VOTE_OPTION_UNSPECIFIED + - VOTE_OPTION_YES + - VOTE_OPTION_ABSTAIN + - VOTE_OPTION_NO + - VOTE_OPTION_NO_WITH_VETO + default: VOTE_OPTION_UNSPECIFIED + options: + type: array + items: + type: object + properties: + option: type: string enum: - VOTE_OPTION_UNSPECIFIED @@ -13784,14 +13835,19 @@ paths: - VOTE_OPTION_NO_WITH_VETO default: VOTE_OPTION_UNSPECIFIED description: >- - VoteOption enumerates the valid vote options for a given - governance proposal. + VoteOption enumerates the valid vote options for a + given governance proposal. - VOTE_OPTION_UNSPECIFIED: VOTE_OPTION_UNSPECIFIED defines a no-op vote option. - VOTE_OPTION_YES: VOTE_OPTION_YES defines a yes vote option. - VOTE_OPTION_ABSTAIN: VOTE_OPTION_ABSTAIN defines an abstain vote option. - VOTE_OPTION_NO: VOTE_OPTION_NO defines a no vote option. - VOTE_OPTION_NO_WITH_VETO: VOTE_OPTION_NO_WITH_VETO defines a no with veto vote option. + weight: + type: string + description: >- + WeightedVoteOption defines a unit of vote for vote + split. description: >- Vote defines a vote on a governance proposal. @@ -42262,6 +42318,28 @@ definitions: voter: type: string option: + description: >- + Deprecated: Prefer to use `options` instead. This field is set in + queries + + if and only if `len(options) == 1` and that option has weight 1. + In all + + other cases, this field will default to VOTE_OPTION_UNSPECIFIED. + type: string + enum: + - VOTE_OPTION_UNSPECIFIED + - VOTE_OPTION_YES + - VOTE_OPTION_ABSTAIN + - VOTE_OPTION_NO + - VOTE_OPTION_NO_WITH_VETO + default: VOTE_OPTION_UNSPECIFIED + options: + type: array + items: + type: object + properties: + option: type: string enum: - VOTE_OPTION_UNSPECIFIED @@ -42279,6 +42357,9 @@ definitions: - VOTE_OPTION_ABSTAIN: VOTE_OPTION_ABSTAIN defines an abstain vote option. - VOTE_OPTION_NO: VOTE_OPTION_NO defines a no vote option. - VOTE_OPTION_NO_WITH_VETO: VOTE_OPTION_NO_WITH_VETO defines a no with veto vote option. + weight: + type: string + description: WeightedVoteOption defines a unit of vote for vote split. description: |- Vote defines a vote on a governance proposal. A Vote consists of a proposal ID, the voter, and the vote option. @@ -42297,6 +42378,28 @@ definitions: voter: type: string option: + description: >- + Deprecated: Prefer to use `options` instead. This field is set + in queries + + if and only if `len(options) == 1` and that option has weight 1. + In all + + other cases, this field will default to VOTE_OPTION_UNSPECIFIED. + type: string + enum: + - VOTE_OPTION_UNSPECIFIED + - VOTE_OPTION_YES + - VOTE_OPTION_ABSTAIN + - VOTE_OPTION_NO + - VOTE_OPTION_NO_WITH_VETO + default: VOTE_OPTION_UNSPECIFIED + options: + type: array + items: + type: object + properties: + option: type: string enum: - VOTE_OPTION_UNSPECIFIED @@ -42314,6 +42417,9 @@ definitions: - VOTE_OPTION_ABSTAIN: VOTE_OPTION_ABSTAIN defines an abstain vote option. - VOTE_OPTION_NO: VOTE_OPTION_NO defines a no vote option. - VOTE_OPTION_NO_WITH_VETO: VOTE_OPTION_NO_WITH_VETO defines a no with veto vote option. + weight: + type: string + description: WeightedVoteOption defines a unit of vote for vote split. description: |- Vote defines a vote on a governance proposal. A Vote consists of a proposal ID, the voter, and the vote option. @@ -42380,6 +42486,28 @@ definitions: voter: type: string option: + description: >- + Deprecated: Prefer to use `options` instead. This field is set in + queries + + if and only if `len(options) == 1` and that option has weight 1. In + all + + other cases, this field will default to VOTE_OPTION_UNSPECIFIED. + type: string + enum: + - VOTE_OPTION_UNSPECIFIED + - VOTE_OPTION_YES + - VOTE_OPTION_ABSTAIN + - VOTE_OPTION_NO + - VOTE_OPTION_NO_WITH_VETO + default: VOTE_OPTION_UNSPECIFIED + options: + type: array + items: + type: object + properties: + option: type: string enum: - VOTE_OPTION_UNSPECIFIED @@ -42389,14 +42517,17 @@ definitions: - VOTE_OPTION_NO_WITH_VETO default: VOTE_OPTION_UNSPECIFIED description: >- - VoteOption enumerates the valid vote options for a given governance - proposal. + VoteOption enumerates the valid vote options for a given + governance proposal. - VOTE_OPTION_UNSPECIFIED: VOTE_OPTION_UNSPECIFIED defines a no-op vote option. - VOTE_OPTION_YES: VOTE_OPTION_YES defines a yes vote option. - VOTE_OPTION_ABSTAIN: VOTE_OPTION_ABSTAIN defines an abstain vote option. - VOTE_OPTION_NO: VOTE_OPTION_NO defines a no vote option. - VOTE_OPTION_NO_WITH_VETO: VOTE_OPTION_NO_WITH_VETO defines a no with veto vote option. + weight: + type: string + description: WeightedVoteOption defines a unit of vote for vote split. description: |- Vote defines a vote on a governance proposal. A Vote consists of a proposal ID, the voter, and the vote option. @@ -42425,6 +42556,30 @@ definitions: type: string description: Length of the voting period. description: VotingParams defines the params for voting on governance proposals. + lbm.gov.v1.WeightedVoteOption: + type: object + properties: + option: + type: string + enum: + - VOTE_OPTION_UNSPECIFIED + - VOTE_OPTION_YES + - VOTE_OPTION_ABSTAIN + - VOTE_OPTION_NO + - VOTE_OPTION_NO_WITH_VETO + default: VOTE_OPTION_UNSPECIFIED + description: >- + VoteOption enumerates the valid vote options for a given governance + proposal. + + - VOTE_OPTION_UNSPECIFIED: VOTE_OPTION_UNSPECIFIED defines a no-op vote option. + - VOTE_OPTION_YES: VOTE_OPTION_YES defines a yes vote option. + - VOTE_OPTION_ABSTAIN: VOTE_OPTION_ABSTAIN defines an abstain vote option. + - VOTE_OPTION_NO: VOTE_OPTION_NO defines a no vote option. + - VOTE_OPTION_NO_WITH_VETO: VOTE_OPTION_NO_WITH_VETO defines a no with veto vote option. + weight: + type: string + description: WeightedVoteOption defines a unit of vote for vote split. lbm.mint.v1.Params: type: object properties: diff --git a/docs/core/proto-docs.md b/docs/core/proto-docs.md index 3f7f62916b..3a5bc94359 100644 --- a/docs/core/proto-docs.md +++ b/docs/core/proto-docs.md @@ -469,6 +469,7 @@ - [TextProposal](#lbm.gov.v1.TextProposal) - [Vote](#lbm.gov.v1.Vote) - [VotingParams](#lbm.gov.v1.VotingParams) + - [WeightedVoteOption](#lbm.gov.v1.WeightedVoteOption) - [ProposalStatus](#lbm.gov.v1.ProposalStatus) - [VoteOption](#lbm.gov.v1.VoteOption) @@ -503,6 +504,8 @@ - [MsgSubmitProposalResponse](#lbm.gov.v1.MsgSubmitProposalResponse) - [MsgVote](#lbm.gov.v1.MsgVote) - [MsgVoteResponse](#lbm.gov.v1.MsgVoteResponse) + - [MsgVoteWeighted](#lbm.gov.v1.MsgVoteWeighted) + - [MsgVoteWeightedResponse](#lbm.gov.v1.MsgVoteWeightedResponse) - [Msg](#lbm.gov.v1.Msg) @@ -7107,7 +7110,8 @@ A Vote consists of a proposal ID, the voter, and the vote option. | ----- | ---- | ----- | ----------- | | `proposal_id` | [uint64](#uint64) | | | | `voter` | [string](#string) | | | -| `option` | [VoteOption](#lbm.gov.v1.VoteOption) | | | +| `option` | [VoteOption](#lbm.gov.v1.VoteOption) | | **Deprecated.** Deprecated: Prefer to use `options` instead. This field is set in queries if and only if `len(options) == 1` and that option has weight 1. In all other cases, this field will default to VOTE_OPTION_UNSPECIFIED. | +| `options` | [WeightedVoteOption](#lbm.gov.v1.WeightedVoteOption) | repeated | | @@ -7128,6 +7132,22 @@ VotingParams defines the params for voting on governance proposals. + + + +### WeightedVoteOption +WeightedVoteOption defines a unit of vote for vote split. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `option` | [VoteOption](#lbm.gov.v1.VoteOption) | | | +| `weight` | [string](#string) | | | + + + + + @@ -7585,6 +7605,33 @@ MsgVoteResponse defines the Msg/Vote response type. + + + +### MsgVoteWeighted +MsgVoteWeighted defines a message to cast a vote. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `proposal_id` | [uint64](#uint64) | | | +| `voter` | [string](#string) | | | +| `options` | [WeightedVoteOption](#lbm.gov.v1.WeightedVoteOption) | repeated | | + + + + + + + + +### MsgVoteWeightedResponse +MsgVoteWeightedResponse defines the Msg/VoteWeighted response type. + + + + + @@ -7601,6 +7648,7 @@ Msg defines the bank Msg service. | ----------- | ------------ | ------------- | ------------| ------- | -------- | | `SubmitProposal` | [MsgSubmitProposal](#lbm.gov.v1.MsgSubmitProposal) | [MsgSubmitProposalResponse](#lbm.gov.v1.MsgSubmitProposalResponse) | SubmitProposal defines a method to create new proposal given a content. | | | `Vote` | [MsgVote](#lbm.gov.v1.MsgVote) | [MsgVoteResponse](#lbm.gov.v1.MsgVoteResponse) | Vote defines a method to add a vote on a specific proposal. | | +| `VoteWeighted` | [MsgVoteWeighted](#lbm.gov.v1.MsgVoteWeighted) | [MsgVoteWeightedResponse](#lbm.gov.v1.MsgVoteWeightedResponse) | VoteWeighted defines a method to add a weighted vote on a specific proposal. | | | `Deposit` | [MsgDeposit](#lbm.gov.v1.MsgDeposit) | [MsgDepositResponse](#lbm.gov.v1.MsgDepositResponse) | Deposit defines a method to add deposit on a specific proposal. | | diff --git a/proto/lbm/gov/v1/gov.proto b/proto/lbm/gov/v1/gov.proto index d8bb0fa271..4b5a24c850 100644 --- a/proto/lbm/gov/v1/gov.proto +++ b/proto/lbm/gov/v1/gov.proto @@ -29,6 +29,16 @@ enum VoteOption { VOTE_OPTION_NO_WITH_VETO = 4 [(gogoproto.enumvalue_customname) = "OptionNoWithVeto"]; } +// WeightedVoteOption defines a unit of vote for vote split. +message WeightedVoteOption { + VoteOption option = 1; + string weight = 2 [ + (gogoproto.customtype) = "github.com/line/lbm-sdk/types.Dec", + (gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"weight\"" + ]; +} + // TextProposal defines a standard text proposal whose changes need to be // manually updated in case of approval. message TextProposal { @@ -119,9 +129,13 @@ message Vote { option (gogoproto.goproto_stringer) = false; option (gogoproto.equal) = false; - uint64 proposal_id = 1 [(gogoproto.moretags) = "yaml:\"proposal_id\""]; - string voter = 2; - VoteOption option = 3; + uint64 proposal_id = 1 [(gogoproto.moretags) = "yaml:\"proposal_id\""]; + string voter = 2; + // Deprecated: Prefer to use `options` instead. This field is set in queries + // if and only if `len(options) == 1` and that option has weight 1. In all + // other cases, this field will default to VOTE_OPTION_UNSPECIFIED. + VoteOption option = 3 [deprecated = true]; + repeated WeightedVoteOption options = 4 [(gogoproto.nullable) = false]; } // DepositParams defines the params for deposits on governance proposals. diff --git a/proto/lbm/gov/v1/tx.proto b/proto/lbm/gov/v1/tx.proto index f2d6cc63a7..3ae23f76ff 100644 --- a/proto/lbm/gov/v1/tx.proto +++ b/proto/lbm/gov/v1/tx.proto @@ -17,6 +17,9 @@ service Msg { // Vote defines a method to add a vote on a specific proposal. rpc Vote(MsgVote) returns (MsgVoteResponse); + // VoteWeighted defines a method to add a weighted vote on a specific proposal. + rpc VoteWeighted(MsgVoteWeighted) returns (MsgVoteWeightedResponse); + // Deposit defines a method to add deposit on a specific proposal. rpc Deposit(MsgDeposit) returns (MsgDepositResponse); } @@ -58,6 +61,21 @@ message MsgVote { // MsgVoteResponse defines the Msg/Vote response type. message MsgVoteResponse {} +// MsgVoteWeighted defines a message to cast a vote. +message MsgVoteWeighted { + option (gogoproto.equal) = false; + option (gogoproto.goproto_stringer) = false; + option (gogoproto.stringer) = false; + option (gogoproto.goproto_getters) = false; + + uint64 proposal_id = 1 [(gogoproto.moretags) = "yaml:\"proposal_id\""]; + string voter = 2; + repeated WeightedVoteOption options = 3 [(gogoproto.nullable) = false]; +} + +// MsgVoteWeightedResponse defines the Msg/VoteWeighted response type. +message MsgVoteWeightedResponse {} + // MsgDeposit defines a message to submit a deposit to an existing proposal. message MsgDeposit { option (gogoproto.equal) = false; diff --git a/simapp/params/weights.go b/simapp/params/weights.go index 0ba377b009..1581d2fc38 100644 --- a/simapp/params/weights.go +++ b/simapp/params/weights.go @@ -10,6 +10,7 @@ const ( DefaultWeightMsgFundCommunityPool int = 50 DefaultWeightMsgDeposit int = 100 DefaultWeightMsgVote int = 67 + DefaultWeightMsgVoteWeighted int = 33 DefaultWeightMsgUnjail int = 100 DefaultWeightMsgCreateValidator int = 100 DefaultWeightMsgEditValidator int = 5 diff --git a/types/tx_msg.go b/types/tx_msg.go index 62b79d937f..ec7af0a725 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -88,3 +88,8 @@ type TxDecoder func(txBytes []byte) (Tx, error) // TxEncoder marshals transaction to bytes type TxEncoder func(tx Tx) ([]byte, error) + +// MsgTypeURL returns the TypeURL of a `sdk.Msg`. +func MsgTypeURL(msg Msg) string { + return "/" + proto.MessageName(msg) +} diff --git a/x/auth/legacy/legacytx/stdsign.go b/x/auth/legacy/legacytx/stdsign.go index 50d382517c..052e2f6fb3 100644 --- a/x/auth/legacy/legacytx/stdsign.go +++ b/x/auth/legacy/legacytx/stdsign.go @@ -12,6 +12,24 @@ import ( "github.com/line/lbm-sdk/types/tx/signing" ) +// LegacyMsg defines the old interface a message must fulfill, containing +// Amino signing method and legacy router info. +// Deprecated: Please use `Msg` instead. +type LegacyMsg interface { + sdk.Msg + + // Get the canonical byte representation of the Msg. + GetSignBytes() []byte + + // Return the message type. + // Must be alphanumeric or empty. + Route() string + + // Returns a human-readable string for the message, intended for utilization + // within tags + Type() string +} + // StdSignDoc is replay-prevention structure. // It includes the result of msg.GetSignBytes(), // as well as the ChainID (prevent cross chain replay) diff --git a/x/gov/abci_test.go b/x/gov/abci_test.go index 9aa97eea8e..8d352798d3 100644 --- a/x/gov/abci_test.go +++ b/x/gov/abci_test.go @@ -307,7 +307,7 @@ func TestProposalPassedEndblocker(t *testing.T) { deposits := initialModuleAccCoins.Add(proposal.TotalDeposit...).Add(proposalCoins...) require.True(t, moduleAccCoins.IsEqual(deposits)) - err = app.GovKeeper.AddVote(ctx, proposal.ProposalId, addrs[0], types.OptionYes) + err = app.GovKeeper.AddVote(ctx, proposal.ProposalId, addrs[0], types.NewNonSplitVoteOption(types.OptionYes)) require.NoError(t, err) newHeader := ctx.BlockHeader() @@ -348,7 +348,7 @@ func TestEndBlockerProposalHandlerFailed(t *testing.T) { handleAndCheck(t, gov.NewHandler(app.GovKeeper), ctx, newDepositMsg) - err = app.GovKeeper.AddVote(ctx, proposal.ProposalId, addrs[0], types.OptionYes) + err = app.GovKeeper.AddVote(ctx, proposal.ProposalId, addrs[0], types.NewNonSplitVoteOption(types.OptionYes)) require.NoError(t, err) newHeader := ctx.BlockHeader() diff --git a/x/gov/client/cli/cli_test.go b/x/gov/client/cli/cli_test.go index 59fe36ce49..3ec7cc4b0d 100644 --- a/x/gov/client/cli/cli_test.go +++ b/x/gov/client/cli/cli_test.go @@ -1,3 +1,4 @@ +//go:build norace // +build norace package cli_test @@ -60,6 +61,18 @@ func (s *IntegrationTestSuite) SetupSuite() { s.Require().NoError(err) _, err = s.network.WaitForHeight(1) s.Require().NoError(err) + + // create a proposal3 with deposit + _, err = govtestutil.MsgSubmitProposal(val.ClientCtx, val.Address.String(), + "Text Proposal 3", "Where is the title!?", types.ProposalTypeText, + fmt.Sprintf("--%s=%s", cli.FlagDeposit, sdk.NewCoin(s.cfg.BondDenom, types.DefaultMinDepositTokens).String())) + s.Require().NoError(err) + _, err = s.network.WaitForHeight(1) + s.Require().NoError(err) + + // vote for proposal3 as val + _, err = govtestutil.MsgVote(val.ClientCtx, val.Address.String(), "3", "yes=0.6,no=0.3,abstain=0.05,no_with_veto=0.05") + s.Require().NoError(err) } func (s *IntegrationTestSuite) TearDownSuite() { @@ -443,7 +456,7 @@ func (s *IntegrationTestSuite) TestCmdGetProposals() { var proposals types.QueryProposalsResponse s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &proposals), out.String()) - s.Require().Len(proposals.Proposals, 2) + s.Require().Len(proposals.Proposals, 3) } }) } @@ -684,9 +697,10 @@ func (s *IntegrationTestSuite) TestCmdQueryVote() { val := s.network.Validators[0] testCases := []struct { - name string - args []string - expectErr bool + name string + args []string + expectErr bool + expVoteOptions types.WeightedVoteOptions }{ { "get vote of non existing proposal", @@ -695,6 +709,7 @@ func (s *IntegrationTestSuite) TestCmdQueryVote() { val.Address.String(), }, true, + types.NewNonSplitVoteOption(types.OptionYes), }, { "get vote by wrong voter", @@ -703,6 +718,7 @@ func (s *IntegrationTestSuite) TestCmdQueryVote() { "wrong address", }, true, + types.NewNonSplitVoteOption(types.OptionYes), }, { "vote for valid proposal", @@ -712,6 +728,22 @@ func (s *IntegrationTestSuite) TestCmdQueryVote() { fmt.Sprintf("--%s=json", ostcli.OutputFlag), }, false, + types.NewNonSplitVoteOption(types.OptionYes), + }, + { + "split vote for valid proposal", + []string{ + "3", + val.Address.String(), + fmt.Sprintf("--%s=json", ostcli.OutputFlag), + }, + false, + types.WeightedVoteOptions{ + types.WeightedVoteOption{Option: types.OptionYes, Weight: sdk.NewDecWithPrec(60, 2)}, + types.WeightedVoteOption{Option: types.OptionNo, Weight: sdk.NewDecWithPrec(30, 2)}, + types.WeightedVoteOption{Option: types.OptionAbstain, Weight: sdk.NewDecWithPrec(5, 2)}, + types.WeightedVoteOption{Option: types.OptionNoWithVeto, Weight: sdk.NewDecWithPrec(5, 2)}, + }, }, } @@ -730,7 +762,11 @@ func (s *IntegrationTestSuite) TestCmdQueryVote() { var vote types.Vote s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &vote), out.String()) - s.Require().Equal(types.OptionYes, vote.Option) + s.Require().Equal(len(vote.Options), len(tc.expVoteOptions)) + for i, option := range tc.expVoteOptions { + s.Require().Equal(option.Option, vote.Options[i].Option) + s.Require().True(option.Weight.Equal(vote.Options[i].Weight)) + } } }) } diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 2087523259..030615f0e8 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -68,6 +68,7 @@ func NewTxCmd(propCmds []*cobra.Command) *cobra.Command { govTxCmd.AddCommand( NewCmdDeposit(), NewCmdVote(), + NewCmdWeightedVote(), cmdSubmitProp, ) @@ -247,3 +248,56 @@ $ %s tx gov vote 1 yes --from mykey return cmd } + +// NewCmdWeightedVote implements creating a new weighted vote command. +func NewCmdWeightedVote() *cobra.Command { + cmd := &cobra.Command{ + Use: "weighted-vote [proposal-id] [weighted-options]", + Args: cobra.ExactArgs(2), + Short: "Vote for an active proposal, options: yes/no/no_with_veto/abstain", + Long: strings.TrimSpace( + fmt.Sprintf(`Submit a vote for an active proposal. You can +find the proposal-id by running "%s query gov proposals". + +Example: +$ %s tx gov weighted-vote 1 yes=0.6,no=0.3,abstain=0.05,no_with_veto=0.05 --from mykey +`, + version.AppName, version.AppName, + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + // Get voter address + from := clientCtx.GetFromAddress() + + // validate that the proposal id is a uint + proposalID, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return fmt.Errorf("proposal-id %s not a valid int, please input a valid proposal-id", args[0]) + } + + // Figure out which vote options user chose + options, err := types.WeightedVoteOptionsFromString(govutils.NormalizeWeightedVoteOptions(args[1])) + if err != nil { + return err + } + + // Build vote message and run basic validation + msg := types.NewMsgVoteWeighted(from, proposalID, options) + err = msg.ValidateBasic() + if err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} diff --git a/x/gov/client/rest/grpc_query_test.go b/x/gov/client/rest/grpc_query_test.go index 1c7a84032d..ace6c285f0 100644 --- a/x/gov/client/rest/grpc_query_test.go +++ b/x/gov/client/rest/grpc_query_test.go @@ -1,5 +1,3 @@ -// +build norace - package rest_test import ( @@ -57,6 +55,18 @@ func (s *IntegrationTestSuite) SetupSuite() { s.Require().NoError(err) _, err = s.network.WaitForHeight(1) s.Require().NoError(err) + + // create a proposal3 with deposit + _, err = govtestutil.MsgSubmitProposal(val.ClientCtx, val.Address.String(), + "Text Proposal 3", "Where is the title!?", types.ProposalTypeText, + fmt.Sprintf("--%s=%s", cli.FlagDeposit, sdk.NewCoin(s.cfg.BondDenom, types.DefaultMinDepositTokens).String())) + s.Require().NoError(err) + _, err = s.network.WaitForHeight(1) + s.Require().NoError(err) + + // vote for proposal3 as val + _, err = govtestutil.MsgVote(val.ClientCtx, val.Address.String(), "3", "yes=0.6,no=0.3,abstain=0.05,no_with_veto=0.05") + s.Require().NoError(err) } func (s *IntegrationTestSuite) TestGetProposalGRPC() { @@ -126,7 +136,7 @@ func (s *IntegrationTestSuite) TestGetProposalsGRPC() { "valid request", fmt.Sprintf("%s/lbm/gov/v1/proposals", val.APIAddress), map[string]string{}, - 2, + 3, false, }, { @@ -163,29 +173,45 @@ func (s *IntegrationTestSuite) TestGetProposalVoteGRPC() { voterAddressBech32 := val.Address.String() testCases := []struct { - name string - url string - expErr bool + name string + url string + expErr bool + expVoteOptions types.WeightedVoteOptions }{ { "empty proposal", fmt.Sprintf("%s/lbm/gov/v1/proposals/%s/votes/%s", val.APIAddress, "", voterAddressBech32), true, + types.NewNonSplitVoteOption(types.OptionYes), }, { "get non existing proposal", fmt.Sprintf("%s/lbm/gov/v1/proposals/%s/votes/%s", val.APIAddress, "10", voterAddressBech32), true, + types.NewNonSplitVoteOption(types.OptionYes), }, { "get proposal with wrong voter address", fmt.Sprintf("%s/lbm/gov/v1/proposals/%s/votes/%s", val.APIAddress, "1", "wrongVoterAddress"), true, + types.NewNonSplitVoteOption(types.OptionYes), }, { "get proposal with id", fmt.Sprintf("%s/lbm/gov/v1/proposals/%s/votes/%s", val.APIAddress, "1", voterAddressBech32), false, + types.NewNonSplitVoteOption(types.OptionYes), + }, + { + "get proposal with id for split vote", + fmt.Sprintf("%s/lbm/gov/v1/proposals/%s/votes/%s", val.APIAddress, "3", voterAddressBech32), + false, + types.WeightedVoteOptions{ + types.WeightedVoteOption{Option: types.OptionYes, Weight: sdk.NewDecWithPrec(60, 2)}, + types.WeightedVoteOption{Option: types.OptionNo, Weight: sdk.NewDecWithPrec(30, 2)}, + types.WeightedVoteOption{Option: types.OptionAbstain, Weight: sdk.NewDecWithPrec(5, 2)}, + types.WeightedVoteOption{Option: types.OptionNoWithVeto, Weight: sdk.NewDecWithPrec(5, 2)}, + }, }, } @@ -203,7 +229,11 @@ func (s *IntegrationTestSuite) TestGetProposalVoteGRPC() { } else { s.Require().NoError(err) s.Require().NotEmpty(vote.Vote) - s.Require().Equal(types.OptionYes, vote.Vote.Option) + s.Require().Equal(len(vote.Vote.Options), len(tc.expVoteOptions)) + for i, option := range tc.expVoteOptions { + s.Require().Equal(option.Option, vote.Vote.Options[i].Option) + s.Require().True(option.Weight.Equal(vote.Vote.Options[i].Weight)) + } } }) } diff --git a/x/gov/client/rest/rest_test.go b/x/gov/client/rest/rest_test.go index 82ab9dd303..5fb15a2f34 100644 --- a/x/gov/client/rest/rest_test.go +++ b/x/gov/client/rest/rest_test.go @@ -1,10 +1,9 @@ -// +build norace - package rest_test import ( "fmt" + sdk "github.com/line/lbm-sdk/types" "github.com/line/lbm-sdk/types/rest" "github.com/line/lbm-sdk/x/gov/types" ) @@ -22,7 +21,7 @@ func (s *IntegrationTestSuite) TestLegacyGetAllProposals() { { "get all existing proposals", fmt.Sprintf("%s/gov/proposals", val.APIAddress), - 2, false, "", + 3, false, "", }, { "get proposals in deposit period", @@ -32,7 +31,7 @@ func (s *IntegrationTestSuite) TestLegacyGetAllProposals() { { "get proposals in voting period", fmt.Sprintf("%s/gov/proposals?status=voting_period", val.APIAddress), - 1, false, "", + 2, false, "", }, { "wrong status parameter", @@ -112,7 +111,7 @@ func (s *IntegrationTestSuite) TestLegacyGetVote() { s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(resp.Result, &vote)) s.Require().Equal(val.Address.String(), vote.Voter) // Note that option is now an int. - s.Require().Equal(types.VoteOption(1), vote.Option) + s.Require().Equal([]types.WeightedVoteOption{{types.VoteOption(1), sdk.NewDec(1)}}, vote.Options) } }) } diff --git a/x/gov/client/testutil/helpers.go b/x/gov/client/testutil/helpers.go index 3939ad1eea..b4ee6d6f66 100644 --- a/x/gov/client/testutil/helpers.go +++ b/x/gov/client/testutil/helpers.go @@ -41,5 +41,5 @@ func MsgVote(clientCtx client.Context, from, id, vote string, extraArgs ...strin args = append(args, extraArgs...) - return clitestutil.ExecTestCLICmd(clientCtx, govcli.NewCmdVote(), args) + return clitestutil.ExecTestCLICmd(clientCtx, govcli.NewCmdWeightedVote(), args) } diff --git a/x/gov/client/utils/query.go b/x/gov/client/utils/query.go index 58ea7e8ca2..e1482ceb2c 100644 --- a/x/gov/client/utils/query.go +++ b/x/gov/client/utils/query.go @@ -73,35 +73,83 @@ func QueryDepositsByTxQuery(clientCtx client.Context, params types.QueryProposal return bz, nil } +// combineEvents queries txs by events with all events from each event group, +// and combines all those events together. +// +// Tx are indexed in tendermint via their Msgs `Type()`, which can be: +// - via legacy Msgs (amino or proto), their `Type()` is a custom string, +// - via ADR-031 proto msgs, their `Type()` is the protobuf FQ method name. +// In searching for events, we search for both `Type()`s, and we use the +// `combineEvents` function here to merge events. +func combineEvents(clientCtx client.Context, page int, eventGroups ...[]string) (*sdk.SearchTxsResult, error) { + // Only the Txs field will be populated in the final SearchTxsResult. + allTxs := []*sdk.TxResponse{} + for _, events := range eventGroups { + res, err := authclient.QueryTxsByEvents(clientCtx, events, page, defaultLimit, "") + if err != nil { + return nil, err + } + allTxs = append(allTxs, res.Txs...) + } + + return &sdk.SearchTxsResult{Txs: allTxs}, nil +} + // QueryVotesByTxQuery will query for votes via a direct txs tags query. It // will fetch and build votes directly from the returned txs and return a JSON // marshalled result or any error that occurred. func QueryVotesByTxQuery(clientCtx client.Context, params types.QueryProposalVotesParams) ([]byte, error) { var ( - events = []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgVote), - fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), - } votes []types.Vote nextTxPage = defaultPage totalLimit = params.Limit * params.Page ) + // query interrupted either if we collected enough votes or tx indexer run out of relevant txs for len(votes) < totalLimit { - searchResult, err := authclient.QueryTxsByEvents(clientCtx, events, nextTxPage, defaultLimit, "") + // Search for both (legacy) votes and weighted votes. + searchResult, err := combineEvents( + clientCtx, nextTxPage, + // Query legacy Vote Msgs + []string{ + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgVote), + fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), + }, + // Query Vote proto Msgs + []string{ + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgVote{})), + fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), + }, + // Query legacy VoteWeighted Msgs + []string{ + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgVoteWeighted), + fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), + }, + // Query VoteWeighted proto Msgs + []string{ + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgVoteWeighted{})), + fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), + }, + ) if err != nil { return nil, err } - nextTxPage++ + for _, info := range searchResult.Txs { for _, msg := range info.GetTx().GetMsgs() { - if msg.Type() == types.TypeMsgVote { - voteMsg := msg.(*types.MsgVote) - + if voteMsg, ok := msg.(*types.MsgVote); ok { votes = append(votes, types.Vote{ Voter: voteMsg.Voter, ProposalId: params.ProposalID, - Option: voteMsg.Option, + Options: types.NewNonSplitVoteOption(voteMsg.Option), + }) + } + + if voteWeightedMsg, ok := msg.(*types.MsgVoteWeighted); ok { + votes = append(votes, types.Vote{ + Voter: voteWeightedMsg.Voter, + ProposalId: params.ProposalID, + Options: voteWeightedMsg.Options, }) } } @@ -109,6 +157,8 @@ func QueryVotesByTxQuery(clientCtx client.Context, params types.QueryProposalVot if len(searchResult.Txs) != defaultLimit { break } + + nextTxPage++ } start, end := client.Paginate(len(votes), params.Page, params.Limit, 100) if start < 0 || end < 0 { diff --git a/x/gov/client/utils/query_test.go b/x/gov/client/utils/query_test.go index f8eb69f9a5..12d12a0210 100644 --- a/x/gov/client/utils/query_test.go +++ b/x/gov/client/utils/query_test.go @@ -2,6 +2,7 @@ package utils_test import ( "context" + "regexp" "testing" "github.com/line/ostracon/rpc/client/mock" @@ -13,12 +14,14 @@ import ( "github.com/line/lbm-sdk/codec" "github.com/line/lbm-sdk/simapp" sdk "github.com/line/lbm-sdk/types" + "github.com/line/lbm-sdk/x/auth/legacy/legacytx" authtypes "github.com/line/lbm-sdk/x/auth/types" "github.com/line/lbm-sdk/x/gov/client/utils" "github.com/line/lbm-sdk/x/gov/types" ) type TxSearchMock struct { + txConfig client.TxConfig mock.Client txs []octypes.Tx } @@ -32,11 +35,34 @@ func (mock TxSearchMock) TxSearch(ctx context.Context, query string, prove bool, *perPage = 0 } + // Get the `message.action` value from the query. + messageAction := regexp.MustCompile(`message\.action='(.*)' .*$`) + msgType := messageAction.FindStringSubmatch(query)[1] + + // Filter only the txs that match the query + matchingTxs := make([]octypes.Tx, 0) + for _, tx := range mock.txs { + sdkTx, err := mock.txConfig.TxDecoder()(tx) + if err != nil { + return nil, err + } + for _, msg := range sdkTx.GetMsgs() { + if msg.(legacytx.LegacyMsg).Type() == msgType { + matchingTxs = append(matchingTxs, tx) + break + } + } + } + start, end := client.Paginate(len(mock.txs), *page, *perPage, 100) if start < 0 || end < 0 { // nil result with nil error crashes utils.QueryTxsByEvents return &ctypes.ResultTxSearch{}, nil } + if len(matchingTxs) < end { + return &ctypes.ResultTxSearch{}, nil + } + txs := mock.txs[start:end] rst := &ctypes.ResultTxSearch{Txs: make([]*ctypes.ResultTx, len(txs)), TotalCount: len(txs)} for i := range txs { @@ -77,7 +103,7 @@ func TestGetPaginatedVotes(t *testing.T) { } acc2Msgs := []sdk.Msg{ types.NewMsgVote(acc2, 0, types.OptionYes), - types.NewMsgVote(acc2, 0, types.OptionYes), + types.NewMsgVoteWeighted(acc2, 0, types.NewNonSplitVoteOption(types.OptionYes)), } for _, tc := range []testCase{ { @@ -89,8 +115,8 @@ func TestGetPaginatedVotes(t *testing.T) { acc2Msgs[:1], }, votes: []types.Vote{ - types.NewVote(0, acc1, types.OptionYes), - types.NewVote(0, acc2, types.OptionYes)}, + types.NewVote(0, acc1, types.NewNonSplitVoteOption(types.OptionYes)), + types.NewVote(0, acc2, types.NewNonSplitVoteOption(types.OptionYes))}, }, { description: "2MsgPerTx1Chunk", @@ -101,8 +127,8 @@ func TestGetPaginatedVotes(t *testing.T) { acc2Msgs, }, votes: []types.Vote{ - types.NewVote(0, acc1, types.OptionYes), - types.NewVote(0, acc1, types.OptionYes)}, + types.NewVote(0, acc1, types.NewNonSplitVoteOption(types.OptionYes)), + types.NewVote(0, acc1, types.NewNonSplitVoteOption(types.OptionYes))}, }, { description: "2MsgPerTx2Chunk", @@ -113,8 +139,8 @@ func TestGetPaginatedVotes(t *testing.T) { acc2Msgs, }, votes: []types.Vote{ - types.NewVote(0, acc2, types.OptionYes), - types.NewVote(0, acc2, types.OptionYes)}, + types.NewVote(0, acc2, types.NewNonSplitVoteOption(types.OptionYes)), + types.NewVote(0, acc2, types.NewNonSplitVoteOption(types.OptionYes))}, }, { description: "IncompleteSearchTx", @@ -123,7 +149,7 @@ func TestGetPaginatedVotes(t *testing.T) { msgs: [][]sdk.Msg{ acc1Msgs[:1], }, - votes: []types.Vote{types.NewVote(0, acc1, types.OptionYes)}, + votes: []types.Vote{types.NewVote(0, acc1, types.NewNonSplitVoteOption(types.OptionYes))}, }, { description: "InvalidPage", @@ -150,7 +176,7 @@ func TestGetPaginatedVotes(t *testing.T) { ) encodingConfig := simapp.MakeTestEncodingConfig() - cli := TxSearchMock{txs: marshalled} + cli := TxSearchMock{txs: marshalled, txConfig: encodingConfig.TxConfig} clientCtx := client.Context{}. WithLegacyAmino(cdc). WithClient(cli). diff --git a/x/gov/client/utils/utils.go b/x/gov/client/utils/utils.go index 3ed90fd3e8..3e6b2beff3 100644 --- a/x/gov/client/utils/utils.go +++ b/x/gov/client/utils/utils.go @@ -1,6 +1,10 @@ package utils -import "github.com/line/lbm-sdk/x/gov/types" +import ( + "strings" + + "github.com/line/lbm-sdk/x/gov/types" +) // NormalizeVoteOption - normalize user specified vote option func NormalizeVoteOption(option string) string { @@ -22,6 +26,20 @@ func NormalizeVoteOption(option string) string { } } +// NormalizeWeightedVoteOptions - normalize vote options param string +func NormalizeWeightedVoteOptions(options string) string { + newOptions := []string{} + for _, option := range strings.Split(options, ",") { + fields := strings.Split(option, "=") + fields[0] = NormalizeVoteOption(fields[0]) + if len(fields) < 2 { + fields = append(fields, "1") + } + newOptions = append(newOptions, strings.Join(fields, "=")) + } + return strings.Join(newOptions, ",") +} + //NormalizeProposalType - normalize user specified proposal type func NormalizeProposalType(proposalType string) string { switch proposalType { diff --git a/x/gov/client/utils/utils_test.go b/x/gov/client/utils/utils_test.go index 5fba2e40cf..18337d7a26 100644 --- a/x/gov/client/utils/utils_test.go +++ b/x/gov/client/utils/utils_test.go @@ -8,6 +8,55 @@ import ( "github.com/line/lbm-sdk/x/gov/client/utils" ) +func TestNormalizeWeightedVoteOptions(t *testing.T) { + cases := map[string]struct { + options string + normalized string + }{ + "simple Yes": { + options: "Yes", + normalized: "VOTE_OPTION_YES=1", + }, + "simple yes": { + options: "yes", + normalized: "VOTE_OPTION_YES=1", + }, + "formal yes": { + options: "yes=1", + normalized: "VOTE_OPTION_YES=1", + }, + "half yes half no": { + options: "yes=0.5,no=0.5", + normalized: "VOTE_OPTION_YES=0.5,VOTE_OPTION_NO=0.5", + }, + "3 options": { + options: "Yes=0.5,No=0.4,NoWithVeto=0.1", + normalized: "VOTE_OPTION_YES=0.5,VOTE_OPTION_NO=0.4,VOTE_OPTION_NO_WITH_VETO=0.1", + }, + "zero weight option": { + options: "Yes=0.5,No=0.5,NoWithVeto=0", + normalized: "VOTE_OPTION_YES=0.5,VOTE_OPTION_NO=0.5,VOTE_OPTION_NO_WITH_VETO=0", + }, + "minus weight option": { + options: "Yes=0.5,No=0.6,NoWithVeto=-0.1", + normalized: "VOTE_OPTION_YES=0.5,VOTE_OPTION_NO=0.6,VOTE_OPTION_NO_WITH_VETO=-0.1", + }, + "empty options": { + options: "", + normalized: "=1", + }, + "not available option": { + options: "Yessss=1", + normalized: "Yessss=1", + }, + } + + for _, tc := range cases { + normalized := utils.NormalizeWeightedVoteOptions(tc.options) + require.Equal(t, normalized, tc.normalized) + } +} + func TestNormalizeProposalStatus(t *testing.T) { type args struct { status string diff --git a/x/gov/handler.go b/x/gov/handler.go index c09bd4aca9..c73c34a930 100644 --- a/x/gov/handler.go +++ b/x/gov/handler.go @@ -27,6 +27,10 @@ func NewHandler(k keeper.Keeper) sdk.Handler { res, err := msgServer.Vote(sdk.WrapSDKContext(ctx), msg) return sdk.WrapServiceResult(ctx, res, err) + case *types.MsgVoteWeighted: + res, err := msgServer.VoteWeighted(sdk.WrapSDKContext(ctx), msg) + return sdk.WrapServiceResult(ctx, res, err) + default: return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", types.ModuleName, msg) } diff --git a/x/gov/keeper/grpc_query.go b/x/gov/keeper/grpc_query.go index 12ea0efae2..d3a7dbfc7f 100644 --- a/x/gov/keeper/grpc_query.go +++ b/x/gov/keeper/grpc_query.go @@ -133,6 +133,7 @@ func (q Keeper) Votes(c context.Context, req *types.QueryVotesRequest) (*types.Q if err := q.cdc.UnmarshalBinaryBare(value, &vote); err != nil { return err } + populateLegacyOption(&vote) votes = append(votes, vote) return nil diff --git a/x/gov/keeper/grpc_query_test.go b/x/gov/keeper/grpc_query_test.go index bc5cf39bf4..3eed8bb07e 100644 --- a/x/gov/keeper/grpc_query_test.go +++ b/x/gov/keeper/grpc_query_test.go @@ -183,7 +183,7 @@ func (suite *KeeperTestSuite) TestGRPCQueryProposals() { func() { testProposals[1].Status = types.StatusVotingPeriod app.GovKeeper.SetProposal(ctx, testProposals[1]) - suite.Require().NoError(app.GovKeeper.AddVote(ctx, testProposals[1].ProposalId, addrs[0], types.OptionAbstain)) + suite.Require().NoError(app.GovKeeper.AddVote(ctx, testProposals[1].ProposalId, addrs[0], types.NewNonSplitVoteOption(types.OptionAbstain))) req = &types.QueryProposalsRequest{ Voter: addrs[0].String(), @@ -291,14 +291,14 @@ func (suite *KeeperTestSuite) TestGRPCQueryVote() { func() { proposal.Status = types.StatusVotingPeriod app.GovKeeper.SetProposal(ctx, proposal) - suite.Require().NoError(app.GovKeeper.AddVote(ctx, proposal.ProposalId, addrs[0], types.OptionAbstain)) + suite.Require().NoError(app.GovKeeper.AddVote(ctx, proposal.ProposalId, addrs[0], types.NewNonSplitVoteOption(types.OptionAbstain))) req = &types.QueryVoteRequest{ ProposalId: proposal.ProposalId, Voter: addrs[0].String(), } - expRes = &types.QueryVoteResponse{Vote: types.NewVote(proposal.ProposalId, addrs[0], types.OptionAbstain)} + expRes = &types.QueryVoteResponse{Vote: types.Vote{ProposalId: proposal.ProposalId, Voter: addrs[0].String(), Option: types.OptionAbstain, Options: []types.WeightedVoteOption{{Option: types.OptionAbstain, Weight: sdk.MustNewDecFromStr("1.0")}}}} }, true, }, @@ -395,17 +395,13 @@ func (suite *KeeperTestSuite) TestGRPCQueryVotes() { app.GovKeeper.SetProposal(ctx, proposal) votes = []types.Vote{ - {proposal.ProposalId, addrs[0].String(), types.OptionAbstain}, - {proposal.ProposalId, addrs[1].String(), types.OptionYes}, - } - err1 := sdk.ValidateAccAddress(votes[0].Voter) - err2 := sdk.ValidateAccAddress(votes[1].Voter) - suite.Require().NoError(err1) - suite.Require().NoError(err2) - suite.Require().NoError(app.GovKeeper.AddVote(ctx, proposal.ProposalId, sdk.AccAddress(votes[0].Voter), - votes[0].Option)) - suite.Require().NoError(app.GovKeeper.AddVote(ctx, proposal.ProposalId, sdk.AccAddress(votes[1].Voter), - votes[1].Option)) + {ProposalId: proposal.ProposalId, Voter: addrs[0].String(), Option: types.OptionAbstain, Options: types.NewNonSplitVoteOption(types.OptionAbstain)}, + {ProposalId: proposal.ProposalId, Voter: addrs[1].String(), Option: types.OptionYes, Options: types.NewNonSplitVoteOption(types.OptionYes)}, + } + accAddr1 := sdk.AccAddress(votes[0].Voter) + accAddr2 := sdk.AccAddress(votes[1].Voter) + suite.Require().NoError(app.GovKeeper.AddVote(ctx, proposal.ProposalId, accAddr1, votes[0].Options)) + suite.Require().NoError(app.GovKeeper.AddVote(ctx, proposal.ProposalId, accAddr2, votes[1].Options)) req = &types.QueryVotesRequest{ ProposalId: proposal.ProposalId, @@ -771,9 +767,9 @@ func (suite *KeeperTestSuite) TestGRPCQueryTally() { proposal.Status = types.StatusVotingPeriod app.GovKeeper.SetProposal(ctx, proposal) - suite.Require().NoError(app.GovKeeper.AddVote(ctx, proposal.ProposalId, addrs[0], types.OptionYes)) - suite.Require().NoError(app.GovKeeper.AddVote(ctx, proposal.ProposalId, addrs[1], types.OptionYes)) - suite.Require().NoError(app.GovKeeper.AddVote(ctx, proposal.ProposalId, addrs[2], types.OptionYes)) + suite.Require().NoError(app.GovKeeper.AddVote(ctx, proposal.ProposalId, addrs[0], types.NewNonSplitVoteOption(types.OptionYes))) + suite.Require().NoError(app.GovKeeper.AddVote(ctx, proposal.ProposalId, addrs[1], types.NewNonSplitVoteOption(types.OptionYes))) + suite.Require().NoError(app.GovKeeper.AddVote(ctx, proposal.ProposalId, addrs[2], types.NewNonSplitVoteOption(types.OptionYes))) req = &types.QueryTallyResultRequest{ProposalId: proposal.ProposalId} diff --git a/x/gov/keeper/msg_server.go b/x/gov/keeper/msg_server.go index 0afb75227f..787d7b0dfc 100644 --- a/x/gov/keeper/msg_server.go +++ b/x/gov/keeper/msg_server.go @@ -62,7 +62,7 @@ func (k msgServer) SubmitProposal(goCtx context.Context, msg *types.MsgSubmitPro func (k msgServer) Vote(goCtx context.Context, msg *types.MsgVote) (*types.MsgVoteResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) accAddr := sdk.AccAddress(msg.Voter) - err := k.Keeper.AddVote(ctx, msg.ProposalId, accAddr, msg.Option) + err := k.Keeper.AddVote(ctx, msg.ProposalId, accAddr, types.NewNonSplitVoteOption(msg.Option)) if err != nil { return nil, err } @@ -86,6 +86,33 @@ func (k msgServer) Vote(goCtx context.Context, msg *types.MsgVote) (*types.MsgVo return &types.MsgVoteResponse{}, nil } +func (k msgServer) VoteWeighted(goCtx context.Context, msg *types.MsgVoteWeighted) (*types.MsgVoteWeightedResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + accAddr := sdk.AccAddress(msg.Voter) + err := k.Keeper.AddVote(ctx, msg.ProposalId, accAddr, msg.Options) + if err != nil { + return nil, err + } + + defer telemetry.IncrCounterWithLabels( + []string{types.ModuleName, "vote"}, + 1, + []metrics.Label{ + telemetry.NewLabel("proposal_id", strconv.Itoa(int(msg.ProposalId))), + }, + ) + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Voter), + ), + ) + + return &types.MsgVoteWeightedResponse{}, nil +} + func (k msgServer) Deposit(goCtx context.Context, msg *types.MsgDeposit) (*types.MsgDepositResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) accAddr := sdk.AccAddress(msg.Depositor) diff --git a/x/gov/keeper/proposal_test.go b/x/gov/keeper/proposal_test.go index 75a5fb3930..ed4907fac9 100644 --- a/x/gov/keeper/proposal_test.go +++ b/x/gov/keeper/proposal_test.go @@ -101,7 +101,7 @@ func TestGetProposalsFiltered(t *testing.T) { if i%2 == 0 { d := types.NewDeposit(proposalID, addr1, nil) - v := types.NewVote(proposalID, addr1, types.OptionYes) + v := types.NewVote(proposalID, addr1, types.NewNonSplitVoteOption(types.OptionYes)) app.GovKeeper.SetDeposit(ctx, d) app.GovKeeper.SetVote(ctx, v) } diff --git a/x/gov/keeper/querier_test.go b/x/gov/keeper/querier_test.go index 3956d0edbe..37e41de7f4 100644 --- a/x/gov/keeper/querier_test.go +++ b/x/gov/keeper/querier_test.go @@ -252,13 +252,13 @@ func TestQueries(t *testing.T) { require.Equal(t, proposal3, proposals[1]) // Addrs[0] votes on proposals #2 & #3 - vote1 := types.NewVote(proposal2.ProposalId, TestAddrs[0], types.OptionYes) - vote2 := types.NewVote(proposal3.ProposalId, TestAddrs[0], types.OptionYes) + vote1 := types.NewVote(proposal2.ProposalId, TestAddrs[0], types.NewNonSplitVoteOption(types.OptionYes)) + vote2 := types.NewVote(proposal3.ProposalId, TestAddrs[0], types.NewNonSplitVoteOption(types.OptionYes)) app.GovKeeper.SetVote(ctx, vote1) app.GovKeeper.SetVote(ctx, vote2) // Addrs[1] votes on proposal #3 - vote3 := types.NewVote(proposal3.ProposalId, TestAddrs[1], types.OptionYes) + vote3 := types.NewVote(proposal3.ProposalId, TestAddrs[1], types.NewNonSplitVoteOption(types.OptionYes)) app.GovKeeper.SetVote(ctx, vote3) // Test query voted by TestAddrs[0] @@ -269,16 +269,16 @@ func TestQueries(t *testing.T) { // Test query votes on types.Proposal 2 votes := getQueriedVotes(t, ctx, legacyQuerierCdc, querier, proposal2.ProposalId, 1, 0) require.Len(t, votes, 1) - require.Equal(t, vote1, votes[0]) + checkEqualVotes(t, vote1, votes[0]) vote := getQueriedVote(t, ctx, legacyQuerierCdc, querier, proposal2.ProposalId, TestAddrs[0]) - require.Equal(t, vote1, vote) + checkEqualVotes(t, vote1, vote) // Test query votes on types.Proposal 3 votes = getQueriedVotes(t, ctx, legacyQuerierCdc, querier, proposal3.ProposalId, 1, 0) require.Len(t, votes, 2) - require.Equal(t, vote2, votes[0]) - require.Equal(t, vote3, votes[1]) + checkEqualVotes(t, vote2, votes[0]) + checkEqualVotes(t, vote3, votes[1]) // Test query all proposals proposals = getQueriedProposals(t, ctx, legacyQuerierCdc, querier, "", "", types.StatusNil, 1, 0) @@ -375,3 +375,14 @@ func TestPaginatedVotesQuery(t *testing.T) { }) } } + +// checkEqualVotes checks that two votes are equal, without taking into account +// graceful fallback for `Option`. +// When querying, the keeper populates the `vote.Option` field when there's +// only 1 vote, this function checks equality of structs while skipping that +// field. +func checkEqualVotes(t *testing.T, vote1, vote2 types.Vote) { + require.Equal(t, vote1.Options, vote2.Options) + require.Equal(t, vote1.Voter, vote2.Voter) + require.Equal(t, vote1.ProposalId, vote2.ProposalId) +} diff --git a/x/gov/keeper/tally.go b/x/gov/keeper/tally.go index 0cd63bc10a..fddf67515a 100644 --- a/x/gov/keeper/tally.go +++ b/x/gov/keeper/tally.go @@ -27,7 +27,7 @@ func (keeper Keeper) Tally(ctx sdk.Context, proposal types.Proposal) (passes boo validator.GetBondedTokens(), validator.GetDelegatorShares(), sdk.ZeroDec(), - types.OptionEmpty, + types.WeightedVoteOptions{}, ) return false @@ -43,7 +43,7 @@ func (keeper Keeper) Tally(ctx sdk.Context, proposal types.Proposal) (passes boo valAddrStr := sdk.BytesToValAddress(voterBytes).String() if val, ok := currValidators[valAddrStr]; ok { - val.Vote = vote.Option + val.Vote = vote.Options currValidators[valAddrStr] = val } @@ -61,7 +61,10 @@ func (keeper Keeper) Tally(ctx sdk.Context, proposal types.Proposal) (passes boo // delegation shares * bonded / total shares votingPower := delegation.GetShares().MulInt(val.BondedTokens).Quo(val.DelegatorShares) - results[vote.Option] = results[vote.Option].Add(votingPower) + for _, option := range vote.Options { + subPower := votingPower.Mul(option.Weight) + results[option.Option] = results[option.Option].Add(subPower) + } totalVotingPower = totalVotingPower.Add(votingPower) } @@ -74,14 +77,17 @@ func (keeper Keeper) Tally(ctx sdk.Context, proposal types.Proposal) (passes boo // iterate over the validators again to tally their voting power for _, val := range currValidators { - if val.Vote == types.OptionEmpty { + if len(val.Vote) == 0 { continue } sharesAfterDeductions := val.DelegatorShares.Sub(val.DelegatorDeductions) votingPower := sharesAfterDeductions.MulInt(val.BondedTokens).Quo(val.DelegatorShares) - results[val.Vote] = results[val.Vote].Add(votingPower) + for _, option := range val.Vote { + subPower := votingPower.Mul(option.Weight) + results[option.Option] = results[option.Option].Add(subPower) + } totalVotingPower = totalVotingPower.Add(votingPower) } diff --git a/x/gov/keeper/tally_test.go b/x/gov/keeper/tally_test.go index 1fba7fdf3d..c9b9954e67 100644 --- a/x/gov/keeper/tally_test.go +++ b/x/gov/keeper/tally_test.go @@ -50,7 +50,7 @@ func TestTallyNoQuorum(t *testing.T) { proposal.Status = types.StatusVotingPeriod app.GovKeeper.SetProposal(ctx, proposal) - err = app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.OptionYes) + err = app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.NewNonSplitVoteOption(types.OptionYes)) require.Nil(t, err) proposal, ok := app.GovKeeper.GetProposal(ctx, proposalID) @@ -73,9 +73,9 @@ func TestTallyOnlyValidatorsAllYes(t *testing.T) { proposal.Status = types.StatusVotingPeriod app.GovKeeper.SetProposal(ctx, proposal) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.OptionYes)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[1], types.OptionYes)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[2], types.OptionYes)) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.NewNonSplitVoteOption(types.OptionYes))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[1], types.NewNonSplitVoteOption(types.OptionYes))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[2], types.NewNonSplitVoteOption(types.OptionYes))) proposal, ok := app.GovKeeper.GetProposal(ctx, proposalID) require.True(t, ok) @@ -99,8 +99,8 @@ func TestTallyOnlyValidators51No(t *testing.T) { proposal.Status = types.StatusVotingPeriod app.GovKeeper.SetProposal(ctx, proposal) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[0], types.OptionNo)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[1], types.OptionYes)) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[0], types.NewNonSplitVoteOption(types.OptionNo))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[1], types.NewNonSplitVoteOption(types.OptionYes))) proposal, ok := app.GovKeeper.GetProposal(ctx, proposalID) require.True(t, ok) @@ -123,8 +123,8 @@ func TestTallyOnlyValidators51Yes(t *testing.T) { proposal.Status = types.StatusVotingPeriod app.GovKeeper.SetProposal(ctx, proposal) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[0], types.OptionYes)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[1], types.OptionNo)) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[0], types.NewNonSplitVoteOption(types.OptionYes))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[1], types.NewNonSplitVoteOption(types.OptionNo))) proposal, ok := app.GovKeeper.GetProposal(ctx, proposalID) require.True(t, ok) @@ -148,9 +148,9 @@ func TestTallyOnlyValidatorsVetoed(t *testing.T) { proposal.Status = types.StatusVotingPeriod app.GovKeeper.SetProposal(ctx, proposal) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[0], types.OptionYes)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[1], types.OptionYes)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[2], types.OptionNoWithVeto)) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[0], types.NewNonSplitVoteOption(types.OptionYes))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[1], types.NewNonSplitVoteOption(types.OptionYes))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[2], types.NewNonSplitVoteOption(types.OptionNoWithVeto))) proposal, ok := app.GovKeeper.GetProposal(ctx, proposalID) require.True(t, ok) @@ -174,9 +174,9 @@ func TestTallyOnlyValidatorsAbstainPasses(t *testing.T) { proposal.Status = types.StatusVotingPeriod app.GovKeeper.SetProposal(ctx, proposal) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[0], types.OptionAbstain)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[1], types.OptionNo)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[2], types.OptionYes)) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[0], types.NewNonSplitVoteOption(types.OptionAbstain))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[1], types.NewNonSplitVoteOption(types.OptionNo))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[2], types.NewNonSplitVoteOption(types.OptionYes))) proposal, ok := app.GovKeeper.GetProposal(ctx, proposalID) require.True(t, ok) @@ -200,9 +200,9 @@ func TestTallyOnlyValidatorsAbstainFails(t *testing.T) { proposal.Status = types.StatusVotingPeriod app.GovKeeper.SetProposal(ctx, proposal) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[0], types.OptionAbstain)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[1], types.OptionYes)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[2], types.OptionNo)) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[0], types.NewNonSplitVoteOption(types.OptionAbstain))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[1], types.NewNonSplitVoteOption(types.OptionYes))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddrs[2], types.NewNonSplitVoteOption(types.OptionNo))) proposal, ok := app.GovKeeper.GetProposal(ctx, proposalID) require.True(t, ok) @@ -227,8 +227,8 @@ func TestTallyOnlyValidatorsNonVoter(t *testing.T) { proposal.Status = types.StatusVotingPeriod app.GovKeeper.SetProposal(ctx, proposal) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddr1, types.OptionYes)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddr2, types.OptionNo)) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddr1, types.NewNonSplitVoteOption(types.OptionYes))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, valAccAddr2, types.NewNonSplitVoteOption(types.OptionNo))) proposal, ok := app.GovKeeper.GetProposal(ctx, proposalID) require.True(t, ok) @@ -261,10 +261,10 @@ func TestTallyDelgatorOverride(t *testing.T) { proposal.Status = types.StatusVotingPeriod app.GovKeeper.SetProposal(ctx, proposal) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[1], types.OptionYes)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[2], types.OptionYes)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[3], types.OptionYes)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[4], types.OptionNo)) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[1], types.NewNonSplitVoteOption(types.OptionYes))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[2], types.NewNonSplitVoteOption(types.OptionYes))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[3], types.NewNonSplitVoteOption(types.OptionYes))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[4], types.NewNonSplitVoteOption(types.OptionNo))) proposal, ok := app.GovKeeper.GetProposal(ctx, proposalID) require.True(t, ok) @@ -297,9 +297,9 @@ func TestTallyDelgatorInherit(t *testing.T) { proposal.Status = types.StatusVotingPeriod app.GovKeeper.SetProposal(ctx, proposal) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.OptionNo)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[1], types.OptionNo)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[2], types.OptionYes)) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.NewNonSplitVoteOption(types.OptionNo))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[1], types.NewNonSplitVoteOption(types.OptionNo))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[2], types.NewNonSplitVoteOption(types.OptionYes))) proposal, ok := app.GovKeeper.GetProposal(ctx, proposalID) require.True(t, ok) @@ -336,10 +336,10 @@ func TestTallyDelgatorMultipleOverride(t *testing.T) { proposal.Status = types.StatusVotingPeriod app.GovKeeper.SetProposal(ctx, proposal) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.OptionYes)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[1], types.OptionYes)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[2], types.OptionYes)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[3], types.OptionNo)) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.NewNonSplitVoteOption(types.OptionYes))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[1], types.NewNonSplitVoteOption(types.OptionYes))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[2], types.NewNonSplitVoteOption(types.OptionYes))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[3], types.NewNonSplitVoteOption(types.OptionNo))) proposal, ok := app.GovKeeper.GetProposal(ctx, proposalID) require.True(t, ok) @@ -378,9 +378,9 @@ func TestTallyDelgatorMultipleInherit(t *testing.T) { proposal.Status = types.StatusVotingPeriod app.GovKeeper.SetProposal(ctx, proposal) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.OptionYes)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[1], types.OptionNo)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[2], types.OptionNo)) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.NewNonSplitVoteOption(types.OptionYes))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[1], types.NewNonSplitVoteOption(types.OptionNo))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[2], types.NewNonSplitVoteOption(types.OptionNo))) proposal, ok := app.GovKeeper.GetProposal(ctx, proposalID) require.True(t, ok) @@ -421,9 +421,9 @@ func TestTallyJailedValidator(t *testing.T) { proposal.Status = types.StatusVotingPeriod app.GovKeeper.SetProposal(ctx, proposal) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.OptionYes)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[1], types.OptionNo)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[2], types.OptionNo)) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.NewNonSplitVoteOption(types.OptionYes))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[1], types.NewNonSplitVoteOption(types.OptionNo))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[2], types.NewNonSplitVoteOption(types.OptionNo))) proposal, ok := app.GovKeeper.GetProposal(ctx, proposalID) require.True(t, ok) @@ -454,9 +454,9 @@ func TestTallyValidatorMultipleDelegations(t *testing.T) { proposal.Status = types.StatusVotingPeriod app.GovKeeper.SetProposal(ctx, proposal) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.OptionYes)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[1], types.OptionNo)) - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[2], types.OptionYes)) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.NewNonSplitVoteOption(types.OptionYes))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[1], types.NewNonSplitVoteOption(types.OptionNo))) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[2], types.NewNonSplitVoteOption(types.OptionYes))) proposal, ok := app.GovKeeper.GetProposal(ctx, proposalID) require.True(t, ok) diff --git a/x/gov/keeper/vote.go b/x/gov/keeper/vote.go index 3135913ec7..dbfdcf9c5c 100644 --- a/x/gov/keeper/vote.go +++ b/x/gov/keeper/vote.go @@ -9,7 +9,7 @@ import ( ) // AddVote adds a vote on a specific proposal -func (keeper Keeper) AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, option types.VoteOption) error { +func (keeper Keeper) AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, options types.WeightedVoteOptions) error { proposal, ok := keeper.GetProposal(ctx, proposalID) if !ok { return sdkerrors.Wrapf(types.ErrUnknownProposal, "%d", proposalID) @@ -18,17 +18,19 @@ func (keeper Keeper) AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.A return sdkerrors.Wrapf(types.ErrInactiveProposal, "%d", proposalID) } - if !types.ValidVoteOption(option) { - return sdkerrors.Wrap(types.ErrInvalidVote, option.String()) + for _, option := range options { + if !types.ValidWeightedVoteOption(option) { + return sdkerrors.Wrap(types.ErrInvalidVote, options.String()) + } } - vote := types.NewVote(proposalID, voterAddr, option) + vote := types.NewVote(proposalID, voterAddr, options) keeper.SetVote(ctx, vote) ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeProposalVote, - sdk.NewAttribute(types.AttributeKeyOption, option.String()), + sdk.NewAttribute(types.AttributeKeyOption, options.String()), sdk.NewAttribute(types.AttributeKeyProposalID, fmt.Sprintf("%d", proposalID)), ), ) @@ -39,6 +41,7 @@ func (keeper Keeper) AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.A // GetAllVotes returns all the votes from the store func (keeper Keeper) GetAllVotes(ctx sdk.Context) (votes types.Votes) { keeper.IterateAllVotes(ctx, func(vote types.Vote) bool { + populateLegacyOption(&vote) votes = append(votes, vote) return false }) @@ -48,6 +51,7 @@ func (keeper Keeper) GetAllVotes(ctx sdk.Context) (votes types.Votes) { // GetVotes returns all the votes from a proposal func (keeper Keeper) GetVotes(ctx sdk.Context, proposalID uint64) (votes types.Votes) { keeper.IterateVotes(ctx, proposalID, func(vote types.Vote) bool { + populateLegacyOption(&vote) votes = append(votes, vote) return false }) @@ -63,11 +67,18 @@ func (keeper Keeper) GetVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.A } keeper.cdc.MustUnmarshalBinaryBare(bz, &vote) + populateLegacyOption(&vote) + return vote, true } // SetVote sets a Vote to the gov store func (keeper Keeper) SetVote(ctx sdk.Context, vote types.Vote) { + // vote.Option is a deprecated field, we don't set it in state + if vote.Option != types.OptionEmpty { //nolint + vote.Option = types.OptionEmpty //nolint + } + store := ctx.KVStore(keeper.storeKey) bz := keeper.cdc.MustMarshalBinaryBare(&vote) addr := sdk.AccAddress(vote.Voter) @@ -83,6 +94,7 @@ func (keeper Keeper) IterateAllVotes(ctx sdk.Context, cb func(vote types.Vote) ( for ; iterator.Valid(); iterator.Next() { var vote types.Vote keeper.cdc.MustUnmarshalBinaryBare(iterator.Value(), &vote) + populateLegacyOption(&vote) if cb(vote) { break @@ -99,6 +111,7 @@ func (keeper Keeper) IterateVotes(ctx sdk.Context, proposalID uint64, cb func(vo for ; iterator.Valid(); iterator.Next() { var vote types.Vote keeper.cdc.MustUnmarshalBinaryBare(iterator.Value(), &vote) + populateLegacyOption(&vote) if cb(vote) { break @@ -111,3 +124,11 @@ func (keeper Keeper) deleteVote(ctx sdk.Context, proposalID uint64, voterAddr sd store := ctx.KVStore(keeper.storeKey) store.Delete(types.VoteKey(proposalID, voterAddr)) } + +// populateLegacyOption adds graceful fallback of deprecated `Option` field, in case +// there's only 1 VoteOption. +func populateLegacyOption(vote *types.Vote) { + if len(vote.Options) == 1 && vote.Options[0].Weight.Equal(sdk.MustNewDecFromStr("1.0")) { + vote.Option = vote.Options[0].Option //nolint + } +} diff --git a/x/gov/keeper/vote_test.go b/x/gov/keeper/vote_test.go index 3c57756de1..604e4780c1 100644 --- a/x/gov/keeper/vote_test.go +++ b/x/gov/keeper/vote_test.go @@ -24,37 +24,55 @@ func TestVotes(t *testing.T) { var invalidOption types.VoteOption = 0x10 - require.Error(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.OptionYes), "proposal not on voting period") - require.Error(t, app.GovKeeper.AddVote(ctx, 10, addrs[0], types.OptionYes), "invalid proposal ID") + require.Error(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.NewNonSplitVoteOption(types.OptionYes)), "proposal not on voting period") + require.Error(t, app.GovKeeper.AddVote(ctx, 10, addrs[0], types.NewNonSplitVoteOption(types.OptionYes)), "invalid proposal ID") proposal.Status = types.StatusVotingPeriod app.GovKeeper.SetProposal(ctx, proposal) - require.Error(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], invalidOption), "invalid option") + require.Error(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.NewNonSplitVoteOption(invalidOption)), "invalid option") // Test first vote - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.OptionAbstain)) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.NewNonSplitVoteOption(types.OptionAbstain))) vote, found := app.GovKeeper.GetVote(ctx, proposalID, addrs[0]) require.True(t, found) require.Equal(t, addrs[0].String(), vote.Voter) require.Equal(t, proposalID, vote.ProposalId) + require.True(t, len(vote.Options) == 1) + require.Equal(t, types.OptionAbstain, vote.Options[0].Option) require.Equal(t, types.OptionAbstain, vote.Option) // Test change of vote - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.OptionYes)) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[0], types.NewNonSplitVoteOption(types.OptionYes))) vote, found = app.GovKeeper.GetVote(ctx, proposalID, addrs[0]) require.True(t, found) require.Equal(t, addrs[0].String(), vote.Voter) require.Equal(t, proposalID, vote.ProposalId) + require.True(t, len(vote.Options) == 1) + require.Equal(t, types.OptionYes, vote.Options[0].Option) require.Equal(t, types.OptionYes, vote.Option) // Test second vote - require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[1], types.OptionNoWithVeto)) + require.NoError(t, app.GovKeeper.AddVote(ctx, proposalID, addrs[1], types.WeightedVoteOptions{ + types.WeightedVoteOption{Option: types.OptionYes, Weight: sdk.NewDecWithPrec(60, 2)}, + types.WeightedVoteOption{Option: types.OptionNo, Weight: sdk.NewDecWithPrec(30, 2)}, + types.WeightedVoteOption{Option: types.OptionAbstain, Weight: sdk.NewDecWithPrec(5, 2)}, + types.WeightedVoteOption{Option: types.OptionNoWithVeto, Weight: sdk.NewDecWithPrec(5, 2)}, + })) vote, found = app.GovKeeper.GetVote(ctx, proposalID, addrs[1]) require.True(t, found) require.Equal(t, addrs[1].String(), vote.Voter) require.Equal(t, proposalID, vote.ProposalId) - require.Equal(t, types.OptionNoWithVeto, vote.Option) + require.True(t, len(vote.Options) == 4) + require.Equal(t, types.OptionYes, vote.Options[0].Option) + require.Equal(t, types.OptionNo, vote.Options[1].Option) + require.Equal(t, types.OptionAbstain, vote.Options[2].Option) + require.Equal(t, types.OptionNoWithVeto, vote.Options[3].Option) + require.True(t, vote.Options[0].Weight.Equal(sdk.NewDecWithPrec(60, 2))) + require.True(t, vote.Options[1].Weight.Equal(sdk.NewDecWithPrec(30, 2))) + require.True(t, vote.Options[2].Weight.Equal(sdk.NewDecWithPrec(5, 2))) + require.True(t, vote.Options[3].Weight.Equal(sdk.NewDecWithPrec(5, 2))) + require.Equal(t, types.OptionEmpty, vote.Option) // Test vote iterator // NOTE order of deposits is determined by the addresses @@ -63,8 +81,14 @@ func TestVotes(t *testing.T) { require.Equal(t, votes, app.GovKeeper.GetVotes(ctx, proposalID)) require.Equal(t, addrs[0].String(), votes[0].Voter) require.Equal(t, proposalID, votes[0].ProposalId) - require.Equal(t, types.OptionYes, votes[0].Option) + require.True(t, len(votes[0].Options) == 1) + require.Equal(t, types.OptionYes, votes[0].Options[0].Option) require.Equal(t, addrs[1].String(), votes[1].Voter) require.Equal(t, proposalID, votes[1].ProposalId) - require.Equal(t, types.OptionNoWithVeto, votes[1].Option) + require.True(t, len(votes[1].Options) == 4) + require.True(t, votes[1].Options[0].Weight.Equal(sdk.NewDecWithPrec(60, 2))) + require.True(t, votes[1].Options[1].Weight.Equal(sdk.NewDecWithPrec(30, 2))) + require.True(t, votes[1].Options[2].Weight.Equal(sdk.NewDecWithPrec(5, 2))) + require.True(t, votes[1].Options[3].Weight.Equal(sdk.NewDecWithPrec(5, 2))) + require.Equal(t, types.OptionEmpty, vote.Option) } diff --git a/x/gov/simulation/decoder_test.go b/x/gov/simulation/decoder_test.go index ce9ec677db..9e1370db08 100644 --- a/x/gov/simulation/decoder_test.go +++ b/x/gov/simulation/decoder_test.go @@ -34,7 +34,7 @@ func TestDecodeStore(t *testing.T) { proposalIDBz := make([]byte, 8) binary.LittleEndian.PutUint64(proposalIDBz, 1) deposit := types.NewDeposit(1, delAddr1, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.OneInt()))) - vote := types.NewVote(1, delAddr1, types.OptionYes) + vote := types.NewVote(1, delAddr1, types.NewNonSplitVoteOption(types.OptionYes)) proposalBz, err := cdc.MarshalBinaryBare(&proposal) require.NoError(t, err) diff --git a/x/gov/simulation/operations.go b/x/gov/simulation/operations.go index a2a381d2e3..933038dc2b 100644 --- a/x/gov/simulation/operations.go +++ b/x/gov/simulation/operations.go @@ -20,8 +20,9 @@ var initialProposalID = uint64(100000000000000) // Simulation operation weights constants const ( - OpWeightMsgDeposit = "op_weight_msg_deposit" - OpWeightMsgVote = "op_weight_msg_vote" + OpWeightMsgDeposit = "op_weight_msg_deposit" + OpWeightMsgVote = "op_weight_msg_vote" + OpWeightMsgVoteWeighted = "op_weight_msg_weighted_vote" ) // WeightedOperations returns all the operations from the module with their respective weights @@ -31,8 +32,9 @@ func WeightedOperations( ) simulation.WeightedOperations { var ( - weightMsgDeposit int - weightMsgVote int + weightMsgDeposit int + weightMsgVote int + weightMsgVoteWeighted int ) appParams.GetOrGenerate(cdc, OpWeightMsgDeposit, &weightMsgDeposit, nil, @@ -47,6 +49,12 @@ func WeightedOperations( }, ) + appParams.GetOrGenerate(cdc, OpWeightMsgVoteWeighted, &weightMsgVoteWeighted, nil, + func(_ *rand.Rand) { + weightMsgVoteWeighted = simappparams.DefaultWeightMsgVoteWeighted + }, + ) + // generate the weighted operations for the proposal contents var wProposalOps simulation.WeightedOperations @@ -74,6 +82,10 @@ func WeightedOperations( weightMsgVote, SimulateMsgVote(ak, bk, k), ), + simulation.NewWeightedOperation( + weightMsgVoteWeighted, + SimulateMsgVoteWeighted(ak, bk, k), + ), } return append(wProposalOps, wGovOps...) @@ -315,6 +327,69 @@ func operationSimulateMsgVote(ak types.AccountKeeper, bk types.BankKeeper, k kee } } +// SimulateMsgVoteWeighted generates a MsgVoteWeighted with random values. +func SimulateMsgVoteWeighted(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { + return operationSimulateMsgVoteWeighted(ak, bk, k, simtypes.Account{}, -1) +} + +func operationSimulateMsgVoteWeighted(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper, + simAccount simtypes.Account, proposalIDInt int64) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + if simAccount.Equals(simtypes.Account{}) { + simAccount, _ = simtypes.RandomAcc(r, accs) + } + + var proposalID uint64 + + switch { + case proposalIDInt < 0: + var ok bool + proposalID, ok = randomProposalID(r, k, ctx, types.StatusVotingPeriod) + if !ok { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgVoteWeighted, "unable to generate proposalID"), nil, nil + } + default: + proposalID = uint64(proposalIDInt) + } + + options := randomWeightedVotingOptions(r) + msg := types.NewMsgVoteWeighted(simAccount.Address, proposalID, options) + + account := ak.GetAccount(ctx, simAccount.Address) + spendable := bk.SpendableCoins(ctx, account.GetAddress()) + + fees, err := simtypes.RandomFees(r, ctx, spendable) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate fees"), nil, err + } + + txGen := simappparams.MakeTestEncodingConfig().TxConfig + tx, err := helpers.GenTx( + txGen, + []sdk.Msg{msg}, + fees, + helpers.DefaultGenTxGas, + ctx.ChainID(), + []uint64{0}, + []uint64{account.GetSequence()}, + simAccount.PrivKey, + ) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err + } + + _, _, err = app.Deliver(txGen.TxEncoder(), tx) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, err + } + + return simtypes.NewOperationMsg(msg, true, ""), nil, nil + } +} + // Pick a random deposit with a random denomination with a // deposit amount between (0, min(balance, minDepositAmount)) // This is to simulate multiple users depositing to get the @@ -393,3 +468,37 @@ func randomVotingOption(r *rand.Rand) types.VoteOption { panic("invalid vote option") } } + +// Pick a random weighted voting options +func randomWeightedVotingOptions(r *rand.Rand) types.WeightedVoteOptions { + w1 := r.Intn(100 + 1) + w2 := r.Intn(100 - w1 + 1) + w3 := r.Intn(100 - w1 - w2 + 1) + w4 := 100 - w1 - w2 - w3 + weightedVoteOptions := types.WeightedVoteOptions{} + if w1 > 0 { + weightedVoteOptions = append(weightedVoteOptions, types.WeightedVoteOption{ + Option: types.OptionYes, + Weight: sdk.NewDecWithPrec(int64(w1), 2), + }) + } + if w2 > 0 { + weightedVoteOptions = append(weightedVoteOptions, types.WeightedVoteOption{ + Option: types.OptionAbstain, + Weight: sdk.NewDecWithPrec(int64(w2), 2), + }) + } + if w3 > 0 { + weightedVoteOptions = append(weightedVoteOptions, types.WeightedVoteOption{ + Option: types.OptionNo, + Weight: sdk.NewDecWithPrec(int64(w3), 2), + }) + } + if w4 > 0 { + weightedVoteOptions = append(weightedVoteOptions, types.WeightedVoteOption{ + Option: types.OptionNoWithVeto, + Weight: sdk.NewDecWithPrec(int64(w4), 2), + }) + } + return weightedVoteOptions +} diff --git a/x/gov/simulation/operations_test.go b/x/gov/simulation/operations_test.go index ae3a4914a8..baf8869937 100644 --- a/x/gov/simulation/operations_test.go +++ b/x/gov/simulation/operations_test.go @@ -80,6 +80,7 @@ func TestWeightedOperations(t *testing.T) { {2, types.ModuleName, "submit_proposal"}, {simappparams.DefaultWeightMsgDeposit, types.ModuleName, types.TypeMsgDeposit}, {simappparams.DefaultWeightMsgVote, types.ModuleName, types.TypeMsgVote}, + {simappparams.DefaultWeightMsgVoteWeighted, types.ModuleName, types.TypeMsgVoteWeighted}, } for i, w := range weightesOps { @@ -208,6 +209,48 @@ func TestSimulateMsgVote(t *testing.T) { } +// TestSimulateMsgVoteWeighted tests the normal scenario of a valid message of type TypeMsgVoteWeighted. +// Abonormal scenarios, where the message is created by an errors are not tested here. +func TestSimulateMsgVoteWeighted(t *testing.T) { + app, ctx := createTestApp(false) + blockTime := time.Now().UTC() + ctx = ctx.WithBlockTime(blockTime) + + // setup 3 accounts + s := rand.NewSource(1) + r := rand.New(s) + accounts := getTestingAccounts(t, r, app, ctx, 3) + + // setup a proposal + content := types.NewTextProposal("Test", "description") + + submitTime := ctx.BlockHeader().Time + depositPeriod := app.GovKeeper.GetDepositParams(ctx).MaxDepositPeriod + + proposal, err := types.NewProposal(content, 1, submitTime, submitTime.Add(depositPeriod)) + require.NoError(t, err) + + app.GovKeeper.ActivateVotingPeriod(ctx, proposal) + + // begin a new block + app.BeginBlock(abci.RequestBeginBlock{Header: ocproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash, Time: blockTime}}) + + // execute operation + op := simulation.SimulateMsgVoteWeighted(app.AccountKeeper, app.BankKeeper, app.GovKeeper) + operationMsg, _, err := op(r, app.BaseApp, ctx, accounts, "") + require.NoError(t, err) + + var msg types.MsgVoteWeighted + types.ModuleCdc.UnmarshalJSON(operationMsg.Msg, &msg) + + require.True(t, operationMsg.OK) + require.Equal(t, uint64(1), msg.ProposalId) + require.Equal(t, "link1ghekyjucln7y67ntx7cf27m9dpuxxemnqk82wt", msg.Voter) + require.True(t, len(msg.Options) >= 1) + require.Equal(t, "gov", msg.Route()) + require.Equal(t, types.TypeMsgVoteWeighted, msg.Type()) +} + // returns context and an app with updated mint keeper func createTestApp(isCheckTx bool) (*simapp.SimApp, sdk.Context) { app := simapp.Setup(isCheckTx) diff --git a/x/gov/spec/04_events.md b/x/gov/spec/04_events.md index dc3b1bf3a2..300d4d187c 100644 --- a/x/gov/spec/04_events.md +++ b/x/gov/spec/04_events.md @@ -41,6 +41,16 @@ The governance module emits the following events: | message | action | vote | | message | sender | {senderAddress} | +### MsgVoteWeighted + +| Type | Attribute Key | Attribute Value | +| ------------- | ------------- | ------------------------ | +| proposal_vote | option | {weightedVoteOptions} | +| proposal_vote | proposal_id | {proposalID} | +| message | module | governance | +| message | action | vote | +| message | sender | {senderAddress} | + ### MsgDeposit | Type | Attribute Key | Attribute Value | diff --git a/x/gov/types/codec.go b/x/gov/types/codec.go index 07f6fbfea0..2252c067ca 100644 --- a/x/gov/types/codec.go +++ b/x/gov/types/codec.go @@ -14,7 +14,7 @@ func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { cdc.RegisterInterface((*Content)(nil), nil) cdc.RegisterConcrete(&MsgSubmitProposal{}, "lbm-sdk/MsgSubmitProposal", nil) cdc.RegisterConcrete(&MsgDeposit{}, "lbm-sdk/MsgDeposit", nil) - cdc.RegisterConcrete(&MsgVote{}, "lbm-sdk/MsgVote", nil) + cdc.RegisterConcrete(&MsgVoteWeighted{}, "lbm-sdk/MsgVoteWeighted", nil) cdc.RegisterConcrete(&TextProposal{}, "lbm-sdk/TextProposal", nil) } @@ -22,6 +22,7 @@ func RegisterInterfaces(registry types.InterfaceRegistry) { registry.RegisterImplementations((*sdk.Msg)(nil), &MsgSubmitProposal{}, &MsgVote{}, + &MsgVoteWeighted{}, &MsgDeposit{}, ) registry.RegisterInterface( diff --git a/x/gov/types/gov.pb.go b/x/gov/types/gov.pb.go index 6166040a31..e27d31de0a 100644 --- a/x/gov/types/gov.pb.go +++ b/x/gov/types/gov.pb.go @@ -121,6 +121,44 @@ func (ProposalStatus) EnumDescriptor() ([]byte, []int) { return fileDescriptor_945deb6a098bdba3, []int{1} } +// WeightedVoteOption defines a unit of vote for vote split. +type WeightedVoteOption struct { + Option VoteOption `protobuf:"varint,1,opt,name=option,proto3,enum=lbm.gov.v1.VoteOption" json:"option,omitempty"` + Weight github_com_line_lbm_sdk_types.Dec `protobuf:"bytes,2,opt,name=weight,proto3,customtype=github.com/line/lbm-sdk/types.Dec" json:"weight" yaml:"weight"` +} + +func (m *WeightedVoteOption) Reset() { *m = WeightedVoteOption{} } +func (*WeightedVoteOption) ProtoMessage() {} +func (*WeightedVoteOption) Descriptor() ([]byte, []int) { + return fileDescriptor_945deb6a098bdba3, []int{0} +} +func (m *WeightedVoteOption) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *WeightedVoteOption) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_WeightedVoteOption.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *WeightedVoteOption) XXX_Merge(src proto.Message) { + xxx_messageInfo_WeightedVoteOption.Merge(m, src) +} +func (m *WeightedVoteOption) XXX_Size() int { + return m.Size() +} +func (m *WeightedVoteOption) XXX_DiscardUnknown() { + xxx_messageInfo_WeightedVoteOption.DiscardUnknown(m) +} + +var xxx_messageInfo_WeightedVoteOption proto.InternalMessageInfo + // TextProposal defines a standard text proposal whose changes need to be // manually updated in case of approval. type TextProposal struct { @@ -131,7 +169,7 @@ type TextProposal struct { func (m *TextProposal) Reset() { *m = TextProposal{} } func (*TextProposal) ProtoMessage() {} func (*TextProposal) Descriptor() ([]byte, []int) { - return fileDescriptor_945deb6a098bdba3, []int{0} + return fileDescriptor_945deb6a098bdba3, []int{1} } func (m *TextProposal) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -171,7 +209,7 @@ type Deposit struct { func (m *Deposit) Reset() { *m = Deposit{} } func (*Deposit) ProtoMessage() {} func (*Deposit) Descriptor() ([]byte, []int) { - return fileDescriptor_945deb6a098bdba3, []int{1} + return fileDescriptor_945deb6a098bdba3, []int{2} } func (m *Deposit) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -216,7 +254,7 @@ type Proposal struct { func (m *Proposal) Reset() { *m = Proposal{} } func (*Proposal) ProtoMessage() {} func (*Proposal) Descriptor() ([]byte, []int) { - return fileDescriptor_945deb6a098bdba3, []int{2} + return fileDescriptor_945deb6a098bdba3, []int{3} } func (m *Proposal) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -256,7 +294,7 @@ type TallyResult struct { func (m *TallyResult) Reset() { *m = TallyResult{} } func (*TallyResult) ProtoMessage() {} func (*TallyResult) Descriptor() ([]byte, []int) { - return fileDescriptor_945deb6a098bdba3, []int{3} + return fileDescriptor_945deb6a098bdba3, []int{4} } func (m *TallyResult) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -288,15 +326,19 @@ var xxx_messageInfo_TallyResult proto.InternalMessageInfo // Vote defines a vote on a governance proposal. // A Vote consists of a proposal ID, the voter, and the vote option. type Vote struct { - ProposalId uint64 `protobuf:"varint,1,opt,name=proposal_id,json=proposalId,proto3" json:"proposal_id,omitempty" yaml:"proposal_id"` - Voter string `protobuf:"bytes,2,opt,name=voter,proto3" json:"voter,omitempty"` - Option VoteOption `protobuf:"varint,3,opt,name=option,proto3,enum=lbm.gov.v1.VoteOption" json:"option,omitempty"` + ProposalId uint64 `protobuf:"varint,1,opt,name=proposal_id,json=proposalId,proto3" json:"proposal_id,omitempty" yaml:"proposal_id"` + Voter string `protobuf:"bytes,2,opt,name=voter,proto3" json:"voter,omitempty"` + // Deprecated: Prefer to use `options` instead. This field is set in queries + // if and only if `len(options) == 1` and that option has weight 1. In all + // other cases, this field will default to VOTE_OPTION_UNSPECIFIED. + Option VoteOption `protobuf:"varint,3,opt,name=option,proto3,enum=lbm.gov.v1.VoteOption" json:"option,omitempty"` // Deprecated: Do not use. + Options []WeightedVoteOption `protobuf:"bytes,4,rep,name=options,proto3" json:"options"` } func (m *Vote) Reset() { *m = Vote{} } func (*Vote) ProtoMessage() {} func (*Vote) Descriptor() ([]byte, []int) { - return fileDescriptor_945deb6a098bdba3, []int{4} + return fileDescriptor_945deb6a098bdba3, []int{5} } func (m *Vote) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -337,7 +379,7 @@ type DepositParams struct { func (m *DepositParams) Reset() { *m = DepositParams{} } func (*DepositParams) ProtoMessage() {} func (*DepositParams) Descriptor() ([]byte, []int) { - return fileDescriptor_945deb6a098bdba3, []int{5} + return fileDescriptor_945deb6a098bdba3, []int{6} } func (m *DepositParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -375,7 +417,7 @@ type VotingParams struct { func (m *VotingParams) Reset() { *m = VotingParams{} } func (*VotingParams) ProtoMessage() {} func (*VotingParams) Descriptor() ([]byte, []int) { - return fileDescriptor_945deb6a098bdba3, []int{6} + return fileDescriptor_945deb6a098bdba3, []int{7} } func (m *VotingParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -419,7 +461,7 @@ type TallyParams struct { func (m *TallyParams) Reset() { *m = TallyParams{} } func (*TallyParams) ProtoMessage() {} func (*TallyParams) Descriptor() ([]byte, []int) { - return fileDescriptor_945deb6a098bdba3, []int{7} + return fileDescriptor_945deb6a098bdba3, []int{8} } func (m *TallyParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -451,6 +493,7 @@ var xxx_messageInfo_TallyParams proto.InternalMessageInfo func init() { proto.RegisterEnum("lbm.gov.v1.VoteOption", VoteOption_name, VoteOption_value) proto.RegisterEnum("lbm.gov.v1.ProposalStatus", ProposalStatus_name, ProposalStatus_value) + proto.RegisterType((*WeightedVoteOption)(nil), "lbm.gov.v1.WeightedVoteOption") proto.RegisterType((*TextProposal)(nil), "lbm.gov.v1.TextProposal") proto.RegisterType((*Deposit)(nil), "lbm.gov.v1.Deposit") proto.RegisterType((*Proposal)(nil), "lbm.gov.v1.Proposal") @@ -464,94 +507,98 @@ func init() { func init() { proto.RegisterFile("lbm/gov/v1/gov.proto", fileDescriptor_945deb6a098bdba3) } var fileDescriptor_945deb6a098bdba3 = []byte{ - // 1380 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x57, 0xc1, 0x6f, 0x1a, 0xc7, - 0x17, 0x66, 0x01, 0x63, 0x33, 0x60, 0x7b, 0x33, 0x26, 0x98, 0xec, 0x2f, 0x3f, 0x76, 0xb3, 0xe9, - 0x21, 0x4d, 0x13, 0x68, 0xdc, 0x4a, 0x55, 0x6c, 0x55, 0x2a, 0x98, 0x4d, 0x4b, 0x15, 0x01, 0x5a, - 0x36, 0x44, 0x49, 0xa5, 0xac, 0x16, 0xd8, 0xe0, 0x6d, 0x77, 0x77, 0x28, 0x3b, 0x50, 0xa3, 0x5c, - 0x7a, 0xa9, 0x14, 0x51, 0x29, 0xcd, 0x31, 0x17, 0xa4, 0x48, 0xbd, 0xf5, 0x56, 0x29, 0x7f, 0x41, - 0x4f, 0x51, 0x95, 0x43, 0xd4, 0x53, 0xd4, 0x03, 0x69, 0x1c, 0xa9, 0x8a, 0x72, 0xf4, 0x5f, 0x50, - 0xed, 0xce, 0x6c, 0x58, 0x70, 0x52, 0xdb, 0x3d, 0x99, 0x79, 0xf3, 0x7d, 0xdf, 0x7b, 0xf3, 0xf1, - 0xde, 0xb3, 0x0d, 0x52, 0x66, 0xd3, 0xca, 0x77, 0xd0, 0x20, 0x3f, 0xb8, 0xe4, 0xfe, 0xc8, 0x75, - 0x7b, 0x08, 0x23, 0x08, 0xcc, 0xa6, 0x95, 0x73, 0x8f, 0x83, 0x4b, 0x5c, 0xda, 0x45, 0x34, 0x35, - 0x47, 0x77, 0x21, 0x2d, 0x64, 0xd8, 0x04, 0xc3, 0xa5, 0x3a, 0xa8, 0x83, 0xbc, 0x8f, 0x79, 0xf7, - 0x13, 0x8d, 0x9e, 0x6a, 0x21, 0xc7, 0x42, 0x8e, 0x4a, 0x2e, 0xc8, 0x81, 0x5e, 0xf1, 0x1d, 0x84, - 0x3a, 0xa6, 0x9e, 0xf7, 0x4e, 0xcd, 0xfe, 0xed, 0x3c, 0x36, 0x2c, 0xdd, 0xc1, 0x9a, 0xd5, 0xf5, - 0xb9, 0xf3, 0x00, 0xcd, 0x1e, 0xd2, 0xab, 0xec, 0xfc, 0x55, 0xbb, 0xdf, 0xd3, 0xb0, 0x81, 0x68, - 0x31, 0xe2, 0x75, 0x90, 0x54, 0xf4, 0x5d, 0x5c, 0xeb, 0xa1, 0x2e, 0x72, 0x34, 0x13, 0xa6, 0xc0, - 0x02, 0x36, 0xb0, 0xa9, 0x67, 0x18, 0x81, 0x39, 0x17, 0x97, 0xc9, 0x01, 0x0a, 0x20, 0xd1, 0xd6, - 0x9d, 0x56, 0xcf, 0xe8, 0xba, 0xd4, 0x4c, 0xd8, 0xbb, 0x0b, 0x86, 0x36, 0x57, 0x5f, 0x3d, 0xe4, - 0x99, 0x3f, 0x1e, 0x5d, 0x5c, 0xdc, 0x46, 0x36, 0xd6, 0x6d, 0x2c, 0xfe, 0xc6, 0x80, 0xc5, 0x92, - 0xde, 0x45, 0x8e, 0x81, 0xe1, 0x27, 0x20, 0xd1, 0xa5, 0x09, 0x54, 0xa3, 0xed, 0x49, 0x47, 0x8b, - 0xe9, 0xfd, 0x09, 0x0f, 0x87, 0x9a, 0x65, 0x6e, 0x8a, 0x81, 0x4b, 0x51, 0x06, 0xfe, 0xa9, 0xdc, - 0x86, 0xa7, 0x41, 0xbc, 0x4d, 0x34, 0x50, 0x8f, 0x66, 0x9d, 0x06, 0x60, 0x03, 0xc4, 0x34, 0x0b, - 0xf5, 0x6d, 0x9c, 0x89, 0x08, 0x91, 0x73, 0x89, 0x8d, 0x13, 0x39, 0xd7, 0x7d, 0xd7, 0xf1, 0xdc, - 0xe0, 0x52, 0x6e, 0x1b, 0x19, 0x76, 0xf1, 0x83, 0xc7, 0x13, 0x3e, 0xf4, 0xcb, 0x73, 0xfe, 0x6c, - 0xc7, 0xc0, 0x3b, 0xfd, 0x66, 0xae, 0x85, 0xac, 0xbc, 0x69, 0xd8, 0x7a, 0xde, 0x6c, 0x5a, 0x17, - 0x9d, 0xf6, 0x37, 0x79, 0x3c, 0xec, 0xea, 0x8e, 0x87, 0x75, 0x64, 0xaa, 0xb6, 0xb9, 0x74, 0xf7, - 0x21, 0x1f, 0x7a, 0xf5, 0x90, 0x0f, 0x89, 0xcf, 0x62, 0x60, 0xe9, 0x8d, 0x35, 0x1f, 0xbf, 0xed, - 0x15, 0x6b, 0xaf, 0x27, 0x7c, 0xd8, 0x68, 0xef, 0x4f, 0xf8, 0x38, 0x79, 0xcb, 0xfc, 0x13, 0xb6, - 0xc0, 0x62, 0x8b, 0x58, 0xe2, 0x3d, 0x20, 0xb1, 0x91, 0xca, 0x91, 0xaf, 0x24, 0xe7, 0x7f, 0x25, - 0xb9, 0x82, 0x3d, 0x2c, 0x26, 0x7e, 0x9f, 0x7a, 0x27, 0xfb, 0x0c, 0x58, 0x01, 0x31, 0x07, 0x6b, - 0xb8, 0xef, 0x64, 0x22, 0x02, 0x73, 0x6e, 0x65, 0x83, 0xcb, 0x4d, 0xfb, 0x2b, 0xe7, 0x17, 0x56, - 0xf7, 0x10, 0x45, 0x6e, 0x7f, 0xc2, 0xa7, 0xe7, 0xfc, 0x24, 0x64, 0x51, 0xa6, 0x2a, 0x70, 0x07, - 0xc0, 0xdb, 0x86, 0xad, 0x99, 0x2a, 0xd6, 0x4c, 0x73, 0xa8, 0xf6, 0x74, 0xa7, 0x6f, 0xe2, 0x4c, - 0xd4, 0xab, 0x6b, 0x3d, 0xa8, 0xad, 0xb8, 0xf7, 0xb2, 0x77, 0x5d, 0x3c, 0xe3, 0x7a, 0xb8, 0x3f, - 0xe1, 0x4f, 0x11, 0xf1, 0x83, 0x02, 0xa2, 0xcc, 0x7a, 0xc1, 0x00, 0x09, 0x7e, 0x05, 0x12, 0x4e, - 0xbf, 0x69, 0x19, 0x58, 0x75, 0x9b, 0x35, 0xb3, 0xe0, 0xa5, 0xe0, 0x0e, 0x3c, 0x5d, 0xf1, 0x3b, - 0xb9, 0x98, 0xa5, 0x59, 0x68, 0x4b, 0x04, 0xc8, 0xe2, 0xfd, 0xe7, 0x3c, 0x23, 0x03, 0x12, 0x71, - 0x09, 0xd0, 0x00, 0x2c, 0xed, 0x02, 0x55, 0xb7, 0xdb, 0x24, 0x43, 0xec, 0xd0, 0x0c, 0x67, 0x69, - 0x86, 0x75, 0x92, 0x61, 0x5e, 0x81, 0xa4, 0x59, 0xa1, 0x61, 0xc9, 0x6e, 0x7b, 0xa9, 0xee, 0x80, - 0x65, 0x8c, 0xb0, 0x66, 0xaa, 0x34, 0x9e, 0x59, 0x7c, 0x57, 0xab, 0x6d, 0x53, 0xf9, 0x14, 0x91, - 0x9f, 0x61, 0x89, 0x47, 0x6d, 0xc1, 0xa4, 0x47, 0xf3, 0xe7, 0xc6, 0x04, 0x27, 0x06, 0x08, 0x1b, - 0x76, 0xc7, 0xfd, 0x22, 0x7b, 0xd4, 0xca, 0xa5, 0x43, 0x1f, 0xfa, 0x1e, 0xad, 0x24, 0x43, 0x2a, - 0x39, 0x20, 0x41, 0x5e, 0xba, 0x4a, 0xe2, 0x75, 0x37, 0xec, 0x3d, 0xf5, 0x36, 0xa0, 0xa1, 0xa9, - 0xa9, 0xf1, 0x43, 0x73, 0x89, 0x34, 0x57, 0x7a, 0x26, 0xd7, 0xac, 0xa7, 0xcb, 0x24, 0x4a, 0x2d, - 0xdd, 0x8c, 0xba, 0xab, 0x42, 0x7c, 0x14, 0x06, 0x89, 0x60, 0xc3, 0x6c, 0x81, 0xc8, 0x50, 0x77, - 0xc8, 0xda, 0x29, 0xbe, 0xef, 0xaa, 0xfe, 0x39, 0xe1, 0xcf, 0xfc, 0xbb, 0x67, 0x65, 0x1b, 0xcb, - 0x2e, 0x0b, 0x6e, 0x83, 0x45, 0xad, 0xe9, 0x60, 0xcd, 0xa0, 0xbb, 0xe9, 0x38, 0x02, 0x3e, 0x13, - 0x5e, 0x06, 0x61, 0x1b, 0x79, 0x83, 0x76, 0x2c, 0x7e, 0xd8, 0x46, 0xb0, 0x09, 0x92, 0x36, 0x52, - 0xbf, 0x33, 0xf0, 0x8e, 0x3a, 0xd0, 0x31, 0xf2, 0x26, 0x2a, 0x5e, 0xfc, 0xec, 0xc8, 0x22, 0xfb, - 0x13, 0x7e, 0x8d, 0x18, 0x18, 0x94, 0x11, 0x65, 0x60, 0xa3, 0xeb, 0x06, 0xde, 0x69, 0xe8, 0x18, - 0x51, 0xdb, 0x7e, 0x62, 0x40, 0xb4, 0x81, 0xb0, 0xfe, 0xdf, 0x77, 0x6a, 0x0a, 0x2c, 0x0c, 0x10, - 0xd6, 0xfd, 0x7d, 0x4a, 0x0e, 0x30, 0x07, 0x62, 0x88, 0x2c, 0x77, 0xb2, 0x69, 0xd2, 0xc1, 0x6d, - 0xe0, 0x26, 0xac, 0x7a, 0xb7, 0x32, 0x45, 0x6d, 0x2e, 0x3d, 0xf0, 0x77, 0xe4, 0xaf, 0x61, 0xb0, - 0x4c, 0x1b, 0xb6, 0xa6, 0xf5, 0x34, 0xcb, 0x81, 0xf7, 0x18, 0x90, 0xb0, 0x0c, 0xfb, 0xcd, 0xc8, - 0x30, 0xef, 0x1a, 0x99, 0x9b, 0xae, 0x41, 0xaf, 0x27, 0xfc, 0xc9, 0x00, 0xfa, 0x02, 0xb2, 0x0c, - 0xac, 0x5b, 0x5d, 0x3c, 0x9c, 0xbe, 0x25, 0x70, 0x7d, 0xe4, 0x49, 0x02, 0x96, 0x61, 0xfb, 0x73, - 0x74, 0x8f, 0x01, 0xd0, 0xd2, 0x76, 0x7d, 0x0d, 0xb5, 0xab, 0xf7, 0x0c, 0xd4, 0xa6, 0xfb, 0xf8, - 0xd4, 0x81, 0xee, 0x2e, 0xd1, 0x5f, 0x91, 0x45, 0x89, 0xd6, 0x77, 0xfa, 0x20, 0x79, 0xa6, 0x4c, - 0xba, 0x19, 0x0f, 0xa2, 0xc4, 0x07, 0x6e, 0xff, 0xb3, 0x96, 0xb6, 0xeb, 0x3b, 0x44, 0xc2, 0x3f, - 0x32, 0x20, 0xd9, 0xf0, 0x86, 0x82, 0x5a, 0x76, 0x07, 0xd0, 0x21, 0xf1, 0x6b, 0x63, 0x0e, 0xab, - 0x6d, 0x8b, 0xd6, 0xb6, 0x3e, 0xc3, 0x9b, 0x29, 0x2b, 0x35, 0x33, 0x93, 0xc1, 0x8a, 0x92, 0x24, - 0x46, 0xab, 0x79, 0xe2, 0x8f, 0x22, 0x2d, 0xe6, 0x1a, 0x88, 0x7d, 0xdb, 0x47, 0xbd, 0xbe, 0xe5, - 0x55, 0x91, 0x2c, 0x7e, 0x7a, 0xb4, 0x3e, 0x2e, 0xe9, 0xad, 0xd7, 0x13, 0x9e, 0x25, 0xd4, 0x69, - 0x21, 0x32, 0x15, 0x83, 0xb7, 0x40, 0x1c, 0xef, 0xf4, 0x74, 0x67, 0x07, 0x99, 0xc4, 0xfb, 0xe4, - 0x51, 0x27, 0x84, 0x28, 0xaf, 0xbd, 0x61, 0x07, 0xc4, 0xa7, 0x92, 0xf0, 0x07, 0x06, 0xac, 0xb8, - 0x63, 0xa3, 0x4e, 0xb3, 0x44, 0xbc, 0x2c, 0xb7, 0x8e, 0x93, 0x25, 0x33, 0x2b, 0x31, 0x63, 0xe8, - 0x49, 0x6a, 0xe8, 0x0c, 0x42, 0x94, 0x97, 0xdd, 0x80, 0xe2, 0x9f, 0xcf, 0xff, 0xcd, 0x00, 0x30, - 0x9d, 0x18, 0x78, 0x01, 0xac, 0x37, 0xaa, 0x8a, 0xa4, 0x56, 0x6b, 0x4a, 0xb9, 0x5a, 0x51, 0xaf, - 0x55, 0xea, 0x35, 0x69, 0xbb, 0x7c, 0xa5, 0x2c, 0x95, 0xd8, 0x10, 0xb7, 0x3a, 0x1a, 0x0b, 0x09, - 0x02, 0x94, 0xdc, 0x24, 0x50, 0x04, 0xab, 0x41, 0xf4, 0x0d, 0xa9, 0xce, 0x32, 0xdc, 0xf2, 0x68, - 0x2c, 0xc4, 0x09, 0xea, 0x86, 0xee, 0xc0, 0xf3, 0x60, 0x2d, 0x88, 0x29, 0x14, 0xeb, 0x4a, 0xa1, - 0x5c, 0x61, 0xc3, 0xdc, 0x89, 0xd1, 0x58, 0x58, 0x26, 0xb8, 0x02, 0x5d, 0x6a, 0x02, 0x58, 0x09, - 0x62, 0x2b, 0x55, 0x36, 0xc2, 0x25, 0x47, 0x63, 0x61, 0x89, 0xc0, 0x2a, 0x08, 0x6e, 0x80, 0xcc, - 0x2c, 0x42, 0xbd, 0x5e, 0x56, 0xbe, 0x50, 0x1b, 0x92, 0x52, 0x65, 0xa3, 0x5c, 0x6a, 0x34, 0x16, - 0x58, 0x1f, 0xeb, 0xef, 0x22, 0x2e, 0x7a, 0xf7, 0xe7, 0x6c, 0xe8, 0xfc, 0x93, 0x30, 0x58, 0x99, - 0xfd, 0x23, 0x04, 0xe6, 0xc0, 0xff, 0x6a, 0x72, 0xb5, 0x56, 0xad, 0x17, 0xae, 0xaa, 0x75, 0xa5, - 0xa0, 0x5c, 0xab, 0xcf, 0x3d, 0xd8, 0x7b, 0x0a, 0x01, 0x57, 0x0c, 0x13, 0x6e, 0x81, 0xec, 0x3c, - 0xbe, 0x24, 0xd5, 0xaa, 0xf5, 0xb2, 0xa2, 0xd6, 0x24, 0xb9, 0x5c, 0x2d, 0xb1, 0x0c, 0xb7, 0x3e, - 0x1a, 0x0b, 0x6b, 0x84, 0x32, 0x33, 0x45, 0xf0, 0x32, 0xf8, 0xff, 0x3c, 0xb9, 0x51, 0x55, 0xca, - 0x95, 0xcf, 0x7d, 0x6e, 0x98, 0x4b, 0x8f, 0xc6, 0x02, 0x24, 0xdc, 0x46, 0xa0, 0xe5, 0xe1, 0x05, - 0x90, 0x9e, 0xa7, 0xd6, 0x0a, 0xf5, 0xba, 0x54, 0x62, 0x23, 0x1c, 0x3b, 0x1a, 0x0b, 0x49, 0xc2, - 0xa9, 0x69, 0x8e, 0xa3, 0xb7, 0xe1, 0x87, 0x20, 0x33, 0x8f, 0x96, 0xa5, 0x2f, 0xa5, 0x6d, 0x45, - 0x2a, 0xb1, 0x51, 0x0e, 0x8e, 0xc6, 0xc2, 0x0a, 0xc1, 0xcb, 0xfa, 0xd7, 0x7a, 0x0b, 0xeb, 0x6f, - 0xd5, 0xbf, 0x52, 0x28, 0x5f, 0x95, 0x4a, 0xec, 0x42, 0x50, 0xff, 0x8a, 0x66, 0x98, 0x7a, 0x9b, - 0xd8, 0x59, 0x2c, 0x3f, 0x7e, 0x91, 0x0d, 0x3d, 0x7b, 0x91, 0x0d, 0x7d, 0xbf, 0x97, 0x0d, 0x3d, - 0xde, 0xcb, 0x32, 0x4f, 0xf7, 0xb2, 0xcc, 0x5f, 0x7b, 0x59, 0xe6, 0xfe, 0xcb, 0x6c, 0xe8, 0xe9, - 0xcb, 0x6c, 0xe8, 0xd9, 0xcb, 0x6c, 0xe8, 0xe6, 0x3b, 0x97, 0xdf, 0xae, 0xf7, 0xdf, 0x88, 0xd7, - 0xca, 0xcd, 0x98, 0xb7, 0x2f, 0x3e, 0xfa, 0x27, 0x00, 0x00, 0xff, 0xff, 0xbd, 0x6f, 0x07, 0x4a, - 0xa5, 0x0c, 0x00, 0x00, + // 1454 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x57, 0xd1, 0x6f, 0xd3, 0x56, + 0x17, 0x8f, 0x93, 0x34, 0x6d, 0x6e, 0xd2, 0xd6, 0xdc, 0x86, 0x36, 0xf8, 0xe3, 0x8b, 0x83, 0xf9, + 0x1e, 0xf8, 0x18, 0x24, 0xa3, 0x9b, 0x34, 0xd1, 0x6a, 0xd3, 0xe2, 0xc6, 0x6c, 0x99, 0x50, 0x12, + 0x39, 0x21, 0x15, 0x4c, 0xc2, 0x72, 0x9a, 0x4b, 0xea, 0xcd, 0xf6, 0xcd, 0xe2, 0x9b, 0xd2, 0x8a, + 0x97, 0xbd, 0x4c, 0x42, 0x99, 0x84, 0x78, 0xe4, 0x25, 0x12, 0xd2, 0xde, 0xf6, 0x36, 0x89, 0xbf, + 0x60, 0x4f, 0x68, 0xe2, 0x01, 0x6d, 0x2f, 0x68, 0x0f, 0x61, 0x14, 0x69, 0x42, 0x3c, 0xf6, 0x2f, + 0x98, 0xec, 0x7b, 0xdd, 0x38, 0x09, 0xd0, 0xb2, 0xa7, 0xfa, 0x9e, 0xfb, 0x3b, 0xbf, 0x73, 0xce, + 0xaf, 0xe7, 0x1c, 0x3b, 0x20, 0x65, 0x36, 0xad, 0x7c, 0x1b, 0xef, 0xe4, 0x77, 0x2e, 0xb9, 0x7f, + 0x72, 0x9d, 0x2e, 0x26, 0x18, 0x02, 0xb3, 0x69, 0xe5, 0xdc, 0xe3, 0xce, 0x25, 0x61, 0xd9, 0x45, + 0x34, 0x75, 0x07, 0xb9, 0x90, 0x2d, 0x6c, 0xd8, 0x14, 0x23, 0xa4, 0xda, 0xb8, 0x8d, 0xbd, 0xc7, + 0xbc, 0xfb, 0xc4, 0xac, 0xa7, 0xb6, 0xb0, 0x63, 0x61, 0x47, 0xa3, 0x17, 0xf4, 0xc0, 0xae, 0xc4, + 0x36, 0xc6, 0x6d, 0x13, 0xe5, 0xbd, 0x53, 0xb3, 0x77, 0x2b, 0x4f, 0x0c, 0x0b, 0x39, 0x44, 0xb7, + 0x3a, 0xbe, 0xef, 0x24, 0x40, 0xb7, 0xf7, 0xd8, 0x55, 0x66, 0xf2, 0xaa, 0xd5, 0xeb, 0xea, 0xc4, + 0xc0, 0x2c, 0x19, 0xe9, 0x01, 0x07, 0xe0, 0x26, 0x32, 0xda, 0xdb, 0x04, 0xb5, 0x1a, 0x98, 0xa0, + 0x4a, 0xc7, 0xbd, 0x84, 0x39, 0x10, 0xc3, 0xde, 0x53, 0x9a, 0xcb, 0x72, 0xe7, 0x16, 0x56, 0x97, + 0x73, 0xa3, 0xc2, 0x72, 0x23, 0x9c, 0xca, 0x50, 0x50, 0x05, 0xb1, 0xdb, 0x1e, 0x4b, 0x3a, 0x9c, + 0xe5, 0xce, 0xc5, 0xe5, 0xb5, 0xc7, 0x43, 0x31, 0xf4, 0xe7, 0x50, 0x3c, 0xd3, 0x36, 0xc8, 0x76, + 0xaf, 0x99, 0xdb, 0xc2, 0x56, 0xde, 0x34, 0x6c, 0x94, 0x37, 0x9b, 0xd6, 0x45, 0xa7, 0xf5, 0x6d, + 0x9e, 0xec, 0x75, 0x90, 0x93, 0x2b, 0xa2, 0xad, 0x83, 0xa1, 0x38, 0xbf, 0xa7, 0x5b, 0xe6, 0x9a, + 0x44, 0x09, 0x24, 0x95, 0x31, 0x49, 0x9b, 0x20, 0x59, 0x47, 0xbb, 0xa4, 0xda, 0xc5, 0x1d, 0xec, + 0xe8, 0x26, 0x4c, 0x81, 0x19, 0x62, 0x10, 0x13, 0x79, 0x29, 0xc5, 0x55, 0x7a, 0x80, 0x59, 0x90, + 0x68, 0x21, 0x67, 0xab, 0x6b, 0xd0, 0x74, 0xbd, 0xf0, 0x6a, 0xd0, 0xb4, 0xb6, 0xf8, 0xea, 0xa1, + 0xc8, 0xfd, 0xfe, 0xe8, 0xe2, 0xec, 0x06, 0xb6, 0x09, 0xb2, 0x89, 0xf4, 0x2b, 0x07, 0x66, 0x8b, + 0xa8, 0x83, 0x1d, 0x83, 0xc0, 0x4f, 0x40, 0xa2, 0xc3, 0x02, 0x68, 0x46, 0xcb, 0xa3, 0x8e, 0xca, + 0xcb, 0x07, 0x43, 0x11, 0xd2, 0xa4, 0x02, 0x97, 0x92, 0x0a, 0xfc, 0x53, 0xa9, 0x05, 0x4f, 0x83, + 0x78, 0x8b, 0x72, 0xe0, 0x2e, 0x8b, 0x3a, 0x32, 0xc0, 0x06, 0x88, 0xe9, 0x16, 0xee, 0xd9, 0x24, + 0x1d, 0xc9, 0x46, 0xce, 0x25, 0x56, 0x4f, 0x78, 0xfa, 0xb9, 0xcd, 0xe0, 0x0a, 0xb8, 0x81, 0x0d, + 0x5b, 0xfe, 0xc0, 0x95, 0xe8, 0xe7, 0xe7, 0xe2, 0xd9, 0x77, 0x4b, 0xe4, 0x62, 0x1d, 0x95, 0xb1, + 0xad, 0xcd, 0xdd, 0x7d, 0x28, 0x86, 0x5e, 0x3d, 0x14, 0x43, 0xd2, 0xb3, 0x18, 0x98, 0x3b, 0x94, + 0xe6, 0xe3, 0x37, 0x55, 0xb1, 0xf4, 0x7a, 0x28, 0x86, 0x8d, 0xd6, 0xc1, 0x50, 0x8c, 0xd3, 0x5a, + 0x26, 0x4b, 0x58, 0x07, 0xb3, 0x5b, 0x54, 0x12, 0xaf, 0x80, 0xc4, 0x6a, 0x2a, 0x47, 0xbb, 0x25, + 0xe7, 0x77, 0x4b, 0xae, 0x60, 0xef, 0xc9, 0x89, 0xdf, 0x46, 0xda, 0xa9, 0xbe, 0x07, 0x2c, 0x83, + 0x98, 0x43, 0x74, 0xd2, 0x73, 0xd2, 0x11, 0xaf, 0x43, 0x84, 0x60, 0x87, 0xf8, 0x89, 0xd5, 0x3c, + 0x84, 0x2c, 0x1c, 0x0c, 0xc5, 0xe5, 0x09, 0x3d, 0xa9, 0xb3, 0xa4, 0x32, 0x16, 0xb8, 0x0d, 0xe0, + 0x2d, 0xc3, 0xd6, 0x4d, 0x8d, 0xe8, 0xa6, 0xb9, 0xa7, 0x75, 0x91, 0xd3, 0x33, 0x49, 0x3a, 0xea, + 0xe5, 0xb5, 0x12, 0xe4, 0xae, 0xbb, 0xf7, 0xaa, 0x77, 0x2d, 0x9f, 0x71, 0x35, 0x3c, 0x18, 0x8a, + 0xa7, 0x28, 0xf9, 0x34, 0x81, 0xa4, 0xf2, 0x9e, 0x31, 0xe0, 0x04, 0xbf, 0x06, 0x09, 0xa7, 0xd7, + 0xb4, 0x0c, 0xa2, 0xb9, 0x73, 0x94, 0x9e, 0xf1, 0x42, 0x08, 0x53, 0xa5, 0xd7, 0xfd, 0x21, 0x93, + 0x33, 0x2c, 0x0a, 0x6b, 0x89, 0x80, 0xb3, 0x74, 0xff, 0xb9, 0xc8, 0xa9, 0x80, 0x5a, 0x5c, 0x07, + 0x68, 0x00, 0x9e, 0x75, 0x81, 0x86, 0xec, 0x16, 0x8d, 0x10, 0x3b, 0x32, 0xc2, 0x59, 0x16, 0x61, + 0x85, 0x46, 0x98, 0x64, 0xa0, 0x61, 0x16, 0x98, 0x59, 0xb1, 0x5b, 0x5e, 0xa8, 0x3b, 0x60, 0x9e, + 0x60, 0xa2, 0x9b, 0x1a, 0xb3, 0xa7, 0x67, 0xdf, 0xd6, 0x6a, 0x1b, 0x8c, 0x3e, 0x45, 0xe9, 0xc7, + 0xbc, 0xa4, 0xe3, 0xb6, 0x60, 0xd2, 0x73, 0xf3, 0xe7, 0xc6, 0x04, 0x27, 0x76, 0x30, 0x31, 0xec, + 0xb6, 0xfb, 0x8f, 0xec, 0x32, 0x29, 0xe7, 0x8e, 0x2c, 0xf4, 0x7f, 0x2c, 0x93, 0x34, 0xcd, 0x64, + 0x8a, 0x82, 0x56, 0xba, 0x48, 0xed, 0x35, 0xd7, 0xec, 0x95, 0x7a, 0x0b, 0x30, 0xd3, 0x48, 0xd4, + 0xf8, 0x91, 0xb1, 0x24, 0x16, 0x6b, 0x79, 0x2c, 0xd6, 0xb8, 0xa6, 0xf3, 0xd4, 0xca, 0x24, 0x5d, + 0x8b, 0xba, 0xab, 0x42, 0x7a, 0x14, 0x06, 0x89, 0x60, 0xc3, 0xac, 0x83, 0xc8, 0x1e, 0x72, 0xe8, + 0xda, 0x91, 0xff, 0x7f, 0xbc, 0xcd, 0x56, 0xb2, 0x89, 0xea, 0x7a, 0xc1, 0x0d, 0x30, 0xab, 0x37, + 0x1d, 0xa2, 0x1b, 0x6c, 0x37, 0xbd, 0x0f, 0x81, 0xef, 0x09, 0x2f, 0x83, 0xb0, 0x8d, 0xbd, 0x41, + 0x7b, 0x2f, 0xff, 0xb0, 0x8d, 0x61, 0x13, 0x24, 0x6d, 0xac, 0xdd, 0x36, 0xc8, 0xb6, 0xb6, 0x83, + 0x08, 0xf6, 0x26, 0x2a, 0x2e, 0x7f, 0x7e, 0x6c, 0x92, 0x83, 0xa1, 0xb8, 0x44, 0x05, 0x0c, 0xd2, + 0x48, 0x2a, 0xb0, 0xf1, 0xa6, 0x41, 0xb6, 0x1b, 0x88, 0x60, 0x26, 0xdb, 0x1f, 0x1c, 0x88, 0xba, + 0xaf, 0x86, 0x7f, 0xbf, 0x53, 0x53, 0x60, 0x66, 0x07, 0x13, 0xe4, 0xef, 0x53, 0x7a, 0x80, 0xab, + 0x87, 0xef, 0xa2, 0xc8, 0xbb, 0xde, 0x45, 0x72, 0x38, 0xcd, 0x1d, 0xbe, 0x8f, 0x3e, 0x03, 0xb3, + 0xf4, 0xc9, 0x49, 0x47, 0xbd, 0xa9, 0xc8, 0x04, 0x9d, 0xa6, 0x5f, 0x78, 0x72, 0xd4, 0x15, 0x44, + 0xf5, 0x9d, 0xd6, 0xe6, 0x1e, 0xf8, 0x7b, 0xf6, 0x97, 0x30, 0x98, 0x67, 0x4d, 0x5f, 0xd5, 0xbb, + 0xba, 0xe5, 0xc0, 0x7b, 0x1c, 0x48, 0x58, 0x86, 0x7d, 0x38, 0x76, 0xdc, 0xdb, 0xc6, 0xee, 0x86, + 0xcb, 0xf9, 0x7a, 0x28, 0x9e, 0x0c, 0xa0, 0x2f, 0x60, 0xcb, 0x20, 0xc8, 0xea, 0x90, 0xbd, 0x91, + 0x1e, 0x81, 0xeb, 0x63, 0x4f, 0x23, 0xb0, 0x0c, 0xdb, 0x9f, 0xc5, 0x7b, 0x1c, 0x80, 0x96, 0xbe, + 0xeb, 0x73, 0x68, 0x1d, 0xd4, 0x35, 0x70, 0x8b, 0xed, 0xf4, 0x53, 0x53, 0x13, 0x52, 0x64, 0x5f, + 0x00, 0xb2, 0xc2, 0xf2, 0x3b, 0x3d, 0xed, 0x3c, 0x96, 0x26, 0xdb, 0xae, 0xd3, 0x28, 0xe9, 0x81, + 0x3b, 0x43, 0xbc, 0xa5, 0xef, 0xfa, 0x0a, 0x51, 0xf3, 0x8f, 0x1c, 0x48, 0x36, 0xbc, 0xc1, 0x62, + 0x92, 0xdd, 0x01, 0x6c, 0xd0, 0xfc, 0xdc, 0xb8, 0xa3, 0x72, 0x5b, 0x67, 0xb9, 0xad, 0x8c, 0xf9, + 0x8d, 0xa5, 0x95, 0x1a, 0x9b, 0xeb, 0x60, 0x46, 0x49, 0x6a, 0x63, 0xd9, 0x3c, 0xf1, 0xc7, 0x99, + 0x25, 0x73, 0x0d, 0xc4, 0xbe, 0xeb, 0xe1, 0x6e, 0xcf, 0xf2, 0xb2, 0x48, 0xca, 0x9f, 0x1e, 0xfb, + 0x5b, 0xe5, 0xf5, 0x50, 0xe4, 0xa9, 0xeb, 0x28, 0x11, 0x95, 0x91, 0xc1, 0x9b, 0x20, 0x4e, 0xb6, + 0xbb, 0xc8, 0xd9, 0xc6, 0x26, 0xd5, 0x3e, 0x79, 0xdc, 0x29, 0xa3, 0xcc, 0x4b, 0x87, 0xde, 0x01, + 0xf2, 0x11, 0x25, 0xfc, 0x81, 0x03, 0x0b, 0xee, 0xe8, 0x69, 0xa3, 0x28, 0x11, 0x2f, 0xca, 0xcd, + 0xf7, 0x89, 0x92, 0x1e, 0xa7, 0x18, 0x13, 0xf4, 0x24, 0x13, 0x74, 0x0c, 0x21, 0xa9, 0xf3, 0xae, + 0xa1, 0xee, 0x9f, 0xcf, 0xff, 0xcd, 0x01, 0x10, 0xf8, 0x52, 0xbc, 0x00, 0x56, 0x1a, 0x95, 0xba, + 0xa2, 0x55, 0xaa, 0xf5, 0x52, 0xa5, 0xac, 0x5d, 0x2b, 0xd7, 0xaa, 0xca, 0x46, 0xe9, 0x4a, 0x49, + 0x29, 0xf2, 0x21, 0x61, 0xb1, 0x3f, 0xc8, 0x26, 0x28, 0x50, 0x71, 0x83, 0x40, 0x09, 0x2c, 0x06, + 0xd1, 0xd7, 0x95, 0x1a, 0xcf, 0x09, 0xf3, 0xfd, 0x41, 0x36, 0x4e, 0x51, 0xd7, 0x91, 0x03, 0xcf, + 0x83, 0xa5, 0x20, 0xa6, 0x20, 0xd7, 0xea, 0x85, 0x52, 0x99, 0x0f, 0x0b, 0x27, 0xfa, 0x83, 0xec, + 0x3c, 0xc5, 0x15, 0xd8, 0x62, 0xcc, 0x82, 0x85, 0x20, 0xb6, 0x5c, 0xe1, 0x23, 0x42, 0xb2, 0x3f, + 0xc8, 0xce, 0x51, 0x58, 0x19, 0xc3, 0x55, 0x90, 0x1e, 0x47, 0x68, 0x9b, 0xa5, 0xfa, 0x97, 0x5a, + 0x43, 0xa9, 0x57, 0xf8, 0xa8, 0x90, 0xea, 0x0f, 0xb2, 0xbc, 0x8f, 0xf5, 0xf7, 0x99, 0x10, 0xbd, + 0xfb, 0x53, 0x26, 0x74, 0xfe, 0x49, 0x18, 0x2c, 0x8c, 0x7f, 0xc8, 0xc0, 0x1c, 0xf8, 0x4f, 0x55, + 0xad, 0x54, 0x2b, 0xb5, 0xc2, 0x55, 0xad, 0x56, 0x2f, 0xd4, 0xaf, 0xd5, 0x26, 0x0a, 0xf6, 0x4a, + 0xa1, 0xe0, 0xb2, 0x61, 0xc2, 0x75, 0x90, 0x99, 0xc4, 0x17, 0x95, 0x6a, 0xa5, 0x56, 0xaa, 0x6b, + 0x55, 0x45, 0x2d, 0x55, 0x8a, 0x3c, 0x27, 0xac, 0xf4, 0x07, 0xd9, 0x25, 0xea, 0x32, 0x36, 0x45, + 0xf0, 0x32, 0xf8, 0xef, 0xa4, 0x73, 0xa3, 0x52, 0x2f, 0x95, 0xbf, 0xf0, 0x7d, 0xc3, 0xc2, 0x72, + 0x7f, 0x90, 0x85, 0xd4, 0xb7, 0x11, 0x68, 0x79, 0x78, 0x01, 0x2c, 0x4f, 0xba, 0x56, 0x0b, 0xb5, + 0x9a, 0x52, 0xe4, 0x23, 0x02, 0xdf, 0x1f, 0x64, 0x93, 0xd4, 0xa7, 0xaa, 0x3b, 0x0e, 0x6a, 0xc1, + 0x0f, 0x41, 0x7a, 0x12, 0xad, 0x2a, 0x5f, 0x29, 0x1b, 0x75, 0xa5, 0xc8, 0x47, 0x05, 0xd8, 0x1f, + 0x64, 0x17, 0x28, 0x5e, 0x45, 0xdf, 0xa0, 0x2d, 0x82, 0xde, 0xc8, 0x7f, 0xa5, 0x50, 0xba, 0xaa, + 0x14, 0xf9, 0x99, 0x20, 0xff, 0x15, 0xdd, 0x30, 0x51, 0x8b, 0xca, 0x29, 0x97, 0x1e, 0xbf, 0xc8, + 0x84, 0x9e, 0xbd, 0xc8, 0x84, 0xbe, 0xdf, 0xcf, 0x84, 0x1e, 0xef, 0x67, 0xb8, 0xa7, 0xfb, 0x19, + 0xee, 0xaf, 0xfd, 0x0c, 0x77, 0xff, 0x65, 0x26, 0xf4, 0xf4, 0x65, 0x26, 0xf4, 0xec, 0x65, 0x26, + 0x74, 0xe3, 0xad, 0xcb, 0x6f, 0xd7, 0xfb, 0xb1, 0xe5, 0xb5, 0x72, 0x33, 0xe6, 0xed, 0x8b, 0x8f, + 0xfe, 0x09, 0x00, 0x00, 0xff, 0xff, 0xc4, 0x42, 0xaa, 0xee, 0x84, 0x0d, 0x00, 0x00, } func (this *TextProposal) Equal(that interface{}) bool { @@ -667,6 +714,44 @@ func (this *TallyResult) Equal(that interface{}) bool { } return true } +func (m *WeightedVoteOption) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *WeightedVoteOption) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *WeightedVoteOption) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.Weight.Size() + i -= size + if _, err := m.Weight.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintGov(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if m.Option != 0 { + i = encodeVarintGov(dAtA, i, uint64(m.Option)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *TextProposal) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -937,6 +1022,20 @@ func (m *Vote) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.Options) > 0 { + for iNdEx := len(m.Options) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Options[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGov(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } if m.Option != 0 { i = encodeVarintGov(dAtA, i, uint64(m.Option)) i-- @@ -1097,6 +1196,20 @@ func encodeVarintGov(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } +func (m *WeightedVoteOption) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Option != 0 { + n += 1 + sovGov(uint64(m.Option)) + } + l = m.Weight.Size() + n += 1 + l + sovGov(uint64(l)) + return n +} + func (m *TextProposal) Size() (n int) { if m == nil { return 0 @@ -1204,6 +1317,12 @@ func (m *Vote) Size() (n int) { if m.Option != 0 { n += 1 + sovGov(uint64(m.Option)) } + if len(m.Options) > 0 { + for _, e := range m.Options { + l = e.Size() + n += 1 + l + sovGov(uint64(l)) + } + } return n } @@ -1256,6 +1375,109 @@ func sovGov(x uint64) (n int) { func sozGov(x uint64) (n int) { return sovGov(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } +func (m *WeightedVoteOption) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: WeightedVoteOption: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: WeightedVoteOption: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Option", wireType) + } + m.Option = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Option |= VoteOption(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Weight", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGov + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGov + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Weight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGov(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGov + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *TextProposal) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -2113,6 +2335,40 @@ func (m *Vote) Unmarshal(dAtA []byte) error { break } } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Options", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGov + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGov + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Options = append(m.Options, WeightedVoteOption{}) + if err := m.Options[len(m.Options)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGov(dAtA[iNdEx:]) diff --git a/x/gov/types/msgs.go b/x/gov/types/msgs.go index d41a43731d..ea8a3ca7c4 100644 --- a/x/gov/types/msgs.go +++ b/x/gov/types/msgs.go @@ -16,11 +16,12 @@ import ( const ( TypeMsgDeposit = "deposit" TypeMsgVote = "vote" + TypeMsgVoteWeighted = "weighted_vote" TypeMsgSubmitProposal = "submit_proposal" ) var ( - _, _, _ sdk.Msg = &MsgSubmitProposal{}, &MsgDeposit{}, &MsgVote{} + _, _, _ sdk.Msg = &MsgSubmitProposal{}, &MsgDeposit{}, &MsgVoteWeighted{} _ types.UnpackInterfacesMessage = &MsgSubmitProposal{} ) @@ -221,3 +222,66 @@ func (msg MsgVote) GetSignBytes() []byte { func (msg MsgVote) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{sdk.AccAddress(msg.Voter)} } + +// NewMsgVoteWeighted creates a message to cast a vote on an active proposal +//nolint:interfacer +func NewMsgVoteWeighted(voter sdk.AccAddress, proposalID uint64, options WeightedVoteOptions) *MsgVoteWeighted { + return &MsgVoteWeighted{proposalID, voter.String(), options} +} + +// Route implements Msg +func (msg MsgVoteWeighted) Route() string { return RouterKey } + +// Type implements Msg +func (msg MsgVoteWeighted) Type() string { return TypeMsgVoteWeighted } + +// ValidateBasic implements Msg +func (msg MsgVoteWeighted) ValidateBasic() error { + if msg.Voter == "" { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, msg.Voter) + } + + if len(msg.Options) == 0 { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, WeightedVoteOptions(msg.Options).String()) + } + + totalWeight := sdk.NewDec(0) + usedOptions := make(map[VoteOption]bool) + for _, option := range msg.Options { + if !ValidWeightedVoteOption(option) { + return sdkerrors.Wrap(ErrInvalidVote, option.String()) + } + totalWeight = totalWeight.Add(option.Weight) + if usedOptions[option.Option] { + return sdkerrors.Wrap(ErrInvalidVote, "Duplicated vote option") + } + usedOptions[option.Option] = true + } + + if totalWeight.GT(sdk.NewDec(1)) { + return sdkerrors.Wrap(ErrInvalidVote, "Total weight overflow 1.00") + } + + if totalWeight.LT(sdk.NewDec(1)) { + return sdkerrors.Wrap(ErrInvalidVote, "Total weight lower than 1.00") + } + + return nil +} + +// String implements the Stringer interface +func (msg MsgVoteWeighted) String() string { + out, _ := yaml.Marshal(msg) + return string(out) +} + +// GetSignBytes implements Msg +func (msg MsgVoteWeighted) GetSignBytes() []byte { + bz := ModuleCdc.MustMarshalJSON(&msg) + return sdk.MustSortJSON(bz) +} + +// GetSigners implements Msg +func (msg MsgVoteWeighted) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{sdk.AccAddress(msg.Voter)} +} diff --git a/x/gov/types/msgs_test.go b/x/gov/types/msgs_test.go index c22cdae67d..f18283dd5d 100644 --- a/x/gov/types/msgs_test.go +++ b/x/gov/types/msgs_test.go @@ -118,6 +118,50 @@ func TestMsgVote(t *testing.T) { } } +// test ValidateBasic for MsgVoteWeighted +func TestMsgVoteWeighted(t *testing.T) { + tests := []struct { + proposalID uint64 + voterAddr sdk.AccAddress + options WeightedVoteOptions + expectPass bool + }{ + {0, addrs[0], NewNonSplitVoteOption(OptionYes), true}, + {0, "", NewNonSplitVoteOption(OptionYes), false}, + {0, addrs[0], NewNonSplitVoteOption(OptionNo), true}, + {0, addrs[0], NewNonSplitVoteOption(OptionNoWithVeto), true}, + {0, addrs[0], NewNonSplitVoteOption(OptionAbstain), true}, + {0, addrs[0], WeightedVoteOptions{ // weight sum > 1 + WeightedVoteOption{Option: OptionYes, Weight: sdk.NewDec(1)}, + WeightedVoteOption{Option: OptionAbstain, Weight: sdk.NewDec(1)}, + }, false}, + {0, addrs[0], WeightedVoteOptions{ // duplicate option + WeightedVoteOption{Option: OptionYes, Weight: sdk.NewDecWithPrec(5, 1)}, + WeightedVoteOption{Option: OptionYes, Weight: sdk.NewDecWithPrec(5, 1)}, + }, false}, + {0, addrs[0], WeightedVoteOptions{ // zero weight + WeightedVoteOption{Option: OptionYes, Weight: sdk.NewDec(0)}, + }, false}, + {0, addrs[0], WeightedVoteOptions{ // negative weight + WeightedVoteOption{Option: OptionYes, Weight: sdk.NewDec(-1)}, + }, false}, + {0, addrs[0], WeightedVoteOptions{}, false}, + {0, addrs[0], NewNonSplitVoteOption(VoteOption(0x13)), false}, + {0, addrs[0], WeightedVoteOptions{ // weight sum <1 + WeightedVoteOption{Option: OptionYes, Weight: sdk.NewDecWithPrec(5, 1)}, + }, false}, + } + + for i, tc := range tests { + msg := NewMsgVoteWeighted(tc.voterAddr, tc.proposalID, tc.options) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", i) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", i) + } + } +} + // this tests that Amino JSON MsgSubmitProposal.GetSignBytes() still works with Content as Any using the ModuleCdc func TestMsgSubmitProposal_GetSignBytes(t *testing.T) { msg, err := NewMsgSubmitProposal(NewTextProposal("test", "abcd"), sdk.NewCoins(), "") diff --git a/x/gov/types/tally.go b/x/gov/types/tally.go index 7b6d5a8765..d2dbb43de1 100644 --- a/x/gov/types/tally.go +++ b/x/gov/types/tally.go @@ -8,16 +8,16 @@ import ( // ValidatorGovInfo used for tallying type ValidatorGovInfo struct { - Address sdk.ValAddress // address of the validator operator - BondedTokens sdk.Int // Power of a Validator - DelegatorShares sdk.Dec // Total outstanding delegator shares - DelegatorDeductions sdk.Dec // Delegator deductions from validator's delegators voting independently - Vote VoteOption // Vote of the validator + Address sdk.ValAddress // address of the validator operator + BondedTokens sdk.Int // Power of a Validator + DelegatorShares sdk.Dec // Total outstanding delegator shares + DelegatorDeductions sdk.Dec // Delegator deductions from validator's delegators voting independently + Vote WeightedVoteOptions // Vote of the validator } // NewValidatorGovInfo creates a ValidatorGovInfo instance func NewValidatorGovInfo(address sdk.ValAddress, bondedTokens sdk.Int, delegatorShares, - delegatorDeductions sdk.Dec, vote VoteOption) ValidatorGovInfo { + delegatorDeductions sdk.Dec, vote WeightedVoteOptions) ValidatorGovInfo { return ValidatorGovInfo{ Address: address, diff --git a/x/gov/types/tx.pb.go b/x/gov/types/tx.pb.go index be2064061b..5a69af390a 100644 --- a/x/gov/types/tx.pb.go +++ b/x/gov/types/tx.pb.go @@ -193,6 +193,82 @@ func (m *MsgVoteResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgVoteResponse proto.InternalMessageInfo +// MsgVoteWeighted defines a message to cast a vote. +type MsgVoteWeighted struct { + ProposalId uint64 `protobuf:"varint,1,opt,name=proposal_id,json=proposalId,proto3" json:"proposal_id,omitempty" yaml:"proposal_id"` + Voter string `protobuf:"bytes,2,opt,name=voter,proto3" json:"voter,omitempty"` + Options []WeightedVoteOption `protobuf:"bytes,3,rep,name=options,proto3" json:"options"` +} + +func (m *MsgVoteWeighted) Reset() { *m = MsgVoteWeighted{} } +func (*MsgVoteWeighted) ProtoMessage() {} +func (*MsgVoteWeighted) Descriptor() ([]byte, []int) { + return fileDescriptor_618877ea39a166a2, []int{4} +} +func (m *MsgVoteWeighted) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgVoteWeighted) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgVoteWeighted.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgVoteWeighted) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgVoteWeighted.Merge(m, src) +} +func (m *MsgVoteWeighted) XXX_Size() int { + return m.Size() +} +func (m *MsgVoteWeighted) XXX_DiscardUnknown() { + xxx_messageInfo_MsgVoteWeighted.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgVoteWeighted proto.InternalMessageInfo + +// MsgVoteWeightedResponse defines the Msg/VoteWeighted response type. +type MsgVoteWeightedResponse struct { +} + +func (m *MsgVoteWeightedResponse) Reset() { *m = MsgVoteWeightedResponse{} } +func (m *MsgVoteWeightedResponse) String() string { return proto.CompactTextString(m) } +func (*MsgVoteWeightedResponse) ProtoMessage() {} +func (*MsgVoteWeightedResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_618877ea39a166a2, []int{5} +} +func (m *MsgVoteWeightedResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgVoteWeightedResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgVoteWeightedResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgVoteWeightedResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgVoteWeightedResponse.Merge(m, src) +} +func (m *MsgVoteWeightedResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgVoteWeightedResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgVoteWeightedResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgVoteWeightedResponse proto.InternalMessageInfo + // MsgDeposit defines a message to submit a deposit to an existing proposal. type MsgDeposit struct { ProposalId uint64 `protobuf:"varint,1,opt,name=proposal_id,json=proposalId,proto3" json:"proposal_id" yaml:"proposal_id"` @@ -203,7 +279,7 @@ type MsgDeposit struct { func (m *MsgDeposit) Reset() { *m = MsgDeposit{} } func (*MsgDeposit) ProtoMessage() {} func (*MsgDeposit) Descriptor() ([]byte, []int) { - return fileDescriptor_618877ea39a166a2, []int{4} + return fileDescriptor_618877ea39a166a2, []int{6} } func (m *MsgDeposit) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -240,7 +316,7 @@ func (m *MsgDepositResponse) Reset() { *m = MsgDepositResponse{} } func (m *MsgDepositResponse) String() string { return proto.CompactTextString(m) } func (*MsgDepositResponse) ProtoMessage() {} func (*MsgDepositResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_618877ea39a166a2, []int{5} + return fileDescriptor_618877ea39a166a2, []int{7} } func (m *MsgDepositResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -274,6 +350,8 @@ func init() { proto.RegisterType((*MsgSubmitProposalResponse)(nil), "lbm.gov.v1.MsgSubmitProposalResponse") proto.RegisterType((*MsgVote)(nil), "lbm.gov.v1.MsgVote") proto.RegisterType((*MsgVoteResponse)(nil), "lbm.gov.v1.MsgVoteResponse") + proto.RegisterType((*MsgVoteWeighted)(nil), "lbm.gov.v1.MsgVoteWeighted") + proto.RegisterType((*MsgVoteWeightedResponse)(nil), "lbm.gov.v1.MsgVoteWeightedResponse") proto.RegisterType((*MsgDeposit)(nil), "lbm.gov.v1.MsgDeposit") proto.RegisterType((*MsgDepositResponse)(nil), "lbm.gov.v1.MsgDepositResponse") } @@ -281,44 +359,48 @@ func init() { func init() { proto.RegisterFile("lbm/gov/v1/tx.proto", fileDescriptor_618877ea39a166a2) } var fileDescriptor_618877ea39a166a2 = []byte{ - // 583 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x31, 0x6f, 0xd3, 0x40, - 0x18, 0xb5, 0x9b, 0xd2, 0xd0, 0x2f, 0x52, 0x4b, 0xdc, 0x28, 0x4a, 0x0d, 0xd8, 0x91, 0x51, 0xa5, - 0x48, 0xa8, 0x67, 0x25, 0x2c, 0xa8, 0x88, 0xa1, 0x29, 0x20, 0x31, 0x44, 0x20, 0x23, 0x65, 0x60, - 0x89, 0xec, 0xc4, 0x18, 0x0b, 0xdb, 0x9f, 0x95, 0xbb, 0x58, 0xcd, 0x06, 0x4c, 0x8c, 0x8c, 0x8c, - 0x59, 0x61, 0xe6, 0x47, 0x54, 0x4c, 0x1d, 0x11, 0xaa, 0x02, 0x4a, 0x16, 0xc4, 0xd8, 0x5f, 0x80, - 0x7c, 0xb6, 0x93, 0x28, 0x4d, 0x11, 0x43, 0xb7, 0x7c, 0xef, 0xde, 0xfb, 0x92, 0xf7, 0xee, 0xe5, - 0x60, 0xc7, 0xb3, 0x7c, 0xdd, 0xc1, 0x48, 0x8f, 0xea, 0x3a, 0x3b, 0x26, 0x61, 0x1f, 0x19, 0x4a, - 0xe0, 0x59, 0x3e, 0x71, 0x30, 0x22, 0x51, 0x5d, 0x2e, 0xc7, 0x04, 0xcb, 0xa4, 0x76, 0xcc, 0xe8, - 0xa2, 0x1b, 0x24, 0x1c, 0xb9, 0xb4, 0x20, 0x8c, 0xa9, 0x09, 0xba, 0xdb, 0x45, 0xea, 0x23, 0xed, - 0xf0, 0x49, 0x4f, 0x86, 0x4c, 0xe0, 0xa0, 0x83, 0x09, 0x1e, 0x7f, 0xca, 0x04, 0x0e, 0xa2, 0xe3, - 0xd9, 0x3a, 0x9f, 0xac, 0xc1, 0x2b, 0xdd, 0x0c, 0x86, 0xc9, 0x91, 0xf6, 0x7e, 0x0d, 0x8a, 0x2d, - 0xea, 0xbc, 0x18, 0x58, 0xbe, 0xcb, 0x9e, 0xf7, 0x31, 0x44, 0x6a, 0x7a, 0xd2, 0x03, 0xc8, 0x77, - 0x31, 0x60, 0x76, 0xc0, 0x2a, 0x62, 0x55, 0xac, 0x15, 0x1a, 0x25, 0x92, 0xac, 0x20, 0xd9, 0x0a, - 0x72, 0x18, 0x0c, 0x9b, 0x85, 0x6f, 0x5f, 0xf7, 0xf3, 0x47, 0x09, 0xd1, 0xc8, 0x14, 0xd2, 0x3b, - 0x11, 0xb6, 0xdd, 0xc0, 0x65, 0xae, 0xe9, 0x75, 0x7a, 0x76, 0x88, 0xd4, 0x65, 0x95, 0xb5, 0x6a, - 0xae, 0x56, 0x68, 0x14, 0x49, 0xec, 0x39, 0xf6, 0x49, 0xa2, 0x3a, 0x39, 0x42, 0x37, 0x68, 0x3e, - 0x3e, 0x19, 0xab, 0xc2, 0xf9, 0x58, 0x2d, 0x0f, 0x4d, 0xdf, 0x3b, 0xd0, 0x96, 0x74, 0xda, 0x97, - 0x9f, 0xea, 0x1d, 0xc7, 0x65, 0xaf, 0x07, 0x16, 0xe9, 0xa2, 0xaf, 0x7b, 0x6e, 0x60, 0xeb, 0x9e, - 0xe5, 0xef, 0xd3, 0xde, 0x1b, 0x9d, 0x0d, 0x43, 0x9b, 0xf2, 0x2d, 0xd4, 0xd8, 0x4a, 0x85, 0x8f, - 0x12, 0x9d, 0x24, 0xc3, 0xf5, 0x90, 0x9b, 0xb1, 0xfb, 0x95, 0x5c, 0x55, 0xac, 0x6d, 0x1a, 0xb3, - 0xf9, 0xe0, 0xc6, 0x87, 0x91, 0x2a, 0x7c, 0x1a, 0xa9, 0xc2, 0xef, 0x91, 0x2a, 0xbc, 0x3d, 0xab, - 0x0a, 0x5a, 0x17, 0x76, 0x2f, 0x64, 0x60, 0xd8, 0x34, 0xc4, 0x80, 0xda, 0xd2, 0x13, 0x28, 0x84, - 0x29, 0xd6, 0x71, 0x7b, 0x3c, 0x8f, 0xf5, 0xe6, 0xde, 0x9f, 0xb1, 0xba, 0x08, 0x9f, 0x8f, 0x55, - 0x29, 0x71, 0xb0, 0x00, 0x6a, 0x06, 0x64, 0xd3, 0xd3, 0x9e, 0xf6, 0x59, 0x84, 0x7c, 0x8b, 0x3a, - 0x6d, 0x64, 0x57, 0xb6, 0x53, 0x2a, 0xc1, 0xb5, 0x08, 0x99, 0xdd, 0xaf, 0xac, 0x71, 0x8f, 0xc9, - 0x20, 0x11, 0xd8, 0xc0, 0x90, 0xb9, 0x18, 0x70, 0xeb, 0x5b, 0x8d, 0x32, 0x99, 0x57, 0x8d, 0xc4, - 0xdf, 0xff, 0x8c, 0x9f, 0x1a, 0x29, 0x6b, 0x45, 0x20, 0x45, 0xd8, 0x4e, 0x7f, 0x6a, 0x16, 0x83, - 0x76, 0x26, 0x02, 0xb4, 0xa8, 0x93, 0x05, 0x7c, 0x55, 0x0e, 0x6e, 0xc1, 0x66, 0x7a, 0xd7, 0x98, - 0xb9, 0x98, 0x03, 0x52, 0x1b, 0x36, 0x4c, 0x1f, 0x07, 0x01, 0xab, 0xe4, 0x2e, 0x2b, 0xd0, 0xdd, - 0xb8, 0x40, 0xff, 0x5b, 0x93, 0x74, 0xdb, 0x0a, 0xc7, 0x25, 0x90, 0xe6, 0xee, 0x32, 0xd3, 0x8d, - 0x1f, 0x22, 0xe4, 0x5a, 0xd4, 0x91, 0xda, 0xb0, 0xb5, 0xf4, 0x0f, 0xb9, 0xbd, 0x98, 0xe9, 0x85, - 0xf2, 0xc8, 0x7b, 0xff, 0x3c, 0x9e, 0x75, 0xeb, 0x3e, 0xac, 0xf3, 0x3e, 0xec, 0x2c, 0xd1, 0x63, - 0x50, 0xbe, 0xb9, 0x02, 0x9c, 0x29, 0x0f, 0x21, 0x9f, 0x5d, 0x45, 0x79, 0x89, 0x97, 0xe2, 0xb2, - 0xb2, 0x1a, 0xcf, 0x56, 0x34, 0x1f, 0x9e, 0x4c, 0x14, 0xf1, 0x74, 0xa2, 0x88, 0xbf, 0x26, 0x8a, - 0xf8, 0x71, 0xaa, 0x08, 0xa7, 0x53, 0x45, 0xf8, 0x3e, 0x55, 0x84, 0x97, 0x97, 0x66, 0x79, 0xcc, - 0x1f, 0x23, 0x9e, 0xa8, 0xb5, 0xc1, 0x9f, 0x82, 0x7b, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x49, - 0xa1, 0xcc, 0x01, 0xdd, 0x04, 0x00, 0x00, + // 654 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0xb1, 0x6f, 0xd3, 0x4e, + 0x14, 0xb6, 0x93, 0xfe, 0x9a, 0x5f, 0x5f, 0x50, 0x4b, 0xdd, 0x28, 0xa4, 0x06, 0xec, 0xc8, 0x55, + 0xa5, 0x4a, 0xa8, 0x67, 0x35, 0x0c, 0xa0, 0x22, 0x90, 0x9a, 0x02, 0x12, 0x43, 0x44, 0x65, 0xa4, + 0x22, 0xb1, 0x54, 0x71, 0x62, 0x5c, 0x0b, 0xdb, 0xcf, 0xca, 0x5d, 0xa2, 0x66, 0x03, 0x26, 0x46, + 0x46, 0xc6, 0xae, 0x20, 0x26, 0xc4, 0x1f, 0x51, 0x31, 0x75, 0x64, 0xa8, 0x02, 0x6a, 0x17, 0xc4, + 0xd8, 0xbf, 0x00, 0xf9, 0xec, 0x4b, 0x42, 0x6a, 0x2a, 0x84, 0xba, 0xf9, 0x7d, 0xef, 0xfb, 0x9e, + 0xdf, 0xf7, 0xee, 0xdd, 0xc1, 0x82, 0x6f, 0x07, 0xa6, 0x8b, 0x3d, 0xb3, 0xb7, 0x66, 0xb2, 0x3d, + 0x12, 0x75, 0x90, 0xa1, 0x02, 0xbe, 0x1d, 0x10, 0x17, 0x7b, 0xa4, 0xb7, 0xa6, 0x96, 0x63, 0x82, + 0xdd, 0xa4, 0x4e, 0xcc, 0x68, 0xa1, 0x17, 0x26, 0x1c, 0xb5, 0x34, 0x26, 0x8c, 0xa9, 0x09, 0xba, + 0xd8, 0x42, 0x1a, 0x20, 0xdd, 0xe1, 0x91, 0x99, 0x04, 0x42, 0xe0, 0xa2, 0x8b, 0x09, 0x1e, 0x7f, + 0x09, 0x81, 0x8b, 0xe8, 0xfa, 0x8e, 0xc9, 0x23, 0xbb, 0xfb, 0xdc, 0x6c, 0x86, 0xfd, 0x24, 0x65, + 0xbc, 0xce, 0xc1, 0x7c, 0x83, 0xba, 0x4f, 0xba, 0x76, 0xe0, 0xb1, 0xad, 0x0e, 0x46, 0x48, 0x9b, + 0xbe, 0x72, 0x07, 0x0a, 0x2d, 0x0c, 0x99, 0x13, 0xb2, 0x8a, 0x5c, 0x95, 0x57, 0x8a, 0xb5, 0x12, + 0x49, 0x4a, 0x10, 0x51, 0x82, 0x6c, 0x84, 0xfd, 0x7a, 0xf1, 0xcb, 0xe7, 0xd5, 0xc2, 0x66, 0x42, + 0xb4, 0x84, 0x42, 0x79, 0x25, 0xc3, 0x9c, 0x17, 0x7a, 0xcc, 0x6b, 0xfa, 0x3b, 0x6d, 0x27, 0x42, + 0xea, 0xb1, 0x4a, 0xae, 0x9a, 0x5f, 0x29, 0xd6, 0xe6, 0x49, 0xec, 0x39, 0xf6, 0x49, 0x7a, 0x6b, + 0x64, 0x13, 0xbd, 0xb0, 0xfe, 0xe0, 0x60, 0xa0, 0x4b, 0xa7, 0x03, 0xbd, 0xdc, 0x6f, 0x06, 0xfe, + 0xba, 0x31, 0xa1, 0x33, 0x3e, 0x7c, 0xd3, 0x97, 0x5c, 0x8f, 0xed, 0x76, 0x6d, 0xd2, 0xc2, 0xc0, + 0xf4, 0xbd, 0xd0, 0x31, 0x7d, 0x3b, 0x58, 0xa5, 0xed, 0x17, 0x26, 0xeb, 0x47, 0x0e, 0xe5, 0x55, + 0xa8, 0x35, 0x9b, 0x0a, 0xef, 0x27, 0x3a, 0x45, 0x85, 0xff, 0x23, 0x6e, 0xc6, 0xe9, 0x54, 0xf2, + 0x55, 0x79, 0x65, 0xc6, 0x1a, 0xc6, 0xeb, 0x97, 0xdf, 0xec, 0xeb, 0xd2, 0xbb, 0x7d, 0x5d, 0xfa, + 0xb1, 0xaf, 0x4b, 0x2f, 0x8f, 0xaa, 0x92, 0xd1, 0x82, 0xc5, 0x33, 0x33, 0xb0, 0x1c, 0x1a, 0x61, + 0x48, 0x1d, 0xe5, 0x21, 0x14, 0xa3, 0x14, 0xdb, 0xf1, 0xda, 0x7c, 0x1e, 0x53, 0xf5, 0xe5, 0x9f, + 0x03, 0x7d, 0x1c, 0x3e, 0x1d, 0xe8, 0x4a, 0xe2, 0x60, 0x0c, 0x34, 0x2c, 0x10, 0xd1, 0xa3, 0xb6, + 0xf1, 0x5e, 0x86, 0x42, 0x83, 0xba, 0xdb, 0xc8, 0x2e, 0xac, 0xa6, 0x52, 0x82, 0xff, 0x7a, 0xc8, + 0x9c, 0x4e, 0x25, 0xc7, 0x3d, 0x26, 0x81, 0x42, 0x60, 0x1a, 0x23, 0xe6, 0x61, 0xc8, 0xad, 0xcf, + 0xd6, 0xca, 0x64, 0xb4, 0x6a, 0x24, 0xfe, 0xff, 0x63, 0x9e, 0xb5, 0x52, 0x56, 0xc6, 0x40, 0xe6, + 0x61, 0x2e, 0x6d, 0x55, 0x8c, 0xc1, 0xf8, 0x24, 0x0f, 0xb1, 0xa7, 0x8e, 0xe7, 0xee, 0x32, 0xa7, + 0xad, 0xdc, 0xca, 0xb2, 0x51, 0xfe, 0xe7, 0xbe, 0xef, 0x41, 0x21, 0xe9, 0x88, 0x56, 0xf2, 0x7c, + 0x5f, 0xb4, 0xf1, 0xc6, 0xc5, 0x5f, 0x47, 0x06, 0xea, 0x53, 0xf1, 0xf2, 0x58, 0x42, 0x94, 0xe1, + 0x63, 0x11, 0xae, 0x4c, 0xf4, 0x3c, 0xf4, 0x73, 0x24, 0x03, 0x34, 0xa8, 0x2b, 0x16, 0xe6, 0xa2, + 0x4e, 0xe4, 0x1a, 0xcc, 0xa4, 0xbb, 0x8b, 0xc2, 0xdd, 0x08, 0x50, 0xb6, 0x61, 0xba, 0x19, 0x60, + 0x37, 0x64, 0xa9, 0xc1, 0x8c, 0x0b, 0x71, 0x23, 0xf6, 0xf4, 0xb7, 0x6b, 0x9f, 0x56, 0xcb, 0x70, + 0x5e, 0x02, 0x65, 0xe4, 0x4e, 0x98, 0xae, 0x7d, 0xcc, 0x41, 0xbe, 0x41, 0x5d, 0x65, 0x1b, 0x66, + 0x27, 0x6e, 0xfc, 0xf5, 0xf1, 0x51, 0x9f, 0xb9, 0x0c, 0xea, 0xf2, 0xb9, 0xe9, 0xe1, 0x5d, 0xb9, + 0x0d, 0x53, 0x7c, 0xbf, 0x17, 0x26, 0xe8, 0x31, 0xa8, 0x5e, 0xcd, 0x00, 0x87, 0xca, 0x2d, 0xb8, + 0xf4, 0xdb, 0x6a, 0x65, 0x91, 0x45, 0x52, 0x5d, 0x3a, 0x27, 0x39, 0xac, 0xb8, 0x01, 0x05, 0x71, + 0xb8, 0xe5, 0x09, 0x7e, 0x8a, 0xab, 0x5a, 0x36, 0x2e, 0x4a, 0xd4, 0xef, 0x1e, 0x1c, 0x6b, 0xf2, + 0xe1, 0xb1, 0x26, 0x7f, 0x3f, 0xd6, 0xe4, 0xb7, 0x27, 0x9a, 0x74, 0x78, 0xa2, 0x49, 0x5f, 0x4f, + 0x34, 0xe9, 0xd9, 0x1f, 0x4f, 0x67, 0x8f, 0x3f, 0xd7, 0xfc, 0x8c, 0xec, 0x69, 0xfe, 0x58, 0xde, + 0xfc, 0x15, 0x00, 0x00, 0xff, 0xff, 0xc3, 0x17, 0x0a, 0x31, 0xff, 0x05, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -337,6 +419,8 @@ type MsgClient interface { SubmitProposal(ctx context.Context, in *MsgSubmitProposal, opts ...grpc.CallOption) (*MsgSubmitProposalResponse, error) // Vote defines a method to add a vote on a specific proposal. Vote(ctx context.Context, in *MsgVote, opts ...grpc.CallOption) (*MsgVoteResponse, error) + // VoteWeighted defines a method to add a weighted vote on a specific proposal. + VoteWeighted(ctx context.Context, in *MsgVoteWeighted, opts ...grpc.CallOption) (*MsgVoteWeightedResponse, error) // Deposit defines a method to add deposit on a specific proposal. Deposit(ctx context.Context, in *MsgDeposit, opts ...grpc.CallOption) (*MsgDepositResponse, error) } @@ -367,6 +451,15 @@ func (c *msgClient) Vote(ctx context.Context, in *MsgVote, opts ...grpc.CallOpti return out, nil } +func (c *msgClient) VoteWeighted(ctx context.Context, in *MsgVoteWeighted, opts ...grpc.CallOption) (*MsgVoteWeightedResponse, error) { + out := new(MsgVoteWeightedResponse) + err := c.cc.Invoke(ctx, "/lbm.gov.v1.Msg/VoteWeighted", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *msgClient) Deposit(ctx context.Context, in *MsgDeposit, opts ...grpc.CallOption) (*MsgDepositResponse, error) { out := new(MsgDepositResponse) err := c.cc.Invoke(ctx, "/lbm.gov.v1.Msg/Deposit", in, out, opts...) @@ -382,6 +475,8 @@ type MsgServer interface { SubmitProposal(context.Context, *MsgSubmitProposal) (*MsgSubmitProposalResponse, error) // Vote defines a method to add a vote on a specific proposal. Vote(context.Context, *MsgVote) (*MsgVoteResponse, error) + // VoteWeighted defines a method to add a weighted vote on a specific proposal. + VoteWeighted(context.Context, *MsgVoteWeighted) (*MsgVoteWeightedResponse, error) // Deposit defines a method to add deposit on a specific proposal. Deposit(context.Context, *MsgDeposit) (*MsgDepositResponse, error) } @@ -396,6 +491,9 @@ func (*UnimplementedMsgServer) SubmitProposal(ctx context.Context, req *MsgSubmi func (*UnimplementedMsgServer) Vote(ctx context.Context, req *MsgVote) (*MsgVoteResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Vote not implemented") } +func (*UnimplementedMsgServer) VoteWeighted(ctx context.Context, req *MsgVoteWeighted) (*MsgVoteWeightedResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method VoteWeighted not implemented") +} func (*UnimplementedMsgServer) Deposit(ctx context.Context, req *MsgDeposit) (*MsgDepositResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Deposit not implemented") } @@ -440,6 +538,24 @@ func _Msg_Vote_Handler(srv interface{}, ctx context.Context, dec func(interface{ return interceptor(ctx, in, info, handler) } +func _Msg_VoteWeighted_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgVoteWeighted) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).VoteWeighted(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lbm.gov.v1.Msg/VoteWeighted", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).VoteWeighted(ctx, req.(*MsgVoteWeighted)) + } + return interceptor(ctx, in, info, handler) +} + func _Msg_Deposit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MsgDeposit) if err := dec(in); err != nil { @@ -470,6 +586,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "Vote", Handler: _Msg_Vote_Handler, }, + { + MethodName: "VoteWeighted", + Handler: _Msg_VoteWeighted_Handler, + }, { MethodName: "Deposit", Handler: _Msg_Deposit_Handler, @@ -626,6 +746,78 @@ func (m *MsgVoteResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *MsgVoteWeighted) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgVoteWeighted) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgVoteWeighted) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Options) > 0 { + for iNdEx := len(m.Options) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Options[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if len(m.Voter) > 0 { + i -= len(m.Voter) + copy(dAtA[i:], m.Voter) + i = encodeVarintTx(dAtA, i, uint64(len(m.Voter))) + i-- + dAtA[i] = 0x12 + } + if m.ProposalId != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.ProposalId)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *MsgVoteWeightedResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgVoteWeightedResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgVoteWeightedResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func (m *MsgDeposit) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -772,6 +964,37 @@ func (m *MsgVoteResponse) Size() (n int) { return n } +func (m *MsgVoteWeighted) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ProposalId != 0 { + n += 1 + sovTx(uint64(m.ProposalId)) + } + l = len(m.Voter) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if len(m.Options) > 0 { + for _, e := range m.Options { + l = e.Size() + n += 1 + l + sovTx(uint64(l)) + } + } + return n +} + +func (m *MsgVoteWeightedResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func (m *MsgDeposit) Size() (n int) { if m == nil { return 0 @@ -1200,6 +1423,191 @@ func (m *MsgVoteResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgVoteWeighted) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgVoteWeighted: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgVoteWeighted: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ProposalId", wireType) + } + m.ProposalId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ProposalId |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Voter", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Voter = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Options", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Options = append(m.Options, WeightedVoteOption{}) + if err := m.Options[len(m.Options)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgVoteWeightedResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgVoteWeightedResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgVoteWeightedResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *MsgDeposit) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/gov/types/vote.go b/x/gov/types/vote.go index a7a7f98694..90f55636d3 100644 --- a/x/gov/types/vote.go +++ b/x/gov/types/vote.go @@ -1,7 +1,9 @@ package types import ( + "encoding/json" "fmt" + "strings" yaml "gopkg.in/yaml.v2" @@ -10,8 +12,8 @@ import ( // NewVote creates a new Vote instance //nolint:interfacer -func NewVote(proposalID uint64, voter sdk.AccAddress, option VoteOption) Vote { - return Vote{proposalID, voter.String(), option} +func NewVote(proposalID uint64, voter sdk.AccAddress, options WeightedVoteOptions) Vote { + return Vote{ProposalId: proposalID, Voter: voter.String(), Options: options} } func (v Vote) String() string { @@ -53,16 +55,67 @@ func (v Vote) Empty() bool { return v.String() == Vote{}.String() } +// NewNonSplitVoteOption creates a single option vote with weight 1 +func NewNonSplitVoteOption(option VoteOption) WeightedVoteOptions { + return WeightedVoteOptions{{option, sdk.NewDec(1)}} +} + +func (v WeightedVoteOption) String() string { + out, _ := json.Marshal(v) + return string(out) +} + +// WeightedVoteOptions describes array of WeightedVoteOptions +type WeightedVoteOptions []WeightedVoteOption + +func (v WeightedVoteOptions) String() (out string) { + for _, opt := range v { + out += opt.String() + "\n" + } + + return strings.TrimSpace(out) +} + +// ValidWeightedVoteOption returns true if the sub vote is valid and false otherwise. +func ValidWeightedVoteOption(option WeightedVoteOption) bool { + if !option.Weight.IsPositive() || option.Weight.GT(sdk.NewDec(1)) { + return false + } + return ValidVoteOption(option.Option) +} + // VoteOptionFromString returns a VoteOption from a string. It returns an error // if the string is invalid. func VoteOptionFromString(str string) (VoteOption, error) { option, ok := VoteOption_value[str] if !ok { - return OptionEmpty, fmt.Errorf("'%s' is not a valid vote option", str) + return OptionEmpty, fmt.Errorf("'%s' is not a valid vote option, available options: yes/no/no_with_veto/abstain", str) } return VoteOption(option), nil } +// WeightedVoteOptionsFromString returns weighted vote options from string. It returns an error +// if the string is invalid. +func WeightedVoteOptionsFromString(str string) (WeightedVoteOptions, error) { + options := WeightedVoteOptions{} + for _, option := range strings.Split(str, ",") { + fields := strings.Split(option, "=") + option, err := VoteOptionFromString(fields[0]) + if err != nil { + return options, err + } + if len(fields) < 2 { + return options, fmt.Errorf("weight field does not exist for %s option", fields[0]) + } + weight, err := sdk.NewDecFromStr(fields[1]) + if err != nil { + return options, err + } + options = append(options, WeightedVoteOption{option, weight}) + } + return options, nil +} + // ValidVoteOption returns true if the vote option is valid and false otherwise. func ValidVoteOption(option VoteOption) bool { if option == OptionYes || diff --git a/x/simulation/expected_keepers.go b/x/simulation/expected_keepers.go new file mode 100644 index 0000000000..2194d95273 --- /dev/null +++ b/x/simulation/expected_keepers.go @@ -0,0 +1,16 @@ +package simulation + +import ( + sdk "github.com/line/lbm-sdk/types" + "github.com/line/lbm-sdk/x/auth/types" +) + +// AccountKeeper defines the expected account keeper used for simulations (noalias) +type AccountKeeper interface { + GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI +} + +// BankKeeper defines the expected interface needed to retrieve account balances. +type BankKeeper interface { + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins +} From 425d1892a7e65eaeb28677f965949b574c52c9d1 Mon Sep 17 00:00:00 2001 From: leesj9476 Date: Tue, 2 Nov 2021 08:39:31 +0900 Subject: [PATCH 2/2] docs: edit CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd601f298b..bb875ec1c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features * (feat) [\#352] (https://github.com/line/lbm-sdk/pull/352) iavl, db & disk stats logging +* (x/gov) [\#368](https://github.com/line/lbm-sdk/pull/368) Governance Split Votes, use `MsgWeightedVote` to send a split vote. Sending a regular `MsgVote` will convert the underlying vote option into a weighted vote with weight 1. ### Improvements * (slashing) [\#347](https://github.com/line/lbm-sdk/pull/347) Introduce VoterSetCounter