Skip to content

Commit

Permalink
Merge pull request #39 from antstalepresh/gov_split_vote
Browse files Browse the repository at this point in the history
Governance split vote
  • Loading branch information
antstalepresh authored Nov 4, 2020
2 parents 09d977e + 381d1b0 commit 2671482
Show file tree
Hide file tree
Showing 34 changed files with 1,620 additions and 274 deletions.
16 changes: 13 additions & 3 deletions proto/cosmos/gov/v1beta1/gov.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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/cosmos/cosmos-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 {
Expand Down Expand Up @@ -119,9 +129,9 @@ 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;
repeated WeightedVoteOption options = 3 [(gogoproto.nullable) = false];
}

// DepositParams defines the params for deposits on governance proposals.
Expand Down
18 changes: 18 additions & 0 deletions proto/cosmos/gov/v1beta1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ service Msg {
// Vote defines a method to add a vote on a specific proposal.
rpc Vote(MsgVote) returns (MsgVoteResponse);

// WeightedVote defines a method to add a weighted vote on a specific proposal.
rpc WeightedVote(MsgWeightedVote) returns (MsgWeightedVoteResponse);

// Deposit defines a method to add deposit on a specific proposal.
rpc Deposit(MsgDeposit) returns (MsgDepositResponse);
}
Expand Down Expand Up @@ -55,9 +58,24 @@ message MsgVote {
VoteOption option = 3;
}

// MsgVote defines a message to cast a vote.
message MsgWeightedVote {
option (gogoproto.equal) = false;
option (gogoproto.goproto_stringer) = false;
option (gogoproto.stringer) = false;
option (gogoproto.goproto_getters) = false;

uint64 proposal_id = 1 [(gogoproto.jsontag) = "proposal_id", (gogoproto.moretags) = "yaml:\"proposal_id\""];
string voter = 2;
repeated WeightedVoteOption options = 3 [(gogoproto.nullable) = false];
}

// MsgVoteResponse defines the Msg/Vote response type.
message MsgVoteResponse {}

// MsgWeightedVoteResponse defines the MsgWeightedVote response type.
message MsgWeightedVoteResponse {}

// MsgDeposit defines a message to submit a deposit to an existing proposal.
message MsgDeposit {
option (gogoproto.equal) = false;
Expand Down
1 change: 1 addition & 0 deletions simapp/params/weights.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const (
DefaultWeightMsgFundCommunityPool int = 50
DefaultWeightMsgDeposit int = 100
DefaultWeightMsgVote int = 67
DefaultWeightMsgWeightedVote int = 33
DefaultWeightMsgUnjail int = 100
DefaultWeightMsgCreateValidator int = 100
DefaultWeightMsgEditValidator int = 5
Expand Down
4 changes: 2 additions & 2 deletions x/gov/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
129 changes: 124 additions & 5 deletions x/gov/client/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,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() {
Expand Down Expand Up @@ -448,7 +460,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)
}
})
}
Expand Down Expand Up @@ -689,9 +701,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",
Expand All @@ -700,6 +713,7 @@ func (s *IntegrationTestSuite) TestCmdQueryVote() {
val.Address.String(),
},
true,
types.NewNonSplitVoteOption(types.OptionYes),
},
{
"get vote by wrong voter",
Expand All @@ -708,6 +722,7 @@ func (s *IntegrationTestSuite) TestCmdQueryVote() {
"wrong address",
},
true,
types.NewNonSplitVoteOption(types.OptionYes),
},
{
"vote for valid proposal",
Expand All @@ -717,6 +732,22 @@ func (s *IntegrationTestSuite) TestCmdQueryVote() {
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
false,
types.NewNonSplitVoteOption(types.OptionYes),
},
{
"split vote for valid proposal",
[]string{
"3",
val.Address.String(),
fmt.Sprintf("--%s=json", tmcli.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)},
},
},
}

Expand All @@ -735,7 +766,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))
}
}
})
}
Expand Down Expand Up @@ -801,6 +836,90 @@ func (s *IntegrationTestSuite) TestNewCmdVote() {
}
}

func (s *IntegrationTestSuite) TestNewCmdWeightedVote() {
val := s.network.Validators[0]

testCases := []struct {
name string
args []string
expectErr bool
expectedCode uint32
}{
{
"invalid vote",
[]string{},
true, 0,
},
{
"vote for invalid proposal",
[]string{
"10",
fmt.Sprintf("%s", "yes"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
},
false, 2,
},
{
"valid vote",
[]string{
"1",
fmt.Sprintf("%s", "yes"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
},
false, 0,
},
{
"invalid valid split vote string",
[]string{
"1",
fmt.Sprintf("%s", "yes/0.6,no/0.3,abstain/0.05,no_with_veto/0.05"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
},
true, 0,
},
{
"valid split vote",
[]string{
"1",
fmt.Sprintf("%s", "yes=0.6,no=0.3,abstain=0.05,no_with_veto=0.05"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
},
false, 0,
},
}

for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
cmd := cli.NewCmdWeightedVote()
clientCtx := val.ClientCtx
var txResp sdk.TxResponse

out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args)

if tc.expectErr {
s.Require().Error(err)
} else {
s.Require().NoError(err)
s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &txResp), out.String())
s.Require().Equal(tc.expectedCode, txResp.Code, out.String())
}
})
}
}

func TestIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(IntegrationTestSuite))
}
56 changes: 55 additions & 1 deletion x/gov/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func NewTxCmd(propCmds []*cobra.Command) *cobra.Command {
govTxCmd.AddCommand(
NewCmdDeposit(),
NewCmdVote(),
NewCmdWeightedVote(),
cmdSubmitProp,
)

Expand Down Expand Up @@ -207,7 +208,6 @@ func NewCmdVote() *cobra.Command {
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 vote 1 yes --from mykey
`,
Expand Down Expand Up @@ -251,3 +251,57 @@ $ %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 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 := client.GetClientContextFromCmd(cmd)
clientCtx, err := client.ReadTxCommandFlags(clientCtx, cmd.Flags())
if err != nil {
return err
}

// Get voting 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.NewMsgWeightedVote(from, proposalID, options)
err = msg.ValidateBasic()
if err != nil {
return err
}

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}

flags.AddTxFlagsToCmd(cmd)

return cmd
}
Loading

0 comments on commit 2671482

Please sign in to comment.