diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a44d372cb75..6b01825915d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -167,6 +167,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### CLI Breaking Changes +* (x/auth/vesting) [#18100](https://github.com/cosmos/cosmos-sdk/pull/18100) `appd tx vesting create-vesting-account` takes an amount of coin as last argument instead of second. Coins are space separated. * (x/distribution) [#17963](https://github.com/cosmos/cosmos-sdk/pull/17963) `appd tx distribution withdraw-rewards` now only withdraws rewards for the delegator's own delegations. For withdrawing validators commission, use `appd tx distribution withdraw-validator-commission`. ### State Machine Breaking diff --git a/x/auth/client/cli/broadcast.go b/x/auth/client/cli/broadcast.go index ef5eefc410b2..3febeb486f4e 100644 --- a/x/auth/client/cli/broadcast.go +++ b/x/auth/client/cli/broadcast.go @@ -2,12 +2,14 @@ package cli import ( "errors" + "fmt" "strings" "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/version" authclient "github.com/cosmos/cosmos-sdk/x/auth/client" ) @@ -19,11 +21,9 @@ func GetBroadcastCommand() *cobra.Command { Long: strings.TrimSpace(`Broadcast transactions created with the --generate-only flag and signed with the sign command. Read a transaction from [file_path] and broadcast it to a node. If you supply a dash (-) argument in place of an input -filename, the command reads from standard input. - -$ tx broadcast ./mytxn.json -`), - Args: cobra.ExactArgs(1), +filename, the command reads from standard input.`), + Example: fmt.Sprintf("%s tx broadcast ", version.AppName), + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { diff --git a/x/auth/module.go b/x/auth/module.go index b19deb714797..5e5fb00208b6 100644 --- a/x/auth/module.go +++ b/x/auth/module.go @@ -6,6 +6,7 @@ import ( "fmt" gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" modulev1 "cosmossdk.io/api/cosmos/auth/module/v1" "cosmossdk.io/core/address" @@ -22,6 +23,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth/keeper" "github.com/cosmos/cosmos-sdk/x/auth/simulation" "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/auth/vesting/client/cli" ) // ConsensusVersion defines the current x/auth module consensus version. @@ -82,6 +84,11 @@ func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) types.RegisterInterfaces(registry) } +// GetTxCmd returns the root tx command for the auth module. +func (ab AppModuleBasic) GetTxCmd() *cobra.Command { + return cli.GetTxCmd() +} + // AppModule implements an application module for the auth module. type AppModule struct { AppModuleBasic diff --git a/x/auth/vesting/autocli.go b/x/auth/vesting/autocli.go new file mode 100644 index 000000000000..29cfd451442b --- /dev/null +++ b/x/auth/vesting/autocli.go @@ -0,0 +1,48 @@ +package vesting + +import ( + autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" + vestingv1beta1 "cosmossdk.io/api/cosmos/vesting/v1beta1" +) + +// AutoCLIOptions implements the autocli.HasAutoCLIConfig interface. +func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions { + return &autocliv1.ModuleOptions{ + Tx: &autocliv1.ServiceCommandDescriptor{ + Service: vestingv1beta1.Msg_ServiceDesc.ServiceName, + RpcCommandOptions: []*autocliv1.RpcCommandOptions{ + { + RpcMethod: "CreateVestingAccount", + Use: "create-vesting-account [to_address] [end_time] [amount]", + Short: "Create a new vesting account funded with an allocation of tokens.", + Long: `Create a new vesting account funded with an allocation of tokens. The +account can either be a delayed or continuous vesting account, which is determined +by the '--delayed' flag. All vesting accounts created will have their start time +set by the committed block's time. The end_time must be provided as a UNIX epoch +timestamp.`, + PositionalArgs: []*autocliv1.PositionalArgDescriptor{ + {ProtoField: "to_address"}, + {ProtoField: "end_time"}, + {ProtoField: "amount", Varargs: true}, + }, + FlagOptions: map[string]*autocliv1.FlagOptions{ + "delayed": {Name: "delayed", Usage: "Create a delayed vesting account if true"}, + }, + }, + { + RpcMethod: "CreatePermanentLockedAccount", + Use: "create-permanent-locked-account [to_address] [amount]", + Short: "Create a new permanently locked account funded with an allocation of tokens.", + Long: `Create a new account funded with an allocation of permanently locked tokens. These + tokens may be used for staking but are non-transferable. Staking rewards will acrue as liquid and transferable + tokens.`, + PositionalArgs: []*autocliv1.PositionalArgDescriptor{ + {ProtoField: "to_address"}, + {ProtoField: "amount", Varargs: true}, + }, + }, + }, + EnhanceCustomCommand: true, + }, + } +} diff --git a/x/auth/vesting/client/cli/test.json b/x/auth/vesting/client/cli/periods.json similarity index 100% rename from x/auth/vesting/client/cli/test.json rename to x/auth/vesting/client/cli/periods.json diff --git a/x/auth/vesting/client/cli/tx.go b/x/auth/vesting/client/cli/tx.go index 06c8ce58296b..fa8927cf6929 100644 --- a/x/auth/vesting/client/cli/tx.go +++ b/x/auth/vesting/client/cli/tx.go @@ -2,10 +2,8 @@ package cli import ( "encoding/json" - "errors" "fmt" "os" - "strconv" "github.com/spf13/cobra" @@ -16,11 +14,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" ) -// Transaction command flags -const ( - FlagDelayed = "delayed" -) - // GetTxCmd returns vesting module's transaction commands. func GetTxCmd() *cobra.Command { txCmd := &cobra.Command{ @@ -32,102 +25,12 @@ func GetTxCmd() *cobra.Command { } txCmd.AddCommand( - NewMsgCreateVestingAccountCmd(), - NewMsgCreatePermanentLockedAccountCmd(), NewMsgCreatePeriodicVestingAccountCmd(), ) return txCmd } -// NewMsgCreateVestingAccountCmd returns a CLI command handler for creating a -// MsgCreateVestingAccount transaction. -func NewMsgCreateVestingAccountCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "create-vesting-account [to_address] [amount] [end_time]", - Short: "Create a new vesting account funded with an allocation of tokens.", - Long: `Create a new vesting account funded with an allocation of tokens. The -account can either be a delayed or continuous vesting account, which is determined -by the '--delayed' flag. All vesting accounts created will have their start time -set by the committed block's time. The end_time must be provided as a UNIX epoch -timestamp.`, - Args: cobra.ExactArgs(3), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - toAddr, err := clientCtx.AddressCodec.StringToBytes(args[0]) - if err != nil { - return err - } - - if args[1] == "" { - return errors.New("amount is empty") - } - - amount, err := sdk.ParseCoinsNormalized(args[1]) - if err != nil { - return err - } - - endTime, err := strconv.ParseInt(args[2], 10, 64) - if err != nil { - return err - } - - delayed, _ := cmd.Flags().GetBool(FlagDelayed) - - msg := types.NewMsgCreateVestingAccount(clientCtx.GetFromAddress(), toAddr, amount, endTime, delayed) - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) - }, - } - - cmd.Flags().Bool(FlagDelayed, false, "Create a delayed vesting account if true") - flags.AddTxFlagsToCmd(cmd) - - return cmd -} - -// NewMsgCreatePermanentLockedAccountCmd returns a CLI command handler for creating a -// MsgCreatePermanentLockedAccount transaction. -func NewMsgCreatePermanentLockedAccountCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "create-permanent-locked-account [to_address] [amount]", - Short: "Create a new permanently locked account funded with an allocation of tokens.", - Long: `Create a new account funded with an allocation of permanently locked tokens. These -tokens may be used for staking but are non-transferable. Staking rewards will acrue as liquid and transferable -tokens.`, - Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - toAddr, err := clientCtx.AddressCodec.StringToBytes(args[0]) - if err != nil { - return err - } - - if args[1] == "" { - return errors.New("amount is empty") - } - - amount, err := sdk.ParseCoinsNormalized(args[1]) - if err != nil { - return err - } - - msg := types.NewMsgCreatePermanentLockedAccount(clientCtx.GetFromAddress(), toAddr, amount) - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) - }, - } - - flags.AddTxFlagsToCmd(cmd) - - return cmd -} - type VestingData struct { StartTime int64 `json:"start_time"` Periods []InputPeriod `json:"periods"` @@ -138,28 +41,27 @@ type InputPeriod struct { Length int64 `json:"length_seconds"` } -// NewMsgCreatePeriodicVestingAccountCmd returns a CLI command handler for creating a -// MsgCreatePeriodicVestingAccountCmd transaction. +// NewMsgCreatePeriodicVestingAccountCmd returns a CLI command handler for creating a MsgCreatePeriodicVestingAccountCmd transaction. +// This command can be migrated to AutoCLI but it would be CLI breaking to do so. func NewMsgCreatePeriodicVestingAccountCmd() *cobra.Command { cmd := &cobra.Command{ Use: "create-periodic-vesting-account [to_address] [periods_json_file]", Short: "Create a new vesting account funded with an allocation of tokens.", Long: `A sequence of coins and period length in seconds. Periods are sequential, in that the duration of of a period only starts at the end of the previous period. The duration of the first period starts upon account creation. For instance, the following periods.json file shows 20 "test" coins vesting 30 days apart from each other. - Where periods.json contains: - - An array of coin strings and unix epoch times for coins to vest -{ "start_time": 1625204910, -"periods":[ - { - "coins": "10test", - "length_seconds":2592000 //30 days - }, - { - "coins": "10test", - "length_seconds":2592000 //30 days - }, -] - } +Where periods.json contains an array of coin strings and unix epoch times for coins to vest: +{ + "start_time": 1625204910, + "periods": [ + { + "coins": "10test", + "length_seconds": 2592000 //30 days + }, + { + "coins": "10test", + "length_seconds": 2592000 //30 days + } + ] +} `, Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/x/auth/vesting/client/cli/tx_test.go b/x/auth/vesting/client/cli/tx_test.go index 8f0716e7e332..ced28393e8cd 100644 --- a/x/auth/vesting/client/cli/tx_test.go +++ b/x/auth/vesting/client/cli/tx_test.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "testing" - "time" rpcclientmock "github.com/cometbft/cometbft/rpc/client/mock" "github.com/stretchr/testify/suite" @@ -52,174 +51,6 @@ func (s *CLITestSuite) SetupSuite() { WithConsensusAddressCodec(addresscodec.NewBech32Codec("cosmosvalcons")) } -func (s *CLITestSuite) TestNewMsgCreateVestingAccountCmd() { - accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 1) - cmd := cli.NewMsgCreateVestingAccountCmd() - cmd.SetOutput(io.Discard) - - extraArgs := []string{ - fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), - fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin("photon", sdkmath.NewInt(10))).String()), - fmt.Sprintf("--%s=test-chain", flags.FlagChainID), - fmt.Sprintf("--%s=%s", flags.FlagFrom, accounts[0].Address), - } - - t := time.Date(2033, time.April, 1, 12, 34, 56, 789, time.UTC).Unix() - - testCases := []struct { - name string - ctxGen func() client.Context - from, to sdk.AccAddress - amount sdk.Coins - endTime int64 - extraArgs []string - expectErr bool - }{ - { - "valid transaction", - func() client.Context { - return s.baseCtx - }, - accounts[0].Address, - accounts[0].Address, - sdk.NewCoins( - sdk.NewCoin("stake", sdkmath.NewInt(10)), - sdk.NewCoin("photon", sdkmath.NewInt(40)), - ), - t, - extraArgs, - false, - }, - { - "invalid to Address", - func() client.Context { - return s.baseCtx - }, - accounts[0].Address, - sdk.AccAddress{}, - sdk.NewCoins( - sdk.NewCoin("stake", sdkmath.NewInt(10)), - sdk.NewCoin("photon", sdkmath.NewInt(40)), - ), - t, - extraArgs, - true, - }, - { - "invalid coins", - func() client.Context { - return s.baseCtx - }, - accounts[0].Address, - accounts[0].Address, - nil, - t, - extraArgs, - true, - }, - } - - for _, tc := range testCases { - tc := tc - s.Run(tc.name, func() { - ctx := svrcmd.CreateExecuteContext(context.Background()) - - cmd.SetContext(ctx) - fmt.Println(tc.amount.String()) - cmd.SetArgs(append([]string{tc.to.String(), tc.amount.String(), fmt.Sprint(tc.endTime)}, tc.extraArgs...)) - - s.Require().NoError(client.SetCmdClientContextHandler(tc.ctxGen(), cmd)) - - err := cmd.Execute() - if tc.expectErr { - s.Require().Error(err) - } else { - s.Require().NoError(err) - } - }) - } -} - -func (s *CLITestSuite) TestNewMsgCreatePermanentLockedAccountCmd() { - accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 1) - cmd := cli.NewMsgCreatePermanentLockedAccountCmd() - cmd.SetOutput(io.Discard) - - extraArgs := []string{ - fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), - fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin("photon", sdkmath.NewInt(10))).String()), - fmt.Sprintf("--%s=test-chain", flags.FlagChainID), - fmt.Sprintf("--%s=%s", flags.FlagFrom, accounts[0].Address), - } - - testCases := []struct { - name string - ctxGen func() client.Context - to sdk.AccAddress - amount sdk.Coins - extraArgs []string - expectErr bool - }{ - { - "valid transaction", - func() client.Context { - return s.baseCtx - }, - accounts[0].Address, - sdk.NewCoins( - sdk.NewCoin("stake", sdkmath.NewInt(10)), - sdk.NewCoin("photon", sdkmath.NewInt(40)), - ), - extraArgs, - false, - }, - { - "invalid to Address", - func() client.Context { - return s.baseCtx - }, - sdk.AccAddress{}, - sdk.NewCoins( - sdk.NewCoin("stake", sdkmath.NewInt(10)), - sdk.NewCoin("photon", sdkmath.NewInt(40)), - ), - extraArgs, - true, - }, - { - "invalid coins", - func() client.Context { - return s.baseCtx - }, - accounts[0].Address, - nil, - extraArgs, - true, - }, - } - - for _, tc := range testCases { - tc := tc - s.Run(tc.name, func() { - ctx := svrcmd.CreateExecuteContext(context.Background()) - - cmd.SetContext(ctx) - cmd.SetArgs(append([]string{tc.to.String(), tc.amount.String()}, tc.extraArgs...)) - - s.Require().NoError(client.SetCmdClientContextHandler(tc.ctxGen(), cmd)) - - err := cmd.Execute() - if tc.expectErr { - s.Require().Error(err) - } else { - s.Require().NoError(err) - } - }) - } -} - func (s *CLITestSuite) TestNewMsgCreatePeriodicVestingAccountCmd() { accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 1) cmd := cli.NewMsgCreatePeriodicVestingAccountCmd() @@ -234,29 +65,22 @@ func (s *CLITestSuite) TestNewMsgCreatePeriodicVestingAccountCmd() { } testCases := []struct { - name string - ctxGen func() client.Context - to sdk.AccAddress - extraArgs []string - expectErr bool + name string + to sdk.AccAddress + extraArgs []string + expectErrMsg string }{ { "valid transaction", - func() client.Context { - return s.baseCtx - }, accounts[0].Address, extraArgs, - false, + "", }, { - "invalid to Address", - func() client.Context { - return s.baseCtx - }, + "invalid to address", sdk.AccAddress{}, extraArgs, - true, + "empty address string is not allowed", }, } @@ -266,13 +90,13 @@ func (s *CLITestSuite) TestNewMsgCreatePeriodicVestingAccountCmd() { ctx := svrcmd.CreateExecuteContext(context.Background()) cmd.SetContext(ctx) - cmd.SetArgs(append([]string{tc.to.String(), "./test.json"}, tc.extraArgs...)) + cmd.SetArgs(append([]string{tc.to.String(), "./periods.json"}, tc.extraArgs...)) - s.Require().NoError(client.SetCmdClientContextHandler(tc.ctxGen(), cmd)) + s.Require().NoError(client.SetCmdClientContextHandler(s.baseCtx, cmd)) err := cmd.Execute() - if tc.expectErr { - s.Require().Error(err) + if tc.expectErrMsg != "" { + s.Require().ErrorContains(err, "empty address string is not allowed") } else { s.Require().NoError(err) } diff --git a/x/auth/vesting/module.go b/x/auth/vesting/module.go index 48c7bc19eddb..3408a36dbd91 100644 --- a/x/auth/vesting/module.go +++ b/x/auth/vesting/module.go @@ -1,11 +1,10 @@ package vesting import ( + "context" "encoding/json" - abci "github.com/cometbft/cometbft/abci/types" gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" - "github.com/spf13/cobra" "google.golang.org/grpc" modulev1 "cosmossdk.io/api/cosmos/vesting/module/v1" @@ -15,15 +14,14 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/x/auth/keeper" - "github.com/cosmos/cosmos-sdk/x/auth/vesting/client/cli" "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" ) var ( _ module.AppModuleBasic = AppModule{} + _ module.HasGenesis = AppModule{} _ appmodule.AppModule = AppModule{} _ appmodule.HasServices = AppModule{} @@ -64,11 +62,6 @@ func (AppModuleBasic) ValidateGenesis(_ codec.JSONCodec, _ client.TxEncodingConf // is a no-op. func (AppModuleBasic) RegisterGRPCGatewayRoutes(_ client.Context, _ *gwruntime.ServeMux) {} -// GetTxCmd returns the root tx command for the auth module. -func (ab AppModuleBasic) GetTxCmd() *cobra.Command { - return cli.GetTxCmd() -} - // AppModule extends the AppModuleBasic implementation by implementing the // AppModule interface. type AppModule struct { @@ -99,12 +92,10 @@ func (am AppModule) RegisterServices(registrar grpc.ServiceRegistrar) error { } // InitGenesis performs a no-op. -func (am AppModule) InitGenesis(_ sdk.Context, _ codec.JSONCodec, _ json.RawMessage) []abci.ValidatorUpdate { - return []abci.ValidatorUpdate{} -} +func (am AppModule) InitGenesis(_ context.Context, _ codec.JSONCodec, _ json.RawMessage) {} // ExportGenesis is always empty, as InitGenesis does nothing either. -func (am AppModule) ExportGenesis(_ sdk.Context, cdc codec.JSONCodec) json.RawMessage { +func (am AppModule) ExportGenesis(_ context.Context, cdc codec.JSONCodec) json.RawMessage { return am.DefaultGenesis(cdc) }