diff --git a/app/experimental_appconfig.go b/app/experimental_appconfig.go
index 820b49e1b8..0a44bffdab 100644
--- a/app/experimental_appconfig.go
+++ b/app/experimental_appconfig.go
@@ -6,7 +6,6 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/types/module"
- authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
moduletypes "github.com/regen-network/regen-ledger/types/module"
servermodule "github.com/regen-network/regen-ledger/types/module/server"
@@ -29,7 +28,7 @@ func setCustomModules(app *RegenApp, interfaceRegistry types.InterfaceRegistry)
newModuleManager := servermodule.NewManager(app.BaseApp, codec.NewProtoCodec(interfaceRegistry))
// BEGIN HACK: this is a total, ugly hack until x/auth supports ADR 033 or we have a suitable alternative
- groupModule := group.Module{AccountKeeper: authkeeper.AccountKeeper{}}
+ groupModule := group.Module{AccountKeeper: app.AccountKeeper}
// use a separate newModules from the global NewModules here because we need to pass state into the group module
newModules := []moduletypes.Module{
ecocredit.Module{},
diff --git a/docs/modules/data/protobuf.md b/docs/modules/data/protobuf.md
index 91dd136005..11da3122be 100644
--- a/docs/modules/data/protobuf.md
+++ b/docs/modules/data/protobuf.md
@@ -571,4 +571,3 @@ The sender in StoreRawData is not attesting to the veracity of the underlying da
| bool | | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass |
| string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) |
| bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ByteString | string | String (ASCII-8BIT) |
-
diff --git a/types/module/configurator.go b/types/module/configurator.go
new file mode 100644
index 0000000000..efe59bd6bf
--- /dev/null
+++ b/types/module/configurator.go
@@ -0,0 +1,43 @@
+package module
+
+import (
+ "github.com/cosmos/cosmos-sdk/codec"
+ sdkmodule "github.com/cosmos/cosmos-sdk/types/module"
+ "github.com/gogo/protobuf/grpc"
+)
+
+// Configurator extends the cosmos sdk Configurator interface
+// with Marshaler()
+type Configurator interface {
+ sdkmodule.Configurator
+
+ Marshaler() codec.Marshaler
+}
+
+type configurator struct {
+ msgServer grpc.Server
+ queryServer grpc.Server
+ cdc codec.Marshaler
+}
+
+// NewConfigurator returns a new Configurator instance
+func NewConfigurator(msgServer grpc.Server, queryServer grpc.Server, cdc codec.Marshaler) Configurator {
+ return configurator{msgServer: msgServer, queryServer: queryServer, cdc: cdc}
+}
+
+var _ Configurator = configurator{}
+
+// MsgServer implements the Configurator.MsgServer method
+func (c configurator) MsgServer() grpc.Server {
+ return c.msgServer
+}
+
+// QueryServer implements the Configurator.QueryServer method
+func (c configurator) QueryServer() grpc.Server {
+ return c.queryServer
+}
+
+// Marshaler implements the Configurator.Marshaler method
+func (c configurator) Marshaler() codec.Marshaler {
+ return c.cdc
+}
diff --git a/types/module/server/manager.go b/types/module/server/manager.go
index da2afbdcfc..926510ee5a 100644
--- a/types/module/server/manager.go
+++ b/types/module/server/manager.go
@@ -98,6 +98,15 @@ func (mm *Manager) RegisterModules(modules []module.Module) error {
serverMod.RegisterServices(cfg)
+ // If mod implements LegacyRouteModule, register module route.
+ // This is currently used for the group module as part of #218.
+ routeMod, ok := mod.(LegacyRouteModule)
+ if ok {
+ if r := routeMod.Route(cfg); !r.Empty() {
+ mm.baseApp.Router().AddRoute(r)
+ }
+ }
+
for typ := range cfg.requiredServices {
mm.requiredServices[typ] = true
}
diff --git a/types/module/server/module.go b/types/module/server/module.go
index 842ea1225d..440188893d 100644
--- a/types/module/server/module.go
+++ b/types/module/server/module.go
@@ -25,3 +25,10 @@ type Configurator interface {
// TODO: remove once #225 addressed
Router() sdk.Router
}
+
+// LegacyRouteModule is the module type that a module must implement
+// to support legacy sdk.Msg routing.
+// This is currently used for the group module as part of #218.
+type LegacyRouteModule interface {
+ Route(Configurator) sdk.Route
+}
diff --git a/x/group/client/query.go b/x/group/client/query.go
new file mode 100644
index 0000000000..4773681668
--- /dev/null
+++ b/x/group/client/query.go
@@ -0,0 +1,440 @@
+package client
+
+import (
+ "strconv"
+
+ "github.com/cosmos/cosmos-sdk/client"
+ "github.com/cosmos/cosmos-sdk/client/flags"
+ "github.com/regen-network/regen-ledger/x/group"
+ "github.com/spf13/cobra"
+)
+
+// QueryCmd returns the cli query commands for the group module.
+func QueryCmd(name string) *cobra.Command {
+ queryCmd := &cobra.Command{
+ Use: name,
+ Short: "Querying commands for the group module",
+ DisableFlagParsing: true,
+ SuggestionsMinimumDistance: 2,
+ RunE: client.ValidateCmd,
+ }
+
+ queryCmd.AddCommand(
+ QueryGroupInfoCmd(),
+ QueryGroupAccountInfoCmd(),
+ QueryGroupMembersCmd(),
+ QueryGroupsByAdminCmd(),
+ QueryGroupAccountsByGroupCmd(),
+ QueryGroupAccountsByAdminCmd(),
+ QueryProposalCmd(),
+ QueryProposalsByGroupAccountCmd(),
+ QueryVoteByProposalVoterCmd(),
+ QueryVotesByProposalCmd(),
+ QueryVotesByVoterCmd(),
+ )
+
+ return queryCmd
+}
+
+// QueryGroupInfoCmd creates a CLI command for Query/GroupInfo.
+func QueryGroupInfoCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "group-info [id]",
+ Short: "Query for group info by group id",
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ clientCtx, err := client.GetClientQueryContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ groupID, err := strconv.ParseUint(args[0], 10, 64)
+ if err != nil {
+ return err
+ }
+
+ queryClient := group.NewQueryClient(clientCtx)
+
+ res, err := queryClient.GroupInfo(cmd.Context(), &group.QueryGroupInfoRequest{
+ GroupId: group.ID(groupID),
+ })
+ if err != nil {
+ return err
+ }
+
+ return clientCtx.PrintProto(res.Info)
+ },
+ }
+
+ flags.AddQueryFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// QueryGroupAccountInfoCmd creates a CLI command for Query/GroupAccountInfo.
+func QueryGroupAccountInfoCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "group-account-info [group-account]",
+ Short: "Query for group account info by group account address",
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ clientCtx, err := client.GetClientQueryContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ queryClient := group.NewQueryClient(clientCtx)
+
+ res, err := queryClient.GroupAccountInfo(cmd.Context(), &group.QueryGroupAccountInfoRequest{
+ GroupAccount: args[0],
+ })
+ if err != nil {
+ return err
+ }
+
+ return clientCtx.PrintProto(res.Info)
+ },
+ }
+
+ flags.AddQueryFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// QueryGroupMembersCmd creates a CLI command for Query/GroupMembers.
+func QueryGroupMembersCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "group-members [id]",
+ Short: "Query for group members by group id with pagination flags",
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ clientCtx, err := client.GetClientQueryContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ groupID, err := strconv.ParseUint(args[0], 10, 64)
+ if err != nil {
+ return err
+ }
+
+ pageReq, err := client.ReadPageRequest(cmd.Flags())
+ if err != nil {
+ return err
+ }
+
+ queryClient := group.NewQueryClient(clientCtx)
+
+ res, err := queryClient.GroupMembers(cmd.Context(), &group.QueryGroupMembersRequest{
+ GroupId: group.ID(groupID),
+ Pagination: pageReq,
+ })
+ if err != nil {
+ return err
+ }
+
+ return clientCtx.PrintProto(res)
+ },
+ }
+
+ flags.AddQueryFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// QueryGroupsByAdminCmd creates a CLI command for Query/GroupsByAdmin.
+func QueryGroupsByAdminCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "groups-by-admin [admin]",
+ Short: "Query for groups by admin account address with pagination flags",
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ clientCtx, err := client.GetClientQueryContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ pageReq, err := client.ReadPageRequest(cmd.Flags())
+ if err != nil {
+ return err
+ }
+
+ queryClient := group.NewQueryClient(clientCtx)
+
+ res, err := queryClient.GroupsByAdmin(cmd.Context(), &group.QueryGroupsByAdminRequest{
+ Admin: args[0],
+ Pagination: pageReq,
+ })
+ if err != nil {
+ return err
+ }
+
+ return clientCtx.PrintProto(res)
+ },
+ }
+
+ flags.AddQueryFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// QueryGroupAccountsByGroupCmd creates a CLI command for Query/GroupAccountsByGroup.
+func QueryGroupAccountsByGroupCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "group-accounts-by-group [group-id]",
+ Short: "Query for group accounts by group id with pagination flags",
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ clientCtx, err := client.GetClientQueryContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ groupID, err := strconv.ParseUint(args[0], 10, 64)
+ if err != nil {
+ return err
+ }
+
+ pageReq, err := client.ReadPageRequest(cmd.Flags())
+ if err != nil {
+ return err
+ }
+
+ queryClient := group.NewQueryClient(clientCtx)
+
+ res, err := queryClient.GroupAccountsByGroup(cmd.Context(), &group.QueryGroupAccountsByGroupRequest{
+ GroupId: group.ID(groupID),
+ Pagination: pageReq,
+ })
+ if err != nil {
+ return err
+ }
+
+ return clientCtx.PrintProto(res)
+ },
+ }
+
+ flags.AddQueryFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// QueryGroupAccountsByAdminCmd creates a CLI command for Query/GroupAccountsByAdmin.
+func QueryGroupAccountsByAdminCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "group-accounts-by-admin [admin]",
+ Short: "Query for group accounts by admin account address with pagination flags",
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ clientCtx, err := client.GetClientQueryContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ pageReq, err := client.ReadPageRequest(cmd.Flags())
+ if err != nil {
+ return err
+ }
+
+ queryClient := group.NewQueryClient(clientCtx)
+
+ res, err := queryClient.GroupAccountsByAdmin(cmd.Context(), &group.QueryGroupAccountsByAdminRequest{
+ Admin: args[0],
+ Pagination: pageReq,
+ })
+ if err != nil {
+ return err
+ }
+
+ return clientCtx.PrintProto(res)
+ },
+ }
+
+ flags.AddQueryFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// QueryProposalCmd creates a CLI command for Query/Proposal.
+func QueryProposalCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "proposal [id]",
+ Short: "Query for proposal by id",
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ clientCtx, err := client.GetClientQueryContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ proposalID, err := strconv.ParseUint(args[0], 10, 64)
+ if err != nil {
+ return err
+ }
+
+ queryClient := group.NewQueryClient(clientCtx)
+
+ res, err := queryClient.Proposal(cmd.Context(), &group.QueryProposalRequest{
+ ProposalId: group.ProposalID(proposalID),
+ })
+ if err != nil {
+ return err
+ }
+
+ return clientCtx.PrintProto(res)
+ },
+ }
+
+ flags.AddQueryFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// QueryProposalsByGroupAccountCmd creates a CLI command for Query/ProposalsByGroupAccount.
+func QueryProposalsByGroupAccountCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "proposals-by-group-account [group-account]",
+ Short: "Query for proposals by group account address with pagination flags",
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ clientCtx, err := client.GetClientQueryContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ pageReq, err := client.ReadPageRequest(cmd.Flags())
+ if err != nil {
+ return err
+ }
+
+ queryClient := group.NewQueryClient(clientCtx)
+
+ res, err := queryClient.ProposalsByGroupAccount(cmd.Context(), &group.QueryProposalsByGroupAccountRequest{
+ GroupAccount: args[0],
+ Pagination: pageReq,
+ })
+ if err != nil {
+ return err
+ }
+
+ return clientCtx.PrintProto(res)
+ },
+ }
+
+ flags.AddQueryFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// QueryVoteByProposalVoterCmd creates a CLI command for Query/VoteByProposalVoter.
+func QueryVoteByProposalVoterCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "vote [proposal-id] [voter]",
+ Short: "Query for vote by proposal id and voter account address",
+ Args: cobra.ExactArgs(2),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ clientCtx, err := client.GetClientQueryContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ proposalID, err := strconv.ParseUint(args[0], 10, 64)
+ if err != nil {
+ return err
+ }
+
+ queryClient := group.NewQueryClient(clientCtx)
+
+ res, err := queryClient.VoteByProposalVoter(cmd.Context(), &group.QueryVoteByProposalVoterRequest{
+ ProposalId: group.ProposalID(proposalID),
+ Voter: args[1],
+ })
+ if err != nil {
+ return err
+ }
+
+ return clientCtx.PrintProto(res)
+ },
+ }
+
+ flags.AddQueryFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// QueryVotesByProposalCmd creates a CLI command for Query/VotesByProposal.
+func QueryVotesByProposalCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "votes-by-proposal [proposal-id]",
+ Short: "Query for votes by proposal id with pagination flags",
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ clientCtx, err := client.GetClientQueryContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ proposalID, err := strconv.ParseUint(args[0], 10, 64)
+ if err != nil {
+ return err
+ }
+
+ pageReq, err := client.ReadPageRequest(cmd.Flags())
+ if err != nil {
+ return err
+ }
+
+ queryClient := group.NewQueryClient(clientCtx)
+
+ res, err := queryClient.VotesByProposal(cmd.Context(), &group.QueryVotesByProposalRequest{
+ ProposalId: group.ProposalID(proposalID),
+ Pagination: pageReq,
+ })
+ if err != nil {
+ return err
+ }
+
+ return clientCtx.PrintProto(res)
+ },
+ }
+
+ flags.AddQueryFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// QueryVotesByVoterCmd creates a CLI command for Query/VotesByVoter.
+func QueryVotesByVoterCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "votes-by-voter [voter]",
+ Short: "Query for votes by voter account address with pagination flags",
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ clientCtx, err := client.GetClientQueryContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ pageReq, err := client.ReadPageRequest(cmd.Flags())
+ if err != nil {
+ return err
+ }
+
+ queryClient := group.NewQueryClient(clientCtx)
+
+ res, err := queryClient.VotesByVoter(cmd.Context(), &group.QueryVotesByVoterRequest{
+ Voter: args[0],
+ Pagination: pageReq,
+ })
+ if err != nil {
+ return err
+ }
+
+ return clientCtx.PrintProto(res)
+ },
+ }
+
+ flags.AddQueryFlagsToCmd(cmd)
+
+ return cmd
+}
diff --git a/x/group/client/testsuite/cli_test.go b/x/group/client/testsuite/cli_test.go
new file mode 100644
index 0000000000..8f12300777
--- /dev/null
+++ b/x/group/client/testsuite/cli_test.go
@@ -0,0 +1,2051 @@
+// +build experimental
+
+package testsuite
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "testing"
+
+ tmcli "github.com/tendermint/tendermint/libs/cli"
+
+ "github.com/cosmos/cosmos-sdk/client/flags"
+ "github.com/cosmos/cosmos-sdk/crypto/hd"
+ "github.com/cosmos/cosmos-sdk/crypto/keyring"
+ "github.com/cosmos/cosmos-sdk/testutil"
+ banktestutil "github.com/cosmos/cosmos-sdk/x/bank/client/testutil"
+ "github.com/gogo/protobuf/proto"
+
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ "github.com/regen-network/regen-ledger/testutil/cli"
+ "github.com/regen-network/regen-ledger/testutil/network"
+
+ "github.com/regen-network/regen-ledger/x/group"
+ "github.com/regen-network/regen-ledger/x/group/client"
+ "github.com/stretchr/testify/suite"
+)
+
+type IntegrationTestSuite struct {
+ suite.Suite
+
+ cfg network.Config
+ network *network.Network
+
+ group *group.GroupInfo
+ groupAccount *group.GroupAccountInfo
+ groupAccount2 *group.GroupAccountInfo
+ groupAccount3 *group.GroupAccountInfo
+ proposal *group.Proposal
+ vote *group.Vote
+}
+
+const validMetadata = "AQ=="
+
+func (s *IntegrationTestSuite) SetupSuite() {
+ s.T().Log("setting up integration test suite")
+
+ cfg := network.DefaultConfig()
+ cfg.NumValidators = 2
+
+ s.cfg = cfg
+ s.network = network.New(s.T(), cfg)
+
+ _, err := s.network.WaitForHeight(1)
+ s.Require().NoError(err)
+
+ val := s.network.Validators[0]
+
+ // create a new account
+ info, _, err := val.ClientCtx.Keyring.NewMnemonic("NewValidator", keyring.English, sdk.FullFundraiserPath, hd.Secp256k1)
+ s.Require().NoError(err)
+
+ account := sdk.AccAddress(info.GetPubKey().Address())
+ _, err = banktestutil.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.BroadcastBlock),
+ fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
+ )
+ s.Require().NoError(err)
+
+ var commonFlags = []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()),
+ }
+
+ // create a group
+ validMembers := fmt.Sprintf(`[{
+ "address": "%s",
+ "weight": "1",
+ "metadata": "%s"
+ }]`, val.Address.String(), validMetadata)
+ validMembersFile := testutil.WriteToNewTempFile(s.T(), validMembers)
+ out, err := cli.ExecTestCLICmd(val.ClientCtx, client.MsgCreateGroupCmd(),
+ append(
+ []string{
+ val.Address.String(),
+ validMetadata,
+ validMembersFile.Name(),
+ },
+ commonFlags...,
+ ),
+ )
+
+ s.Require().NoError(err, out.String())
+ var txResp = sdk.TxResponse{}
+ s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &txResp), out.String())
+ s.Require().Equal(uint32(0), txResp.Code, out.String())
+
+ s.group = &group.GroupInfo{GroupId: group.ID(1), Admin: val.Address.String(), Metadata: []byte{1}, TotalWeight: "1", Version: 1}
+
+ // create 3 group accounts
+ for i := 0; i < 3; i++ {
+ out, err = cli.ExecTestCLICmd(val.ClientCtx, client.MsgCreateGroupAccountCmd(),
+ append(
+ []string{
+ val.Address.String(),
+ "1",
+ validMetadata,
+ "{\"@type\":\"/regen.group.v1alpha1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"30000s\"}",
+ },
+ commonFlags...,
+ ),
+ )
+ s.Require().NoError(err, out.String())
+ s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &txResp), out.String())
+ s.Require().Equal(uint32(0), txResp.Code, out.String())
+
+ out, err = cli.ExecTestCLICmd(val.ClientCtx, client.QueryGroupAccountsByGroupCmd(), []string{"1", fmt.Sprintf("--%s=json", tmcli.OutputFlag)})
+ s.Require().NoError(err, out.String())
+ }
+
+ var res group.QueryGroupAccountsByGroupResponse
+ s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &res))
+ s.Require().Equal(len(res.GroupAccounts), 3)
+ s.groupAccount = res.GroupAccounts[0]
+ s.groupAccount2 = res.GroupAccounts[1]
+ s.groupAccount3 = res.GroupAccounts[2]
+
+ // create a proposal
+ validTxFileName := getTxSendFileName(s, s.groupAccount.GroupAccount, val.Address.String())
+ out, err = cli.ExecTestCLICmd(val.ClientCtx, client.MsgCreateProposalCmd(),
+ append(
+ []string{
+ s.groupAccount.GroupAccount,
+ val.Address.String(),
+ validTxFileName,
+ "",
+ fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
+ },
+ commonFlags...,
+ ),
+ )
+ s.Require().NoError(err, out.String())
+ s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &txResp), out.String())
+ s.Require().Equal(uint32(0), txResp.Code, out.String())
+
+ // vote
+ out, err = cli.ExecTestCLICmd(val.ClientCtx, client.MsgVoteCmd(),
+ append(
+ []string{
+ "1",
+ val.Address.String(),
+ "CHOICE_YES",
+ "",
+ },
+ commonFlags...,
+ ),
+ )
+ s.Require().NoError(err, out.String())
+ s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &txResp), out.String())
+ s.Require().Equal(uint32(0), txResp.Code, out.String())
+
+ out, err = cli.ExecTestCLICmd(val.ClientCtx, client.QueryProposalCmd(), []string{"1", fmt.Sprintf("--%s=json", tmcli.OutputFlag)})
+ s.Require().NoError(err, out.String())
+
+ var proposalRes group.QueryProposalResponse
+ s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &proposalRes))
+ s.proposal = proposalRes.Proposal
+
+ out, err = cli.ExecTestCLICmd(val.ClientCtx, client.QueryVoteByProposalVoterCmd(), []string{"1", val.Address.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag)})
+ s.Require().NoError(err, out.String())
+
+ var voteRes group.QueryVoteByProposalVoterResponse
+ s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &voteRes))
+ s.vote = voteRes.Vote
+}
+
+func (s *IntegrationTestSuite) TearDownSuite() {
+ s.T().Log("tearing down integration test suite")
+ s.network.Cleanup()
+}
+
+func (s *IntegrationTestSuite) TestQueryGroupInfo() {
+ val := s.network.Validators[0]
+ clientCtx := val.ClientCtx
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ expectedCode uint32
+ }{
+ {
+ "group not found",
+ []string{"12345", fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ true,
+ "not found: invalid request",
+ 0,
+ },
+ {
+ "group id invalid",
+ []string{"", fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ true,
+ "strconv.ParseUint: parsing \"\": invalid syntax",
+ 0,
+ },
+ {
+ "group found",
+ []string{strconv.FormatUint(s.group.GroupId.Uint64(), 10), fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ false,
+ "",
+ 0,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.QueryGroupInfoCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+
+ var g group.GroupInfo
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &g))
+ s.Require().Equal(s.group.GroupId, g.GroupId)
+ s.Require().Equal(s.group.Admin, g.Admin)
+ s.Require().Equal(s.group.TotalWeight, g.TotalWeight)
+ s.Require().Equal(s.group.Metadata, g.Metadata)
+ s.Require().Equal(s.group.Version, g.Version)
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestQueryGroupMembers() {
+ val := s.network.Validators[0]
+ clientCtx := val.ClientCtx
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ expectedCode uint32
+ expectMembers []*group.GroupMember
+ }{
+ {
+ "no group",
+ []string{"12345", fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ false,
+ "",
+ 0,
+ []*group.GroupMember{},
+ },
+ {
+ "members found",
+ []string{strconv.FormatUint(s.group.GroupId.Uint64(), 10), fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ false,
+ "",
+ 0,
+ []*group.GroupMember{
+ {
+ GroupId: s.group.GroupId,
+ Member: &group.Member{
+ Address: val.Address.String(),
+ Weight: "1",
+ Metadata: []byte{1},
+ },
+ },
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.QueryGroupMembersCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+
+ var res group.QueryGroupMembersResponse
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &res))
+ s.Require().Equal(len(res.Members), len(tc.expectMembers))
+ for i := range res.Members {
+ s.Require().Equal(res.Members[i].GroupId, tc.expectMembers[i].GroupId)
+ s.Require().Equal(res.Members[i].Member.Address, tc.expectMembers[i].Member.Address)
+ s.Require().Equal(res.Members[i].Member.Metadata, tc.expectMembers[i].Member.Metadata)
+ s.Require().Equal(res.Members[i].Member.Weight, tc.expectMembers[i].Member.Weight)
+ }
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestQueryGroupsByAdmin() {
+ val := s.network.Validators[0]
+ clientCtx := val.ClientCtx
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ expectedCode uint32
+ expectGroups []*group.GroupInfo
+ }{
+ {
+ "invalid admin address",
+ []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", tmcli.OutputFlag)},
+ false,
+ "",
+ 0,
+ []*group.GroupInfo{},
+ },
+ {
+ "found groups",
+ []string{val.Address.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ false,
+ "",
+ 0,
+ []*group.GroupInfo{
+ s.group,
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.QueryGroupsByAdminCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+
+ var res group.QueryGroupsByAdminResponse
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &res))
+ s.Require().Equal(len(res.Groups), len(tc.expectGroups))
+ for i := range res.Groups {
+ s.Require().Equal(res.Groups[i].GroupId, tc.expectGroups[i].GroupId)
+ s.Require().Equal(res.Groups[i].Metadata, tc.expectGroups[i].Metadata)
+ s.Require().Equal(res.Groups[i].Version, tc.expectGroups[i].Version)
+ s.Require().Equal(res.Groups[i].TotalWeight, tc.expectGroups[i].TotalWeight)
+ s.Require().Equal(res.Groups[i].Admin, tc.expectGroups[i].Admin)
+ }
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestQueryGroupAccountInfo() {
+ val := s.network.Validators[0]
+ clientCtx := val.ClientCtx
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ expectedCode uint32
+ }{
+ {
+ "invalid account address",
+ []string{"invalid", fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ true,
+ "decoding bech32 failed: invalid bech32",
+ 0,
+ },
+ {
+ "group account not found",
+ []string{val.Address.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ true,
+ "not found: invalid request",
+ 0,
+ },
+ {
+ "group account found",
+ []string{s.groupAccount.GroupAccount, fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ false,
+ "",
+ 0,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.QueryGroupAccountInfoCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+
+ var g group.GroupAccountInfo
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &g))
+ s.Require().Equal(s.groupAccount.GroupId, g.GroupId)
+ s.Require().Equal(s.groupAccount.GroupAccount, g.GroupAccount)
+ s.Require().Equal(s.groupAccount.Admin, g.Admin)
+ s.Require().Equal(s.groupAccount.Metadata, g.Metadata)
+ s.Require().Equal(s.groupAccount.Version, g.Version)
+ s.Require().Equal(s.groupAccount.GetDecisionPolicy(), g.GetDecisionPolicy())
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestQueryGroupAccountsByGroup() {
+ val := s.network.Validators[0]
+ clientCtx := val.ClientCtx
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ expectedCode uint32
+ expectGroupAccounts []*group.GroupAccountInfo
+ }{
+ {
+ "invalid group id",
+ []string{""},
+ true,
+ "strconv.ParseUint: parsing \"\": invalid syntax",
+ 0,
+ []*group.GroupAccountInfo{},
+ },
+ {
+ "no group account",
+ []string{"12345", fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ false,
+ "",
+ 0,
+ []*group.GroupAccountInfo{},
+ },
+ {
+ "found group accounts",
+ []string{strconv.FormatUint(s.group.GroupId.Uint64(), 10), fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ false,
+ "",
+ 0,
+ []*group.GroupAccountInfo{
+ s.groupAccount,
+ s.groupAccount2,
+ s.groupAccount3,
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.QueryGroupAccountsByGroupCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+
+ var res group.QueryGroupAccountsByGroupResponse
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &res))
+ s.Require().Equal(len(res.GroupAccounts), len(tc.expectGroupAccounts))
+ for i := range res.GroupAccounts {
+ s.Require().Equal(res.GroupAccounts[i].GroupId, tc.expectGroupAccounts[i].GroupId)
+ s.Require().Equal(res.GroupAccounts[i].Metadata, tc.expectGroupAccounts[i].Metadata)
+ s.Require().Equal(res.GroupAccounts[i].Version, tc.expectGroupAccounts[i].Version)
+ s.Require().Equal(res.GroupAccounts[i].Admin, tc.expectGroupAccounts[i].Admin)
+ s.Require().Equal(res.GroupAccounts[i].GetDecisionPolicy(), tc.expectGroupAccounts[i].GetDecisionPolicy())
+ }
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestQueryGroupAccountsByAdmin() {
+ val := s.network.Validators[0]
+ clientCtx := val.ClientCtx
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ expectedCode uint32
+ expectGroupAccounts []*group.GroupAccountInfo
+ }{
+ {
+ "invalid admin address",
+ []string{"invalid"},
+ true,
+ "decoding bech32 failed: invalid bech32 string",
+ 0,
+ []*group.GroupAccountInfo{},
+ },
+ {
+ "no group account",
+ []string{s.network.Validators[1].Address.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ false,
+ "",
+ 0,
+ []*group.GroupAccountInfo{},
+ },
+ {
+ "found group accounts",
+ []string{val.Address.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ false,
+ "",
+ 0,
+ []*group.GroupAccountInfo{
+ s.groupAccount,
+ s.groupAccount2,
+ s.groupAccount3,
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.QueryGroupAccountsByAdminCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+
+ var res group.QueryGroupAccountsByAdminResponse
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &res))
+ s.Require().Equal(len(res.GroupAccounts), len(tc.expectGroupAccounts))
+ for i := range res.GroupAccounts {
+ s.Require().Equal(res.GroupAccounts[i].GroupId, tc.expectGroupAccounts[i].GroupId)
+ s.Require().Equal(res.GroupAccounts[i].Metadata, tc.expectGroupAccounts[i].Metadata)
+ s.Require().Equal(res.GroupAccounts[i].Version, tc.expectGroupAccounts[i].Version)
+ s.Require().Equal(res.GroupAccounts[i].Admin, tc.expectGroupAccounts[i].Admin)
+ s.Require().Equal(res.GroupAccounts[i].GetDecisionPolicy(), tc.expectGroupAccounts[i].GetDecisionPolicy())
+ }
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestQueryProposal() {
+ val := s.network.Validators[0]
+ clientCtx := val.ClientCtx
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ expectedCode uint32
+ }{
+ {
+ "not found",
+ []string{"12345", fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ true,
+ "not found",
+ 0,
+ },
+ {
+ "invalid proposal id",
+ []string{"", fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ true,
+ "strconv.ParseUint: parsing \"\": invalid syntax",
+ 0,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.QueryProposalCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestQueryProposalsByGroupAccount() {
+ val := s.network.Validators[0]
+ clientCtx := val.ClientCtx
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ expectedCode uint32
+ expectProposals []*group.Proposal
+ }{
+ {
+ "invalid group account address",
+ []string{"invalid"},
+ true,
+ "decoding bech32 failed: invalid bech32 string",
+ 0,
+ []*group.Proposal{},
+ },
+ {
+ "no group account",
+ []string{s.network.Validators[1].Address.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ false,
+ "",
+ 0,
+ []*group.Proposal{},
+ },
+ {
+ "found proposals",
+ []string{s.groupAccount.GroupAccount, fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ false,
+ "",
+ 0,
+ []*group.Proposal{
+ s.proposal,
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.QueryProposalsByGroupAccountCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+
+ var res group.QueryProposalsByGroupAccountResponse
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &res))
+ s.Require().Equal(len(res.Proposals), len(tc.expectProposals))
+ for i := range res.Proposals {
+ s.Require().Equal(res.Proposals[i], tc.expectProposals[i])
+ }
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestQueryVoteByProposalVoter() {
+ val := s.network.Validators[0]
+ clientCtx := val.ClientCtx
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ expectedCode uint32
+ }{
+ {
+ "invalid voter address",
+ []string{"1", "invalid", fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ true,
+ "decoding bech32 failed: invalid bech32",
+ 0,
+ },
+ {
+ "invalid proposal id",
+ []string{"", val.Address.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ true,
+ "strconv.ParseUint: parsing \"\": invalid syntax",
+ 0,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.QueryVoteByProposalVoterCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestQueryVotesByProposal() {
+ val := s.network.Validators[0]
+ clientCtx := val.ClientCtx
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ expectedCode uint32
+ expectVotes []*group.Vote
+ }{
+ {
+ "invalid proposal id",
+ []string{"", fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ true,
+ "strconv.ParseUint: parsing \"\": invalid syntax",
+ 0,
+ []*group.Vote{},
+ },
+ {
+ "no votes",
+ []string{"12345", fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ false,
+ "",
+ 0,
+ []*group.Vote{},
+ },
+ {
+ "found votes",
+ []string{"1", fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ false,
+ "",
+ 0,
+ []*group.Vote{
+ s.vote,
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.QueryVotesByProposalCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+
+ var res group.QueryVotesByProposalResponse
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &res))
+ s.Require().Equal(len(res.Votes), len(tc.expectVotes))
+ for i := range res.Votes {
+ s.Require().Equal(res.Votes[i], tc.expectVotes[i])
+ }
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestQueryVotesByVoter() {
+ val := s.network.Validators[0]
+ clientCtx := val.ClientCtx
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ expectedCode uint32
+ expectVotes []*group.Vote
+ }{
+ {
+ "invalid voter address",
+ []string{"abcd", fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ true,
+ "decoding bech32 failed: invalid bech32",
+ 0,
+ []*group.Vote{},
+ },
+ {
+ "no votes",
+ []string{s.groupAccount.GroupAccount, fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ true,
+ "",
+ 0,
+ []*group.Vote{},
+ },
+ {
+ "found votes",
+ []string{val.Address.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
+ false,
+ "",
+ 0,
+ []*group.Vote{
+ s.vote,
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.QueryVotesByVoterCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+
+ var res group.QueryVotesByVoterResponse
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &res))
+ s.Require().Equal(len(res.Votes), len(tc.expectVotes))
+ for i := range res.Votes {
+ s.Require().Equal(res.Votes[i], tc.expectVotes[i])
+ }
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestTxCreateGroup() {
+ val := s.network.Validators[0]
+ clientCtx := val.ClientCtx
+
+ var commonFlags = []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()),
+ }
+
+ validMembers := fmt.Sprintf(`[{
+ "address": "%s",
+ "weight": "1",
+ "metadata": "%s"
+ }]`, val.Address.String(), validMetadata)
+ validMembersFile := testutil.WriteToNewTempFile(s.T(), validMembers)
+
+ invalidMembersAddress := `[{
+ "address": "",
+ "weight": "1"
+}]`
+ invalidMembersAddressFile := testutil.WriteToNewTempFile(s.T(), invalidMembersAddress)
+
+ invalidMembersWeight := fmt.Sprintf(`[{
+ "address": "%s",
+ "weight": "0"
+ }]`, val.Address.String())
+ invalidMembersWeightFile := testutil.WriteToNewTempFile(s.T(), invalidMembersWeight)
+
+ invalidMembersMetadata := fmt.Sprintf(`[{
+ "address": "%s",
+ "weight": "1",
+ "metadata": "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ=="
+ }]`, val.Address.String())
+ invalidMembersMetadataFile := testutil.WriteToNewTempFile(s.T(), invalidMembersMetadata)
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ respType proto.Message
+ expectedCode uint32
+ }{
+ {
+ "correct data",
+ append(
+ []string{
+ val.Address.String(),
+ "",
+ validMembersFile.Name(),
+ },
+ commonFlags...,
+ ),
+ false,
+ "",
+ &sdk.TxResponse{},
+ 0,
+ },
+ {
+ "group metadata too long",
+ append(
+ []string{
+ val.Address.String(),
+ "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ==",
+ "",
+ },
+ commonFlags...,
+ ),
+ true,
+ "group metadata: limit exceeded",
+ nil,
+ 0,
+ },
+ {
+ "invalid members address",
+ append(
+ []string{
+ val.Address.String(),
+ "null",
+ invalidMembersAddressFile.Name(),
+ },
+ commonFlags...,
+ ),
+ true,
+ "message validation failed: members: address: empty address string is not allowed",
+ nil,
+ 0,
+ },
+ {
+ "invalid members weight",
+ append(
+ []string{
+ val.Address.String(),
+ "null",
+ invalidMembersWeightFile.Name(),
+ },
+ commonFlags...,
+ ),
+ true,
+ "message validation failed: member weight: expected a positive decimal, got 0",
+ nil,
+ 0,
+ },
+ {
+ "members metadata too long",
+ append(
+ []string{
+ val.Address.String(),
+ "null",
+ invalidMembersMetadataFile.Name(),
+ },
+ commonFlags...,
+ ),
+ true,
+ "member metadata: limit exceeded",
+ nil,
+ 0,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.MsgCreateGroupCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String())
+
+ txResp := tc.respType.(*sdk.TxResponse)
+ s.Require().Equal(tc.expectedCode, txResp.Code, out.String())
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestTxUpdateGroupAdmin() {
+ val := s.network.Validators[0]
+ clientCtx := val.ClientCtx
+
+ var commonFlags = []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()),
+ }
+
+ validMembers := fmt.Sprintf(`[{
+ "address": "%s",
+ "weight": "1",
+ "metadata": "%s"
+ }]`, val.Address.String(), validMetadata)
+ validMembersFile := testutil.WriteToNewTempFile(s.T(), validMembers)
+ out, err := cli.ExecTestCLICmd(val.ClientCtx, client.MsgCreateGroupCmd(),
+ append(
+ []string{
+ val.Address.String(),
+ validMetadata,
+ validMembersFile.Name(),
+ },
+ commonFlags...,
+ ),
+ )
+
+ s.Require().NoError(err, out.String())
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ respType proto.Message
+ expectedCode uint32
+ }{
+ {
+ "correct data",
+ append(
+ []string{
+ val.Address.String(),
+ "3",
+ s.network.Validators[1].Address.String(),
+ },
+ commonFlags...,
+ ),
+ false,
+ "",
+ &sdk.TxResponse{},
+ 0,
+ },
+ {
+ "group id invalid",
+ append(
+ []string{
+ val.Address.String(),
+ "",
+ s.network.Validators[1].Address.String(),
+ },
+ commonFlags...,
+ ),
+ true,
+ "strconv.ParseUint: parsing \"\": invalid syntax",
+ nil,
+ 0,
+ },
+ {
+ "group doesn't exist",
+ append(
+ []string{
+ val.Address.String(),
+ "12345",
+ s.network.Validators[1].Address.String(),
+ },
+ commonFlags...,
+ ),
+ true,
+ "not found",
+ nil,
+ 0,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.MsgUpdateGroupAdminCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String())
+
+ txResp := tc.respType.(*sdk.TxResponse)
+ s.Require().Equal(tc.expectedCode, txResp.Code, out.String())
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestTxUpdateGroupMetadata() {
+ val := s.network.Validators[0]
+ clientCtx := val.ClientCtx
+
+ var commonFlags = []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()),
+ }
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ respType proto.Message
+ expectedCode uint32
+ }{
+ {
+ "correct data",
+ append(
+ []string{
+ val.Address.String(),
+ "2",
+ validMetadata,
+ },
+ commonFlags...,
+ ),
+ false,
+ "",
+ &sdk.TxResponse{},
+ 0,
+ },
+ {
+ "group metadata too long",
+ append(
+ []string{
+ val.Address.String(),
+ strconv.FormatUint(s.group.GroupId.Uint64(), 10),
+ "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ==",
+ },
+ commonFlags...,
+ ),
+ true,
+ "group metadata: limit exceeded",
+ nil,
+ 0,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.MsgUpdateGroupMetadataCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String())
+
+ txResp := tc.respType.(*sdk.TxResponse)
+ s.Require().Equal(tc.expectedCode, txResp.Code, out.String())
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestTxUpdateGroupMembers() {
+ val := s.network.Validators[0]
+ clientCtx := val.ClientCtx
+
+ var commonFlags = []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()),
+ }
+
+ validUpdatedMembersFileName := testutil.WriteToNewTempFile(s.T(), fmt.Sprintf(`[{
+ "address": "%s",
+ "weight": "0",
+ "metadata": "%s"
+ }, {
+ "address": "%s",
+ "weight": "1",
+ "metadata": "%s"
+ }]`, val.Address.String(), validMetadata, s.groupAccount.GroupAccount, validMetadata)).Name()
+
+ invalidMembersMetadata := fmt.Sprintf(`[{
+ "address": "%s",
+ "weight": "1",
+ "metadata": "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ=="
+ }]`, val.Address.String())
+ invalidMembersMetadataFileName := testutil.WriteToNewTempFile(s.T(), invalidMembersMetadata).Name()
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ respType proto.Message
+ expectedCode uint32
+ }{
+ {
+ "correct data",
+ append(
+ []string{
+ val.Address.String(),
+ "2",
+ validUpdatedMembersFileName,
+ },
+ commonFlags...,
+ ),
+ false,
+ "",
+ &sdk.TxResponse{},
+ 0,
+ },
+ {
+ "group member metadata too long",
+ append(
+ []string{
+ val.Address.String(),
+ strconv.FormatUint(s.group.GroupId.Uint64(), 10),
+ invalidMembersMetadataFileName,
+ },
+ commonFlags...,
+ ),
+ true,
+ "group member metadata: limit exceeded",
+ nil,
+ 0,
+ },
+ {
+ "group doesn't exist",
+ append(
+ []string{
+ val.Address.String(),
+ "12345",
+ validUpdatedMembersFileName,
+ },
+ commonFlags...,
+ ),
+ true,
+ "not found",
+ nil,
+ 0,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.MsgUpdateGroupMembersCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String())
+
+ txResp := tc.respType.(*sdk.TxResponse)
+ s.Require().Equal(tc.expectedCode, txResp.Code, out.String())
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestTxCreateGroupAccount() {
+ val := s.network.Validators[0]
+ wrongAdmin := s.network.Validators[1].Address
+ clientCtx := val.ClientCtx
+
+ var commonFlags = []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()),
+ }
+
+ groupID := s.group.GroupId
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ respType proto.Message
+ expectedCode uint32
+ }{
+ {
+ "correct data",
+ append(
+ []string{
+ val.Address.String(),
+ fmt.Sprintf("%v", groupID),
+ validMetadata,
+ "{\"@type\":\"/regen.group.v1alpha1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}",
+ },
+ commonFlags...,
+ ),
+ false,
+ "",
+ &sdk.TxResponse{},
+ 0,
+ },
+ {
+ "wrong admin",
+ append(
+ []string{
+ wrongAdmin.String(),
+ fmt.Sprintf("%v", groupID),
+ validMetadata,
+ "{\"@type\":\"/regen.group.v1alpha1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}",
+ },
+ commonFlags...,
+ ),
+ true,
+ "The specified item could not be found in the keyring",
+ &sdk.TxResponse{},
+ 0,
+ },
+ {
+ "metadata too long",
+ append(
+ []string{
+ val.Address.String(),
+ fmt.Sprintf("%v", groupID),
+ strings.Repeat("a", 500),
+ "{\"@type\":\"/regen.group.v1alpha1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}",
+ },
+ commonFlags...,
+ ),
+ true,
+ "group account metadata: limit exceeded",
+ &sdk.TxResponse{},
+ 0,
+ },
+ {
+ "wrong group id",
+ append(
+ []string{
+ val.Address.String(),
+ "10",
+ validMetadata,
+ "{\"@type\":\"/regen.group.v1alpha1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}",
+ },
+ commonFlags...,
+ ),
+ true,
+ "not found",
+ &sdk.TxResponse{},
+ 0,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.MsgCreateGroupAccountCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String())
+
+ txResp := tc.respType.(*sdk.TxResponse)
+ s.Require().Equal(tc.expectedCode, txResp.Code, out.String())
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestTxUpdateGroupAccountAdmin() {
+ val := s.network.Validators[0]
+ newAdmin := s.network.Validators[1].Address
+ clientCtx := val.ClientCtx
+ groupAccount := s.groupAccount2
+
+ var commonFlags = []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()),
+ }
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ respType proto.Message
+ expectedCode uint32
+ }{
+ {
+ "correct data",
+ append(
+ []string{
+ groupAccount.Admin,
+ groupAccount.GroupAccount,
+ newAdmin.String(),
+ },
+ commonFlags...,
+ ),
+ false,
+ "",
+ &sdk.TxResponse{},
+ 0,
+ },
+ {
+ "wrong admin",
+ append(
+ []string{
+ newAdmin.String(),
+ groupAccount.GroupAccount,
+ newAdmin.String(),
+ },
+ commonFlags...,
+ ),
+ true,
+ "The specified item could not be found in the keyring",
+ &sdk.TxResponse{},
+ 0,
+ },
+ {
+ "wrong group account",
+ append(
+ []string{
+ groupAccount.Admin,
+ newAdmin.String(),
+ newAdmin.String(),
+ },
+ commonFlags...,
+ ),
+ true,
+ "load group account: not found",
+ &sdk.TxResponse{},
+ 0,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.MsgUpdateGroupAccountAdminCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String())
+
+ txResp := tc.respType.(*sdk.TxResponse)
+ s.Require().Equal(tc.expectedCode, txResp.Code, out.String())
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestTxUpdateGroupAccountDecisionPolicy() {
+ val := s.network.Validators[0]
+ newAdmin := s.network.Validators[1].Address
+ clientCtx := val.ClientCtx
+ groupAccount := s.groupAccount3
+
+ var commonFlags = []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()),
+ }
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ respType proto.Message
+ expectedCode uint32
+ }{
+ {
+ "correct data",
+ append(
+ []string{
+ groupAccount.Admin,
+ groupAccount.GroupAccount,
+ "{\"@type\":\"/regen.group.v1alpha1.ThresholdDecisionPolicy\", \"threshold\":\"2\", \"timeout\":\"2s\"}",
+ },
+ commonFlags...,
+ ),
+ false,
+ "",
+ &sdk.TxResponse{},
+ 0,
+ },
+ {
+ "wrong admin",
+ append(
+ []string{
+ newAdmin.String(),
+ groupAccount.GroupAccount,
+ "{\"@type\":\"/regen.group.v1alpha1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}",
+ },
+ commonFlags...,
+ ),
+ true,
+ "The specified item could not be found in the keyring",
+ &sdk.TxResponse{},
+ 0,
+ },
+ {
+ "wrong group account",
+ append(
+ []string{
+ groupAccount.Admin,
+ newAdmin.String(),
+ "{\"@type\":\"/regen.group.v1alpha1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}",
+ },
+ commonFlags...,
+ ),
+ true,
+ "load group account: not found",
+ &sdk.TxResponse{},
+ 0,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.MsgUpdateGroupAccountDecisionPolicyCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String())
+
+ txResp := tc.respType.(*sdk.TxResponse)
+ s.Require().Equal(tc.expectedCode, txResp.Code, out.String())
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestTxUpdateGroupAccountMetadata() {
+ val := s.network.Validators[0]
+ newAdmin := s.network.Validators[1].Address
+ clientCtx := val.ClientCtx
+ groupAccount := s.groupAccount3
+
+ var commonFlags = []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()),
+ }
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ respType proto.Message
+ expectedCode uint32
+ }{
+ {
+ "correct data",
+ append(
+ []string{
+ groupAccount.Admin,
+ groupAccount.GroupAccount,
+ validMetadata,
+ },
+ commonFlags...,
+ ),
+ false,
+ "",
+ &sdk.TxResponse{},
+ 0,
+ },
+ {
+ "long metadata",
+ append(
+ []string{
+ groupAccount.Admin,
+ groupAccount.GroupAccount,
+ strings.Repeat("a", 500),
+ },
+ commonFlags...,
+ ),
+ true,
+ "group account metadata: limit exceeded",
+ &sdk.TxResponse{},
+ 0,
+ },
+ {
+ "wrong admin",
+ append(
+ []string{
+ newAdmin.String(),
+ groupAccount.GroupAccount,
+ validMetadata,
+ },
+ commonFlags...,
+ ),
+ true,
+ "The specified item could not be found in the keyring",
+ &sdk.TxResponse{},
+ 0,
+ },
+ {
+ "wrong group account",
+ append(
+ []string{
+ groupAccount.Admin,
+ newAdmin.String(),
+ validMetadata,
+ },
+ commonFlags...,
+ ),
+ true,
+ "load group account: not found",
+ &sdk.TxResponse{},
+ 0,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.MsgUpdateGroupAccountMetadataCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String())
+
+ txResp := tc.respType.(*sdk.TxResponse)
+ s.Require().Equal(tc.expectedCode, txResp.Code, out.String())
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestTxCreateProposal() {
+ val := s.network.Validators[0]
+ clientCtx := val.ClientCtx
+
+ var commonFlags = []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()),
+ }
+
+ validTxFileName := getTxSendFileName(s, s.groupAccount.GroupAccount, val.Address.String())
+ unauthzTxFileName := getTxSendFileName(s, val.Address.String(), s.groupAccount.GroupAccount)
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ respType proto.Message
+ expectedCode uint32
+ }{
+ {
+ "correct data",
+ append(
+ []string{
+ s.groupAccount.GroupAccount,
+ val.Address.String(),
+ validTxFileName,
+ "",
+ fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
+ },
+ commonFlags...,
+ ),
+ false,
+ "",
+ &sdk.TxResponse{},
+ 0,
+ },
+ {
+ "metadata too long",
+ append(
+ []string{
+ s.groupAccount.GroupAccount,
+ val.Address.String(),
+ validTxFileName,
+ "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ==",
+ fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
+ },
+ commonFlags...,
+ ),
+ true,
+ "metadata: limit exceeded",
+ nil,
+ 0,
+ },
+ {
+ "unauthorized msg",
+ append(
+ []string{
+ s.groupAccount.GroupAccount,
+ val.Address.String(),
+ unauthzTxFileName,
+ "",
+ fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
+ },
+ commonFlags...,
+ ),
+ true,
+ "msg does not have group account authorization: unauthorized",
+ nil,
+ 0,
+ },
+ {
+ "invalid proposers",
+ append(
+ []string{
+ s.groupAccount.GroupAccount,
+ "invalid",
+ validTxFileName,
+ "",
+ fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
+ },
+ commonFlags...,
+ ),
+ true,
+ "proposers: decoding bech32 failed",
+ nil,
+ 0,
+ },
+ {
+ "invalid group account",
+ append(
+ []string{
+ "invalid",
+ val.Address.String(),
+ validTxFileName,
+ "",
+ fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
+ },
+ commonFlags...,
+ ),
+ true,
+ "group account: decoding bech32 failed",
+ nil,
+ 0,
+ },
+ {
+ "no group account",
+ append(
+ []string{
+ val.Address.String(),
+ val.Address.String(),
+ validTxFileName,
+ "",
+ fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
+ },
+ commonFlags...,
+ ),
+ true,
+ "group account: not found",
+ nil,
+ 0,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.MsgCreateProposalCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String())
+
+ txResp := tc.respType.(*sdk.TxResponse)
+ s.Require().Equal(tc.expectedCode, txResp.Code, out.String())
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestTxVote() {
+ val := s.network.Validators[0]
+ clientCtx := val.ClientCtx
+
+ var commonFlags = []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()),
+ }
+
+ validTxFileName := getTxSendFileName(s, s.groupAccount.GroupAccount, val.Address.String())
+ out, err := cli.ExecTestCLICmd(val.ClientCtx, client.MsgCreateProposalCmd(),
+ append(
+ []string{
+ s.groupAccount.GroupAccount,
+ val.Address.String(),
+ validTxFileName,
+ "",
+ fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
+ },
+ commonFlags...,
+ ),
+ )
+ s.Require().NoError(err, out.String())
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ respType proto.Message
+ expectedCode uint32
+ }{
+ {
+ "correct data",
+ append(
+ []string{
+ "2",
+ val.Address.String(),
+ "CHOICE_YES",
+ "",
+ },
+ commonFlags...,
+ ),
+ false,
+ "",
+ &sdk.TxResponse{},
+ 0,
+ },
+ {
+ "invalid proposal id",
+ append(
+ []string{
+ "abcd",
+ val.Address.String(),
+ "CHOICE_YES",
+ "",
+ },
+ commonFlags...,
+ ),
+ true,
+ "invalid syntax",
+ nil,
+ 0,
+ },
+ {
+ "proposal not found",
+ append(
+ []string{
+ "1234",
+ val.Address.String(),
+ "CHOICE_YES",
+ "",
+ },
+ commonFlags...,
+ ),
+ true,
+ "proposal: not found",
+ nil,
+ 0,
+ },
+ {
+ "metadata too long",
+ append(
+ []string{
+ "2",
+ val.Address.String(),
+ "CHOICE_YES",
+ "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ==",
+ },
+ commonFlags...,
+ ),
+ true,
+ "metadata: limit exceeded",
+ nil,
+ 0,
+ },
+ {
+ "invalid choice",
+ append(
+ []string{
+ "2",
+ val.Address.String(),
+ "INVALID_CHOICE",
+ "",
+ },
+ commonFlags...,
+ ),
+ true,
+ "not a valid vote choice",
+ nil,
+ 0,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.MsgVoteCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String())
+
+ txResp := tc.respType.(*sdk.TxResponse)
+ s.Require().Equal(tc.expectedCode, txResp.Code, out.String())
+ }
+ })
+ }
+}
+
+func (s *IntegrationTestSuite) TestTxExec() {
+ val := s.network.Validators[0]
+ clientCtx := val.ClientCtx
+
+ var commonFlags = []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()),
+ }
+
+ // create proposal
+ validTxFileName := getTxSendFileName(s, s.groupAccount.GroupAccount, val.Address.String())
+ out, err := cli.ExecTestCLICmd(val.ClientCtx, client.MsgCreateProposalCmd(),
+ append(
+ []string{
+ s.groupAccount.GroupAccount,
+ val.Address.String(),
+ validTxFileName,
+ "",
+ fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
+ },
+ commonFlags...,
+ ),
+ )
+ s.Require().NoError(err, out.String())
+
+ // vote
+ out, err = cli.ExecTestCLICmd(val.ClientCtx, client.MsgVoteCmd(),
+ append(
+ []string{
+ "3",
+ val.Address.String(),
+ "CHOICE_YES",
+ "",
+ },
+ commonFlags...,
+ ),
+ )
+ s.Require().NoError(err, out.String())
+
+ testCases := []struct {
+ name string
+ args []string
+ expectErr bool
+ expectErrMsg string
+ respType proto.Message
+ expectedCode uint32
+ }{
+ {
+ "correct data",
+ append(
+ []string{
+ "3",
+ fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
+ },
+ commonFlags...,
+ ),
+ false,
+ "",
+ &sdk.TxResponse{},
+ 0,
+ },
+ {
+ "invalid proposal id",
+ append(
+ []string{
+ "abcd",
+ fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
+ },
+ commonFlags...,
+ ),
+ true,
+ "invalid syntax",
+ nil,
+ 0,
+ },
+ {
+ "proposal not found",
+ append(
+ []string{
+ "1234",
+ fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
+ },
+ commonFlags...,
+ ),
+ true,
+ "proposal: not found",
+ nil,
+ 0,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+
+ s.Run(tc.name, func() {
+ cmd := client.MsgExecCmd()
+
+ out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args)
+ if tc.expectErr {
+ s.Require().Contains(out.String(), tc.expectErrMsg)
+ } else {
+ s.Require().NoError(err, out.String())
+ s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String())
+
+ txResp := tc.respType.(*sdk.TxResponse)
+ s.Require().Equal(tc.expectedCode, txResp.Code, out.String())
+ }
+ })
+ }
+}
+
+func getTxSendFileName(s *IntegrationTestSuite, from string, to string) string {
+ tx := fmt.Sprintf(
+ `{"body":{"messages":[{"@type":"/cosmos.bank.v1beta1.MsgSend","from_address":"%s","to_address":"%s","amount":[{"denom":"%s","amount":"10"}]}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"200000","payer":"","granter":""}},"signatures":[]}`,
+ from, to, s.cfg.BondDenom,
+ )
+ return testutil.WriteToNewTempFile(s.T(), tx).Name()
+}
+
+func TestIntegrationTestSuite(t *testing.T) {
+ suite.Run(t, new(IntegrationTestSuite))
+}
diff --git a/x/group/client/tx.go b/x/group/client/tx.go
new file mode 100644
index 0000000000..458ba59f10
--- /dev/null
+++ b/x/group/client/tx.go
@@ -0,0 +1,637 @@
+package client
+
+import (
+ "encoding/base64"
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/cosmos/cosmos-sdk/client"
+ "github.com/cosmos/cosmos-sdk/client/flags"
+ "github.com/cosmos/cosmos-sdk/client/tx"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
+ "github.com/cosmos/cosmos-sdk/version"
+ authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
+
+ "github.com/regen-network/regen-ledger/x/group"
+ "github.com/spf13/cobra"
+)
+
+const flagMembers = "members"
+
+// TxCmd returns a root CLI command handler for all x/group transaction commands.
+func TxCmd(name string) *cobra.Command {
+ txCmd := &cobra.Command{
+ Use: name,
+ Short: "Group transaction subcommands",
+ DisableFlagParsing: true,
+ SuggestionsMinimumDistance: 2,
+ RunE: client.ValidateCmd,
+ }
+
+ txCmd.AddCommand(
+ MsgCreateGroupCmd(),
+ MsgUpdateGroupAdminCmd(),
+ MsgUpdateGroupMetadataCmd(),
+ MsgUpdateGroupMembersCmd(),
+ MsgCreateGroupAccountCmd(),
+ MsgUpdateGroupAccountAdminCmd(),
+ MsgUpdateGroupAccountDecisionPolicyCmd(),
+ MsgUpdateGroupAccountMetadataCmd(),
+ MsgCreateProposalCmd(),
+ MsgVoteCmd(),
+ MsgExecCmd(),
+ )
+
+ return txCmd
+}
+
+// MsgCreateGroupCmd creates a CLI command for Msg/CreateGroup.
+func MsgCreateGroupCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "create-group [admin] [metadata] [members-json-file]",
+ Short: "Create a group which is an aggregation " +
+ "of member accounts with associated weights and " +
+ "an administrator account. Note, the '--from' flag is " +
+ "ignored as it is implied from [admin].",
+ Long: strings.TrimSpace(
+ fmt.Sprintf(`Create a group which is an aggregation of member accounts with associated weights and
+an administrator account. Note, the '--from' flag is ignored as it is implied from [admin].
+Members accounts can be given through a members JSON file that contains an array of members.
+
+Example:
+$ %s tx group create-group [admin] [metadata] [members-json-file]
+
+Where members.json contains:
+
+[
+ {
+ "address": "addr1",
+ "weight": "1",
+ "metadata": "some metadata"
+ },
+ {
+ "address": "addr2",
+ "weight": "1",
+ "metadata": "some metadata"
+ }
+]
+`,
+ version.AppName,
+ ),
+ ),
+ Args: cobra.ExactArgs(3),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ err := cmd.Flags().Set(flags.FlagFrom, args[0])
+ if err != nil {
+ return err
+ }
+
+ clientCtx, err := client.GetClientTxContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ members, err := parseMembers(args[2])
+ if err != nil {
+ return err
+ }
+
+ b, err := base64.StdEncoding.DecodeString(args[1])
+ if err != nil {
+ return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "metadata is malformed, proper base64 string is required")
+ }
+
+ msg := &group.MsgCreateGroupRequest{
+ Admin: clientCtx.GetFromAddress().String(),
+ Members: members,
+ Metadata: b,
+ }
+ if err = msg.ValidateBasic(); err != nil {
+ return fmt.Errorf("message validation failed: %w", err)
+ }
+
+ return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
+ },
+ }
+
+ cmd.Flags().String(flagMembers, "", "Members file path")
+ flags.AddTxFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// MsgUpdateGroupMembersCmd creates a CLI command for Msg/UpdateGroupMembers.
+func MsgUpdateGroupMembersCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "update-group-members [admin] [group-id] [members-json-file]",
+ Short: "Update a group's members. Set a member's weight to \"0\" to delete it.",
+ Long: strings.TrimSpace(
+ fmt.Sprintf(`Update a group's members
+
+Example:
+$ %s tx group update-group-members [admin] [group-id] [members-json-file]
+
+Where members.json contains:
+
+[
+ {
+ "address": "addr1",
+ "weight": "1",
+ "metadata": "some new metadata"
+ },
+ {
+ "address": "addr2",
+ "weight": "0",
+ "metadata": "some metadata"
+ }
+]
+
+Set a member's weight to "0" to delete it.
+`,
+ version.AppName,
+ ),
+ ),
+ Args: cobra.ExactArgs(3),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ err := cmd.Flags().Set(flags.FlagFrom, args[0])
+ if err != nil {
+ return err
+ }
+
+ clientCtx, err := client.GetClientTxContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ members, err := parseMembers(args[2])
+ if err != nil {
+ return err
+ }
+
+ groupID, err := strconv.ParseUint(args[1], 10, 64)
+ if err != nil {
+ return err
+ }
+
+ msg := &group.MsgUpdateGroupMembersRequest{
+ Admin: clientCtx.GetFromAddress().String(),
+ MemberUpdates: members,
+ GroupId: group.ID(groupID),
+ }
+ if err = msg.ValidateBasic(); err != nil {
+ return fmt.Errorf("message validation failed: %w", err)
+ }
+
+ return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
+ },
+ }
+
+ cmd.Flags().String(flagMembers, "", "Members file path")
+ flags.AddTxFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// MsgUpdateGroupAdminCmd creates a CLI command for Msg/UpdateGroupAdmin.
+func MsgUpdateGroupAdminCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "update-group-admin [admin] [group-id] [new-admin]",
+ Short: "Update a group's admin",
+ Args: cobra.ExactArgs(3),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ err := cmd.Flags().Set(flags.FlagFrom, args[0])
+ if err != nil {
+ return err
+ }
+
+ clientCtx, err := client.GetClientTxContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ groupID, err := strconv.ParseUint(args[1], 10, 64)
+ if err != nil {
+ return err
+ }
+
+ msg := &group.MsgUpdateGroupAdminRequest{
+ Admin: clientCtx.GetFromAddress().String(),
+ NewAdmin: args[2],
+ GroupId: group.ID(groupID),
+ }
+ if err = msg.ValidateBasic(); err != nil {
+ return fmt.Errorf("message validation failed: %w", err)
+ }
+
+ return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
+ },
+ }
+
+ flags.AddTxFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// MsgUpdateGroupMetadataCmd creates a CLI command for Msg/UpdateGroupMetadata.
+func MsgUpdateGroupMetadataCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "update-group-admin [admin] [group-id] [metadata]",
+ Short: "Update a group's admin",
+ Args: cobra.ExactArgs(3),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ err := cmd.Flags().Set(flags.FlagFrom, args[0])
+ if err != nil {
+ return err
+ }
+
+ clientCtx, err := client.GetClientTxContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ groupID, err := strconv.ParseUint(args[1], 10, 64)
+ if err != nil {
+ return err
+ }
+
+ b, err := base64.StdEncoding.DecodeString(args[2])
+ if err != nil {
+ return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "metadata is malformed, proper base64 string is required")
+ }
+
+ msg := &group.MsgUpdateGroupMetadataRequest{
+ Admin: clientCtx.GetFromAddress().String(),
+ Metadata: b,
+ GroupId: group.ID(groupID),
+ }
+ if err = msg.ValidateBasic(); err != nil {
+ return fmt.Errorf("message validation failed: %w", err)
+ }
+
+ return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
+ },
+ }
+
+ flags.AddTxFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// MsgCreateGroupAccountCmd creates a CLI command for Msg/CreateGroupAccount.
+func MsgCreateGroupAccountCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "create-group-account [admin] [group-id] [metadata] [decision-policy]",
+ Short: "Create a group account which is an account " +
+ "associated with a group and a decision policy. " +
+ "Note, the '--from' flag is " +
+ "ignored as it is implied from [admin].",
+ Long: strings.TrimSpace(
+ fmt.Sprintf(`Create a group account which is an account associated with a group and a decision policy.
+Note, the '--from' flag is ignored as it is implied from [admin].
+
+Example:
+$ %s tx group create-group-account [admin] [group-id] [metadata] \
+'{"@type":"/regen.group.v1alpha1.ThresholdDecisionPolicy", "threshold":"1", "timeout":"1s"}'
+`,
+ version.AppName,
+ ),
+ ),
+ Args: cobra.ExactArgs(4),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ err := cmd.Flags().Set(flags.FlagFrom, args[0])
+ if err != nil {
+ return err
+ }
+
+ clientCtx, err := client.GetClientTxContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ groupID, err := strconv.ParseUint(args[1], 10, 64)
+ if err != nil {
+ return err
+ }
+
+ var policy group.DecisionPolicy
+ if err := clientCtx.JSONMarshaler.UnmarshalInterfaceJSON([]byte(args[3]), &policy); err != nil {
+ return err
+ }
+
+ b, err := base64.StdEncoding.DecodeString(args[2])
+ if err != nil {
+ return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "metadata is malformed, proper base64 string is required")
+ }
+
+ msg, err := group.NewMsgCreateGroupAccountRequest(
+ clientCtx.GetFromAddress(),
+ group.ID(groupID),
+ b,
+ policy,
+ )
+ if err != nil {
+ return err
+ }
+ if err = msg.ValidateBasic(); err != nil {
+ return fmt.Errorf("message validation failed: %w", err)
+ }
+
+ return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
+ },
+ }
+
+ flags.AddTxFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// MsgUpdateGroupAccountAdminCmd creates a CLI command for Msg/UpdateGroupAccountAdmin.
+func MsgUpdateGroupAccountAdminCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "update-group-account-admin [admin] [group-account] [new-admin]",
+ Short: "Update a group account admin",
+ Args: cobra.ExactArgs(3),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ err := cmd.Flags().Set(flags.FlagFrom, args[0])
+ if err != nil {
+ return err
+ }
+
+ clientCtx, err := client.GetClientTxContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ msg := &group.MsgUpdateGroupAccountAdminRequest{
+ Admin: clientCtx.GetFromAddress().String(),
+ GroupAccount: args[1],
+ NewAdmin: args[2],
+ }
+ if err = msg.ValidateBasic(); err != nil {
+ return fmt.Errorf("message validation failed: %w", err)
+ }
+
+ return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
+ },
+ }
+
+ flags.AddTxFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// MsgUpdateGroupAccountDecisionPolicyCmd creates a CLI command for Msg/UpdateGroupAccountDecisionPolicy.
+func MsgUpdateGroupAccountDecisionPolicyCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "update-group-account-policy [admin] [group-account] [decision-policy]",
+ Short: "Update a group account decision policy",
+ Args: cobra.ExactArgs(3),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ err := cmd.Flags().Set(flags.FlagFrom, args[0])
+ if err != nil {
+ return err
+ }
+
+ clientCtx, err := client.GetClientTxContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ var policy group.DecisionPolicy
+ if err := clientCtx.JSONMarshaler.UnmarshalInterfaceJSON([]byte(args[2]), &policy); err != nil {
+ return err
+ }
+
+ accountAddress, err := sdk.AccAddressFromBech32(args[1])
+ if err != nil {
+ return err
+ }
+
+ msg, err := group.NewMsgUpdateGroupAccountDecisionPolicyRequest(
+ clientCtx.GetFromAddress(),
+ accountAddress,
+ policy,
+ )
+ if err != nil {
+ return err
+ }
+
+ if err = msg.ValidateBasic(); err != nil {
+ return fmt.Errorf("message validation failed: %w", err)
+ }
+
+ return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
+ },
+ }
+
+ flags.AddTxFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// MsgUpdateGroupAccountMetadataCmd creates a CLI command for Msg/MsgUpdateGroupAccountMetadata.
+func MsgUpdateGroupAccountMetadataCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "update-group-account-metadata [admin] [group-account] [new-metadata]",
+ Short: "Update a group account metadata",
+ Args: cobra.ExactArgs(3),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ err := cmd.Flags().Set(flags.FlagFrom, args[0])
+ if err != nil {
+ return err
+ }
+
+ clientCtx, err := client.GetClientTxContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ b, err := base64.StdEncoding.DecodeString(args[2])
+ if err != nil {
+ return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "metadata is malformed, proper base64 string is required")
+ }
+
+ msg := &group.MsgUpdateGroupAccountMetadataRequest{
+ Admin: clientCtx.GetFromAddress().String(),
+ GroupAccount: args[1],
+ Metadata: b,
+ }
+ if err = msg.ValidateBasic(); err != nil {
+ return fmt.Errorf("message validation failed: %w", err)
+ }
+
+ return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
+ },
+ }
+
+ flags.AddTxFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// MsgCreateProposalCmd creates a CLI command for Msg/CreateProposal.
+func MsgCreateProposalCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "create-proposal [group-account] [proposer[,proposer]*] [msg_tx_json_file] [metadata]",
+ Short: "Submit a new proposal",
+ Long: `Submit a new proposal.
+
+Parameters:
+ group-account: address of the group account
+ proposer: comma separated (no spaces) list of proposer account addresses. Example: "addr1,addr2"
+ Metadata: metadata for the proposal
+ msg_tx_json_file: path to json file with messages that will be executed if the proposal is accepted.
+`,
+ Args: cobra.ExactArgs(4),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ proposers := strings.Split(args[1], ",")
+ for i := range proposers {
+ proposers[i] = strings.TrimSpace(proposers[i])
+ }
+
+ clientCtx, err := client.GetClientTxContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ theTx, err := authclient.ReadTxFromFile(clientCtx, args[2])
+ if err != nil {
+ return err
+ }
+ msgs := theTx.GetMsgs()
+
+ b, err := base64.StdEncoding.DecodeString(args[3])
+ if err != nil {
+ return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "metadata is malformed, proper base64 string is required")
+ }
+
+ msg, err := group.NewMsgCreateProposalRequest(
+ args[0],
+ proposers,
+ msgs,
+ b,
+ )
+ if err != nil {
+ return err
+ }
+
+ if err = msg.ValidateBasic(); err != nil {
+ return fmt.Errorf("message validation failed: %w", err)
+ }
+
+ return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
+ },
+ }
+
+ flags.AddTxFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// MsgVoteCmd creates a CLI command for Msg/Vote.
+func MsgVoteCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "vote [proposal-id] [voter] [choice] [metadata]",
+ Short: "Vote on a proposal",
+ Long: `Vote on a proposal.
+
+Parameters:
+ proposal-id: unique ID of the proposal
+ voter: voter account addresses.
+ choice: choice of the voter(s)
+ CHOICE_UNSPECIFIED: no-op
+ CHOICE_NO: no
+ CHOICE_YES: yes
+ CHOICE_ABSTAIN: abstain
+ CHOICE_VETO: veto
+ Metadata: metadata for the vote
+`,
+ Args: cobra.ExactArgs(4),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ err := cmd.Flags().Set(flags.FlagFrom, args[1])
+ if err != nil {
+ return err
+ }
+
+ clientCtx, err := client.GetClientTxContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ proposalID, err := strconv.ParseUint(args[0], 10, 64)
+ if err != nil {
+ return err
+ }
+
+ choice, err := group.ChoiceFromString(args[2])
+ if err != nil {
+ return err
+ }
+
+ b, err := base64.StdEncoding.DecodeString(args[3])
+ if err != nil {
+ return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "metadata is malformed, proper base64 string is required")
+ }
+
+ msg := &group.MsgVoteRequest{
+ ProposalId: group.ProposalID(proposalID),
+ Voter: args[1],
+ Choice: choice,
+ Metadata: b,
+ }
+ if err != nil {
+ return err
+ }
+
+ if err = msg.ValidateBasic(); err != nil {
+ return fmt.Errorf("message validation failed: %w", err)
+ }
+
+ return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
+ },
+ }
+
+ flags.AddTxFlagsToCmd(cmd)
+
+ return cmd
+}
+
+// MsgExecCmd creates a CLI command for Msg/MsgExec.
+func MsgExecCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "exec [proposal-id]",
+ Short: "Execute a proposal",
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ clientCtx, err := client.GetClientTxContext(cmd)
+ if err != nil {
+ return err
+ }
+
+ proposalID, err := strconv.ParseUint(args[0], 10, 64)
+ if err != nil {
+ return err
+ }
+
+ msg := &group.MsgExecRequest{
+ ProposalId: group.ProposalID(proposalID),
+ Signer: clientCtx.GetFromAddress().String(),
+ }
+ if err != nil {
+ return err
+ }
+
+ if err = msg.ValidateBasic(); err != nil {
+ return fmt.Errorf("message validation failed: %w", err)
+ }
+
+ return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
+ },
+ }
+
+ flags.AddTxFlagsToCmd(cmd)
+
+ return cmd
+}
diff --git a/x/group/client/util.go b/x/group/client/util.go
new file mode 100644
index 0000000000..04083ea7a9
--- /dev/null
+++ b/x/group/client/util.go
@@ -0,0 +1,28 @@
+package client
+
+import (
+ "encoding/json"
+ "io/ioutil"
+
+ "github.com/regen-network/regen-ledger/x/group"
+)
+
+func parseMembers(membersFile string) ([]group.Member, error) {
+ members := []group.Member{}
+
+ if membersFile == "" {
+ return members, nil
+ }
+
+ contents, err := ioutil.ReadFile(membersFile)
+ if err != nil {
+ return nil, err
+ }
+
+ err = json.Unmarshal(contents, &members)
+ if err != nil {
+ return nil, err
+ }
+
+ return members, nil
+}
diff --git a/x/group/codec.go b/x/group/codec.go
index e07d9362ab..75be45627f 100644
--- a/x/group/codec.go
+++ b/x/group/codec.go
@@ -1,11 +1,47 @@
package group
import (
+ "github.com/cosmos/cosmos-sdk/codec"
cdctypes "github.com/cosmos/cosmos-sdk/codec/types"
+ cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
+ sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/msgservice"
)
+// RegisterLegacyAminoCodec registers all the necessary group module concrete
+// types and interfaces with the provided codec reference.
+// These types are used for Amino JSON serialization.
+func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
+ cdc.RegisterInterface((*DecisionPolicy)(nil), nil)
+ cdc.RegisterConcrete(&ThresholdDecisionPolicy{}, "cosmos-sdk/ThresholdDecisionPolicy", nil)
+ cdc.RegisterConcrete(&MsgCreateGroupRequest{}, "cosmos-sdk/MsgCreateGroup", nil)
+ cdc.RegisterConcrete(&MsgUpdateGroupMembersRequest{}, "cosmos-sdk/MsgUpdateGroupMembers", nil)
+ cdc.RegisterConcrete(&MsgUpdateGroupAdminRequest{}, "cosmos-sdk/MsgUpdateGroupAdmin", nil)
+ cdc.RegisterConcrete(&MsgUpdateGroupMetadataRequest{}, "cosmos-sdk/MsgUpdateGroupMetadata", nil)
+ cdc.RegisterConcrete(&MsgCreateGroupAccountRequest{}, "cosmos-sdk/MsgCreateGroupAccount", nil)
+ cdc.RegisterConcrete(&MsgUpdateGroupAccountAdminRequest{}, "cosmos-sdk/MsgUpdateGroupAccountAdmin", nil)
+ cdc.RegisterConcrete(&MsgUpdateGroupAccountDecisionPolicyRequest{}, "cosmos-sdk/MsgUpdateGroupAccountDecisionPolicy", nil)
+ cdc.RegisterConcrete(&MsgUpdateGroupAccountMetadataRequest{}, "cosmos-sdk/MsgUpdateGroupAccountMetadata", nil)
+ cdc.RegisterConcrete(&MsgCreateProposalRequest{}, "cosmos-sdk/group/MsgCreateProposal", nil)
+ cdc.RegisterConcrete(&MsgVoteRequest{}, "cosmos-sdk/group/MsgVote", nil)
+ cdc.RegisterConcrete(&MsgExecRequest{}, "cosmos-sdk/group/MsgExec", nil)
+}
+
func RegisterTypes(registry cdctypes.InterfaceRegistry) {
+ registry.RegisterImplementations((*sdk.Msg)(nil),
+ &MsgCreateGroupRequest{},
+ &MsgUpdateGroupMembersRequest{},
+ &MsgUpdateGroupAdminRequest{},
+ &MsgUpdateGroupMetadataRequest{},
+ &MsgCreateGroupAccountRequest{},
+ &MsgUpdateGroupAccountAdminRequest{},
+ &MsgUpdateGroupAccountDecisionPolicyRequest{},
+ &MsgUpdateGroupAccountMetadataRequest{},
+ &MsgCreateProposalRequest{},
+ &MsgVoteRequest{},
+ &MsgExecRequest{},
+ )
+
msgservice.RegisterMsgServiceDesc(registry, &Msg_ServiceDesc)
registry.RegisterInterface(
@@ -14,3 +50,13 @@ func RegisterTypes(registry cdctypes.InterfaceRegistry) {
&ThresholdDecisionPolicy{},
)
}
+
+var (
+ amino = codec.NewLegacyAmino()
+ ModuleCdc = codec.NewAminoCodec(amino)
+)
+
+func init() {
+ RegisterLegacyAminoCodec(amino)
+ cryptocodec.RegisterCrypto(amino)
+}
diff --git a/x/group/keys.go b/x/group/keys.go
index 1aed1a30de..4086423f60 100644
--- a/x/group/keys.go
+++ b/x/group/keys.go
@@ -8,4 +8,7 @@ const (
StoreKey = ModuleName
DefaultParamspace = ModuleName
+
+ // RouterKey defines the module's message routing key
+ RouterKey = ModuleName
)
diff --git a/x/group/module/module.go b/x/group/module/module.go
index 8c1229df67..df7ab72532 100644
--- a/x/group/module/module.go
+++ b/x/group/module/module.go
@@ -8,11 +8,13 @@ import (
climodule "github.com/regen-network/regen-ledger/types/module/client/cli"
servermodule "github.com/regen-network/regen-ledger/types/module/server"
"github.com/regen-network/regen-ledger/x/group"
+ "github.com/regen-network/regen-ledger/x/group/client"
"github.com/regen-network/regen-ledger/x/group/server"
- "github.com/cosmos/cosmos-sdk/client"
+ sdkclient "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
@@ -25,6 +27,7 @@ type Module struct {
var _ module.AppModuleBasic = Module{}
var _ servermodule.Module = Module{}
var _ climodule.Module = Module{}
+var _ servermodule.LegacyRouteModule = Module{}
func (a Module) Name() string {
return group.ModuleName
@@ -43,7 +46,7 @@ func (a Module) DefaultGenesis(marshaler codec.JSONMarshaler) json.RawMessage {
return marshaler.MustMarshalJSON(group.NewGenesisState())
}
-func (a Module) ValidateGenesis(cdc codec.JSONMarshaler, config client.TxEncodingConfig, bz json.RawMessage) error {
+func (a Module) ValidateGenesis(cdc codec.JSONMarshaler, config sdkclient.TxEncodingConfig, bz json.RawMessage) error {
var data group.GenesisState
if err := cdc.UnmarshalJSON(bz, &data); err != nil {
return fmt.Errorf("failed to unmarshal %s genesis state: %w", group.ModuleName, err)
@@ -51,18 +54,22 @@ func (a Module) ValidateGenesis(cdc codec.JSONMarshaler, config client.TxEncodin
return data.Validate()
}
-func (a Module) RegisterGRPCGatewayRoutes(client.Context, *runtime.ServeMux) {}
+func (a Module) RegisterGRPCGatewayRoutes(sdkclient.Context, *runtime.ServeMux) {}
func (a Module) GetTxCmd() *cobra.Command {
- // TODO #209
- return nil
+ return client.TxCmd(a.Name())
}
func (a Module) GetQueryCmd() *cobra.Command {
- // TODO #209
- return nil
+ return client.QueryCmd(a.Name())
}
/**** DEPRECATED ****/
-func (a Module) RegisterRESTRoutes(client.Context, *mux.Router) {}
-func (a Module) RegisterLegacyAminoCodec(*codec.LegacyAmino) {}
+func (a Module) RegisterRESTRoutes(sdkclient.Context, *mux.Router) {}
+func (a Module) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
+ group.RegisterLegacyAminoCodec(cdc)
+}
+
+func (a Module) Route(configurator servermodule.Configurator) sdk.Route {
+ return sdk.NewRoute(group.RouterKey, server.NewHandler(configurator, a.AccountKeeper))
+}
diff --git a/x/group/msgs.go b/x/group/msgs.go
index d04b45947d..7231ac4ea3 100644
--- a/x/group/msgs.go
+++ b/x/group/msgs.go
@@ -10,7 +10,33 @@ import (
"github.com/regen-network/regen-ledger/math"
)
-var _ sdk.MsgRequest = &MsgCreateGroupRequest{}
+// Group message types and routes
+const (
+ TypeMsgCreateGroup = "create_group"
+ TypeMsgUpdateGroupAdmin = "update_group_admin"
+ TypeMsgUpdateGroupComment = "update_group_comment"
+ TypeMsgUpdateGroupMembers = "update_group_members"
+ TypeMsgCreateGroupAccount = "create_group_account"
+ TypeMsgUpdateGroupAccountAdmin = "update_group_account_admin"
+ TypeMsgUpdateGroupAccountDecisionPolicy = "update_group_account_decision_policy"
+ TypeMsgUpdateGroupAccountComment = "update_group_account_comment"
+ TypeMsgCreateProposal = "create_proposal"
+ TypeMsgVote = "vote"
+ TypeMsgExec = "exec"
+)
+
+var _ sdk.Msg = &MsgCreateGroupRequest{}
+
+// Route Implements Msg.
+func (m MsgCreateGroupRequest) Route() string { return RouterKey }
+
+// Type Implements Msg.
+func (m MsgCreateGroupRequest) Type() string { return TypeMsgCreateGroup }
+
+// GetSignBytes Implements Msg.
+func (m MsgCreateGroupRequest) GetSignBytes() []byte {
+ return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m))
+}
// GetSigners returns the expected signers for a MsgCreateGroupRequest.
func (m MsgCreateGroupRequest) GetSigners() []sdk.AccAddress {
@@ -52,7 +78,18 @@ func (m Member) ValidateBasic() error {
return nil
}
-var _ sdk.MsgRequest = &MsgUpdateGroupAdminRequest{}
+var _ sdk.Msg = &MsgUpdateGroupAdminRequest{}
+
+// Route Implements Msg.
+func (m MsgUpdateGroupAdminRequest) Route() string { return RouterKey }
+
+// Type Implements Msg.
+func (m MsgUpdateGroupAdminRequest) Type() string { return TypeMsgUpdateGroupAdmin }
+
+// GetSignBytes Implements Msg.
+func (m MsgUpdateGroupAdminRequest) GetSignBytes() []byte {
+ return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m))
+}
// GetSigners returns the expected signers for a MsgUpdateGroupAdminRequest.
func (m MsgUpdateGroupAdminRequest) GetSigners() []sdk.AccAddress {
@@ -89,7 +126,18 @@ func (m *MsgUpdateGroupAdminRequest) GetGroupID() ID {
return m.GroupId
}
-var _ sdk.MsgRequest = &MsgUpdateGroupMetadataRequest{}
+var _ sdk.Msg = &MsgUpdateGroupMetadataRequest{}
+
+// Route Implements Msg.
+func (m MsgUpdateGroupMetadataRequest) Route() string { return RouterKey }
+
+// Type Implements Msg.
+func (m MsgUpdateGroupMetadataRequest) Type() string { return TypeMsgUpdateGroupComment }
+
+// GetSignBytes Implements Msg.
+func (m MsgUpdateGroupMetadataRequest) GetSignBytes() []byte {
+ return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m))
+}
// GetSigners returns the expected signers for a MsgUpdateGroupMetadataRequest.
func (m MsgUpdateGroupMetadataRequest) GetSigners() []sdk.AccAddress {
@@ -117,6 +165,19 @@ func (m *MsgUpdateGroupMetadataRequest) GetGroupID() ID {
return m.GroupId
}
+var _ sdk.Msg = &MsgUpdateGroupMembersRequest{}
+
+// Route Implements Msg.
+func (m MsgUpdateGroupMembersRequest) Route() string { return RouterKey }
+
+// Type Implements Msg.
+func (m MsgUpdateGroupMembersRequest) Type() string { return TypeMsgUpdateGroupMembers }
+
+// GetSignBytes Implements Msg.
+func (m MsgUpdateGroupMembersRequest) GetSignBytes() []byte {
+ return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m))
+}
+
var _ sdk.MsgRequest = &MsgUpdateGroupMembersRequest{}
// GetSigners returns the expected signers for a MsgUpdateGroupMembersRequest.
@@ -152,7 +213,18 @@ func (m *MsgUpdateGroupMembersRequest) GetGroupID() ID {
return m.GroupId
}
-var _ sdk.MsgRequest = &MsgCreateGroupAccountRequest{}
+var _ sdk.Msg = &MsgCreateGroupAccountRequest{}
+
+// Route Implements Msg.
+func (m MsgCreateGroupAccountRequest) Route() string { return RouterKey }
+
+// Type Implements Msg.
+func (m MsgCreateGroupAccountRequest) Type() string { return TypeMsgCreateGroupAccount }
+
+// GetSignBytes Implements Msg.
+func (m MsgCreateGroupAccountRequest) GetSignBytes() []byte {
+ return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m))
+}
// GetSigners returns the expected signers for a MsgCreateGroupAccountRequest.
func (m MsgCreateGroupAccountRequest) GetSigners() []sdk.AccAddress {
@@ -184,7 +256,18 @@ func (m MsgCreateGroupAccountRequest) ValidateBasic() error {
return nil
}
-var _ sdk.MsgRequest = &MsgUpdateGroupAccountAdminRequest{}
+var _ sdk.Msg = &MsgUpdateGroupAccountAdminRequest{}
+
+// Route Implements Msg.
+func (m MsgUpdateGroupAccountAdminRequest) Route() string { return RouterKey }
+
+// Type Implements Msg.
+func (m MsgUpdateGroupAccountAdminRequest) Type() string { return TypeMsgUpdateGroupAccountAdmin }
+
+// GetSignBytes Implements Msg.
+func (m MsgUpdateGroupAccountAdminRequest) GetSignBytes() []byte {
+ return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m))
+}
// GetSigners returns the expected signers for a MsgUpdateGroupAccountAdminRequest.
func (m MsgUpdateGroupAccountAdminRequest) GetSigners() []sdk.AccAddress {
@@ -218,7 +301,7 @@ func (m MsgUpdateGroupAccountAdminRequest) ValidateBasic() error {
return nil
}
-var _ sdk.MsgRequest = &MsgUpdateGroupAccountDecisionPolicyRequest{}
+var _ sdk.Msg = &MsgUpdateGroupAccountDecisionPolicyRequest{}
var _ types.UnpackInterfacesMessage = MsgUpdateGroupAccountDecisionPolicyRequest{}
func NewMsgUpdateGroupAccountDecisionPolicyRequest(admin sdk.AccAddress, groupAccount sdk.AccAddress, decisionPolicy DecisionPolicy) (*MsgUpdateGroupAccountDecisionPolicyRequest, error) {
@@ -246,6 +329,19 @@ func (m *MsgUpdateGroupAccountDecisionPolicyRequest) SetDecisionPolicy(decisionP
return nil
}
+// Route Implements Msg.
+func (m MsgUpdateGroupAccountDecisionPolicyRequest) Route() string { return RouterKey }
+
+// Type Implements Msg.
+func (m MsgUpdateGroupAccountDecisionPolicyRequest) Type() string {
+ return TypeMsgUpdateGroupAccountDecisionPolicy
+}
+
+// GetSignBytes Implements Msg.
+func (m MsgUpdateGroupAccountDecisionPolicyRequest) GetSignBytes() []byte {
+ return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m))
+}
+
// GetSigners returns the expected signers for a MsgUpdateGroupAccountDecisionPolicyRequest.
func (m MsgUpdateGroupAccountDecisionPolicyRequest) GetSigners() []sdk.AccAddress {
admin, err := sdk.AccAddressFromBech32(m.Admin)
@@ -293,7 +389,18 @@ func (m MsgUpdateGroupAccountDecisionPolicyRequest) UnpackInterfaces(unpacker ty
return unpacker.UnpackAny(m.DecisionPolicy, &decisionPolicy)
}
-var _ sdk.MsgRequest = &MsgUpdateGroupAccountMetadataRequest{}
+var _ sdk.Msg = &MsgUpdateGroupAccountMetadataRequest{}
+
+// Route Implements Msg.
+func (m MsgUpdateGroupAccountMetadataRequest) Route() string { return RouterKey }
+
+// Type Implements Msg.
+func (m MsgUpdateGroupAccountMetadataRequest) Type() string { return TypeMsgUpdateGroupAccountComment }
+
+// GetSignBytes Implements Msg.
+func (m MsgUpdateGroupAccountMetadataRequest) GetSignBytes() []byte {
+ return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m))
+}
// GetSigners returns the expected signers for a MsgUpdateGroupAccountMetadataRequest.
func (m MsgUpdateGroupAccountMetadataRequest) GetSigners() []sdk.AccAddress {
@@ -319,7 +426,7 @@ func (m MsgUpdateGroupAccountMetadataRequest) ValidateBasic() error {
return nil
}
-var _ sdk.MsgRequest = &MsgCreateGroupAccountRequest{}
+var _ sdk.Msg = &MsgCreateGroupAccountRequest{}
var _ types.UnpackInterfacesMessage = MsgCreateGroupAccountRequest{}
// NewMsgCreateGroupAccountRequest creates a new MsgCreateGroupAccountRequest.
@@ -375,7 +482,32 @@ func (m MsgCreateGroupAccountRequest) UnpackInterfaces(unpacker types.AnyUnpacke
return unpacker.UnpackAny(m.DecisionPolicy, &decisionPolicy)
}
-var _ sdk.MsgRequest = &MsgCreateProposalRequest{}
+var _ sdk.Msg = &MsgCreateProposalRequest{}
+
+// NewMsgCreateProposalRequest creates a new MsgCreateProposalRequest.
+func NewMsgCreateProposalRequest(acc string, proposers []string, msgs []sdk.Msg, metadata []byte) (*MsgCreateProposalRequest, error) {
+ m := &MsgCreateProposalRequest{
+ GroupAccount: acc,
+ Proposers: proposers,
+ Metadata: metadata,
+ }
+ err := m.SetMsgs(msgs)
+ if err != nil {
+ return nil, err
+ }
+ return m, nil
+}
+
+// Route Implements Msg.
+func (m MsgCreateProposalRequest) Route() string { return RouterKey }
+
+// Type Implements Msg.
+func (m MsgCreateProposalRequest) Type() string { return TypeMsgCreateProposal }
+
+// GetSignBytes Implements Msg.
+func (m MsgCreateProposalRequest) GetSignBytes() []byte {
+ return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m))
+}
// GetSigners returns the expected signers for a MsgCreateProposalRequest.
func (m MsgCreateProposalRequest) GetSigners() []sdk.AccAddress {
@@ -412,11 +544,8 @@ func (m MsgCreateProposalRequest) ValidateBasic() error {
return sdkerrors.Wrap(err, "proposers")
}
- for i, any := range m.Msgs {
- msg, ok := any.GetCachedValue().(sdk.Msg)
- if !ok {
- return sdkerrors.Wrapf(sdkerrors.ErrUnpackAny, "cannot unpack Any into sdk.Msg %T", any)
- }
+ msgs := m.GetMsgs()
+ for i, msg := range msgs {
if err := msg.ValidateBasic(); err != nil {
return sdkerrors.Wrapf(err, "msg %d", i)
}
@@ -454,8 +583,15 @@ func (m MsgCreateProposalRequest) GetMsgs() []sdk.Msg {
// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces
func (m MsgCreateProposalRequest) UnpackInterfaces(unpacker types.AnyUnpacker) error {
- for _, m := range m.Msgs {
- err := types.UnpackInterfaces(m, unpacker)
+ // for _, m := range m.Msgs {
+ // err := types.UnpackInterfaces(m, unpacker)
+ // if err != nil {
+ // return err
+ // }
+ // }
+ for _, any := range m.Msgs {
+ var msg sdk.Msg
+ err := unpacker.UnpackAny(any, &msg)
if err != nil {
return err
}
@@ -464,7 +600,18 @@ func (m MsgCreateProposalRequest) UnpackInterfaces(unpacker types.AnyUnpacker) e
return nil
}
-var _ sdk.MsgRequest = &MsgVoteRequest{}
+var _ sdk.Msg = &MsgVoteRequest{}
+
+// Route Implements Msg.
+func (m MsgVoteRequest) Route() string { return RouterKey }
+
+// Type Implements Msg.
+func (m MsgVoteRequest) Type() string { return TypeMsgVote }
+
+// GetSignBytes Implements Msg.
+func (m MsgVoteRequest) GetSignBytes() []byte {
+ return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m))
+}
// GetSigners returns the expected signers for a MsgVoteRequest.
func (m MsgVoteRequest) GetSigners() []sdk.AccAddress {
@@ -493,7 +640,18 @@ func (m MsgVoteRequest) ValidateBasic() error {
return nil
}
-var _ sdk.MsgRequest = &MsgExecRequest{}
+var _ sdk.Msg = &MsgExecRequest{}
+
+// Route Implements Msg.
+func (m MsgExecRequest) Route() string { return RouterKey }
+
+// Type Implements Msg.
+func (m MsgExecRequest) Type() string { return TypeMsgExec }
+
+// GetSignBytes Implements Msg.
+func (m MsgExecRequest) GetSignBytes() []byte {
+ return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m))
+}
// GetSigners returns the expected signers for a MsgExecRequest.
func (m MsgExecRequest) GetSigners() []sdk.AccAddress {
diff --git a/x/group/server/handler.go b/x/group/server/handler.go
new file mode 100644
index 0000000000..a63f46dfad
--- /dev/null
+++ b/x/group/server/handler.go
@@ -0,0 +1,69 @@
+package server
+
+import (
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ "github.com/cosmos/cosmos-sdk/types/errors"
+ "github.com/regen-network/regen-ledger/types"
+ servermodule "github.com/regen-network/regen-ledger/types/module/server"
+ "github.com/regen-network/regen-ledger/x/group"
+)
+
+// NewHandler creates an sdk.Handler for all the group type messages.
+// This is needed for supporting amino-json signing.
+func NewHandler(configurator servermodule.Configurator, accountKeeper AccountKeeper) sdk.Handler {
+ impl := newServer(configurator.ModuleKey(), configurator.Router(), accountKeeper, configurator.Marshaler())
+
+ return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
+ ctx = ctx.WithEventManager(sdk.NewEventManager())
+ regenCtx := types.Context{Context: ctx}
+
+ switch msg := msg.(type) {
+ case *group.MsgCreateGroupRequest:
+ res, err := impl.CreateGroup(regenCtx, msg)
+ return sdk.WrapServiceResult(ctx, res, err)
+
+ case *group.MsgUpdateGroupMembersRequest:
+ res, err := impl.UpdateGroupMembers(regenCtx, msg)
+ return sdk.WrapServiceResult(ctx, res, err)
+
+ case *group.MsgUpdateGroupAdminRequest:
+ res, err := impl.UpdateGroupAdmin(regenCtx, msg)
+ return sdk.WrapServiceResult(ctx, res, err)
+
+ case *group.MsgUpdateGroupMetadataRequest:
+ res, err := impl.UpdateGroupMetadata(regenCtx, msg)
+ return sdk.WrapServiceResult(ctx, res, err)
+
+ case *group.MsgCreateGroupAccountRequest:
+ res, err := impl.CreateGroupAccount(regenCtx, msg)
+ return sdk.WrapServiceResult(ctx, res, err)
+
+ case *group.MsgUpdateGroupAccountAdminRequest:
+ res, err := impl.UpdateGroupAccountAdmin(regenCtx, msg)
+ return sdk.WrapServiceResult(ctx, res, err)
+
+ case *group.MsgUpdateGroupAccountDecisionPolicyRequest:
+ res, err := impl.UpdateGroupAccountDecisionPolicy(regenCtx, msg)
+ return sdk.WrapServiceResult(ctx, res, err)
+
+ case *group.MsgUpdateGroupAccountMetadataRequest:
+ res, err := impl.UpdateGroupAccountMetadata(regenCtx, msg)
+ return sdk.WrapServiceResult(ctx, res, err)
+
+ case *group.MsgCreateProposalRequest:
+ res, err := impl.CreateProposal(regenCtx, msg)
+ return sdk.WrapServiceResult(ctx, res, err)
+
+ case *group.MsgVoteRequest:
+ res, err := impl.Vote(regenCtx, msg)
+ return sdk.WrapServiceResult(ctx, res, err)
+
+ case *group.MsgExecRequest:
+ res, err := impl.Exec(regenCtx, msg)
+ return sdk.WrapServiceResult(ctx, res, err)
+
+ default:
+ return nil, errors.Wrapf(errors.ErrUnknownRequest, "unrecognized %s message type: %T", group.ModuleName, msg)
+ }
+ }
+}
diff --git a/x/group/server/msg_server.go b/x/group/server/msg_server.go
index fc1a61f2e5..3273de714c 100644
--- a/x/group/server/msg_server.go
+++ b/x/group/server/msg_server.go
@@ -5,6 +5,7 @@ import (
"encoding/binary"
"fmt"
"reflect"
+ "strconv"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
@@ -15,7 +16,6 @@ import (
"github.com/regen-network/regen-ledger/math"
"github.com/regen-network/regen-ledger/orm"
"github.com/regen-network/regen-ledger/types"
- "github.com/regen-network/regen-ledger/util"
"github.com/regen-network/regen-ledger/x/group"
)
@@ -28,15 +28,14 @@ func (s serverImpl) CreateGroup(ctx types.Context, req *group.MsgCreateGroupRequ
return nil, err
}
- maxMetadataLength := s.maxMetadataLength(ctx)
- if err := assertMetadataLength(metadata, maxMetadataLength, "group metadata"); err != nil {
+ if err := assertMetadataLength(metadata, "group metadata"); err != nil {
return nil, err
}
totalWeight := apd.New(0, 0)
for i := range members {
m := members[i]
- if err := assertMetadataLength(m.Metadata, maxMetadataLength, "member metadata"); err != nil {
+ if err := assertMetadataLength(m.Metadata, "member metadata"); err != nil {
return nil, err
}
@@ -82,8 +81,7 @@ func (s serverImpl) CreateGroup(ctx types.Context, req *group.MsgCreateGroupRequ
}
}
- groupIDStr := util.Uint64ToBase58Check(groupID.Uint64())
- err = ctx.EventManager().EmitTypedEvent(&group.EventCreateGroup{GroupId: groupIDStr})
+ err = ctx.EventManager().EmitTypedEvent(&group.EventCreateGroup{GroupId: strconv.FormatUint(groupID.Uint64(), 10)})
if err != nil {
return nil, err
}
@@ -98,6 +96,9 @@ func (s serverImpl) UpdateGroupMembers(ctx types.Context, req *group.MsgUpdateGr
return err
}
for i := range req.MemberUpdates {
+ if err := assertMetadataLength(req.MemberUpdates[i].Metadata, "group member metadata"); err != nil {
+ return err
+ }
groupMember := group.GroupMember{GroupId: req.GroupId,
Member: &group.Member{
Address: req.MemberUpdates[i].Address,
@@ -208,6 +209,10 @@ func (s serverImpl) UpdateGroupMetadata(ctx types.Context, req *group.MsgUpdateG
return s.groupTable.Save(ctx, g.GroupId.Bytes(), g)
}
+ if err := assertMetadataLength(req.Metadata, "group metadata"); err != nil {
+ return nil, err
+ }
+
err := s.doUpdateGroup(ctx, req, action, "metadata updated")
if err != nil {
return nil, err
@@ -225,7 +230,7 @@ func (s serverImpl) CreateGroupAccount(ctx types.Context, req *group.MsgCreateGr
groupID := req.GetGroupID()
metadata := req.GetMetadata()
- if err := assertMetadataLength(metadata, s.maxMetadataLength(ctx), "group account metadata"); err != nil {
+ if err := assertMetadataLength(metadata, "group account metadata"); err != nil {
return nil, err
}
@@ -341,7 +346,7 @@ func (s serverImpl) UpdateGroupAccountMetadata(ctx types.Context, req *group.Msg
return s.groupAccountTable.Save(ctx, groupAccount)
}
- if err := assertMetadataLength(metadata, s.maxMetadataLength(ctx), "group account metadata"); err != nil {
+ if err := assertMetadataLength(metadata, "group account metadata"); err != nil {
return nil, err
}
@@ -362,7 +367,7 @@ func (s serverImpl) CreateProposal(ctx types.Context, req *group.MsgCreatePropos
proposers := req.Proposers
msgs := req.GetMsgs()
- if err := assertMetadataLength(metadata, s.maxMetadataLength(ctx), "metadata"); err != nil {
+ if err := assertMetadataLength(metadata, "metadata"); err != nil {
return nil, err
}
@@ -453,7 +458,7 @@ func (s serverImpl) Vote(ctx types.Context, req *group.MsgVoteRequest) (*group.M
choice := req.Choice
metadata := req.Metadata
- if err := assertMetadataLength(metadata, s.maxMetadataLength(ctx), "metadata"); err != nil {
+ if err := assertMetadataLength(metadata, "metadata"); err != nil {
return nil, err
}
@@ -692,8 +697,7 @@ func (s serverImpl) doUpdateGroup(ctx types.Context, req authNGroupReq, action a
return err
}
- groupIDStr := util.Uint64ToBase58Check(req.GetGroupID().Uint64())
- err = ctx.EventManager().EmitTypedEvent(&group.EventUpdateGroup{GroupId: groupIDStr})
+ err = ctx.EventManager().EmitTypedEvent(&group.EventUpdateGroup{GroupId: strconv.FormatUint(req.GetGroupID().Uint64(), 10)})
if err != nil {
return err
}
@@ -725,15 +729,10 @@ func (s serverImpl) doAuthenticated(ctx types.Context, req authNGroupReq, action
return nil
}
-// maxMetadataLength returns the maximum length of a metadata field.
-func (s serverImpl) maxMetadataLength(ctx types.Context) int {
- return group.MaxMetadataLength
-}
-
// assertMetadataLength returns an error if given metadata length
// is greater than a fixed maxMetadataLength.
-func assertMetadataLength(metadata []byte, maxMetadataLength int, description string) error {
- if len(metadata) > maxMetadataLength {
+func assertMetadataLength(metadata []byte, description string) error {
+ if len(metadata) > group.MaxMetadataLength {
return sdkerrors.Wrap(group.ErrMaxLimit, description)
}
return nil
diff --git a/x/group/server/testsuite/suite.go b/x/group/server/testsuite/suite.go
index a32ddc4fa2..2c1c3c7046 100644
--- a/x/group/server/testsuite/suite.go
+++ b/x/group/server/testsuite/suite.go
@@ -163,7 +163,7 @@ func (s *IntegrationTestSuite) TestCreateGroup() {
},
expGroups: expGroups,
},
- "group comment too long": {
+ "group metadata too long": {
req: &group.MsgCreateGroupRequest{
Admin: s.addr1.String(),
Members: members,
@@ -171,7 +171,7 @@ func (s *IntegrationTestSuite) TestCreateGroup() {
},
expErr: true,
},
- "member comment too long": {
+ "member metadata too long": {
req: &group.MsgCreateGroupRequest{
Admin: s.addr1.String(),
Members: []group.Member{{
@@ -751,7 +751,7 @@ func (s *IntegrationTestSuite) TestCreateGroupAccount() {
),
expErr: true,
},
- "comment too long": {
+ "metadata too long": {
req: &group.MsgCreateGroupAccountRequest{
Admin: s.addr1.String(),
Metadata: []byte(strings.Repeat("a", 256)),
@@ -1148,14 +1148,14 @@ func (s *IntegrationTestSuite) TestCreateProposal() {
Amount: sdk.Coins{sdk.NewInt64Coin("token", 100)},
}},
},
- // "comment too long": {
- // req: &group.MsgCreateProposalRequest{
- // GroupAccount: accountAddr.String(),
- // Metadata: strings.Repeat("a", 256),
- // Proposers: []string{s.addr2.String()},
- // },
- // expErr: true,
- // },
+ "metadata too long": {
+ req: &group.MsgCreateProposalRequest{
+ GroupAccount: accountAddr.String(),
+ Metadata: bytes.Repeat([]byte{1}, 256),
+ Proposers: []string{s.addr2.String()},
+ },
+ expErr: true,
+ },
"group account required": {
req: &group.MsgCreateProposalRequest{
Metadata: nil,
diff --git a/x/group/types.go b/x/group/types.go
index 8ae26b3818..64ac5e54cf 100644
--- a/x/group/types.go
+++ b/x/group/types.go
@@ -268,6 +268,16 @@ func (v Vote) ValidateBasic() error {
return nil
}
+// ChoiceFromString returns a Choice from a string. It returns an error
+// if the string is invalid.
+func ChoiceFromString(str string) (Choice, error) {
+ choice, ok := Choice_value[str]
+ if !ok {
+ return Choice_CHOICE_UNSPECIFIED, fmt.Errorf("'%s' is not a valid vote choice", str)
+ }
+ return Choice(choice), nil
+}
+
// MaxMetadataLength defines the max length of the metadata bytes field
// for various entities within the group module
// TODO: This could be used as params once x/params is upgraded to use protobuf
@@ -464,3 +474,27 @@ func (t Tally) ValidateBasic() error {
}
return nil
}
+
+// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces
+func (q QueryGroupAccountsByGroupResponse) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
+ for _, g := range q.GroupAccounts {
+ err := g.UnpackInterfaces(unpacker)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces
+func (q QueryGroupAccountsByAdminResponse) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
+ for _, g := range q.GroupAccounts {
+ err := g.UnpackInterfaces(unpacker)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}