test: rewrite group tx e2e tests as unit tests (#15909)

Co-authored-by: Julien Robert <julien@rbrt.fr>
This commit is contained in:
Marko 2023-04-22 00:38:24 +02:00 committed by GitHub
parent 56e0b01f8c
commit be081b1051
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 229 additions and 418 deletions

View File

@ -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))
}

View File

@ -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,
},
}

View File

@ -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,
}
}

View File

@ -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)
})
}
}