diff --git a/tests/integration/slashing/keeper/keeper_test.go b/tests/integration/slashing/keeper/keeper_test.go index 74c97015a6c5..78c1222f0781 100644 --- a/tests/integration/slashing/keeper/keeper_test.go +++ b/tests/integration/slashing/keeper/keeper_test.go @@ -87,6 +87,7 @@ func TestUnJailNotBonded(t *testing.T) { amt := f.stakingKeeper.TokensFromConsensusPower(f.ctx, 50) msg := tstaking.CreateValidatorMsg(addr, val, amt) msg.MinSelfDelegation = amt + msg.Description = stakingtypes.Description{Moniker: "TestValidator"} res, err := tstaking.CreateValidatorWithMsg(f.ctx, msg) assert.NilError(t, err) assert.Assert(t, res != nil) diff --git a/tests/integration/staking/keeper/msg_server_test.go b/tests/integration/staking/keeper/msg_server_test.go index 7ab5c27e4898..50b3e9d3c47e 100644 --- a/tests/integration/staking/keeper/msg_server_test.go +++ b/tests/integration/staking/keeper/msg_server_test.go @@ -76,47 +76,58 @@ func TestCancelUnbondingDelegation(t *testing.T) { assert.DeepEqual(t, ubd, resUnbond) testCases := []struct { - Name string - ExceptErr bool + name string + exceptErr bool req types.MsgCancelUnbondingDelegation expErrMsg string }{ { - Name: "invalid height", - ExceptErr: true, + name: "entry not found at height", + exceptErr: true, req: types.MsgCancelUnbondingDelegation{ DelegatorAddress: resUnbond.DelegatorAddress, ValidatorAddress: resUnbond.ValidatorAddress, Amount: sdk.NewCoin(stakingKeeper.BondDenom(ctx), sdk.NewInt(4)), - CreationHeight: 0, + CreationHeight: 11, }, expErrMsg: "unbonding delegation entry is not found at block height", }, { - Name: "invalid coin", - ExceptErr: true, + name: "invalid height", + exceptErr: true, req: types.MsgCancelUnbondingDelegation{ DelegatorAddress: resUnbond.DelegatorAddress, ValidatorAddress: resUnbond.ValidatorAddress, - Amount: sdk.NewCoin("dump_coin", sdk.NewInt(4)), + Amount: sdk.NewCoin(stakingKeeper.BondDenom(ctx), sdk.NewInt(4)), CreationHeight: 0, }, + expErrMsg: "invalid height", + }, + { + name: "invalid coin", + exceptErr: true, + req: types.MsgCancelUnbondingDelegation{ + DelegatorAddress: resUnbond.DelegatorAddress, + ValidatorAddress: resUnbond.ValidatorAddress, + Amount: sdk.NewCoin("dump_coin", sdk.NewInt(4)), + CreationHeight: 10, + }, expErrMsg: "invalid coin denomination", }, { - Name: "validator not exists", - ExceptErr: true, + name: "validator not exists", + exceptErr: true, req: types.MsgCancelUnbondingDelegation{ DelegatorAddress: resUnbond.DelegatorAddress, ValidatorAddress: sdk.ValAddress(sdk.AccAddress("asdsad")).String(), Amount: unbondingAmount, - CreationHeight: 0, + CreationHeight: 10, }, expErrMsg: "validator does not exist", }, { - Name: "invalid delegator address", - ExceptErr: true, + name: "invalid delegator address", + exceptErr: true, req: types.MsgCancelUnbondingDelegation{ DelegatorAddress: "invalid_delegator_addrtess", ValidatorAddress: resUnbond.ValidatorAddress, @@ -126,8 +137,8 @@ func TestCancelUnbondingDelegation(t *testing.T) { expErrMsg: "decoding bech32 failed", }, { - Name: "invalid amount", - ExceptErr: true, + name: "invalid amount", + exceptErr: true, req: types.MsgCancelUnbondingDelegation{ DelegatorAddress: resUnbond.DelegatorAddress, ValidatorAddress: resUnbond.ValidatorAddress, @@ -137,8 +148,8 @@ func TestCancelUnbondingDelegation(t *testing.T) { expErrMsg: "amount is greater than the unbonding delegation entry balance", }, { - Name: "success", - ExceptErr: false, + name: "success", + exceptErr: false, req: types.MsgCancelUnbondingDelegation{ DelegatorAddress: resUnbond.DelegatorAddress, ValidatorAddress: resUnbond.ValidatorAddress, @@ -147,8 +158,8 @@ func TestCancelUnbondingDelegation(t *testing.T) { }, }, { - Name: "success", - ExceptErr: false, + name: "success", + exceptErr: false, req: types.MsgCancelUnbondingDelegation{ DelegatorAddress: resUnbond.DelegatorAddress, ValidatorAddress: resUnbond.ValidatorAddress, @@ -159,9 +170,9 @@ func TestCancelUnbondingDelegation(t *testing.T) { } for _, testCase := range testCases { - t.Run(testCase.Name, func(t *testing.T) { + t.Run(testCase.name, func(t *testing.T) { _, err := msgServer.CancelUnbondingDelegation(ctx, &testCase.req) - if testCase.ExceptErr { + if testCase.exceptErr { assert.ErrorContains(t, err, testCase.expErrMsg) } else { assert.NilError(t, err) diff --git a/testutil/testnet/genesis.go b/testutil/testnet/genesis.go index 1f526283c3f8..7b867dee7360 100644 --- a/testutil/testnet/genesis.go +++ b/testutil/testnet/genesis.go @@ -7,6 +7,7 @@ import ( "time" cmttypes "github.com/cometbft/cometbft/types" + "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" @@ -110,12 +111,8 @@ func (b *GenesisBuilder) GenTx(privVal secp256k1.PrivKey, val cmttypes.GenesisVa if err != nil { panic(err) } - _, err = sdk.ValAddressFromBech32(msg.ValidatorAddress) - if err != nil { - panic(err) - } - if err := msg.ValidateBasic(); err != nil { + if err := msg.Validate(); err != nil { panic(err) } diff --git a/x/distribution/testutil/staking_helper.go b/x/distribution/testutil/staking_helper.go index 9e1b259a069c..a568898aabf3 100644 --- a/x/distribution/testutil/staking_helper.go +++ b/x/distribution/testutil/staking_helper.go @@ -4,6 +4,7 @@ import ( "fmt" "cosmossdk.io/math" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/distribution/keeper" @@ -12,7 +13,7 @@ import ( func CreateValidator(pk cryptotypes.PubKey, stake math.Int) (stakingtypes.Validator, error) { valConsAddr := sdk.GetConsAddress(pk) - val, err := stakingtypes.NewValidator(sdk.ValAddress(valConsAddr), pk, stakingtypes.Description{}) + val, err := stakingtypes.NewValidator(sdk.ValAddress(valConsAddr), pk, stakingtypes.Description{Moniker: "TestValidator"}) val.Tokens = stake val.DelegatorShares = math.LegacyNewDecFromInt(val.Tokens) return val, err diff --git a/x/staking/client/cli/tx.go b/x/staking/client/cli/tx.go index 9453c10aef1a..8c8191cdd256 100644 --- a/x/staking/client/cli/tx.go +++ b/x/staking/client/cli/tx.go @@ -379,7 +379,7 @@ func newBuildCreateValidatorMsg(clientCtx client.Context, txf tx.Factory, fs *fl if err != nil { return txf, nil, err } - if err := msg.ValidateBasic(); err != nil { + if err := msg.Validate(); err != nil { return txf, nil, err } diff --git a/x/staking/keeper/keeper_test.go b/x/staking/keeper/keeper_test.go index cf7bf5f15303..4379a7e3448c 100644 --- a/x/staking/keeper/keeper_test.go +++ b/x/staking/keeper/keeper_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "errors" "testing" "cosmossdk.io/math" @@ -52,6 +53,9 @@ func (s *KeeperTestSuite) SetupTest() { accountKeeper.EXPECT().GetModuleAddress(stakingtypes.NotBondedPoolName).Return(notBondedAcc.GetAddress()) accountKeeper.EXPECT().StringToBytes(authtypes.NewModuleAddress(govtypes.ModuleName).String()).Return(authtypes.NewModuleAddress(govtypes.ModuleName), nil).AnyTimes() accountKeeper.EXPECT().BytesToString(authtypes.NewModuleAddress(govtypes.ModuleName)).Return(authtypes.NewModuleAddress(govtypes.ModuleName).String(), nil).AnyTimes() + accountKeeper.EXPECT().StringToBytes("").Return(nil, errors.New("empty address string is not allowed")).AnyTimes() + accountKeeper.EXPECT().StringToBytes("invalid").Return(nil, errors.New("invalid bech32 string")).AnyTimes() + bankKeeper := stakingtestutil.NewMockBankKeeper(ctrl) keeper := stakingkeeper.NewKeeper( diff --git a/x/staking/keeper/msg_server.go b/x/staking/keeper/msg_server.go index 8f97e1d0ac7a..83bdd0149edd 100644 --- a/x/staking/keeper/msg_server.go +++ b/x/staking/keeper/msg_server.go @@ -5,12 +5,13 @@ import ( "strconv" "time" + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/math" + "github.com/armon/go-metrics" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - errorsmod "cosmossdk.io/errors" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" @@ -33,13 +34,17 @@ var _ types.MsgServer = msgServer{} // CreateValidator defines a method for creating a new validator func (k msgServer) CreateValidator(goCtx context.Context, msg *types.MsgCreateValidator) (*types.MsgCreateValidatorResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress) if err != nil { + return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid validator address: %s", err) + } + + if err := msg.Validate(); err != nil { return nil, err } + ctx := sdk.UnwrapSDKContext(goCtx) + if msg.Commission.Rate.LT(k.MinCommissionRate(ctx)) { return nil, errorsmod.Wrapf(types.ErrCommissionLTMinRate, "cannot set validator commission to less than minimum rate of %s", k.MinCommissionRate(ctx)) } @@ -134,11 +139,30 @@ func (k msgServer) CreateValidator(goCtx context.Context, msg *types.MsgCreateVa // EditValidator defines a method for editing an existing validator func (k msgServer) EditValidator(goCtx context.Context, msg *types.MsgEditValidator) (*types.MsgEditValidatorResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress) if err != nil { - return nil, err + return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid validator address: %s", err) + } + + if msg.Description == (types.Description{}) { + return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "empty description") + } + + if msg.MinSelfDelegation != nil && !msg.MinSelfDelegation.IsPositive() { + return nil, errorsmod.Wrap( + sdkerrors.ErrInvalidRequest, + "minimum self delegation must be a positive integer", + ) } + + if msg.CommissionRate != nil { + if msg.CommissionRate.GT(math.LegacyOneDec()) || msg.CommissionRate.IsNegative() { + return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "commission rate must be between 0 and 1 (inclusive)") + } + } + + ctx := sdk.UnwrapSDKContext(goCtx) + // validator must already be registered validator, found := k.GetValidator(ctx, valAddr) if !found { @@ -194,22 +218,30 @@ func (k msgServer) EditValidator(goCtx context.Context, msg *types.MsgEditValida // Delegate defines a method for performing a delegation of coins from a delegator to a validator func (k msgServer) Delegate(goCtx context.Context, msg *types.MsgDelegate) (*types.MsgDelegateResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) valAddr, valErr := sdk.ValAddressFromBech32(msg.ValidatorAddress) if valErr != nil { - return nil, valErr + return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid validator address: %s", valErr) } + delegatorAddress, err := k.authKeeper.StringToBytes(msg.DelegatorAddress) + if err != nil { + return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid delegator address: %s", err) + } + + if !msg.Amount.IsValid() || !msg.Amount.Amount.IsPositive() { + return nil, errorsmod.Wrap( + sdkerrors.ErrInvalidRequest, + "invalid delegation amount", + ) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + validator, found := k.GetValidator(ctx, valAddr) if !found { return nil, types.ErrNoValidatorFound } - delegatorAddress, err := k.authKeeper.StringToBytes(msg.DelegatorAddress) - if err != nil { - return nil, err - } - bondDenom := k.BondDenom(ctx) if msg.Amount.Denom != bondDenom { return nil, errorsmod.Wrapf( @@ -248,15 +280,30 @@ func (k msgServer) Delegate(goCtx context.Context, msg *types.MsgDelegate) (*typ // BeginRedelegate defines a method for performing a redelegation of coins from a delegator and source validator to a destination validator func (k msgServer) BeginRedelegate(goCtx context.Context, msg *types.MsgBeginRedelegate) (*types.MsgBeginRedelegateResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) valSrcAddr, err := sdk.ValAddressFromBech32(msg.ValidatorSrcAddress) if err != nil { - return nil, err + return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid source validator address: %s", err) + } + + valDstAddr, err := sdk.ValAddressFromBech32(msg.ValidatorDstAddress) + if err != nil { + return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid destination validator address: %s", err) } + delegatorAddress, err := k.authKeeper.StringToBytes(msg.DelegatorAddress) if err != nil { - return nil, err + return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid delegator address: %s", err) + } + + if !msg.Amount.IsValid() || !msg.Amount.Amount.IsPositive() { + return nil, errorsmod.Wrap( + sdkerrors.ErrInvalidRequest, + "invalid shares amount", + ) } + + ctx := sdk.UnwrapSDKContext(goCtx) + shares, err := k.ValidateUnbondAmount( ctx, delegatorAddress, valSrcAddr, msg.Amount.Amount, ) @@ -271,11 +318,6 @@ func (k msgServer) BeginRedelegate(goCtx context.Context, msg *types.MsgBeginRed ) } - valDstAddr, err := sdk.ValAddressFromBech32(msg.ValidatorDstAddress) - if err != nil { - return nil, err - } - completionTime, err := k.BeginRedelegation( ctx, delegatorAddress, valSrcAddr, valDstAddr, shares, ) @@ -311,16 +353,25 @@ func (k msgServer) BeginRedelegate(goCtx context.Context, msg *types.MsgBeginRed // Undelegate defines a method for performing an undelegation from a delegate and a validator func (k msgServer) Undelegate(goCtx context.Context, msg *types.MsgUndelegate) (*types.MsgUndelegateResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - addr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress) if err != nil { - return nil, err + return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid validator address: %s", err) } + delegatorAddress, err := k.authKeeper.StringToBytes(msg.DelegatorAddress) if err != nil { - return nil, err + return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid delegator address: %s", err) + } + + if !msg.Amount.IsValid() || !msg.Amount.Amount.IsPositive() { + return nil, errorsmod.Wrap( + sdkerrors.ErrInvalidRequest, + "invalid shares amount", + ) } + + ctx := sdk.UnwrapSDKContext(goCtx) + shares, err := k.ValidateUnbondAmount( ctx, delegatorAddress, addr, msg.Amount.Amount, ) @@ -371,18 +422,32 @@ func (k msgServer) Undelegate(goCtx context.Context, msg *types.MsgUndelegate) ( // CancelUnbondingDelegation defines a method for canceling the unbonding delegation // and delegate back to the validator. func (k msgServer) CancelUnbondingDelegation(goCtx context.Context, msg *types.MsgCancelUnbondingDelegation) (*types.MsgCancelUnbondingDelegationResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress) if err != nil { - return nil, err + return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid validator address: %s", err) } delegatorAddress, err := k.authKeeper.StringToBytes(msg.DelegatorAddress) if err != nil { - return nil, err + return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid delegator address: %s", err) + } + + if !msg.Amount.IsValid() || !msg.Amount.Amount.IsPositive() { + return nil, errorsmod.Wrap( + sdkerrors.ErrInvalidRequest, + "invalid amount", + ) + } + + if msg.CreationHeight <= 0 { + return nil, errorsmod.Wrap( + sdkerrors.ErrInvalidRequest, + "invalid height", + ) } + ctx := sdk.UnwrapSDKContext(goCtx) + bondDenom := k.BondDenom(ctx) if msg.Amount.Denom != bondDenom { return nil, errorsmod.Wrapf( @@ -476,12 +541,16 @@ func (k msgServer) CancelUnbondingDelegation(goCtx context.Context, msg *types.M } func (k msgServer) UpdateParams(goCtx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - if k.authority != msg.Authority { return nil, errorsmod.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", k.authority, msg.Authority) } + if err := msg.Params.Validate(); err != nil { + return nil, err + } + + ctx := sdk.UnwrapSDKContext(goCtx) + // store params if err := k.SetParams(ctx, msg.Params); err != nil { return nil, err diff --git a/x/staking/keeper/msg_server_test.go b/x/staking/keeper/msg_server_test.go index 87a7dff90936..30cf6e6b963c 100644 --- a/x/staking/keeper/msg_server_test.go +++ b/x/staking/keeper/msg_server_test.go @@ -2,11 +2,998 @@ package keeper_test import ( "testing" + "time" "cosmossdk.io/math" + + "github.com/golang/mock/gomock" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) +var ( + PKS = simtestutil.CreateTestPubKeys(3) + Addr = sdk.AccAddress(PKS[0].Address()) + ValAddr = sdk.ValAddress(Addr) +) + +func (s *KeeperTestSuite) execExpectCalls() { + s.accountKeeper.EXPECT().StringToBytes(Addr.String()).Return(Addr, nil).AnyTimes() + s.accountKeeper.EXPECT().BytesToString(Addr).Return(Addr.String(), nil).AnyTimes() + s.bankKeeper.EXPECT().DelegateCoinsFromAccountToModule(gomock.Any(), Addr, stakingtypes.NotBondedPoolName, gomock.Any()).AnyTimes() +} + +func (s *KeeperTestSuite) TestMsgCreateValidator() { + ctx, msgServer := s.ctx, s.msgServer + require := s.Require() + s.execExpectCalls() + + pk1 := ed25519.GenPrivKey().PubKey() + require.NotNil(pk1) + + pubkey, err := codectypes.NewAnyWithValue(pk1) + require.NoError(err) + + testCases := []struct { + name string + input *stakingtypes.MsgCreateValidator + expErr bool + expErrMsg string + }{ + { + name: "empty description", + input: &stakingtypes.MsgCreateValidator{ + Description: stakingtypes.Description{}, + Commission: stakingtypes.CommissionRates{ + Rate: sdk.NewDecWithPrec(5, 1), + MaxRate: sdk.NewDecWithPrec(5, 1), + MaxChangeRate: math.LegacyNewDec(0), + }, + MinSelfDelegation: math.NewInt(1), + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Pubkey: pubkey, + Value: sdk.NewInt64Coin("stake", 10000), + }, + expErr: true, + expErrMsg: "empty description", + }, + { + name: "invalid validator address", + input: &stakingtypes.MsgCreateValidator{ + Description: stakingtypes.Description{ + Moniker: "NewValidator", + }, + Commission: stakingtypes.CommissionRates{ + Rate: sdk.NewDecWithPrec(5, 1), + MaxRate: sdk.NewDecWithPrec(5, 1), + MaxChangeRate: math.LegacyNewDec(0), + }, + MinSelfDelegation: math.NewInt(1), + DelegatorAddress: Addr.String(), + ValidatorAddress: sdk.AccAddress([]byte("invalid")).String(), + Pubkey: pubkey, + Value: sdk.NewInt64Coin("stake", 10000), + }, + expErr: true, + expErrMsg: "invalid validator address", + }, + { + name: "empty validator pubkey", + input: &stakingtypes.MsgCreateValidator{ + Description: stakingtypes.Description{ + Moniker: "NewValidator", + }, + Commission: stakingtypes.CommissionRates{ + Rate: sdk.NewDecWithPrec(5, 1), + MaxRate: sdk.NewDecWithPrec(5, 1), + MaxChangeRate: math.LegacyNewDec(0), + }, + MinSelfDelegation: math.NewInt(1), + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Pubkey: nil, + Value: sdk.NewInt64Coin("stake", 10000), + }, + expErr: true, + expErrMsg: "empty validator public key", + }, + { + name: "empty delegation amount", + input: &stakingtypes.MsgCreateValidator{ + Description: stakingtypes.Description{ + Moniker: "NewValidator", + }, + Commission: stakingtypes.CommissionRates{ + Rate: sdk.NewDecWithPrec(5, 1), + MaxRate: sdk.NewDecWithPrec(5, 1), + MaxChangeRate: math.LegacyNewDec(0), + }, + MinSelfDelegation: math.NewInt(1), + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Pubkey: pubkey, + Value: sdk.NewInt64Coin("stake", 0), + }, + expErr: true, + expErrMsg: "invalid delegation amount", + }, + { + name: "nil delegation amount", + input: &stakingtypes.MsgCreateValidator{ + Description: stakingtypes.Description{ + Moniker: "NewValidator", + }, + Commission: stakingtypes.CommissionRates{ + Rate: sdk.NewDecWithPrec(5, 1), + MaxRate: sdk.NewDecWithPrec(5, 1), + MaxChangeRate: math.LegacyNewDec(0), + }, + MinSelfDelegation: math.NewInt(1), + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Pubkey: pubkey, + Value: sdk.Coin{}, + }, + expErr: true, + expErrMsg: "invalid delegation amount", + }, + { + name: "zero minimum self delegation", + input: &stakingtypes.MsgCreateValidator{ + Description: stakingtypes.Description{ + Moniker: "NewValidator", + }, + Commission: stakingtypes.CommissionRates{ + Rate: sdk.NewDecWithPrec(5, 1), + MaxRate: sdk.NewDecWithPrec(5, 1), + MaxChangeRate: math.LegacyNewDec(0), + }, + MinSelfDelegation: math.NewInt(0), + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Pubkey: pubkey, + Value: sdk.NewInt64Coin("stake", 10000), + }, + expErr: true, + expErrMsg: "minimum self delegation must be a positive integer", + }, + { + name: "negative minimum self delegation", + input: &stakingtypes.MsgCreateValidator{ + Description: stakingtypes.Description{ + Moniker: "NewValidator", + }, + Commission: stakingtypes.CommissionRates{ + Rate: sdk.NewDecWithPrec(5, 1), + MaxRate: sdk.NewDecWithPrec(5, 1), + MaxChangeRate: math.LegacyNewDec(0), + }, + MinSelfDelegation: math.NewInt(-1), + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Pubkey: pubkey, + Value: sdk.NewInt64Coin("stake", 10000), + }, + expErr: true, + expErrMsg: "minimum self delegation must be a positive integer", + }, + { + name: "delegation less than minimum self delegation", + input: &stakingtypes.MsgCreateValidator{ + Description: stakingtypes.Description{ + Moniker: "NewValidator", + }, + Commission: stakingtypes.CommissionRates{ + Rate: sdk.NewDecWithPrec(5, 1), + MaxRate: sdk.NewDecWithPrec(5, 1), + MaxChangeRate: math.LegacyNewDec(0), + }, + MinSelfDelegation: math.NewInt(100), + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Pubkey: pubkey, + Value: sdk.NewInt64Coin("stake", 10), + }, + expErr: true, + expErrMsg: "validator's self delegation must be greater than their minimum self delegation", + }, + { + name: "valid msg", + input: &stakingtypes.MsgCreateValidator{ + Description: stakingtypes.Description{ + Moniker: "NewValidator", + Identity: "xyz", + Website: "xyz.com", + SecurityContact: "xyz@gmail.com", + Details: "details", + }, + Commission: stakingtypes.CommissionRates{ + Rate: sdk.NewDecWithPrec(5, 1), + MaxRate: sdk.NewDecWithPrec(5, 1), + MaxChangeRate: math.LegacyNewDec(0), + }, + MinSelfDelegation: math.NewInt(1), + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Pubkey: pubkey, + Value: sdk.NewInt64Coin("stake", 10000), + }, + expErr: false, + }, + } + for _, tc := range testCases { + tc := tc + s.T().Run(tc.name, func(t *testing.T) { + _, err := msgServer.CreateValidator(ctx, tc.input) + if tc.expErr { + require.Error(err) + require.Contains(err.Error(), tc.expErrMsg) + } else { + require.NoError(err) + } + }) + } +} + +func (s *KeeperTestSuite) TestMsgEditValidator() { + ctx, msgServer := s.ctx, s.msgServer + require := s.Require() + s.execExpectCalls() + + // create new context with updated block time + newCtx := ctx.WithBlockTime(ctx.BlockTime().AddDate(0, 0, 1)) + + pk := ed25519.GenPrivKey().PubKey() + require.NotNil(pk) + + comm := stakingtypes.NewCommissionRates(math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0)) + msg, err := stakingtypes.NewMsgCreateValidator(ValAddr, pk, sdk.NewCoin("stake", sdk.NewInt(10)), stakingtypes.Description{Moniker: "NewVal"}, comm, sdk.OneInt()) + require.NoError(err) + + res, err := msgServer.CreateValidator(ctx, msg) + require.NoError(err) + require.NotNil(res) + + newRate := math.LegacyZeroDec() + invalidRate := sdk.NewDec(2) + + lowSelfDel := math.OneInt() + highSelfDel := math.NewInt(100) + negSelfDel := math.NewInt(-1) + newSelfDel := math.NewInt(3) + + testCases := []struct { + name string + ctx sdk.Context + input *stakingtypes.MsgEditValidator + expErr bool + expErrMsg string + }{ + { + name: "invalid validator", + ctx: newCtx, + input: &stakingtypes.MsgEditValidator{ + Description: stakingtypes.Description{ + Moniker: "TestValidator", + }, + ValidatorAddress: sdk.AccAddress([]byte("invalid")).String(), + CommissionRate: &newRate, + MinSelfDelegation: &newSelfDel, + }, + expErr: true, + expErrMsg: "invalid validator address", + }, + { + name: "empty description", + ctx: newCtx, + input: &stakingtypes.MsgEditValidator{ + Description: stakingtypes.Description{}, + ValidatorAddress: ValAddr.String(), + CommissionRate: &newRate, + MinSelfDelegation: &newSelfDel, + }, + expErr: true, + expErrMsg: "empty description", + }, + { + name: "negative self delegation", + ctx: newCtx, + input: &stakingtypes.MsgEditValidator{ + Description: stakingtypes.Description{ + Moniker: "TestValidator", + }, + ValidatorAddress: ValAddr.String(), + CommissionRate: &newRate, + MinSelfDelegation: &negSelfDel, + }, + expErr: true, + expErrMsg: "minimum self delegation must be a positive integer", + }, + { + name: "invalid commission rate", + ctx: newCtx, + input: &stakingtypes.MsgEditValidator{ + Description: stakingtypes.Description{ + Moniker: "TestValidator", + }, + ValidatorAddress: ValAddr.String(), + CommissionRate: &invalidRate, + MinSelfDelegation: &newSelfDel, + }, + expErr: true, + expErrMsg: "commission rate must be between 0 and 1 (inclusive)", + }, + { + name: "validator does not exist", + ctx: newCtx, + input: &stakingtypes.MsgEditValidator{ + Description: stakingtypes.Description{ + Moniker: "TestValidator", + }, + ValidatorAddress: sdk.ValAddress([]byte("val")).String(), + CommissionRate: &newRate, + MinSelfDelegation: &newSelfDel, + }, + expErr: true, + expErrMsg: "validator does not exist", + }, + { + name: "change commmission rate in <24hrs", + ctx: ctx, + input: &stakingtypes.MsgEditValidator{ + Description: stakingtypes.Description{ + Moniker: "TestValidator", + }, + ValidatorAddress: ValAddr.String(), + CommissionRate: &newRate, + MinSelfDelegation: &newSelfDel, + }, + expErr: true, + expErrMsg: "commission cannot be changed more than once in 24h", + }, + { + name: "minimum self delegation cannot decrease", + ctx: newCtx, + input: &stakingtypes.MsgEditValidator{ + Description: stakingtypes.Description{ + Moniker: "TestValidator", + }, + ValidatorAddress: ValAddr.String(), + CommissionRate: &newRate, + MinSelfDelegation: &lowSelfDel, + }, + expErr: true, + expErrMsg: "minimum self delegation cannot be decrease", + }, + { + name: "validator self-delegation must be greater than min self delegation", + ctx: newCtx, + input: &stakingtypes.MsgEditValidator{ + Description: stakingtypes.Description{ + Moniker: "TestValidator", + }, + ValidatorAddress: ValAddr.String(), + CommissionRate: &newRate, + MinSelfDelegation: &highSelfDel, + }, + expErr: true, + expErrMsg: "validator's self delegation must be greater than their minimum self delegation", + }, + { + name: "valid msg", + ctx: newCtx, + input: &stakingtypes.MsgEditValidator{ + Description: stakingtypes.Description{ + Moniker: "TestValidator", + Identity: "abc", + Website: "abc.com", + SecurityContact: "abc@gmail.com", + Details: "newDetails", + }, + ValidatorAddress: ValAddr.String(), + CommissionRate: &newRate, + MinSelfDelegation: &newSelfDel, + }, + expErr: false, + }, + } + for _, tc := range testCases { + tc := tc + s.T().Run(tc.name, func(t *testing.T) { + _, err := msgServer.EditValidator(tc.ctx, tc.input) + if tc.expErr { + require.Error(err) + require.Contains(err.Error(), tc.expErrMsg) + } else { + require.NoError(err) + } + }) + } +} + +func (s *KeeperTestSuite) TestMsgDelegate() { + ctx, keeper, msgServer := s.ctx, s.stakingKeeper, s.msgServer + require := s.Require() + s.execExpectCalls() + + pk := ed25519.GenPrivKey().PubKey() + require.NotNil(pk) + + comm := stakingtypes.NewCommissionRates(math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0)) + + msg, err := stakingtypes.NewMsgCreateValidator(ValAddr, pk, sdk.NewCoin("stake", sdk.NewInt(10)), stakingtypes.Description{Moniker: "NewVal"}, comm, sdk.OneInt()) + require.NoError(err) + + res, err := msgServer.CreateValidator(ctx, msg) + require.NoError(err) + require.NotNil(res) + + testCases := []struct { + name string + input *stakingtypes.MsgDelegate + expErr bool + expErrMsg string + }{ + { + name: "invalid validator", + input: &stakingtypes.MsgDelegate{ + DelegatorAddress: Addr.String(), + ValidatorAddress: sdk.AccAddress([]byte("invalid")).String(), + Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))}, + }, + expErr: true, + expErrMsg: "invalid validator address", + }, + { + name: "empty delegator", + input: &stakingtypes.MsgDelegate{ + DelegatorAddress: "", + ValidatorAddress: ValAddr.String(), + Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))}, + }, + expErr: true, + expErrMsg: "invalid delegator address: empty address string is not allowed", + }, + { + name: "invalid delegator", + input: &stakingtypes.MsgDelegate{ + DelegatorAddress: "invalid", + ValidatorAddress: ValAddr.String(), + Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))}, + }, + expErr: true, + expErrMsg: "invalid delegator address: invalid bech32 string", + }, + { + name: "validator does not exist", + input: &stakingtypes.MsgDelegate{ + DelegatorAddress: Addr.String(), + ValidatorAddress: sdk.ValAddress([]byte("val")).String(), + Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))}, + }, + expErr: true, + expErrMsg: "validator does not exist", + }, + { + name: "zero amount", + input: &stakingtypes.MsgDelegate{ + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(0))}, + }, + expErr: true, + expErrMsg: "invalid delegation amount", + }, + { + name: "negative amount", + input: &stakingtypes.MsgDelegate{ + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(-1))}, + }, + expErr: true, + expErrMsg: "invalid delegation amount", + }, + { + name: "invalid BondDenom", + input: &stakingtypes.MsgDelegate{ + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Amount: sdk.Coin{Denom: "test", Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))}, + }, + expErr: true, + expErrMsg: "invalid coin denomination", + }, + { + name: "valid msg", + input: &stakingtypes.MsgDelegate{ + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))}, + }, + expErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + s.T().Run(tc.name, func(t *testing.T) { + _, err := msgServer.Delegate(ctx, tc.input) + if tc.expErr { + require.Error(err) + require.Contains(err.Error(), tc.expErrMsg) + } else { + require.NoError(err) + } + }) + } +} + +func (s *KeeperTestSuite) TestMsgBeginRedelegate() { + ctx, keeper, msgServer := s.ctx, s.stakingKeeper, s.msgServer + require := s.Require() + s.execExpectCalls() + + srcValAddr := ValAddr + addr2 := sdk.AccAddress(PKS[1].Address()) + dstValAddr := sdk.ValAddress(addr2) + + pk := ed25519.GenPrivKey().PubKey() + require.NotNil(pk) + dstPk := ed25519.GenPrivKey().PubKey() + require.NotNil(dstPk) + + comm := stakingtypes.NewCommissionRates(math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0)) + amt := sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))} + + msg, err := stakingtypes.NewMsgCreateValidator(srcValAddr, pk, amt, stakingtypes.Description{Moniker: "NewVal"}, comm, sdk.OneInt()) + require.NoError(err) + res, err := msgServer.CreateValidator(ctx, msg) + require.NoError(err) + require.NotNil(res) + + s.accountKeeper.EXPECT().StringToBytes(addr2.String()).Return(addr2, nil).AnyTimes() + s.accountKeeper.EXPECT().BytesToString(addr2).Return(addr2.String(), nil).AnyTimes() + s.bankKeeper.EXPECT().DelegateCoinsFromAccountToModule(gomock.Any(), addr2, stakingtypes.NotBondedPoolName, gomock.Any()).AnyTimes() + + msg, err = stakingtypes.NewMsgCreateValidator(dstValAddr, dstPk, amt, stakingtypes.Description{Moniker: "NewVal"}, comm, sdk.OneInt()) + require.NoError(err) + + res, err = msgServer.CreateValidator(ctx, msg) + require.NoError(err) + require.NotNil(res) + + shares := math.LegacyNewDec(100) + del := stakingtypes.NewDelegation(Addr, srcValAddr, shares) + keeper.SetDelegation(ctx, del) + _, found := keeper.GetDelegation(ctx, Addr, srcValAddr) + require.True(found) + + testCases := []struct { + name string + input *stakingtypes.MsgBeginRedelegate + expErr bool + expErrMsg string + }{ + { + name: "invalid source validator", + input: &stakingtypes.MsgBeginRedelegate{ + DelegatorAddress: Addr.String(), + ValidatorSrcAddress: sdk.AccAddress([]byte("invalid")).String(), + ValidatorDstAddress: dstValAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()), + }, + expErr: true, + expErrMsg: "invalid source validator address", + }, + { + name: "empty delegator", + input: &stakingtypes.MsgBeginRedelegate{ + DelegatorAddress: "", + ValidatorSrcAddress: srcValAddr.String(), + ValidatorDstAddress: dstValAddr.String(), + Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))}, + }, + expErr: true, + expErrMsg: "invalid delegator address: empty address string is not allowed", + }, + { + name: "invalid delegator", + input: &stakingtypes.MsgBeginRedelegate{ + DelegatorAddress: "invalid", + ValidatorSrcAddress: srcValAddr.String(), + ValidatorDstAddress: dstValAddr.String(), + Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))}, + }, + expErr: true, + expErrMsg: "invalid delegator address: invalid bech32 string", + }, + { + name: "invalid destination validator", + input: &stakingtypes.MsgBeginRedelegate{ + DelegatorAddress: Addr.String(), + ValidatorSrcAddress: srcValAddr.String(), + ValidatorDstAddress: sdk.AccAddress([]byte("invalid")).String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()), + }, + expErr: true, + expErrMsg: "invalid destination validator address", + }, + { + name: "validator does not exist", + input: &stakingtypes.MsgBeginRedelegate{ + DelegatorAddress: Addr.String(), + ValidatorSrcAddress: sdk.ValAddress([]byte("invalid")).String(), + ValidatorDstAddress: dstValAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()), + }, + expErr: true, + expErrMsg: "validator does not exist", + }, + { + name: "self redelegation", + input: &stakingtypes.MsgBeginRedelegate{ + DelegatorAddress: Addr.String(), + ValidatorSrcAddress: srcValAddr.String(), + ValidatorDstAddress: srcValAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()), + }, + expErr: true, + expErrMsg: "cannot redelegate to the same validator", + }, + { + name: "amount greater than delegated shares amount", + input: &stakingtypes.MsgBeginRedelegate{ + DelegatorAddress: Addr.String(), + ValidatorSrcAddress: srcValAddr.String(), + ValidatorDstAddress: dstValAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(101)), + }, + expErr: true, + expErrMsg: "invalid shares amount", + }, + { + name: "zero amount", + input: &stakingtypes.MsgBeginRedelegate{ + DelegatorAddress: Addr.String(), + ValidatorSrcAddress: srcValAddr.String(), + ValidatorDstAddress: dstValAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(0)), + }, + expErr: true, + expErrMsg: "invalid shares amount", + }, + { + name: "invalid coin denom", + input: &stakingtypes.MsgBeginRedelegate{ + DelegatorAddress: Addr.String(), + ValidatorSrcAddress: srcValAddr.String(), + ValidatorDstAddress: dstValAddr.String(), + Amount: sdk.NewCoin("test", shares.RoundInt()), + }, + expErr: true, + expErrMsg: "invalid coin denomination", + }, + { + name: "valid msg", + input: &stakingtypes.MsgBeginRedelegate{ + DelegatorAddress: Addr.String(), + ValidatorSrcAddress: srcValAddr.String(), + ValidatorDstAddress: dstValAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()), + }, + expErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + s.T().Run(tc.name, func(t *testing.T) { + _, err := msgServer.BeginRedelegate(ctx, tc.input) + if tc.expErr { + require.Error(err) + require.Contains(err.Error(), tc.expErrMsg) + } else { + require.NoError(err) + } + }) + } +} + +func (s *KeeperTestSuite) TestMsgUndelegate() { + ctx, keeper, msgServer := s.ctx, s.stakingKeeper, s.msgServer + require := s.Require() + s.execExpectCalls() + + pk := ed25519.GenPrivKey().PubKey() + require.NotNil(pk) + + comm := stakingtypes.NewCommissionRates(math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0)) + amt := sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))} + + msg, err := stakingtypes.NewMsgCreateValidator(ValAddr, pk, amt, stakingtypes.Description{Moniker: "NewVal"}, comm, sdk.OneInt()) + require.NoError(err) + res, err := msgServer.CreateValidator(ctx, msg) + require.NoError(err) + require.NotNil(res) + + shares := math.LegacyNewDec(100) + del := stakingtypes.NewDelegation(Addr, ValAddr, shares) + keeper.SetDelegation(ctx, del) + _, found := keeper.GetDelegation(ctx, Addr, ValAddr) + require.True(found) + + testCases := []struct { + name string + input *stakingtypes.MsgUndelegate + expErr bool + expErrMsg string + }{ + { + name: "invalid validator", + input: &stakingtypes.MsgUndelegate{ + DelegatorAddress: Addr.String(), + ValidatorAddress: sdk.AccAddress([]byte("invalid")).String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()), + }, + expErr: true, + expErrMsg: "invalid validator address", + }, + { + name: "empty delegator", + input: &stakingtypes.MsgUndelegate{ + DelegatorAddress: "", + ValidatorAddress: ValAddr.String(), + Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: shares.RoundInt()}, + }, + expErr: true, + expErrMsg: "invalid delegator address: empty address string is not allowed", + }, + { + name: "invalid delegator", + input: &stakingtypes.MsgUndelegate{ + DelegatorAddress: "invalid", + ValidatorAddress: ValAddr.String(), + Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: shares.RoundInt()}, + }, + expErr: true, + expErrMsg: "invalid delegator address: invalid bech32 string", + }, + { + name: "validator does not exist", + input: &stakingtypes.MsgUndelegate{ + DelegatorAddress: Addr.String(), + ValidatorAddress: sdk.ValAddress([]byte("invalid")).String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()), + }, + expErr: true, + expErrMsg: "validator does not exist", + }, + { + name: "amount greater than delegated shares amount", + input: &stakingtypes.MsgUndelegate{ + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(101)), + }, + expErr: true, + expErrMsg: "invalid shares amount", + }, + { + name: "zero amount", + input: &stakingtypes.MsgUndelegate{ + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(0)), + }, + expErr: true, + expErrMsg: "invalid shares amount", + }, + { + name: "invalid coin denom", + input: &stakingtypes.MsgUndelegate{ + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Amount: sdk.NewCoin("test", shares.RoundInt()), + }, + expErr: true, + expErrMsg: "invalid coin denomination", + }, + { + name: "valid msg", + input: &stakingtypes.MsgUndelegate{ + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()), + }, + expErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + s.T().Run(tc.name, func(t *testing.T) { + _, err := msgServer.Undelegate(ctx, tc.input) + if tc.expErr { + require.Error(err) + require.Contains(err.Error(), tc.expErrMsg) + } else { + require.NoError(err) + } + }) + } +} + +func (s *KeeperTestSuite) TestMsgCancelUnbondingDelegation() { + ctx, keeper, msgServer := s.ctx, s.stakingKeeper, s.msgServer + require := s.Require() + + pk := ed25519.GenPrivKey().PubKey() + require.NotNil(pk) + + comm := stakingtypes.NewCommissionRates(math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0)) + amt := sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))} + + s.accountKeeper.EXPECT().StringToBytes(Addr.String()).Return(Addr, nil).AnyTimes() + s.accountKeeper.EXPECT().BytesToString(Addr).Return(Addr.String(), nil).AnyTimes() + + s.bankKeeper.EXPECT().DelegateCoinsFromAccountToModule(gomock.Any(), Addr, stakingtypes.NotBondedPoolName, gomock.Any()).AnyTimes() + + msg, err := stakingtypes.NewMsgCreateValidator(ValAddr, pk, amt, stakingtypes.Description{Moniker: "NewVal"}, comm, sdk.OneInt()) + require.NoError(err) + res, err := msgServer.CreateValidator(ctx, msg) + require.NoError(err) + require.NotNil(res) + + shares := math.LegacyNewDec(100) + del := stakingtypes.NewDelegation(Addr, ValAddr, shares) + keeper.SetDelegation(ctx, del) + resDel, found := keeper.GetDelegation(ctx, Addr, ValAddr) + require.True(found) + require.Equal(del, resDel) + + ubd := stakingtypes.NewUnbondingDelegation(Addr, ValAddr, 10, ctx.BlockTime().Add(time.Minute*10), shares.RoundInt(), 0) + keeper.SetUnbondingDelegation(ctx, ubd) + resUnbond, found := keeper.GetUnbondingDelegation(ctx, Addr, ValAddr) + require.True(found) + require.Equal(ubd, resUnbond) + + testCases := []struct { + name string + input *stakingtypes.MsgCancelUnbondingDelegation + expErr bool + expErrMsg string + }{ + { + name: "invalid validator", + input: &stakingtypes.MsgCancelUnbondingDelegation{ + DelegatorAddress: Addr.String(), + ValidatorAddress: sdk.AccAddress([]byte("invalid")).String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()), + CreationHeight: 10, + }, + expErr: true, + expErrMsg: "invalid validator address", + }, + { + name: "empty delegator", + input: &stakingtypes.MsgCancelUnbondingDelegation{ + DelegatorAddress: "", + ValidatorAddress: ValAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()), + CreationHeight: 10, + }, + expErr: true, + expErrMsg: "invalid delegator address: empty address string is not allowed", + }, + { + name: "invalid delegator", + input: &stakingtypes.MsgCancelUnbondingDelegation{ + DelegatorAddress: "invalid", + ValidatorAddress: ValAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()), + CreationHeight: 10, + }, + expErr: true, + expErrMsg: "invalid delegator address: invalid bech32 string", + }, + { + name: "entry not found at height", + input: &stakingtypes.MsgCancelUnbondingDelegation{ + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()), + CreationHeight: 11, + }, + expErr: true, + expErrMsg: "unbonding delegation entry is not found at block height", + }, + { + name: "invalid height", + input: &stakingtypes.MsgCancelUnbondingDelegation{ + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()), + CreationHeight: -1, + }, + expErr: true, + expErrMsg: "invalid height", + }, + { + name: "invalid coin", + input: &stakingtypes.MsgCancelUnbondingDelegation{ + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Amount: sdk.NewCoin("test", shares.RoundInt()), + CreationHeight: 10, + }, + expErr: true, + expErrMsg: "invalid coin denomination", + }, + { + name: "validator does not exist", + input: &stakingtypes.MsgCancelUnbondingDelegation{ + DelegatorAddress: Addr.String(), + ValidatorAddress: sdk.ValAddress([]byte("invalid")).String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()), + CreationHeight: 10, + }, + expErr: true, + expErrMsg: "validator does not exist", + }, + { + name: "amount is greater than balance", + input: &stakingtypes.MsgCancelUnbondingDelegation{ + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(101)), + CreationHeight: 10, + }, + expErr: true, + expErrMsg: "amount is greater than the unbonding delegation entry balance", + }, + { + name: "zero amount", + input: &stakingtypes.MsgCancelUnbondingDelegation{ + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(0)), + CreationHeight: 10, + }, + expErr: true, + expErrMsg: "invalid amount", + }, + { + name: "valid msg", + input: &stakingtypes.MsgCancelUnbondingDelegation{ + DelegatorAddress: Addr.String(), + ValidatorAddress: ValAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()), + CreationHeight: 10, + }, + expErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + s.T().Run(tc.name, func(t *testing.T) { + _, err := msgServer.CancelUnbondingDelegation(ctx, tc.input) + if tc.expErr { + require.Error(err) + require.Contains(err.Error(), tc.expErrMsg) + } else { + require.NoError(err) + } + }) + } +} + func (s *KeeperTestSuite) TestMsgUpdateParams() { ctx, keeper, msgServer := s.ctx, s.stakingKeeper, s.msgServer require := s.Require() @@ -83,7 +1070,7 @@ func (s *KeeperTestSuite) TestMsgUpdateParams() { expErrMsg: "bond denom cannot be blank", }, { - name: "max validators most be positive", + name: "max validators must be positive", input: &stakingtypes.MsgUpdateParams{ Authority: keeper.GetAuthority(), Params: stakingtypes.Params{ @@ -114,6 +1101,22 @@ func (s *KeeperTestSuite) TestMsgUpdateParams() { expErr: true, expErrMsg: "max entries must be positive", }, + { + name: "negative unbounding time", + input: &stakingtypes.MsgUpdateParams{ + Authority: keeper.GetAuthority(), + Params: stakingtypes.Params{ + UnbondingTime: time.Hour * 24 * 7 * 3 * -1, + MaxEntries: stakingtypes.DefaultMaxEntries, + MaxValidators: stakingtypes.DefaultMaxValidators, + HistoricalEntries: stakingtypes.DefaultHistoricalEntries, + MinCommissionRate: stakingtypes.DefaultMinCommissionRate, + BondDenom: "denom", + }, + }, + expErr: true, + expErrMsg: "unbonding time must be positive", + }, } for _, tc := range testCases { diff --git a/x/staking/keeper/params.go b/x/staking/keeper/params.go index 3e1bb083916e..74316694d9bf 100644 --- a/x/staking/keeper/params.go +++ b/x/staking/keeper/params.go @@ -50,11 +50,8 @@ func (k Keeper) MinCommissionRate(ctx sdk.Context) math.LegacyDec { } // SetParams sets the x/staking module parameters. +// CONTRACT: This method performs no validation of the parameters. func (k Keeper) SetParams(ctx sdk.Context, params types.Params) error { - if err := params.Validate(); err != nil { - return err - } - store := ctx.KVStore(k.storeKey) bz, err := k.cdc.Marshal(¶ms) if err != nil { diff --git a/x/staking/testutil/helpers.go b/x/staking/testutil/helpers.go index 38f75a0d5fa4..a7a2298d039b 100644 --- a/x/staking/testutil/helpers.go +++ b/x/staking/testutil/helpers.go @@ -61,7 +61,7 @@ func (sh *Helper) CreateValidatorWithMsg(ctx context.Context, msg *stakingtypes. } func (sh *Helper) createValidator(addr sdk.ValAddress, pk cryptotypes.PubKey, coin sdk.Coin, ok bool) { - msg, err := stakingtypes.NewMsgCreateValidator(addr, pk, coin, stakingtypes.Description{}, sh.Commission, math.OneInt()) + msg, err := stakingtypes.NewMsgCreateValidator(addr, pk, coin, stakingtypes.Description{Moniker: "TestValidator"}, sh.Commission, math.OneInt()) require.NoError(sh.t, err) res, err := sh.msgSrvr.CreateValidator(sh.Ctx, msg) if ok { diff --git a/x/staking/types/data_test.go b/x/staking/types/data_test.go index d875b088fdbb..f8b927ef1e6f 100644 --- a/x/staking/types/data_test.go +++ b/x/staking/types/data_test.go @@ -5,7 +5,6 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -17,9 +16,6 @@ var ( valAddr1 = sdk.ValAddress(pk1.Address()) valAddr2 = sdk.ValAddress(pk2.Address()) valAddr3 = sdk.ValAddress(pk3.Address()) - - emptyAddr sdk.ValAddress - emptyPubkey cryptotypes.PubKey ) func init() { diff --git a/x/staking/types/msg.go b/x/staking/types/msg.go index 5a8a765020e3..6899e5e35f95 100644 --- a/x/staking/types/msg.go +++ b/x/staking/types/msg.go @@ -71,8 +71,8 @@ func (msg MsgCreateValidator) GetSignBytes() []byte { return sdk.MustSortJSON(bz) } -// ValidateBasic implements the sdk.Msg interface. -func (msg MsgCreateValidator) ValidateBasic() error { +// Validate validates the MsgCreateValidator sdk msg. +func (msg MsgCreateValidator) Validate() error { // note that unmarshaling from bech32 ensures both non-empty and valid _, err := sdk.ValAddressFromBech32(msg.ValidatorAddress) if err != nil { @@ -141,32 +141,6 @@ func (msg MsgEditValidator) GetSignBytes() []byte { return sdk.MustSortJSON(bz) } -// ValidateBasic implements the sdk.Msg interface. -func (msg MsgEditValidator) ValidateBasic() error { - if _, err := sdk.ValAddressFromBech32(msg.ValidatorAddress); err != nil { - return sdkerrors.ErrInvalidAddress.Wrapf("invalid validator address: %s", err) - } - - if msg.Description == (Description{}) { - return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "empty description") - } - - if msg.MinSelfDelegation != nil && !msg.MinSelfDelegation.IsPositive() { - return errorsmod.Wrap( - sdkerrors.ErrInvalidRequest, - "minimum self delegation must be a positive integer", - ) - } - - if msg.CommissionRate != nil { - if msg.CommissionRate.GT(math.LegacyOneDec()) || msg.CommissionRate.IsNegative() { - return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "commission rate must be between 0 and 1 (inclusive)") - } - } - - return nil -} - // NewMsgDelegate creates a new MsgDelegate instance. func NewMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Coin) *MsgDelegate { return &MsgDelegate{ @@ -188,25 +162,6 @@ func (msg MsgDelegate) GetSignBytes() []byte { return sdk.MustSortJSON(bz) } -// ValidateBasic implements the sdk.Msg interface. -func (msg MsgDelegate) ValidateBasic() error { - if _, err := sdk.AccAddressFromBech32(msg.DelegatorAddress); err != nil { - return sdkerrors.ErrInvalidAddress.Wrapf("invalid delegator address: %s", err) - } - if _, err := sdk.ValAddressFromBech32(msg.ValidatorAddress); err != nil { - return sdkerrors.ErrInvalidAddress.Wrapf("invalid validator address: %s", err) - } - - if !msg.Amount.IsValid() || !msg.Amount.Amount.IsPositive() { - return errorsmod.Wrap( - sdkerrors.ErrInvalidRequest, - "invalid delegation amount", - ) - } - - return nil -} - // NewMsgBeginRedelegate creates a new MsgBeginRedelegate instance. func NewMsgBeginRedelegate( delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, amount sdk.Coin, @@ -231,28 +186,6 @@ func (msg MsgBeginRedelegate) GetSignBytes() []byte { return sdk.MustSortJSON(bz) } -// ValidateBasic implements the sdk.Msg interface. -func (msg MsgBeginRedelegate) ValidateBasic() error { - if _, err := sdk.AccAddressFromBech32(msg.DelegatorAddress); err != nil { - return sdkerrors.ErrInvalidAddress.Wrapf("invalid delegator address: %s", err) - } - if _, err := sdk.ValAddressFromBech32(msg.ValidatorSrcAddress); err != nil { - return sdkerrors.ErrInvalidAddress.Wrapf("invalid source validator address: %s", err) - } - if _, err := sdk.ValAddressFromBech32(msg.ValidatorDstAddress); err != nil { - return sdkerrors.ErrInvalidAddress.Wrapf("invalid destination validator address: %s", err) - } - - if !msg.Amount.IsValid() || !msg.Amount.Amount.IsPositive() { - return errorsmod.Wrap( - sdkerrors.ErrInvalidRequest, - "invalid shares amount", - ) - } - - return nil -} - // NewMsgUndelegate creates a new MsgUndelegate instance. func NewMsgUndelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Coin) *MsgUndelegate { return &MsgUndelegate{ @@ -274,25 +207,6 @@ func (msg MsgUndelegate) GetSignBytes() []byte { return sdk.MustSortJSON(bz) } -// ValidateBasic implements the sdk.Msg interface. -func (msg MsgUndelegate) ValidateBasic() error { - if _, err := sdk.AccAddressFromBech32(msg.DelegatorAddress); err != nil { - return sdkerrors.ErrInvalidAddress.Wrapf("invalid delegator address: %s", err) - } - if _, err := sdk.ValAddressFromBech32(msg.ValidatorAddress); err != nil { - return sdkerrors.ErrInvalidAddress.Wrapf("invalid validator address: %s", err) - } - - if !msg.Amount.IsValid() || !msg.Amount.Amount.IsPositive() { - return errorsmod.Wrap( - sdkerrors.ErrInvalidRequest, - "invalid shares amount", - ) - } - - return nil -} - // NewMsgCancelUnbondingDelegation creates a new MsgCancelUnbondingDelegation instance. func NewMsgCancelUnbondingDelegation(delAddr sdk.AccAddress, valAddr sdk.ValAddress, creationHeight int64, amount sdk.Coin) *MsgCancelUnbondingDelegation { return &MsgCancelUnbondingDelegation{ @@ -314,32 +228,6 @@ func (msg MsgCancelUnbondingDelegation) GetSignBytes() []byte { return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg)) } -// ValidateBasic implements the sdk.Msg interface. -func (msg MsgCancelUnbondingDelegation) ValidateBasic() error { - if _, err := sdk.AccAddressFromBech32(msg.DelegatorAddress); err != nil { - return sdkerrors.ErrInvalidAddress.Wrapf("invalid delegator address: %s", err) - } - if _, err := sdk.ValAddressFromBech32(msg.ValidatorAddress); err != nil { - return sdkerrors.ErrInvalidAddress.Wrapf("invalid validator address: %s", err) - } - - if !msg.Amount.IsValid() || !msg.Amount.Amount.IsPositive() { - return errorsmod.Wrap( - sdkerrors.ErrInvalidRequest, - "invalid amount", - ) - } - - if msg.CreationHeight <= 0 { - return errorsmod.Wrap( - sdkerrors.ErrInvalidRequest, - "invalid height", - ) - } - - return nil -} - // GetSignBytes returns the raw bytes for a MsgUpdateParams message that // the expected signer needs to sign. func (m MsgUpdateParams) GetSignBytes() []byte { @@ -347,14 +235,6 @@ func (m MsgUpdateParams) GetSignBytes() []byte { return sdk.MustSortJSON(bz) } -// ValidateBasic executes sanity validation on the provided data -func (m MsgUpdateParams) ValidateBasic() error { - if _, err := sdk.AccAddressFromBech32(m.Authority); err != nil { - return errorsmod.Wrap(err, "invalid authority address") - } - return m.Params.Validate() -} - // GetSigners returns the expected signers for a MsgUpdateParams message func (m MsgUpdateParams) GetSigners() []sdk.AccAddress { addr, _ := sdk.AccAddressFromBech32(m.Authority) diff --git a/x/staking/types/msg_test.go b/x/staking/types/msg_test.go index 428e25c95594..a59a897bc42a 100644 --- a/x/staking/types/msg_test.go +++ b/x/staking/types/msg_test.go @@ -2,11 +2,8 @@ package types_test import ( "testing" - "time" "cosmossdk.io/math" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/stretchr/testify/require" @@ -19,10 +16,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking/types" ) -var ( - coinPos = sdk.NewInt64Coin(sdk.DefaultBondDenom, 1000) - coinZero = sdk.NewInt64Coin(sdk.DefaultBondDenom, 0) -) +var coinPos = sdk.NewInt64Coin(sdk.DefaultBondDenom, 1000) func TestMsgDecode(t *testing.T) { registry := codectypes.NewInterfaceRegistry() @@ -55,300 +49,3 @@ func TestMsgDecode(t *testing.T) { require.True(t, msg.Value.IsEqual(msg2.Value)) require.True(t, msg.Pubkey.Equal(msg2.Pubkey)) } - -// test ValidateBasic for MsgCreateValidator -func TestMsgCreateValidator(t *testing.T) { - commission1 := types.NewCommissionRates(math.LegacyZeroDec(), math.LegacyZeroDec(), math.LegacyZeroDec()) - commission2 := types.NewCommissionRates(math.LegacyNewDec(5), math.LegacyNewDec(5), math.LegacyNewDec(5)) - - tests := []struct { - name, moniker, identity, website, securityContact, details string - CommissionRates types.CommissionRates - minSelfDelegation math.Int - validatorAddr sdk.ValAddress - pubkey cryptotypes.PubKey - bond sdk.Coin - expectPass bool - }{ - {"basic good", "a", "b", "c", "d", "e", commission1, math.OneInt(), valAddr1, pk1, coinPos, true}, - {"partial description", "", "", "c", "", "", commission1, math.OneInt(), valAddr1, pk1, coinPos, true}, - {"empty description", "", "", "", "", "", commission2, math.OneInt(), valAddr1, pk1, coinPos, false}, - {"empty address", "a", "b", "c", "d", "e", commission2, math.OneInt(), emptyAddr, pk1, coinPos, false}, - {"empty pubkey", "a", "b", "c", "d", "e", commission1, math.OneInt(), valAddr1, emptyPubkey, coinPos, false}, - {"empty bond", "a", "b", "c", "d", "e", commission2, math.OneInt(), valAddr1, pk1, coinZero, false}, - {"nil bond", "a", "b", "c", "d", "e", commission2, math.OneInt(), valAddr1, pk1, sdk.Coin{}, false}, - {"zero min self delegation", "a", "b", "c", "d", "e", commission1, math.ZeroInt(), valAddr1, pk1, coinPos, false}, - {"negative min self delegation", "a", "b", "c", "d", "e", commission1, sdk.NewInt(-1), valAddr1, pk1, coinPos, false}, - {"delegation less than min self delegation", "a", "b", "c", "d", "e", commission1, coinPos.Amount.Add(math.OneInt()), valAddr1, pk1, coinPos, false}, - } - - for _, tc := range tests { - description := types.NewDescription(tc.moniker, tc.identity, tc.website, tc.securityContact, tc.details) - msg, err := types.NewMsgCreateValidator(tc.validatorAddr, tc.pubkey, tc.bond, description, tc.CommissionRates, tc.minSelfDelegation) - require.NoError(t, err) - if tc.expectPass { - require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - -// test ValidateBasic for MsgEditValidator -func TestMsgEditValidator(t *testing.T) { - tests := []struct { - name, moniker, identity, website, securityContact, details string - validatorAddr sdk.ValAddress - expectPass bool - minSelfDelegation math.Int - }{ - {"basic good", "a", "b", "c", "d", "e", valAddr1, true, math.OneInt()}, - {"partial description", "", "", "c", "", "", valAddr1, true, math.OneInt()}, - {"empty description", "", "", "", "", "", valAddr1, false, math.OneInt()}, - {"empty address", "a", "b", "c", "d", "e", emptyAddr, false, math.OneInt()}, - {"nil int", "a", "b", "c", "d", "e", emptyAddr, false, math.Int{}}, - } - - for _, tc := range tests { - description := types.NewDescription(tc.moniker, tc.identity, tc.website, tc.securityContact, tc.details) - newRate := math.LegacyZeroDec() - - msg := types.NewMsgEditValidator(tc.validatorAddr, description, &newRate, &tc.minSelfDelegation) - if tc.expectPass { - require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - -// test ValidateBasic for MsgDelegate -func TestMsgDelegate(t *testing.T) { - tests := []struct { - name string - delegatorAddr sdk.AccAddress - validatorAddr sdk.ValAddress - bond sdk.Coin - expectPass bool - }{ - {"basic good", sdk.AccAddress(valAddr1), valAddr2, coinPos, true}, - {"self bond", sdk.AccAddress(valAddr1), valAddr1, coinPos, true}, - {"empty delegator", sdk.AccAddress(emptyAddr), valAddr1, coinPos, false}, - {"empty validator", sdk.AccAddress(valAddr1), emptyAddr, coinPos, false}, - {"empty bond", sdk.AccAddress(valAddr1), valAddr2, coinZero, false}, - {"nil bold", sdk.AccAddress(valAddr1), valAddr2, sdk.Coin{}, false}, - } - - for _, tc := range tests { - msg := types.NewMsgDelegate(tc.delegatorAddr, tc.validatorAddr, tc.bond) - if tc.expectPass { - require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - -// test ValidateBasic for MsgUnbond -func TestMsgBeginRedelegate(t *testing.T) { - tests := []struct { - name string - delegatorAddr sdk.AccAddress - validatorSrcAddr sdk.ValAddress - validatorDstAddr sdk.ValAddress - amount sdk.Coin - expectPass bool - }{ - {"regular", sdk.AccAddress(valAddr1), valAddr2, valAddr3, sdk.NewInt64Coin(sdk.DefaultBondDenom, 1), true}, - {"zero amount", sdk.AccAddress(valAddr1), valAddr2, valAddr3, sdk.NewInt64Coin(sdk.DefaultBondDenom, 0), false}, - {"nil amount", sdk.AccAddress(valAddr1), valAddr2, valAddr3, sdk.Coin{}, false}, - {"empty delegator", sdk.AccAddress(emptyAddr), valAddr1, valAddr3, sdk.NewInt64Coin(sdk.DefaultBondDenom, 1), false}, - {"empty source validator", sdk.AccAddress(valAddr1), emptyAddr, valAddr3, sdk.NewInt64Coin(sdk.DefaultBondDenom, 1), false}, - {"empty destination validator", sdk.AccAddress(valAddr1), valAddr2, emptyAddr, sdk.NewInt64Coin(sdk.DefaultBondDenom, 1), false}, - } - - for _, tc := range tests { - msg := types.NewMsgBeginRedelegate(tc.delegatorAddr, tc.validatorSrcAddr, tc.validatorDstAddr, tc.amount) - if tc.expectPass { - require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - -// test ValidateBasic for MsgUnbond -func TestMsgUndelegate(t *testing.T) { - tests := []struct { - name string - delegatorAddr sdk.AccAddress - validatorAddr sdk.ValAddress - amount sdk.Coin - expectPass bool - }{ - {"regular", sdk.AccAddress(valAddr1), valAddr2, sdk.NewInt64Coin(sdk.DefaultBondDenom, 1), true}, - {"zero amount", sdk.AccAddress(valAddr1), valAddr2, sdk.NewInt64Coin(sdk.DefaultBondDenom, 0), false}, - {"nil amount", sdk.AccAddress(valAddr1), valAddr2, sdk.Coin{}, false}, - {"empty delegator", sdk.AccAddress(emptyAddr), valAddr1, sdk.NewInt64Coin(sdk.DefaultBondDenom, 1), false}, - {"empty validator", sdk.AccAddress(valAddr1), emptyAddr, sdk.NewInt64Coin(sdk.DefaultBondDenom, 1), false}, - } - - for _, tc := range tests { - msg := types.NewMsgUndelegate(tc.delegatorAddr, tc.validatorAddr, tc.amount) - if tc.expectPass { - require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - -func TestMsgUpdateParams(t *testing.T) { - msg := types.MsgUpdateParams{ - Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), - Params: types.DefaultParams(), - } - - require.Equal(t, []sdk.AccAddress{authtypes.NewModuleAddress(govtypes.ModuleName)}, msg.GetSigners()) -} - -func TestMsgUpdateParamsValidateBasic(t *testing.T) { - tests := []struct { - name string - msgUpdateParams types.MsgUpdateParams - expFail bool - expError string - }{ - { - "valid msg", - types.MsgUpdateParams{ - Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), - Params: types.DefaultParams(), - }, - false, - "", - }, - { - "negative unbounding time", - types.MsgUpdateParams{ - Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), - Params: types.Params{ - UnbondingTime: time.Hour * 24 * 7 * 3 * -1, - MaxEntries: types.DefaultMaxEntries, - MaxValidators: types.DefaultMaxValidators, - HistoricalEntries: types.DefaultHistoricalEntries, - MinCommissionRate: types.DefaultMinCommissionRate, - BondDenom: "denom", - }, - }, - true, - "unbonding time must be positive:", - }, - { - "cero value max validator", - types.MsgUpdateParams{ - Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), - Params: types.Params{ - UnbondingTime: time.Hour * 24 * 7 * 3, - MaxEntries: types.DefaultMaxEntries, - MaxValidators: 0, - HistoricalEntries: types.DefaultHistoricalEntries, - MinCommissionRate: types.DefaultMinCommissionRate, - BondDenom: "denom", - }, - }, - true, - "max validators must be positive:", - }, - { - "cero value max validator", - types.MsgUpdateParams{ - Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), - Params: types.Params{ - UnbondingTime: time.Hour * 24 * 7 * 3, - MaxEntries: 0, - MaxValidators: types.DefaultMaxValidators, - HistoricalEntries: types.DefaultHistoricalEntries, - MinCommissionRate: types.DefaultMinCommissionRate, - BondDenom: "denom", - }, - }, - true, - "max entries must be positive:", - }, - { - "negative commission rate", - types.MsgUpdateParams{ - Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), - Params: types.Params{ - UnbondingTime: time.Hour * 24 * 7 * 3, - MaxEntries: types.DefaultMaxEntries, - MaxValidators: types.DefaultMaxValidators, - HistoricalEntries: types.DefaultHistoricalEntries, - MinCommissionRate: math.LegacyNewDec(-1), - BondDenom: "denom", - }, - }, - true, - "minimum commission rate cannot be negative:", - }, - { - "negative commission rate", - types.MsgUpdateParams{ - Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), - Params: types.Params{ - UnbondingTime: time.Hour * 24 * 7 * 3, - MaxEntries: types.DefaultMaxEntries, - MaxValidators: types.DefaultMaxValidators, - HistoricalEntries: types.DefaultHistoricalEntries, - MinCommissionRate: math.LegacyNewDec(2), - BondDenom: "denom", - }, - }, - true, - "minimum commission rate cannot be greater than 100", - }, - { - "blank bonddenom", - types.MsgUpdateParams{ - Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), - Params: types.Params{ - UnbondingTime: time.Hour * 24 * 7 * 3, - MaxEntries: types.DefaultMaxEntries, - MaxValidators: types.DefaultMaxValidators, - HistoricalEntries: types.DefaultHistoricalEntries, - MinCommissionRate: types.DefaultMinCommissionRate, - BondDenom: "", - }, - }, - true, - "bond denom cannot be blank", - }, - { - "Invalid authority", - types.MsgUpdateParams{ - Authority: "invalid", - Params: types.Params{ - UnbondingTime: time.Hour * 24 * 7 * 3, - MaxEntries: types.DefaultMaxEntries, - MaxValidators: types.DefaultMaxValidators, - HistoricalEntries: types.DefaultHistoricalEntries, - MinCommissionRate: types.DefaultMinCommissionRate, - BondDenom: "denom", - }, - }, - true, - "invalid authority address", - }, - } - - for _, tc := range tests { - err := tc.msgUpdateParams.ValidateBasic() - if tc.expFail { - require.Error(t, err) - require.Contains(t, err.Error(), tc.expError) - } else { - require.NoError(t, err) - } - } -}