diff --git a/CHANGELOG.md b/CHANGELOG.md index d81a0a91fc0c..d9da4394faa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements +* (x/staking) [#14864](https://github.com/cosmos/cosmos-sdk/pull/14864) `create-validator` CLI command now takes a json file as an arg instead of having a bunch of required flags to it. * (cli) [#14659](https://github.com/cosmos/cosmos-sdk/pull/14659) Added ability to query blocks by either height/hash `simd q block --type=height|hash `. * (store) [#14410](https://github.com/cosmos/cosmos-sdk/pull/14410) `rootmulti.Store.loadVersion` has validation to check if all the module stores' height is correct, it will error if any module store has incorrect height. * (x/evidence) [#14757](https://github.com/cosmos/cosmos-sdk/pull/14757) Evidence messages do not need to implement a `.Type()` anymore. @@ -263,6 +264,7 @@ extension interfaces. `module.Manager.Modules` is now of type `map[string]interf ### CLI Breaking Changes +* (x/staking) [#14864](https://github.com/cosmos/cosmos-sdk/pull/14864) `create-validator` CLI command now takes a json file as an arg instead of having a bunch of required flags to it. * (cli) [#14659](https://github.com/cosmos/cosmos-sdk/pull/14659) `simd q block ` is removed as it just output json. The new command allows either height/hash and is `simd q block --type=height|hash `. * (x/gov) [#14880](https://github.com/cosmos/cosmos-sdk/pull/14880) Remove `simd tx gov submit-legacy-proposal cancel-software-upgrade` and `software-upgrade` commands. These commands are now in the `x/upgrade` module and using gov v1. Use `tx upgrade software-upgrade` instead. * (grpc-web) [#14652](https://github.com/cosmos/cosmos-sdk/pull/14652) Remove `grpc-web.address` flag. diff --git a/tests/e2e/staking/suite.go b/tests/e2e/staking/suite.go index 94449fa1babe..ee291910b81c 100644 --- a/tests/e2e/staking/suite.go +++ b/tests/e2e/staking/suite.go @@ -16,7 +16,6 @@ import ( "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/crypto/keys/ed25519" "github.com/cosmos/cosmos-sdk/testutil" clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" "github.com/cosmos/cosmos-sdk/testutil/network" @@ -97,11 +96,6 @@ func (s *E2ETestSuite) TestNewCreateValidatorCmd() { require := s.Require() val := s.network.Validators[0] - consPrivKey := ed25519.GenPrivKey() - consPubKeyBz, err := s.cfg.Codec.MarshalInterfaceJSON(consPrivKey.PubKey()) - require.NoError(err) - require.NotNil(consPubKeyBz) - k, _, err := val.ClientCtx.Keyring.NewMnemonic("NewValidator", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) require.NoError(err) @@ -120,6 +114,71 @@ func (s *E2ETestSuite) TestNewCreateValidatorCmd() { require.NoError(err) s.Require().NoError(s.network.WaitForNextBlock()) + validJSON := fmt.Sprintf(` + { + "pubkey": {"@type":"/cosmos.crypto.ed25519.PubKey","key":"oWg2ISpLF405Jcm2vXV+2v4fnjodh6aafuIdeoW+rUw="}, + "amount": "%dstake", + "moniker": "NewValidator", + "commission-rate": "0.5", + "commission-max-rate": "1.0", + "commission-max-change-rate": "0.1", + "min-self-delegation": "1" + }`, 100) + validJSONFile := testutil.WriteToNewTempFile(s.T(), validJSON) + defer func() { + if err := validJSONFile.Close(); err != nil { + val.Ctx.Logger.Info("Error closing file: %s\n", err) + } + }() + + noAmountJSON := ` + { + "pubkey": {"@type":"/cosmos.crypto.ed25519.PubKey","key":"oWg2ISpLF405Jcm2vXV+2v4fnjodh6aafuIdeoW+rUw="}, + "moniker": "NewValidator", + "commission-rate": "0.5", + "commission-max-rate": "1.0", + "commission-max-change-rate": "0.1", + "min-self-delegation": "1" + }` + noAmountJSONFile := testutil.WriteToNewTempFile(s.T(), noAmountJSON) + defer func() { + if err := noAmountJSONFile.Close(); err != nil { + val.Ctx.Logger.Info("Error closing file: %s\n", err) + } + }() + + noPubKeyJSON := fmt.Sprintf(` + { + "amount": "%dstake", + "moniker": "NewValidator", + "commission-rate": "0.5", + "commission-max-rate": "1.0", + "commission-max-change-rate": "0.1", + "min-self-delegation": "1" + }`, 100) + noPubKeyJSONFile := testutil.WriteToNewTempFile(s.T(), noPubKeyJSON) + defer func() { + if err := noPubKeyJSONFile.Close(); err != nil { + val.Ctx.Logger.Info("Error closing file: %s\n", err) + } + }() + + noMonikerJSON := fmt.Sprintf(` + { + "pubkey": {"@type":"/cosmos.crypto.ed25519.PubKey","key":"oWg2ISpLF405Jcm2vXV+2v4fnjodh6aafuIdeoW+rUw="}, + "amount": "%dstake", + "commission-rate": "0.5", + "commission-max-rate": "1.0", + "commission-max-change-rate": "0.1", + "min-self-delegation": "1" + }`, 100) + noMonikerJSONFile := testutil.WriteToNewTempFile(s.T(), noMonikerJSON) + defer func() { + if err := noMonikerJSONFile.Close(); err != nil { + val.Ctx.Logger.Info("Error closing file: %s\n", err) + } + }() + testCases := []struct { name string args []string @@ -130,14 +189,7 @@ func (s *E2ETestSuite) TestNewCreateValidatorCmd() { { "invalid transaction (missing amount)", []string{ - fmt.Sprintf("--%s=AFAF00C4", cli.FlagIdentity), - fmt.Sprintf("--%s=https://newvalidator.io", cli.FlagWebsite), - fmt.Sprintf("--%s=contact@newvalidator.io", cli.FlagSecurityContact), - fmt.Sprintf("--%s='Hey, I am a new validator. Please delegate!'", cli.FlagDetails), - fmt.Sprintf("--%s=0.5", cli.FlagCommissionRate), - fmt.Sprintf("--%s=1.0", cli.FlagCommissionMaxRate), - fmt.Sprintf("--%s=0.1", cli.FlagCommissionMaxChangeRate), - fmt.Sprintf("--%s=1", cli.FlagMinSelfDelegation), + noAmountJSONFile.Name(), fmt.Sprintf("--%s=%s", flags.FlagFrom, newAddr), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), @@ -148,15 +200,7 @@ func (s *E2ETestSuite) TestNewCreateValidatorCmd() { { "invalid transaction (missing pubkey)", []string{ - fmt.Sprintf("--%s=%dstake", cli.FlagAmount, 100), - fmt.Sprintf("--%s=AFAF00C4", cli.FlagIdentity), - fmt.Sprintf("--%s=https://newvalidator.io", cli.FlagWebsite), - fmt.Sprintf("--%s=contact@newvalidator.io", cli.FlagSecurityContact), - fmt.Sprintf("--%s='Hey, I am a new validator. Please delegate!'", cli.FlagDetails), - fmt.Sprintf("--%s=0.5", cli.FlagCommissionRate), - fmt.Sprintf("--%s=1.0", cli.FlagCommissionMaxRate), - fmt.Sprintf("--%s=0.1", cli.FlagCommissionMaxChangeRate), - fmt.Sprintf("--%s=1", cli.FlagMinSelfDelegation), + noPubKeyJSONFile.Name(), fmt.Sprintf("--%s=%s", flags.FlagFrom, newAddr), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), @@ -167,16 +211,7 @@ func (s *E2ETestSuite) TestNewCreateValidatorCmd() { { "invalid transaction (missing moniker)", []string{ - fmt.Sprintf("--%s=%s", cli.FlagPubKey, consPubKeyBz), - fmt.Sprintf("--%s=%dstake", cli.FlagAmount, 100), - fmt.Sprintf("--%s=AFAF00C4", cli.FlagIdentity), - fmt.Sprintf("--%s=https://newvalidator.io", cli.FlagWebsite), - fmt.Sprintf("--%s=contact@newvalidator.io", cli.FlagSecurityContact), - fmt.Sprintf("--%s='Hey, I am a new validator. Please delegate!'", cli.FlagDetails), - fmt.Sprintf("--%s=0.5", cli.FlagCommissionRate), - fmt.Sprintf("--%s=1.0", cli.FlagCommissionMaxRate), - fmt.Sprintf("--%s=0.1", cli.FlagCommissionMaxChangeRate), - fmt.Sprintf("--%s=1", cli.FlagMinSelfDelegation), + noMonikerJSONFile.Name(), fmt.Sprintf("--%s=%s", flags.FlagFrom, newAddr), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), @@ -187,17 +222,7 @@ func (s *E2ETestSuite) TestNewCreateValidatorCmd() { { "valid transaction", []string{ - fmt.Sprintf("--%s=%s", cli.FlagPubKey, consPubKeyBz), - fmt.Sprintf("--%s=%dstake", cli.FlagAmount, 100), - fmt.Sprintf("--%s=NewValidator", cli.FlagMoniker), - fmt.Sprintf("--%s=AFAF00C4", cli.FlagIdentity), - fmt.Sprintf("--%s=https://newvalidator.io", cli.FlagWebsite), - fmt.Sprintf("--%s=contact@newvalidator.io", cli.FlagSecurityContact), - fmt.Sprintf("--%s='Hey, I am a new validator. Please delegate!'", cli.FlagDetails), - fmt.Sprintf("--%s=0.5", cli.FlagCommissionRate), - fmt.Sprintf("--%s=1.0", cli.FlagCommissionMaxRate), - fmt.Sprintf("--%s=0.1", cli.FlagCommissionMaxChangeRate), - fmt.Sprintf("--%s=1", cli.FlagMinSelfDelegation), + validJSONFile.Name(), fmt.Sprintf("--%s=%s", flags.FlagFrom, newAddr), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), diff --git a/x/staking/README.md b/x/staking/README.md index b8511c8aef74..35bbb6bd389c 100644 --- a/x/staking/README.md +++ b/x/staking/README.md @@ -1565,29 +1565,39 @@ The command `create-validator` allows users to create new validator initialized Usage: ```bash -simd tx staking create-validator [flags] +simd tx staking create-validator [path/to/validator.json] [flags] ``` Example: ```bash -simd tx staking create-validator \ - --amount=1000000stake \ - --pubkey=$(simd tendermint show-validator) \ - --moniker="my-moniker" \ - --website="https://myweb.site" \ - --details="description of your validator" \ +simd tx staking create-validator /path/to/validator.json \ --chain-id="name_of_chain_id" \ - --commission-rate="0.10" \ - --commission-max-rate="0.20" \ - --commission-max-change-rate="0.01" \ - --min-self-delegation="1" \ --gas="auto" \ --gas-adjustment="1.2" \ --gas-prices="0.025stake" \ --from=mykey ``` +where `validator.json` contains: + +```json +{ + "pubkey": {"@type":"/cosmos.crypto.ed25519.PubKey","key":"BnbwFpeONLqvWqJb3qaUbL5aoIcW3fSuAp9nT3z5f20="}, + "amount": "1000000stake", + "moniker": "my-moniker", + "website": "https://myweb.site", + "security": "security-contact@gmail.com", + "details": "description of your validator", + "commission-rate": "0.10", + "commission-max-rate": "0.20", + "commission-max-change-rate": "0.01", + "min-self-delegation": "1" +} +``` + +and pubkey can be obtained by using `simd tendermint show-validator` command. + ##### delegate The command `delegate` allows users to delegate liquid tokens to a validator. diff --git a/x/staking/client/cli/flags.go b/x/staking/client/cli/flags.go index 2bdc93168b6a..d4388e5c13f8 100644 --- a/x/staking/client/cli/flags.go +++ b/x/staking/client/cli/flags.go @@ -100,15 +100,3 @@ func flagSetCommissionUpdate() *flag.FlagSet { return fs } - -func flagSetDescriptionCreate() *flag.FlagSet { - fs := flag.NewFlagSet("", flag.ContinueOnError) - - fs.String(FlagMoniker, "", "The validator's name") - fs.String(FlagIdentity, "", "The optional identity signature (ex. UPort or Keybase)") - fs.String(FlagWebsite, "", "The validator's (optional) website") - fs.String(FlagSecurityContact, "", "The validator's (optional) security contact email") - fs.String(FlagDetails, "", "The validator's (optional) details") - - return fs -} diff --git a/x/staking/client/cli/tx.go b/x/staking/client/cli/tx.go index 258a6c248f11..a88abb735080 100644 --- a/x/staking/client/cli/tx.go +++ b/x/staking/client/cli/tx.go @@ -58,17 +58,46 @@ func NewTxCmd() *cobra.Command { // NewCreateValidatorCmd returns a CLI command handler for creating a MsgCreateValidator transaction. func NewCreateValidatorCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "create-validator", + Use: "create-validator [path/to/validator.json]", Short: "create new validator initialized with a self-delegation to it", + Args: cobra.ExactArgs(1), + Long: `Create a new validator initialized with a self-delegation by submitting a JSON file with the new validator details.`, + Example: strings.TrimSpace( + fmt.Sprintf(` +$ %s tx staking create-validator path/to/validator.json --from keyname + +Where validator.json contains: + +{ + "pubkey": {"@type":"/cosmos.crypto.ed25519.PubKey","key":"oWg2ISpLF405Jcm2vXV+2v4fnjodh6aafuIdeoW+rUw="}, + "amount": "1000000stake", + "moniker": "myvalidator", + "identity": "optional identity signature (ex. UPort or Keybase)", + "website": "validator's (optional) website", + "security": "validator's (optional) security contact email", + "details": "validator's (optional) details", + "commission-rate": "0.1", + "commission-max-rate": "0.2", + "commission-max-change-rate": "0.01", + "min-self-delegation": "1" +} + +where we can get the pubkey using "%s tendermint show-validator" +`, version.AppName, version.AppName)), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } + validator, err := parseAndValidateValidatorJSON(clientCtx.Codec, args[0]) + if err != nil { + return err + } + txf := tx.NewFactoryCLI(clientCtx, cmd.Flags()). WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) - txf, msg, err := newBuildCreateValidatorMsg(clientCtx, txf, cmd.Flags()) + txf, msg, err := newBuildCreateValidatorMsg(clientCtx, txf, cmd.Flags(), validator) if err != nil { return err } @@ -77,20 +106,11 @@ func NewCreateValidatorCmd() *cobra.Command { }, } - cmd.Flags().AddFlagSet(FlagSetPublicKey()) - cmd.Flags().AddFlagSet(FlagSetAmount()) - cmd.Flags().AddFlagSet(flagSetDescriptionCreate()) - cmd.Flags().AddFlagSet(FlagSetCommissionCreate()) - cmd.Flags().AddFlagSet(FlagSetMinSelfDelegation()) - cmd.Flags().String(FlagIP, "", fmt.Sprintf("The node's public IP. It takes effect only when used in combination with --%s", flags.FlagGenerateOnly)) cmd.Flags().String(FlagNodeID, "", "The node's ID") flags.AddTxFlagsToCmd(cmd) _ = cmd.MarkFlagRequired(flags.FlagFrom) - _ = cmd.MarkFlagRequired(FlagAmount) - _ = cmd.MarkFlagRequired(FlagPubKey) - _ = cmd.MarkFlagRequired(FlagMoniker) return cmd } @@ -339,57 +359,19 @@ $ %s tx staking cancel-unbond %s1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj 100stake return cmd } -func newBuildCreateValidatorMsg(clientCtx client.Context, txf tx.Factory, fs *flag.FlagSet) (tx.Factory, *types.MsgCreateValidator, error) { - fAmount, _ := fs.GetString(FlagAmount) - amount, err := sdk.ParseCoinNormalized(fAmount) - if err != nil { - return txf, nil, err - } - +func newBuildCreateValidatorMsg(clientCtx client.Context, txf tx.Factory, fs *flag.FlagSet, val validator) (tx.Factory, *types.MsgCreateValidator, error) { valAddr := clientCtx.GetFromAddress() - pkStr, err := fs.GetString(FlagPubKey) - if err != nil { - return txf, nil, err - } - var pk cryptotypes.PubKey - if err := clientCtx.Codec.UnmarshalInterfaceJSON([]byte(pkStr), &pk); err != nil { - return txf, nil, err - } - - moniker, _ := fs.GetString(FlagMoniker) - identity, _ := fs.GetString(FlagIdentity) - website, _ := fs.GetString(FlagWebsite) - security, _ := fs.GetString(FlagSecurityContact) - details, _ := fs.GetString(FlagDetails) description := types.NewDescription( - moniker, - identity, - website, - security, - details, + val.Moniker, + val.Identity, + val.Website, + val.Security, + val.Details, ) - // get the initial validator commission parameters - rateStr, _ := fs.GetString(FlagCommissionRate) - maxRateStr, _ := fs.GetString(FlagCommissionMaxRate) - maxChangeRateStr, _ := fs.GetString(FlagCommissionMaxChangeRate) - - commissionRates, err := buildCommissionRates(rateStr, maxRateStr, maxChangeRateStr) - if err != nil { - return txf, nil, err - } - - // get the initial validator min self delegation - msbStr, _ := fs.GetString(FlagMinSelfDelegation) - - minSelfDelegation, ok := sdk.NewIntFromString(msbStr) - if !ok { - return txf, nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "minimum self delegation must be a positive integer") - } - msg, err := types.NewMsgCreateValidator( - sdk.ValAddress(valAddr), pk, amount, description, commissionRates, minSelfDelegation, + sdk.ValAddress(valAddr), val.PubKey, val.Amount, description, val.CommissionRates, val.MinSelfDelegation, ) if err != nil { return txf, nil, err diff --git a/x/staking/client/cli/tx_test.go b/x/staking/client/cli/tx_test.go index d07b6c07fd6c..c595e00d512d 100644 --- a/x/staking/client/cli/tx_test.go +++ b/x/staking/client/cli/tx_test.go @@ -18,6 +18,7 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + "github.com/cosmos/cosmos-sdk/testutil" clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" @@ -163,112 +164,152 @@ func (s *CLITestSuite) TestNewCreateValidatorCmd() { require := s.Require() cmd := cli.NewCreateValidatorCmd() - consPrivKey := ed25519.GenPrivKey() - consPubKeyBz, err := s.encCfg.Codec.MarshalInterfaceJSON(consPrivKey.PubKey()) - require.NoError(err) - require.NotNil(consPubKeyBz) + validJSON := fmt.Sprintf(` + { + "pubkey": {"@type":"/cosmos.crypto.ed25519.PubKey","key":"oWg2ISpLF405Jcm2vXV+2v4fnjodh6aafuIdeoW+rUw="}, + "amount": "%dstake", + "moniker": "NewValidator", + "identity": "AFAF00C4", + "website": "https://newvalidator.io", + "security": "contact@newvalidator.io", + "details": "'Hey, I am a new validator. Please delegate!'", + "commission-rate": "0.5", + "commission-max-rate": "1.0", + "commission-max-change-rate": "0.1", + "min-self-delegation": "1" + }`, 100) + validJSONFile := testutil.WriteToNewTempFile(s.T(), validJSON) + defer validJSONFile.Close() + + validJSONWithoutOptionalFields := fmt.Sprintf(` + { + "pubkey": {"@type":"/cosmos.crypto.ed25519.PubKey","key":"oWg2ISpLF405Jcm2vXV+2v4fnjodh6aafuIdeoW+rUw="}, + "amount": "%dstake", + "moniker": "NewValidator", + "commission-rate": "0.5", + "commission-max-rate": "1.0", + "commission-max-change-rate": "0.1", + "min-self-delegation": "1" + }`, 100) + validJSONWOOptionalFile := testutil.WriteToNewTempFile(s.T(), validJSONWithoutOptionalFields) + defer validJSONWOOptionalFile.Close() + + noAmountJSON := ` + { + "pubkey": {"@type":"/cosmos.crypto.ed25519.PubKey","key":"oWg2ISpLF405Jcm2vXV+2v4fnjodh6aafuIdeoW+rUw="}, + "moniker": "NewValidator", + "commission-rate": "0.5", + "commission-max-rate": "1.0", + "commission-max-change-rate": "0.1", + "min-self-delegation": "1" + }` + noAmountJSONFile := testutil.WriteToNewTempFile(s.T(), noAmountJSON) + defer noAmountJSONFile.Close() + + noPubKeyJSON := fmt.Sprintf(` + { + "amount": "%dstake", + "moniker": "NewValidator", + "commission-rate": "0.5", + "commission-max-rate": "1.0", + "commission-max-change-rate": "0.1", + "min-self-delegation": "1" + }`, 100) + noPubKeyJSONFile := testutil.WriteToNewTempFile(s.T(), noPubKeyJSON) + defer noPubKeyJSONFile.Close() + + noMonikerJSON := fmt.Sprintf(` + { + "pubkey": {"@type":"/cosmos.crypto.ed25519.PubKey","key":"oWg2ISpLF405Jcm2vXV+2v4fnjodh6aafuIdeoW+rUw="}, + "amount": "%dstake", + "commission-rate": "0.5", + "commission-max-rate": "1.0", + "commission-max-change-rate": "0.1", + "min-self-delegation": "1" + }`, 100) + noMonikerJSONFile := testutil.WriteToNewTempFile(s.T(), noMonikerJSON) + defer noMonikerJSONFile.Close() testCases := []struct { - name string - args []string - expectErr bool - expectedCode uint32 - respType proto.Message + name string + args []string + expectErr bool + expErrMsg string + respType proto.Message }{ { "invalid transaction (missing amount)", []string{ - fmt.Sprintf("--%s=AFAF00C4", cli.FlagIdentity), - fmt.Sprintf("--%s=https://newvalidator.io", cli.FlagWebsite), - fmt.Sprintf("--%s=contact@newvalidator.io", cli.FlagSecurityContact), - fmt.Sprintf("--%s='Hey, I am a new validator. Please delegate!'", cli.FlagDetails), - fmt.Sprintf("--%s=0.5", cli.FlagCommissionRate), - fmt.Sprintf("--%s=1.0", cli.FlagCommissionMaxRate), - fmt.Sprintf("--%s=0.1", cli.FlagCommissionMaxChangeRate), - fmt.Sprintf("--%s=1", cli.FlagMinSelfDelegation), + noAmountJSONFile.Name(), fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0]), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10))).String()), }, - true, 0, nil, + true, + "must specify amount of coins to bond", + nil, }, { "invalid transaction (missing pubkey)", []string{ - fmt.Sprintf("--%s=%dstake", cli.FlagAmount, 100), - fmt.Sprintf("--%s=AFAF00C4", cli.FlagIdentity), - fmt.Sprintf("--%s=https://newvalidator.io", cli.FlagWebsite), - fmt.Sprintf("--%s=contact@newvalidator.io", cli.FlagSecurityContact), - fmt.Sprintf("--%s='Hey, I am a new validator. Please delegate!'", cli.FlagDetails), - fmt.Sprintf("--%s=0.5", cli.FlagCommissionRate), - fmt.Sprintf("--%s=1.0", cli.FlagCommissionMaxRate), - fmt.Sprintf("--%s=0.1", cli.FlagCommissionMaxChangeRate), - fmt.Sprintf("--%s=1", cli.FlagMinSelfDelegation), + noPubKeyJSONFile.Name(), fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0]), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10))).String()), }, - true, 0, nil, + true, + "must specify the JSON encoded pubkey", + nil, }, { "invalid transaction (missing moniker)", []string{ - fmt.Sprintf("--%s=%s", cli.FlagPubKey, consPubKeyBz), - fmt.Sprintf("--%s=%dstake", cli.FlagAmount, 100), - fmt.Sprintf("--%s=AFAF00C4", cli.FlagIdentity), - fmt.Sprintf("--%s=https://newvalidator.io", cli.FlagWebsite), - fmt.Sprintf("--%s=contact@newvalidator.io", cli.FlagSecurityContact), - fmt.Sprintf("--%s='Hey, I am a new validator. Please delegate!'", cli.FlagDetails), - fmt.Sprintf("--%s=0.5", cli.FlagCommissionRate), - fmt.Sprintf("--%s=1.0", cli.FlagCommissionMaxRate), - fmt.Sprintf("--%s=0.1", cli.FlagCommissionMaxChangeRate), - fmt.Sprintf("--%s=1", cli.FlagMinSelfDelegation), + noMonikerJSONFile.Name(), fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0]), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10))).String()), }, - true, 0, nil, + true, + "must specify the moniker name", + nil, }, { - "valid transaction", + "valid transaction with all fields", []string{ - fmt.Sprintf("--%s=%s", cli.FlagPubKey, consPubKeyBz), - fmt.Sprintf("--%s=%dstake", cli.FlagAmount, 100), - fmt.Sprintf("--%s=NewValidator", cli.FlagMoniker), - fmt.Sprintf("--%s=AFAF00C4", cli.FlagIdentity), - fmt.Sprintf("--%s=https://newvalidator.io", cli.FlagWebsite), - fmt.Sprintf("--%s=contact@newvalidator.io", cli.FlagSecurityContact), - fmt.Sprintf("--%s='Hey, I am a new validator. Please delegate!'", cli.FlagDetails), - fmt.Sprintf("--%s=0.5", cli.FlagCommissionRate), - fmt.Sprintf("--%s=1.0", cli.FlagCommissionMaxRate), - fmt.Sprintf("--%s=0.1", cli.FlagCommissionMaxChangeRate), - fmt.Sprintf("--%s=1", cli.FlagMinSelfDelegation), + validJSONFile.Name(), fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0]), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10))).String()), }, - false, 0, &sdk.TxResponse{}, + false, "", &sdk.TxResponse{}, + }, + { + "valid transaction without optional fields", + []string{ + validJSONWOOptionalFile.Name(), + fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0]), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10))).String()), + }, + false, "", &sdk.TxResponse{}, }, } - for _, tc := range testCases { tc := tc s.Run(tc.name, func() { out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, tc.args) if tc.expectErr { require.Error(err) + require.Contains(err.Error(), tc.expErrMsg) } else { require.NoError(err, "test: %s\noutput: %s", tc.name, out.String()) err = s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType) require.NoError(err, out.String(), "test: %s, output\n:", tc.name, out.String()) - - txResp := tc.respType.(*sdk.TxResponse) - require.Equal(tc.expectedCode, txResp.Code, - "test: %s, output\n:", tc.name, out.String()) } }) } diff --git a/x/staking/client/cli/utils.go b/x/staking/client/cli/utils.go index 241ac314054f..de3f0655992f 100644 --- a/x/staking/client/cli/utils.go +++ b/x/staking/client/cli/utils.go @@ -1,12 +1,106 @@ package cli import ( + "encoding/json" "errors" + "fmt" + "os" + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/codec" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/staking/types" ) +// validator struct to define the fields of the validator +type validator struct { + Amount sdk.Coin + PubKey cryptotypes.PubKey + Moniker string + Identity string + Website string + Security string + Details string + CommissionRates types.CommissionRates + MinSelfDelegation math.Int +} + +func parseAndValidateValidatorJSON(cdc codec.Codec, path string) (validator, error) { + type internalVal struct { + Amount string `json:"amount"` + PubKey json.RawMessage `json:"pubkey"` + Moniker string `json:"moniker"` + Identity string `json:"identity,omitempty"` + Website string `json:"website,omitempty"` + Security string `json:"security,omitempty"` + Details string `json:"details,omitempty"` + CommissionRate string `json:"commission-rate"` + CommissionMaxRate string `json:"commission-max-rate"` + CommissionMaxChange string `json:"commission-max-change-rate"` + MinSelfDelegation string `json:"min-self-delegation"` + } + + contents, err := os.ReadFile(path) + if err != nil { + return validator{}, err + } + + var v internalVal + err = json.Unmarshal(contents, &v) + if err != nil { + return validator{}, err + } + + if v.Amount == "" { + return validator{}, fmt.Errorf("must specify amount of coins to bond") + } + amount, err := sdk.ParseCoinNormalized(v.Amount) + if err != nil { + return validator{}, err + } + + if v.PubKey == nil { + return validator{}, fmt.Errorf("must specify the JSON encoded pubkey") + } + var pk cryptotypes.PubKey + if err := cdc.UnmarshalInterfaceJSON(v.PubKey, &pk); err != nil { + return validator{}, err + } + + if v.Moniker == "" { + return validator{}, fmt.Errorf("must specify the moniker name") + } + + commissionRates, err := buildCommissionRates(v.CommissionRate, v.CommissionMaxRate, v.CommissionMaxChange) + if err != nil { + return validator{}, err + } + + if v.MinSelfDelegation == "" { + return validator{}, fmt.Errorf("must specify minimum self delegation") + } + minSelfDelegation, ok := sdk.NewIntFromString(v.MinSelfDelegation) + if !ok { + return validator{}, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "minimum self delegation must be a positive integer") + } + + return validator{ + Amount: amount, + PubKey: pk, + Moniker: v.Moniker, + Identity: v.Identity, + Website: v.Website, + Security: v.Security, + Details: v.Details, + CommissionRates: commissionRates, + MinSelfDelegation: minSelfDelegation, + }, nil +} + func buildCommissionRates(rateStr, maxRateStr, maxChangeRateStr string) (commission types.CommissionRates, err error) { if rateStr == "" || maxRateStr == "" || maxChangeRateStr == "" { return commission, errors.New("must specify all validator commission parameters")