diff --git a/x/staking/keeper/msg_server.go b/x/staking/keeper/msg_server.go index 3642cef1e5a..c9175f4c263 100644 --- a/x/staking/keeper/msg_server.go +++ b/x/staking/keeper/msg_server.go @@ -74,6 +74,11 @@ func (k msgServer) CreateValidator(goCtx context.Context, msg *types.MsgCreateVa if err != nil { return nil, err } + + if msg.Commission.Rate.LT(k.MinCommissionRate(ctx)) { + return nil, sdkerrors.Wrapf(types.ErrCommissionLTMinRate, "cannot set validator commission to less than minimum rate of %s", k.MinCommissionRate(ctx)) + } + commission := types.NewCommissionWithTime( msg.Commission.Rate, msg.Commission.MaxRate, msg.Commission.MaxChangeRate, ctx.BlockHeader().Time, diff --git a/x/staking/keeper/msg_server_test.go b/x/staking/keeper/msg_server_test.go new file mode 100644 index 00000000000..5557f77d4bf --- /dev/null +++ b/x/staking/keeper/msg_server_test.go @@ -0,0 +1,42 @@ +package keeper_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +func TestCreateValidatorWithLessThanMinCommission(t *testing.T) { + PKS := simapp.CreateTestPubKeys(1) + valConsPk1 := PKS[0] + + app := simapp.Setup(false) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + addrs := simapp.AddTestAddrs(app, ctx, 3, sdk.NewInt(1234)) + + // set min commission rate to non-zero + params := app.StakingKeeper.GetParams(ctx) + params.MinCommissionRate = sdk.NewDecWithPrec(1, 2) + app.StakingKeeper.SetParams(ctx, params) + + // create validator with 0% commission + msg, err := stakingtypes.NewMsgCreateValidator( + sdk.ValAddress(addrs[0]), + valConsPk1, + sdk.NewInt64Coin(sdk.DefaultBondDenom, 100), + stakingtypes.Description{}, + stakingtypes.NewCommissionRates(sdk.NewDec(0), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)), + sdk.OneInt()) + require.NoError(t, err) + + sh := staking.NewHandler(app.StakingKeeper) + _, err = sh(ctx, msg) + require.Error(t, err) +} diff --git a/x/staking/keeper/validator.go b/x/staking/keeper/validator.go index 8539cbe0ec8..b3d9bb917a2 100644 --- a/x/staking/keeper/validator.go +++ b/x/staking/keeper/validator.go @@ -148,6 +148,23 @@ func (k Keeper) UpdateValidatorCommission(ctx sdk.Context, return commission, nil } +// MustUpdateValidatorCommission updates a validator's commission rate, +// ignoring the max change rate. +func (k Keeper) MustUpdateValidatorCommission(ctx sdk.Context, + validator types.Validator, newRate sdk.Dec) (types.Commission, error) { + commission := validator.Commission + blockTime := ctx.BlockHeader().Time + + if newRate.LT(k.MinCommissionRate(ctx)) { + return commission, fmt.Errorf("cannot set validator commission to less than minimum rate of %s", k.MinCommissionRate(ctx)) + } + + commission.Rate = newRate + commission.UpdateTime = blockTime + + return commission, nil +} + // remove the validator record and associated indexes // except for the bonded validator index which is only handled in ApplyAndReturnTendermintUpdates // TODO, this function panics, and it's not good. diff --git a/x/staking/types/errors.go b/x/staking/types/errors.go index 777941e53cf..a9a6e43e35b 100644 --- a/x/staking/types/errors.go +++ b/x/staking/types/errors.go @@ -49,4 +49,5 @@ var ( ErrInvalidHistoricalInfo = sdkerrors.Register(ModuleName, 37, "invalid historical info") ErrNoHistoricalInfo = sdkerrors.Register(ModuleName, 38, "no historical info found") ErrEmptyValidatorPubKey = sdkerrors.Register(ModuleName, 39, "empty validator public key") + ErrCommissionLTMinRate = sdkerrors.Register(ModuleName, 40, "commission cannot be less than min rate") )