diff --git a/CHANGELOG.md b/CHANGELOG.md index d24d5d04bf51..4fdd78122573 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Breaking Changes +* (x/staking) [#25724](https://github.com/cosmos/cosmos-sdk/issues/25724) Validate `BondDenom` in `MsgUpdateParams` to prevent setting non-existent or zero-supply denoms. * [#25778](https://github.com/cosmos/cosmos-sdk/pull/25778) Update `log` to log v2. * [#25090](https://github.com/cosmos/cosmos-sdk/pull/25090) Moved deprecated modules to `./contrib`. These modules are still available but will no longer be actively maintained or supported in the Cosmos SDK Bug Bounty program. * `x/group` diff --git a/x/staking/keeper/msg_server.go b/x/staking/keeper/msg_server.go index 6e3afe01d622..33a4f26c982f 100644 --- a/x/staking/keeper/msg_server.go +++ b/x/staking/keeper/msg_server.go @@ -594,6 +594,14 @@ func (k msgServer) UpdateParams(ctx context.Context, msg *types.MsgUpdateParams) return nil, err } + // Validate that the bond denom exists on-chain by checking if it has supply. + // This prevents governance from setting bond_denom to a non-existent denom, + // which would place the chain in an unsafe state. + supply := k.bankKeeper.GetSupply(ctx, msg.Params.BondDenom) + if supply.IsZero() { + return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "bond denom %s does not exist or has zero supply", msg.Params.BondDenom) + } + // 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 bd44fa311707..8fb824f0d7bf 100644 --- a/x/staking/keeper/msg_server_test.go +++ b/x/staking/keeper/msg_server_test.go @@ -995,6 +995,7 @@ func (s *KeeperTestSuite) TestMsgUpdateParams() { input *stakingtypes.MsgUpdateParams expErr bool expErrMsg string + setup func() }{ { name: "valid params", @@ -1003,6 +1004,9 @@ func (s *KeeperTestSuite) TestMsgUpdateParams() { Params: stakingtypes.DefaultParams(), }, expErr: false, + setup: func() { + s.bankKeeper.EXPECT().GetSupply(gomock.Any(), stakingtypes.DefaultParams().BondDenom).Return(sdk.NewInt64Coin(stakingtypes.DefaultParams().BondDenom, 1000000)) + }, }, { name: "invalid authority", @@ -1061,6 +1065,25 @@ func (s *KeeperTestSuite) TestMsgUpdateParams() { expErr: true, expErrMsg: "bond denom cannot be blank", }, + { + name: "invalid bond denom - zero supply", + input: &stakingtypes.MsgUpdateParams{ + Authority: keeper.GetAuthority(), + Params: stakingtypes.Params{ + MinCommissionRate: stakingtypes.DefaultMinCommissionRate, + UnbondingTime: stakingtypes.DefaultUnbondingTime, + MaxValidators: stakingtypes.DefaultMaxValidators, + MaxEntries: stakingtypes.DefaultMaxEntries, + HistoricalEntries: stakingtypes.DefaultHistoricalEntries, + BondDenom: "ghosttoken", + }, + }, + expErr: true, + expErrMsg: "does not exist or has zero supply", + setup: func() { + s.bankKeeper.EXPECT().GetSupply(gomock.Any(), "ghosttoken").Return(sdk.NewInt64Coin("ghosttoken", 0)) + }, + }, { name: "max validators must be positive", input: &stakingtypes.MsgUpdateParams{ @@ -1113,6 +1136,11 @@ func (s *KeeperTestSuite) TestMsgUpdateParams() { for _, tc := range testCases { s.T().Run(tc.name, func(t *testing.T) { + // Setup mocks if specified + if tc.setup != nil { + tc.setup() + } + _, err := msgServer.UpdateParams(ctx, tc.input) if tc.expErr { require.Error(err)