From be081b10515fce26aac2dbfd9ea0f4a8052e0bd1 Mon Sep 17 00:00:00 2001 From: Marko Date: Sat, 22 Apr 2023 00:38:24 +0200 Subject: [PATCH] test: rewrite group tx e2e tests as unit tests (#15909) Co-authored-by: Julien Robert --- tests/e2e/group/cli_test.go | 2 +- tests/e2e/group/query.go | 116 +---------- tests/e2e/group/{tx.go => suite.go} | 307 +--------------------------- x/group/keeper/msg_server_test.go | 222 ++++++++++++++++++++ 4 files changed, 229 insertions(+), 418 deletions(-) rename tests/e2e/group/{tx.go => suite.go} (51%) diff --git a/tests/e2e/group/cli_test.go b/tests/e2e/group/cli_test.go index 7ff48ff729..438686bc38 100644 --- a/tests/e2e/group/cli_test.go +++ b/tests/e2e/group/cli_test.go @@ -14,6 +14,6 @@ import ( func TestE2ETestSuite(t *testing.T) { cfg := network.DefaultConfig(simapp.NewTestNetworkFixture) - cfg.NumValidators = 2 + cfg.NumValidators = 1 suite.Run(t, NewE2ETestSuite(cfg)) } diff --git a/tests/e2e/group/query.go b/tests/e2e/group/query.go index c4cbc4fbde..fc7db9cb56 100644 --- a/tests/e2e/group/query.go +++ b/tests/e2e/group/query.go @@ -21,28 +21,24 @@ func (s *E2ETestSuite) TestQueryGroupInfo() { args []string expectErr bool expectErrMsg string - expectedCode uint32 }{ { "group not found", []string{"12345", fmt.Sprintf("--%s=json", flags.FlagOutput)}, true, "group: not found", - 0, }, { "group id invalid", []string{"", fmt.Sprintf("--%s=json", flags.FlagOutput)}, true, "strconv.ParseUint: parsing \"\": invalid syntax", - 0, }, { "group found", []string{strconv.FormatUint(s.group.Id, 10), fmt.Sprintf("--%s=json", flags.FlagOutput)}, false, "", - 0, }, } @@ -162,55 +158,6 @@ func (s *E2ETestSuite) TestQueryGroupsByMembers() { } } -func (s *E2ETestSuite) TestQueryGroups() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - require := s.Require() - - testCases := []struct { - name string - args []string - expectErr bool - expectErrMsg string - numItems int - expectGroups []*group.GroupInfo - }{ - { - name: "valid req", - args: []string{fmt.Sprintf("--%s=json", flags.FlagOutput)}, - expectErr: false, - numItems: 5, - }, - { - name: "valid req with pagination", - args: []string{ - "--limit=2", - fmt.Sprintf("--%s=json", flags.FlagOutput), - }, - expectErr: false, - numItems: 2, - }, - } - - for _, tc := range testCases { - tc := tc - s.Run(tc.name, func() { - cmd := client.QueryGroupsCmd() - out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) - if tc.expectErr { - require.Contains(out.String(), tc.expectErrMsg) - } else { - require.NoError(err, out.String()) - - var resp group.QueryGroupsResponse - val.ClientCtx.Codec.MustUnmarshalJSON(out.Bytes(), &resp) - - require.Len(resp.Groups, tc.numItems) - } - }) - } -} - func (s *E2ETestSuite) TestQueryGroupMembers() { val := s.network.Validators[0] clientCtx := val.ClientCtx @@ -220,7 +167,6 @@ func (s *E2ETestSuite) TestQueryGroupMembers() { args []string expectErr bool expectErrMsg string - expectedCode uint32 expectMembers []*group.GroupMember }{ { @@ -228,7 +174,6 @@ func (s *E2ETestSuite) TestQueryGroupMembers() { []string{"12345", fmt.Sprintf("--%s=json", flags.FlagOutput)}, false, "", - 0, []*group.GroupMember{}, }, { @@ -236,7 +181,6 @@ func (s *E2ETestSuite) TestQueryGroupMembers() { []string{strconv.FormatUint(s.group.Id, 10), fmt.Sprintf("--%s=json", flags.FlagOutput)}, false, "", - 0, []*group.GroupMember{ { GroupId: s.group.Id, @@ -257,7 +201,6 @@ func (s *E2ETestSuite) TestQueryGroupMembers() { }, false, "", - 0, []*group.GroupMember{ { GroupId: s.group.Id, @@ -306,7 +249,6 @@ func (s *E2ETestSuite) TestQueryGroupsByAdmin() { args []string expectErr bool expectErrMsg string - expectedCode uint32 expectGroups []*group.GroupInfo }{ { @@ -314,15 +256,13 @@ func (s *E2ETestSuite) TestQueryGroupsByAdmin() { []string{"invalid"}, true, "decoding bech32 failed: invalid bech32 string", - 0, []*group.GroupInfo{}, }, { "no group", - []string{s.network.Validators[1].Address.String(), fmt.Sprintf("--%s=json", flags.FlagOutput)}, + []string{"cosmos139f7kncmglres2nf3h4hc4tade85ekfr8sulz5", fmt.Sprintf("--%s=json", flags.FlagOutput)}, false, "", - 0, []*group.GroupInfo{}, }, { @@ -330,7 +270,6 @@ func (s *E2ETestSuite) TestQueryGroupsByAdmin() { []string{val.Address.String(), fmt.Sprintf("--%s=json", flags.FlagOutput)}, false, "", - 0, []*group.GroupInfo{ s.group, }, @@ -344,7 +283,6 @@ func (s *E2ETestSuite) TestQueryGroupsByAdmin() { }, false, "", - 0, []*group.GroupInfo{ s.group, }, @@ -387,21 +325,18 @@ func (s *E2ETestSuite) TestQueryGroupPolicyInfo() { args []string expectErr bool expectErrMsg string - expectedCode uint32 }{ { "group policy not found", []string{val.Address.String(), fmt.Sprintf("--%s=json", flags.FlagOutput)}, true, "not found", - 0, }, { "group policy found", []string{s.groupPolicies[0].Address, fmt.Sprintf("--%s=json", flags.FlagOutput)}, false, "", - 0, }, } @@ -443,7 +378,6 @@ func (s *E2ETestSuite) TestQueryGroupPoliciesByGroup() { args []string expectErr bool expectErrMsg string - expectedCode uint32 expectGroupPolicies []*group.GroupPolicyInfo }{ { @@ -451,7 +385,6 @@ func (s *E2ETestSuite) TestQueryGroupPoliciesByGroup() { []string{""}, true, "strconv.ParseUint: parsing \"\": invalid syntax", - 0, []*group.GroupPolicyInfo{}, }, { @@ -459,7 +392,6 @@ func (s *E2ETestSuite) TestQueryGroupPoliciesByGroup() { []string{"12345", fmt.Sprintf("--%s=json", flags.FlagOutput)}, false, "", - 0, []*group.GroupPolicyInfo{}, }, { @@ -467,7 +399,6 @@ func (s *E2ETestSuite) TestQueryGroupPoliciesByGroup() { []string{strconv.FormatUint(s.group.Id, 10), fmt.Sprintf("--%s=json", flags.FlagOutput)}, false, "", - 0, []*group.GroupPolicyInfo{ s.groupPolicies[0], s.groupPolicies[1], @@ -486,7 +417,6 @@ func (s *E2ETestSuite) TestQueryGroupPoliciesByGroup() { }, false, "", - 0, []*group.GroupPolicyInfo{ s.groupPolicies[0], s.groupPolicies[1], @@ -534,7 +464,6 @@ func (s *E2ETestSuite) TestQueryGroupPoliciesByAdmin() { args []string expectErr bool expectErrMsg string - expectedCode uint32 expectGroupPolicies []*group.GroupPolicyInfo }{ { @@ -542,15 +471,13 @@ func (s *E2ETestSuite) TestQueryGroupPoliciesByAdmin() { []string{"invalid"}, true, "decoding bech32 failed: invalid bech32 string", - 0, []*group.GroupPolicyInfo{}, }, { "no group policy", - []string{s.network.Validators[1].Address.String(), fmt.Sprintf("--%s=json", flags.FlagOutput)}, + []string{"cosmos139f7kncmglres2nf3h4hc4tade85ekfr8sulz5", fmt.Sprintf("--%s=json", flags.FlagOutput)}, false, "", - 0, []*group.GroupPolicyInfo{}, }, { @@ -558,7 +485,6 @@ func (s *E2ETestSuite) TestQueryGroupPoliciesByAdmin() { []string{val.Address.String(), fmt.Sprintf("--%s=json", flags.FlagOutput)}, false, "", - 0, []*group.GroupPolicyInfo{ s.groupPolicies[0], s.groupPolicies[1], @@ -577,7 +503,7 @@ func (s *E2ETestSuite) TestQueryGroupPoliciesByAdmin() { }, false, "", - 0, + []*group.GroupPolicyInfo{ s.groupPolicies[0], s.groupPolicies[1], @@ -625,21 +551,18 @@ func (s *E2ETestSuite) TestQueryProposal() { args []string expectErr bool expectErrMsg string - expectedCode uint32 }{ { "not found", []string{"12345", fmt.Sprintf("--%s=json", flags.FlagOutput)}, true, "not found", - 0, }, { "invalid proposal id", []string{"", fmt.Sprintf("--%s=json", flags.FlagOutput)}, true, "strconv.ParseUint: parsing \"\": invalid syntax", - 0, }, } @@ -668,7 +591,6 @@ func (s *E2ETestSuite) TestQueryProposalsByGroupPolicy() { args []string expectErr bool expectErrMsg string - expectedCode uint32 expectProposals []*group.Proposal }{ { @@ -676,15 +598,13 @@ func (s *E2ETestSuite) TestQueryProposalsByGroupPolicy() { []string{"invalid"}, true, "decoding bech32 failed: invalid bech32 string", - 0, []*group.Proposal{}, }, { "no group policy", - []string{s.network.Validators[1].Address.String(), fmt.Sprintf("--%s=json", flags.FlagOutput)}, + []string{"cosmos139f7kncmglres2nf3h4hc4tade85ekfr8sulz5", fmt.Sprintf("--%s=json", flags.FlagOutput)}, false, "", - 0, []*group.Proposal{}, }, { @@ -692,7 +612,6 @@ func (s *E2ETestSuite) TestQueryProposalsByGroupPolicy() { []string{s.groupPolicies[0].Address, fmt.Sprintf("--%s=json", flags.FlagOutput)}, false, "", - 0, []*group.Proposal{ s.proposal, }, @@ -706,7 +625,6 @@ func (s *E2ETestSuite) TestQueryProposalsByGroupPolicy() { }, false, "", - 0, []*group.Proposal{ s.proposal, }, @@ -745,21 +663,18 @@ func (s *E2ETestSuite) TestQueryVoteByProposalVoter() { args []string expectErr bool expectErrMsg string - expectedCode uint32 }{ { "invalid voter address", []string{"1", "invalid", fmt.Sprintf("--%s=json", flags.FlagOutput)}, true, "decoding bech32 failed: invalid bech32", - 0, }, { "invalid proposal id", []string{"", val.Address.String(), fmt.Sprintf("--%s=json", flags.FlagOutput)}, true, "strconv.ParseUint: parsing \"\": invalid syntax", - 0, }, } @@ -788,7 +703,6 @@ func (s *E2ETestSuite) TestQueryVotesByProposal() { args []string expectErr bool expectErrMsg string - expectedCode uint32 expectVotes []*group.Vote }{ { @@ -796,7 +710,6 @@ func (s *E2ETestSuite) TestQueryVotesByProposal() { []string{"", fmt.Sprintf("--%s=json", flags.FlagOutput)}, true, "strconv.ParseUint: parsing \"\": invalid syntax", - 0, []*group.Vote{}, }, { @@ -804,7 +717,6 @@ func (s *E2ETestSuite) TestQueryVotesByProposal() { []string{"12345", fmt.Sprintf("--%s=json", flags.FlagOutput)}, false, "", - 0, []*group.Vote{}, }, { @@ -812,7 +724,6 @@ func (s *E2ETestSuite) TestQueryVotesByProposal() { []string{"1", fmt.Sprintf("--%s=json", flags.FlagOutput)}, false, "", - 0, []*group.Vote{ s.vote, }, @@ -826,7 +737,6 @@ func (s *E2ETestSuite) TestQueryVotesByProposal() { }, false, "", - 0, []*group.Vote{ s.vote, }, @@ -865,7 +775,6 @@ func (s *E2ETestSuite) TestQueryVotesByVoter() { args []string expectErr bool expectErrMsg string - expectedCode uint32 expectVotes []*group.Vote }{ { @@ -873,7 +782,6 @@ func (s *E2ETestSuite) TestQueryVotesByVoter() { []string{"abcd", fmt.Sprintf("--%s=json", flags.FlagOutput)}, true, "decoding bech32 failed: invalid bech32", - 0, []*group.Vote{}, }, { @@ -881,7 +789,6 @@ func (s *E2ETestSuite) TestQueryVotesByVoter() { []string{s.groupPolicies[0].Address, fmt.Sprintf("--%s=json", flags.FlagOutput)}, true, "", - 0, []*group.Vote{}, }, { @@ -889,7 +796,6 @@ func (s *E2ETestSuite) TestQueryVotesByVoter() { []string{val.Address.String(), fmt.Sprintf("--%s=json", flags.FlagOutput)}, false, "", - 0, []*group.Vote{ s.vote, }, @@ -903,7 +809,6 @@ func (s *E2ETestSuite) TestQueryVotesByVoter() { }, false, "", - 0, []*group.Vote{ s.vote, }, @@ -939,12 +844,6 @@ func (s *E2ETestSuite) TestTallyResult() { member := s.voter - commonFlags := []string{ - fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), - fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), - } - // create a proposal out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, client.MsgSubmitProposalCmd(), append( @@ -954,7 +853,7 @@ func (s *E2ETestSuite) TestTallyResult() { s.groupPolicies[0].Address, val.Address.String(), "", "title", "summary"), }, - commonFlags..., + s.commonFlags..., ), ) s.Require().NoError(err, out.String()) @@ -973,7 +872,6 @@ func (s *E2ETestSuite) TestTallyResult() { expectErr bool expTallyResult group.TallyResult expectErrMsg string - expectedCode uint32 }{ { "not found", @@ -984,7 +882,6 @@ func (s *E2ETestSuite) TestTallyResult() { true, group.TallyResult{}, "not found", - 0, }, { "invalid proposal id", @@ -995,7 +892,6 @@ func (s *E2ETestSuite) TestTallyResult() { true, group.TallyResult{}, "strconv.ParseUint: parsing \"\": invalid syntax", - 0, }, { "valid proposal id with no votes", @@ -1006,7 +902,6 @@ func (s *E2ETestSuite) TestTallyResult() { false, group.DefaultTallyResult(), "", - 0, }, { "valid proposal id", @@ -1022,7 +917,6 @@ func (s *E2ETestSuite) TestTallyResult() { NoWithVetoCount: "0", }, "", - 0, }, } diff --git a/tests/e2e/group/tx.go b/tests/e2e/group/suite.go similarity index 51% rename from tests/e2e/group/tx.go rename to tests/e2e/group/suite.go index 86c02e38c9..7f3422b81f 100644 --- a/tests/e2e/group/tx.go +++ b/tests/e2e/group/suite.go @@ -6,8 +6,6 @@ import ( "fmt" "strings" - "github.com/cosmos/gogoproto/proto" - "github.com/google/uuid" "github.com/stretchr/testify/suite" // without this import amino json encoding will fail when resolving any types @@ -121,7 +119,6 @@ func (s *E2ETestSuite) SetupSuite() { s.Require().NoError(err, out.String()) s.Require().NoError(s.network.WaitForNextBlock()) } - percentage := 0.5 // create group policy with percentage decision policy out, err = clitestutil.ExecTestCLICmd(val.ClientCtx, client.MsgCreateGroupPolicyCmd(), append( @@ -129,7 +126,7 @@ func (s *E2ETestSuite) SetupSuite() { val.Address.String(), "1", validMetadata, - testutil.WriteToNewTempFile(s.T(), fmt.Sprintf(`{"@type":"/cosmos.group.v1.PercentageDecisionPolicy", "percentage":"%f", "windows":{"voting_period":"30000s"}}`, percentage)).Name(), + testutil.WriteToNewTempFile(s.T(), fmt.Sprintf(`{"@type":"/cosmos.group.v1.PercentageDecisionPolicy", "percentage":"%f", "windows":{"voting_period":"30000s"}}`, 0.5)).Name(), }, s.commonFlags..., ), @@ -204,212 +201,6 @@ func (s *E2ETestSuite) TearDownSuite() { s.network.Cleanup() } -func (s *E2ETestSuite) TestExecProposalsWhenMemberLeavesOrIsUpdated() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - - weights := []string{"1", "1", "2"} - accounts := s.createAccounts(3) - testCases := []struct { - name string - votes []string - members []string - malleate func(groupID string) error - expectLogErr bool - errMsg string - respType proto.Message - }{ - { - "member leaves while all others vote yes", - []string{"VOTE_OPTION_YES", "VOTE_OPTION_YES", "VOTE_OPTION_YES"}, - accounts, - func(groupID string) error { - leavingMemberIdx := 1 - args := append( - []string{ - accounts[leavingMemberIdx], - groupID, - - fmt.Sprintf("--%s=%s", flags.FlagFrom, accounts[leavingMemberIdx]), - }, - s.commonFlags..., - ) - out, err := clitestutil.ExecTestCLICmd(clientCtx, client.MsgLeaveGroupCmd(), args) - s.Require().NoError(err, out.String()) - var resp sdk.TxResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &resp), out.String()) - s.Require().NoError(clitestutil.CheckTxCode(s.network, clientCtx, resp.TxHash, 0)) - - return err - }, - false, - "", - &sdk.TxResponse{}, - }, - { - "member leaves while all others vote yes and no", - []string{"VOTE_OPTION_NO", "VOTE_OPTION_YES", "VOTE_OPTION_YES"}, - accounts, - func(groupID string) error { - leavingMemberIdx := 1 - args := append( - []string{ - accounts[leavingMemberIdx], - groupID, - - fmt.Sprintf("--%s=%s", flags.FlagFrom, accounts[leavingMemberIdx]), - }, - s.commonFlags..., - ) - out, err := clitestutil.ExecTestCLICmd(clientCtx, client.MsgLeaveGroupCmd(), args) - s.Require().NoError(err, out.String()) - var resp sdk.TxResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &resp), out.String()) - s.Require().NoError(clitestutil.CheckTxCode(s.network, clientCtx, resp.TxHash, 0)) - - return err - }, - true, - "PROPOSAL_EXECUTOR_RESULT_NOT_RUN", - &sdk.TxResponse{}, - }, - { - "member that leaves does affect the threshold policy outcome", - []string{"VOTE_OPTION_YES", "VOTE_OPTION_YES"}, - accounts, - func(groupID string) error { - leavingMemberIdx := 2 - args := append( - []string{ - accounts[leavingMemberIdx], - groupID, - - fmt.Sprintf("--%s=%s", flags.FlagFrom, accounts[leavingMemberIdx]), - }, - s.commonFlags..., - ) - out, err := clitestutil.ExecTestCLICmd(clientCtx, client.MsgLeaveGroupCmd(), args) - s.Require().NoError(err, out.String()) - var resp sdk.TxResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &resp), out.String()) - s.Require().NoError(clitestutil.CheckTxCode(s.network, clientCtx, resp.TxHash, 0)) - - return err - }, - false, - "", - &sdk.TxResponse{}, - }, - { - "update group policy voids the proposal", - []string{"VOTE_OPTION_YES", "VOTE_OPTION_NO"}, - accounts, - func(groupID string) error { - updateGroup := s.newValidMembers(weights[0:1], accounts[0:1]) - - updateGroupByte, err := json.Marshal(updateGroup) - s.Require().NoError(err) - - validUpdateMemberFileName := testutil.WriteToNewTempFile(s.T(), string(updateGroupByte)).Name() - - args := append( - []string{ - accounts[0], - groupID, - validUpdateMemberFileName, - }, - s.commonFlags..., - ) - out, err := clitestutil.ExecTestCLICmd(clientCtx, client.MsgUpdateGroupMembersCmd(), args) - s.Require().NoError(err, out.String()) - var resp sdk.TxResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &resp), out.String()) - s.Require().NoError(clitestutil.CheckTxCode(s.network, clientCtx, resp.TxHash, 0)) - - return err - }, - true, - "PROPOSAL_EXECUTOR_RESULT_NOT_RUN", - &sdk.TxResponse{}, - }, - } - - for _, tc := range testCases { - tc := tc - - s.Run(tc.name, func() { - cmdSubmitProposal := client.MsgSubmitProposalCmd() - cmdMsgExec := client.MsgExecCmd() - - groupID := s.createGroupWithMembers(weights, accounts) - groupPolicyAddress := s.createGroupThresholdPolicyWithBalance(accounts[0], groupID, 3, 100) - - // Submit proposal - proposal := s.createCLIProposal( - groupPolicyAddress, tc.members[0], - groupPolicyAddress, tc.members[0], - "", "title", "summary", - ) - submitProposalArgs := append([]string{ - proposal, - }, - s.commonFlags..., - ) - var submitProposalResp sdk.TxResponse - out, err := clitestutil.ExecTestCLICmd(clientCtx, cmdSubmitProposal, submitProposalArgs) - s.Require().NoError(err, out.String()) - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &submitProposalResp), out.String()) - submitProposalResp, err = clitestutil.GetTxResponse(s.network, val.ClientCtx, submitProposalResp.TxHash) - s.Require().NoError(err) - proposalID := s.getProposalIDFromTxResponse(submitProposalResp) - - for i, vote := range tc.votes { - memberAddress := tc.members[i] - out, err = clitestutil.ExecTestCLICmd(val.ClientCtx, client.MsgVoteCmd(), - append( - []string{ - proposalID, - memberAddress, - vote, - "", - }, - s.commonFlags..., - ), - ) - - var txResp sdk.TxResponse - s.Require().NoError(err, out.String()) - s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &txResp), out.String()) - s.Require().NoError(clitestutil.CheckTxCode(s.network, clientCtx, txResp.TxHash, 0)) - - } - - err = tc.malleate(groupID) - s.Require().NoError(err) - s.Require().NoError(s.network.WaitForNextBlock()) - - args := append( - []string{ - proposalID, - fmt.Sprintf("--%s=%s", flags.FlagFrom, tc.members[0]), - }, - s.commonFlags..., - ) - out, err = clitestutil.ExecTestCLICmd(clientCtx, cmdMsgExec, args) - s.Require().NoError(err) - - var execResp sdk.TxResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &execResp), out.String()) - execResp, err = clitestutil.GetTxResponse(s.network, val.ClientCtx, execResp.TxHash) - s.Require().NoError(err) - - if tc.expectLogErr { - s.Require().Contains(execResp.RawLog, tc.errMsg) - } - }) - } -} - func (s *E2ETestSuite) getProposalIDFromTxResponse(txResp sdk.TxResponse) string { s.Require().Greater(len(txResp.Logs), 0) s.Require().NotNil(txResp.Logs[0].Events) @@ -425,21 +216,6 @@ func (s *E2ETestSuite) getProposalIDFromTxResponse(txResp sdk.TxResponse) string return "" } -func (s *E2ETestSuite) getGroupIDFromTxResponse(txResp sdk.TxResponse) string { - s.Require().Greater(len(txResp.Logs), 0) - s.Require().NotNil(txResp.Logs[0].Events) - events := txResp.Logs[0].Events - createProposalEvent, _ := sdk.TypedEventToEvent(&group.EventCreateGroup{}) - - for _, e := range events { - if e.Type == createProposalEvent.Type { - return strings.ReplaceAll(e.Attributes[0].Value, "\"", "") - } - } - - return "" -} - // createCLIProposal writes a CLI proposal with a MsgSend to a file. Returns // the path to the JSON file. func (s *E2ETestSuite) createCLIProposal(groupPolicyAddress, proposer, sendFrom, sendTo, metadata, title, summary string) string { @@ -469,69 +245,6 @@ func (s *E2ETestSuite) createCLIProposal(groupPolicyAddress, proposer, sendFrom, return testutil.WriteToNewTempFile(s.T(), string(bz)).Name() } -func (s *E2ETestSuite) createAccounts(quantity int) []string { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - accounts := make([]string, quantity) - - for i := 1; i <= quantity; i++ { - memberNumber := uuid.New().String() - - info, _, err := clientCtx.Keyring.NewMnemonic(fmt.Sprintf("member%s", memberNumber), keyring.English, sdk.FullFundraiserPath, - keyring.DefaultBIP39Passphrase, hd.Secp256k1) - s.Require().NoError(err) - - pk, err := info.GetPubKey() - s.Require().NoError(err) - - account := sdk.AccAddress(pk.Address()) - accounts[i-1] = account.String() - - _, err = clitestutil.MsgSendExec( - val.ClientCtx, - val.Address, - account, - sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(2000))), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), - fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), - ) - s.Require().NoError(err) - s.Require().NoError(s.network.WaitForNextBlock()) - } - - return accounts -} - -func (s *E2ETestSuite) createGroupWithMembers(membersWeight, membersAddress []string) string { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - - s.Require().Equal(len(membersWeight), len(membersAddress)) - - membersValid := s.newValidMembers(membersWeight, membersAddress) - membersByte, err := json.Marshal(membersValid) - - s.Require().NoError(err) - - validMembersFile := testutil.WriteToNewTempFile(s.T(), string(membersByte)) - out, err := clitestutil.ExecTestCLICmd(clientCtx, client.MsgCreateGroupCmd(), - append( - []string{ - membersAddress[0], - validMetadata, - validMembersFile.Name(), - }, - s.commonFlags..., - ), - ) - s.Require().NoError(err, out.String()) - var txResp sdk.TxResponse - s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &txResp), out.String()) - txResp, err = clitestutil.GetTxResponse(s.network, val.ClientCtx, txResp.TxHash) - s.Require().NoError(err) - return s.getGroupIDFromTxResponse(txResp) -} - func (s *E2ETestSuite) createGroupThresholdPolicyWithBalance(adminAddress, groupID string, threshold int, tokens int64) string { s.Require().NoError(s.network.WaitForNextBlock()) @@ -570,21 +283,3 @@ func (s *E2ETestSuite) createGroupThresholdPolicyWithBalance(adminAddress, group s.Require().NoError(err) return groupPolicyAddress } - -func (s *E2ETestSuite) newValidMembers(weights, membersAddress []string) struct{ Members []group.MemberRequest } { - s.Require().Equal(len(weights), len(membersAddress)) - membersValid := []group.MemberRequest{} - for i, address := range membersAddress { - membersValid = append(membersValid, group.MemberRequest{ - Address: address, - Weight: weights[i], - Metadata: validMetadata, - }) - } - - return struct { - Members []group.MemberRequest - }{ - Members: membersValid, - } -} diff --git a/x/group/keeper/msg_server_test.go b/x/group/keeper/msg_server_test.go index 4f80159f98..d3f42a4bb5 100644 --- a/x/group/keeper/msg_server_test.go +++ b/x/group/keeper/msg_server_test.go @@ -15,6 +15,7 @@ import ( banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/cosmos/cosmos-sdk/x/group" "github.com/cosmos/cosmos-sdk/x/group/internal/math" + "github.com/cosmos/cosmos-sdk/x/group/keeper" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" "github.com/golang/mock/gomock" ) @@ -3076,3 +3077,224 @@ func (s *TestSuite) TestLeaveGroup() { }) } } + +func (s *TestSuite) TestExecProposalsWhenMemberLeavesOrIsUpdated() { + proposers := []string{s.addrs[1].String()} + + specs := map[string]struct { + votes []group.VoteOption + members []group.MemberRequest + setupProposal func(ctx context.Context, groupPolicyAddr string) uint64 + malleate func(ctx context.Context, k keeper.Keeper, groupPolicyAddr string, groupID uint64) error + expErrMsg string + }{ + "member leaves while all others vote yes: proposal accepted": { + members: []group.MemberRequest{ + {Address: s.addrs[4].String(), Weight: "1"}, + {Address: s.addrs[1].String(), Weight: "2"}, + {Address: s.addrs[3].String(), Weight: "1"}, + {Address: s.addrs[5].String(), Weight: "2"}, + {Address: s.addrs[2].String(), Weight: "2"}, + }, + votes: []group.VoteOption{ + group.VOTE_OPTION_YES, group.VOTE_OPTION_YES, + group.VOTE_OPTION_YES, group.VOTE_OPTION_YES, + group.VOTE_OPTION_YES, + }, + setupProposal: func(ctx context.Context, groupPolicyAddr string) uint64 { + msgSend1 := &banktypes.MsgSend{ + FromAddress: groupPolicyAddr, + ToAddress: s.addrs[1].String(), + Amount: sdk.Coins{sdk.NewInt64Coin("test", 100)}, + } + + // the proposal will pass and be executed + s.bankKeeper.EXPECT().Send(gomock.Any(), msgSend1).Return(nil, nil).MaxTimes(1) + + msgs := []sdk.Msg{msgSend1} + proposalReq := &group.MsgSubmitProposal{ + GroupPolicyAddress: groupPolicyAddr, + Proposers: proposers, + } + err := proposalReq.SetMsgs(msgs) + s.Require().NoError(err) + + proposalRes, err := s.groupKeeper.SubmitProposal(ctx, proposalReq) + s.Require().NoError(err) + + return proposalRes.ProposalId + }, + malleate: func(ctx context.Context, k keeper.Keeper, _ string, groupID uint64) error { + _, err := k.LeaveGroup(ctx, &group.MsgLeaveGroup{GroupId: groupID, Address: s.addrs[5].String()}) + return err + }, + }, + "member leaves while all others vote yes and no: proposal rejected": { + members: []group.MemberRequest{ + {Address: s.addrs[4].String(), Weight: "2"}, + {Address: s.addrs[1].String(), Weight: "2"}, + {Address: s.addrs[3].String(), Weight: "2"}, + {Address: s.addrs[2].String(), Weight: "2"}, + }, + votes: []group.VoteOption{ + group.VOTE_OPTION_NO, group.VOTE_OPTION_NO, + group.VOTE_OPTION_YES, group.VOTE_OPTION_YES, + }, + setupProposal: func(ctx context.Context, groupPolicyAddr string) uint64 { + msgSend1 := &banktypes.MsgSend{ + FromAddress: groupPolicyAddr, + ToAddress: s.addrs[1].String(), + Amount: sdk.Coins{sdk.NewInt64Coin("test", 100)}, + } + msgs := []sdk.Msg{msgSend1, msgSend1} + + proposalReq := &group.MsgSubmitProposal{ + GroupPolicyAddress: groupPolicyAddr, + Proposers: proposers, + } + err := proposalReq.SetMsgs(msgs) + s.Require().NoError(err) + + proposalRes, err := s.groupKeeper.SubmitProposal(ctx, proposalReq) + s.Require().NoError(err) + + return proposalRes.ProposalId + }, + malleate: func(ctx context.Context, k keeper.Keeper, _ string, groupID uint64) error { + _, err := k.LeaveGroup(ctx, &group.MsgLeaveGroup{GroupId: groupID, Address: s.addrs[3].String()}) + return err + }, + }, + "member that leaves does affect the threshold policy outcome": { + members: []group.MemberRequest{ + {Address: s.addrs[3].String(), Weight: "6"}, + {Address: s.addrs[1].String(), Weight: "1"}, + {Address: s.addrs[5].String(), Weight: "1"}, + {Address: s.addrs[2].String(), Weight: "1"}, + }, + votes: []group.VoteOption{ + group.VOTE_OPTION_YES, group.VOTE_OPTION_NO, + group.VOTE_OPTION_YES, group.VOTE_OPTION_YES, + }, + setupProposal: func(ctx context.Context, addr string) uint64 { + msgSend1 := &banktypes.MsgSend{ + FromAddress: addr, + ToAddress: s.addrs[1].String(), + Amount: sdk.Coins{sdk.NewInt64Coin("test", 100)}, + } + msgs := []sdk.Msg{msgSend1, msgSend1} + + proposalReq := &group.MsgSubmitProposal{ + GroupPolicyAddress: addr, + Proposers: proposers, + } + err := proposalReq.SetMsgs(msgs) + s.Require().NoError(err) + + proposalRes, err := s.groupKeeper.SubmitProposal(ctx, proposalReq) + s.Require().NoError(err) + + return proposalRes.ProposalId + }, + malleate: func(ctx context.Context, k keeper.Keeper, _ string, groupID uint64) error { + _, err := k.LeaveGroup(ctx, &group.MsgLeaveGroup{GroupId: groupID, Address: s.addrs[3].String()}) + return err + }, + }, + "update group policy voids the proposal": { + members: []group.MemberRequest{ + {Address: s.addrs[3].String(), Weight: "2"}, + {Address: s.addrs[2].String(), Weight: "2"}, + {Address: s.addrs[1].String(), Weight: "2"}, + {Address: s.addrs[4].String(), Weight: "2"}, + }, + votes: []group.VoteOption{ + group.VOTE_OPTION_YES, group.VOTE_OPTION_NO, + group.VOTE_OPTION_YES, group.VOTE_OPTION_NO, + }, + setupProposal: func(ctx context.Context, groupPolicyAddr string) uint64 { + msgSend1 := &banktypes.MsgSend{ + FromAddress: groupPolicyAddr, + ToAddress: s.addrs[1].String(), + Amount: sdk.Coins{sdk.NewInt64Coin("test", 100)}, + } + msgs := []sdk.Msg{msgSend1, msgSend1} + proposalReq := &group.MsgSubmitProposal{ + GroupPolicyAddress: groupPolicyAddr, + Proposers: proposers, + } + err := proposalReq.SetMsgs(msgs) + s.Require().NoError(err) + + proposalRes, err := s.groupKeeper.SubmitProposal(ctx, proposalReq) + s.Require().NoError(err) + + return proposalRes.ProposalId + }, + malleate: func(ctx context.Context, k keeper.Keeper, groupPolicyAddr string, groupID uint64) error { + newGroupPolicy := &group.MsgUpdateGroupPolicyDecisionPolicy{ + Admin: s.addrs[0].String(), + GroupPolicyAddress: groupPolicyAddr, + } + newGroupPolicy.SetDecisionPolicy(group.NewThresholdDecisionPolicy("10", time.Second, minExecutionPeriod)) + + _, err := k.UpdateGroupPolicyDecisionPolicy(ctx, newGroupPolicy) + return err + }, + expErrMsg: "PROPOSAL_STATUS_ABORTED", + }, + } + for msg, spec := range specs { + spec := spec + s.Run(msg, func() { + sdkCtx, _ := s.sdkCtx.CacheContext() + + s.setNextAccount() + groupRes, err := s.groupKeeper.CreateGroup(s.ctx, &group.MsgCreateGroup{ + Admin: s.addrs[0].String(), + Members: spec.members, + }) + s.Require().NoError(err) + groupID := groupRes.GroupId + + policy := group.NewThresholdDecisionPolicy("4", time.Second, minExecutionPeriod) + policyReq := &group.MsgCreateGroupPolicy{ + Admin: s.addrs[0].String(), + GroupId: groupID, + } + err = policyReq.SetDecisionPolicy(policy) + s.Require().NoError(err) + + s.setNextAccount() + + s.groupKeeper.GetGroupSequence(s.sdkCtx) + policyRes, err := s.groupKeeper.CreateGroupPolicy(s.ctx, policyReq) + s.Require().NoError(err) + + // Setup and submit proposal + proposalID := spec.setupProposal(sdkCtx, policyRes.Address) + + // vote on the proposals + for i, vote := range spec.votes { + _, err := s.groupKeeper.Vote(sdkCtx, &group.MsgVote{ + ProposalId: proposalID, + Voter: spec.members[i].Address, + Option: vote, + }) + s.Require().NoError(err) + } + + err = spec.malleate(sdkCtx, s.groupKeeper, policyRes.Address, groupID) + s.Require().NoError(err) + + // travel in time + sdkCtx = sdkCtx.WithBlockTime(s.blockTime.Add(minExecutionPeriod + 1)) + _, err = s.groupKeeper.Exec(sdkCtx, &group.MsgExec{Executor: s.addrs[1].String(), ProposalId: proposalID}) + if spec.expErrMsg != "" { + s.Require().Contains(err.Error(), spec.expErrMsg) + return + } + s.Require().NoError(err) + }) + } +}