diff --git a/go.mod b/go.mod index 95d155db91..e47232b944 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/cosmos/interchain-security/v3 go 1.21 -toolchain go1.21.3 +toolchain go1.21.1 require ( cosmossdk.io/errors v1.0.0 diff --git a/proto/interchain_security/ccv/provider/v1/tx.proto b/proto/interchain_security/ccv/provider/v1/tx.proto index 7109cf2e67..69ccee0a73 100644 --- a/proto/interchain_security/ccv/provider/v1/tx.proto +++ b/proto/interchain_security/ccv/provider/v1/tx.proto @@ -160,4 +160,4 @@ message MsgChangeRewardDenoms { } // MsgChangeRewardDenomsResponse defines response type for MsgChangeRewardDenoms messages -message MsgChangeRewardDenomsResponse {} \ No newline at end of file +message MsgChangeRewardDenomsResponse {} diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index 74ae6dd635..8c4efb3af6 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -2,17 +2,52 @@ package cli import ( "fmt" - - "github.com/spf13/cobra" + "os" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - + "github.com/cosmos/cosmos-sdk/types/address" + govcli "github.com/cosmos/cosmos-sdk/x/gov/client/cli" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" + "github.com/spf13/cobra" ) +const FlagAuthority = "authority" + +// Get authority address from command line arguments or +// governance address as default if not provided on cli +func getAuthority(cmd *cobra.Command) (string, error) { + authority, _ := cmd.Flags().GetString(FlagAuthority) + if authority != "" { + if _, err := sdk.AccAddressFromBech32(authority); err != nil { + return "", fmt.Errorf("invalid authority address: %w", err) + } + } else { + authority = sdk.AccAddress(address.Module(govtypes.ModuleName)).String() + } + return authority, nil +} + +// Unmarshall json content of a file to given message type +func createMessageFromFile(ctx client.Context, cmd *cobra.Command, msg sdk.Msg, filePath string) error { + content, err := os.ReadFile(filePath) + if err != nil { + return fmt.Errorf("error reading file: %w", err) + } + + cdc := codec.NewProtoCodec(ctx.InterfaceRegistry) + + err = cdc.UnmarshalJSON(content, msg) + if err != nil { + return fmt.Errorf("invalid json content: %w", err) + } + return nil +} + // GetTxCmd returns the transaction commands for this module func GetTxCmd() *cobra.Command { cmd := &cobra.Command{ @@ -23,7 +58,11 @@ func GetTxCmd() *cobra.Command { RunE: client.ValidateCmd, } - cmd.AddCommand(NewAssignConsumerKeyCmd()) + cmd.AddCommand( + NewAssignConsumerKeyCmd(), + NewConsumerAdditionProposalCmd(), + NewConsumerRemovalProposalCmd(), + NewChangeRewardDenomsCmd()) return cmd } @@ -66,3 +105,218 @@ func NewAssignConsumerKeyCmd() *cobra.Command { return cmd } + +// NewConsumerAdditionProposalCmd creates a CLI command to submit a ConsumerAdditon proposal. +func NewConsumerAdditionProposalCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "consumer-addition [proposal-file] [flags]", + Args: cobra.ExactArgs(1), + Short: "submit a consumer addition proposal", + Long: ` + Submit a consumer addition proposal along with an initial deposit. + The proposal details must be supplied via a JSON file. + + Example: + $ tx provider consumer-addition --from= --title= --summary=<summary> + + Where proposal.json contains: + + { + "chain_id": "consumer", + "initial_height": { + "revision_number": "1", + "revision_height": "1" + }, + "genesis_hash": "Z2VuX2hhc2g=", + "binary_hash": "YmluX2hhc2g=", + "spawn_time": "2022-06-25T09:02:14.718477-08:00", + "unbonding_period": "86400s", + "ccv_timeout_period": "259200s", + "transfer_timeout_period": "1800s", + "consumer_redistribution_fraction": "0.75", + "blocks_per_distribution_transmission": "1000", + "historical_entries": "10000", + "distribution_transmission_channel": "" + }`, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + submitProposal, err := govcli.ReadGovPropFlags(clientCtx, cmd.Flags()) + if err != nil { + return err + } + + authority, err := getAuthority(cmd) + if err != nil { + return err + } + + msgFileName := args[0] + var msg types.MsgConsumerAddition + err = createMessageFromFile(clientCtx, cmd, &msg, msgFileName) + if err != nil { + return err + } + + msg.Signer = authority + + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("error validating %T: %w", types.MsgConsumerAddition{}, err) + } + + if err := submitProposal.SetMsgs([]sdk.Msg{&msg}); err != nil { + return fmt.Errorf("failed to create consumer addition proposal message: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), submitProposal) + }, + } + + cmd.Flags().String(FlagAuthority, "", "The address of the client module authority (defaults to gov)") + + flags.AddTxFlagsToCmd(cmd) + govcli.AddGovPropFlagsToCmd(cmd) + err := cmd.MarkFlagRequired(govcli.FlagTitle) + if err != nil { + panic(err) + } + + return cmd +} + +// NewConsumerRemovalProposalCmd creates a CLI command to submit a ConsumerRemoval proposal. +func NewConsumerRemovalProposalCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "consumer-removal [proposal-file] [flags]", + Args: cobra.ExactArgs(1), + Short: "submit a consumer removal proposal", + Long: ` + Submit a consumer removal proposal along with an initial deposit. + The proposal details must be supplied via a JSON file. + + Example: + $ <appd> tx gov submit-legacy-proposal consumer-removal <path/to/proposal.json> --from=<key_or_address> --title=<title> --summary=<summary> + + Where proposal.json contains: + + { + "chain_id": "foochain", + "stop_time": "2022-01-27T15:59:50.121607-08:00", + }`, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + submitProposal, err := govcli.ReadGovPropFlags(clientCtx, cmd.Flags()) + if err != nil { + return err + } + + msgFileName := args[0] + var msg types.MsgConsumerRemoval + err = createMessageFromFile(clientCtx, cmd, &msg, msgFileName) + if err != nil { + return err + } + + authority, err := getAuthority(cmd) + if err != nil { + return err + } + msg.Signer = authority + + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("error validating %T: %w", types.MsgConsumerRemoval{}, err) + } + + if err := submitProposal.SetMsgs([]sdk.Msg{&msg}); err != nil { + return fmt.Errorf("failed to create consumer addition proposal message: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), submitProposal) + }, + } + + cmd.Flags().String(FlagAuthority, "", "The address of the client module authority (defaults to gov)") + + flags.AddTxFlagsToCmd(cmd) + govcli.AddGovPropFlagsToCmd(cmd) + err := cmd.MarkFlagRequired(govcli.FlagTitle) + if err != nil { + panic(err) + } + + return cmd +} + +// NewChangeRewardDenomsCmd creates a CLI command to submit a ChangeRewardDenoms proposal. +func NewChangeRewardDenomsCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "change-reward-denoms [proposal-file] [flags]", + Args: cobra.ExactArgs(1), + Short: "Submit a change reward denoms proposal", + Long: `Submit an change reward denoms proposal with an initial deposit. + The proposal details must be supplied via a JSON file. + + Example: + $ <appd> tx gov submit-legacy-proposal change-reward-denoms <path/to/proposal.json> --from=<key_or_address> + + Where proposal.json contains: + { + "denoms_to_add": ["untrn"], + "denoms_to_remove": ["stake"], + "deposit": "10000stake" + } + `, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + submitProposal, err := govcli.ReadGovPropFlags(clientCtx, cmd.Flags()) + if err != nil { + return err + } + + msgFileName := args[0] + var msg types.MsgChangeRewardDenoms + err = createMessageFromFile(clientCtx, cmd, &msg, msgFileName) + if err != nil { + return err + } + + authority, err := getAuthority(cmd) + if err != nil { + return err + } + msg.Signer = authority + + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("error validating %T: %w", types.MsgConsumerRemoval{}, err) + } + + if err := submitProposal.SetMsgs([]sdk.Msg{&msg}); err != nil { + return fmt.Errorf("failed to create consumer addition proposal message: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), submitProposal) + }, + } + + cmd.Flags().String(FlagAuthority, "", "The address of the client module authority (defaults to gov)") + + flags.AddTxFlagsToCmd(cmd) + govcli.AddGovPropFlagsToCmd(cmd) + err := cmd.MarkFlagRequired(govcli.FlagTitle) + if err != nil { + panic(err) + } + + return cmd +} diff --git a/x/ccv/provider/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index c59705cf22..9b97305c0d 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -24,7 +24,7 @@ import ( ccv "github.com/cosmos/interchain-security/v3/x/ccv/types" ) -// Wrapper for the new proposal messages MsgConsumerAddition. +// Wrapper for the new proposal message MsgConsumerAddition // Will replace legacy handler HandleConsumerAdditionProposal func (k Keeper) HandleNewConsumerAdditionProposal(ctx sdk.Context, proposal *types.MsgConsumerAddition) error { p := types.ConsumerAdditionProposal{ @@ -46,7 +46,7 @@ func (k Keeper) HandleNewConsumerAdditionProposal(ctx sdk.Context, proposal *typ } -// Wrapper for the new proposal messages MsgConsumerRemoval. +// Wrapper for the new proposal message MsgConsumerRemoval // Will replace legacy handler HandleConsumerRemovalProposal func (k Keeper) HandleNewConsumerRemovalProposal(ctx sdk.Context, proposal *types.MsgConsumerRemoval) error { p := types.ConsumerRemovalProposal{ @@ -57,7 +57,7 @@ func (k Keeper) HandleNewConsumerRemovalProposal(ctx sdk.Context, proposal *type } -// Wrapper for the new proposal messages MsgChangeRewardDenoms. +// Wrapper for the new proposal message MsgChangeRewardDenoms // Will replace legacy handler HandleConsumerRewardDenomProposal func (k Keeper) HandleNewConsumerRewardDenomProposal(ctx sdk.Context, proposal *types.MsgChangeRewardDenoms) error { p := types.ChangeRewardDenomsProposal{