Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ Ref: https://keepachangelog.com/en/1.0.0/

# Changelog

## [Unreleased]
## [v0.50.14](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.50.14) - 2025-07-08

### Bug Fixes

* (baseapp) [#21979](https://github.com/cosmos/cosmos-sdk/pull/21979) Create CheckTxHandler to allow extending the logic of CheckTx.
* (baseapp) [#24074](https://github.com/cosmos/cosmos-sdk/pull/24074) Use CometBFT's ComputeProtoSizeForTxs in defaultTxSelector.SelectTxForProposal for consistency.
Expand Down
17 changes: 8 additions & 9 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
# Cosmos SDK v0.50.12 Release Notes

💬 [**Release Discussion**](https://github.com/orgs/cosmos/discussions/58)
# Cosmos SDK v0.50.14 Release Notes

## 🚀 Highlights

This patch release fixes [GHSA-x5vx-95h7-rv4p](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-x5vx-95h7-rv4p).
It resolves a `x/group` module issue that can halt chain when handling a malicious proposal.
Only users of the `x/group` module are affected by this issue.
This patch release fixes [GHSA-p22h-3m2v-cmgh](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-p22h-3m2v-cmgh).
It resolves a `x/distribution` module issue that can halt chains when the historical rewards pool overflows.
Chains using the `x/distribution` module are affected by this issue.

We recommended upgrading to this patch release as soon as possible.

We recommended to upgrade to this patch release as soon as possible.
When upgrading from <= v0.50.11, please use a chain upgrade to ensure that 2/3 of the validator power upgrade to v0.50.12.
This patch is state-breaking; chains must perform a coordinated upgrade. This patch cannot be applied in a rolling upgrade.

## 📝 Changelog

Check out the [changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.50.12/CHANGELOG.md) for an exhaustive list of changes, or [compare changes](https://github.com/cosmos/cosmos-sdk/compare/v0.50.11...v0.50.12) from the last release.
Check out the [changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.50.14/CHANGELOG.md) for an exhaustive list of changes or [compare changes](https://github.com/cosmos/cosmos-sdk/compare/v0.50.13...v0.50.14) from the last release.
127 changes: 122 additions & 5 deletions tests/integration/distribution/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
package keeper_test

import (
"encoding/hex"
"fmt"
"testing"

cmtabcitypes "github.com/cometbft/cometbft/abci/types"
cmtproto "github.com/cometbft/cometbft/api/cometbft/types/v1"
"github.com/stretchr/testify/require"
"gotest.tools/v3/assert"

"cosmossdk.io/core/appmodule"
"cosmossdk.io/log"
"cosmossdk.io/math"
storetypes "cosmossdk.io/store/types"
cmtabcitypes "github.com/cometbft/cometbft/abci/types"
cmtproto "github.com/cometbft/cometbft/api/cometbft/types/v1"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/stretchr/testify/require"
"gotest.tools/v3/assert"

"github.com/cosmos/cosmos-sdk/codec"
addresscodec "github.com/cosmos/cosmos-sdk/codec/address"
Expand All @@ -30,6 +32,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/distribution"
distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper"
distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
"github.com/cosmos/cosmos-sdk/x/staking"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
stakingtestutil "github.com/cosmos/cosmos-sdk/x/staking/testutil"
Expand Down Expand Up @@ -81,6 +84,7 @@ func initFixture(t testing.TB) *fixture {

maccPerms := map[string][]string{
distrtypes.ModuleName: {authtypes.Minter},
minttypes.ModuleName: {authtypes.Minter},
stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking},
stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking},
}
Expand Down Expand Up @@ -144,10 +148,18 @@ func initFixture(t testing.TB) *fixture {
})

sdkCtx := sdk.UnwrapSDKContext(integrationApp.Context())
require.NoError(t, stakingKeeper.SetParams(sdkCtx, stakingtypes.DefaultParams()))

stakingKeeper.SetHooks(
stakingtypes.NewMultiStakingHooks(
distrKeeper.Hooks(), // Needed for reward distribution on staking events
),
)

// Register MsgServer and QueryServer
distrtypes.RegisterMsgServer(integrationApp.MsgServiceRouter(), distrkeeper.NewMsgServerImpl(distrKeeper))
distrtypes.RegisterQueryServer(integrationApp.QueryHelper(), distrkeeper.NewQuerier(distrKeeper))
stakingtypes.RegisterMsgServer(integrationApp.MsgServiceRouter(), stakingkeeper.NewMsgServerImpl(stakingKeeper))

return &fixture{
app: integrationApp,
Expand Down Expand Up @@ -996,3 +1008,108 @@ func TestMsgDepositValidatorRewardsPool(t *testing.T) {
})
}
}

func TestCannotDepositIfRewardPoolFull(t *testing.T) {
f := initFixture(t)
err := f.distrKeeper.FeePool.Set(f.sdkCtx, distrtypes.FeePool{
CommunityPool: sdk.NewDecCoins(sdk.DecCoin{Denom: sdk.DefaultBondDenom, Amount: math.LegacyNewDec(10000)}),
})
assert.NilError(t, err)
assert.NilError(t, f.distrKeeper.Params.Set(f.sdkCtx, distrtypes.DefaultParams()))
_, err = f.distrKeeper.FeePool.Get(f.sdkCtx)
assert.NilError(t, err)

ctx := f.sdkCtx.WithIsCheckTx(false).WithBlockHeight(1)
populateValidators(t, f)

valPubKey := newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB53")
operatorAddr := sdk.ValAddress(valPubKey.Address())

tstaking := stakingtestutil.NewHelper(t, ctx, f.stakingKeeper)

assert.NilError(t, f.bankKeeper.MintCoins(f.sdkCtx, minttypes.ModuleName, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt))))
assert.NilError(t, f.bankKeeper.SendCoinsFromModuleToAccount(f.sdkCtx, minttypes.ModuleName, sdk.AccAddress(operatorAddr), sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt))))

tstaking.Commission = stakingtypes.NewCommissionRates(math.LegacyZeroDec(), math.LegacyZeroDec(), math.LegacyZeroDec())
selfDelegation := math.OneInt()
tstaking.CreateValidator(operatorAddr, valPubKey, selfDelegation, true)

_, err = f.stakingKeeper.EndBlocker(f.sdkCtx)
assert.NilError(t, err)

maxSupply, ok := math.NewIntFromString("105792089237316195423570985008687907853269984665640564039457584007913129639934")
assert.Assert(t, ok)

maxCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, maxSupply))
assert.NilError(t, f.bankKeeper.MintCoins(f.sdkCtx, minttypes.ModuleName, maxCoins))
assert.NilError(t, f.bankKeeper.SendCoinsFromModuleToAccount(f.sdkCtx, minttypes.ModuleName, sdk.AccAddress(operatorAddr), maxCoins))

fundValMsg := &distrtypes.MsgDepositValidatorRewardsPool{
Depositor: sdk.AccAddress(operatorAddr).String(),
ValidatorAddress: operatorAddr.String(),
Amount: maxCoins,
}

// fund the rewards pool. this will set the current rewards.
_, err = f.app.RunMsg(
fundValMsg,
integration.WithAutomaticFinalizeBlock(),
integration.WithAutomaticCommit(),
)
assert.NilError(t, err)

// now we delegate to increment the validator period, setting the current rewards to the previous.
power := int64(1)
delegationAmount := sdk.TokensFromConsensusPower(power, sdk.DefaultPowerReduction)
delMsg := stakingtypes.NewMsgDelegate(sdk.AccAddress(operatorAddr).String(), operatorAddr.String(), sdk.NewCoin(sdk.DefaultBondDenom, delegationAmount))
_, err = f.app.RunMsg(
delMsg,
integration.WithAutomaticFinalizeBlock(),
integration.WithAutomaticCommit(),
)
assert.NilError(t, err)

// this should fail since this amount cannot be added to the previous amount without overflowing.
_, err = f.app.RunMsg(
fundValMsg,
integration.WithAutomaticFinalizeBlock(),
integration.WithAutomaticCommit(),
)
assert.ErrorContains(t, err, "unable to deposit coins")
}

var (
pubkeys = []cryptotypes.PubKey{
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB50"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52"),
}

valAddresses = []sdk.ValAddress{
sdk.ValAddress(pubkeys[0].Address()),
sdk.ValAddress(pubkeys[1].Address()),
sdk.ValAddress(pubkeys[2].Address()),
}

initAmt = sdk.TokensFromConsensusPower(1000000, sdk.DefaultPowerReduction)
initCoins = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt))
)

func populateValidators(t assert.TestingT, f *fixture) {
totalSupplyAmt := initAmt.MulRaw(int64(len(valAddresses)))
totalSupply := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, totalSupplyAmt))
assert.NilError(t, f.bankKeeper.MintCoins(f.sdkCtx, distrtypes.ModuleName, totalSupply))

for _, addr := range valAddresses {
assert.NilError(t, f.bankKeeper.SendCoinsFromModuleToAccount(f.sdkCtx, distrtypes.ModuleName, (sdk.AccAddress)(addr), initCoins))
}
}

func newPubKey(pk string) (res cryptotypes.PubKey) {
pkBytes, err := hex.DecodeString(pk)
if err != nil {
panic(err)
}
pubkey := &ed25519.PubKey{Key: pkBytes}
return pubkey
}
31 changes: 31 additions & 0 deletions x/distribution/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper

import (
"context"
"fmt"

"github.com/hashicorp/go-metrics"

Expand Down Expand Up @@ -211,6 +212,36 @@ func (k msgServer) DepositValidatorRewardsPool(ctx context.Context, msg *types.M
return nil, err
}

// make sure the reward pool isn't already full.
if !validator.GetTokens().IsZero() {
rewards, err := k.GetValidatorCurrentRewards(ctx, valAddr)
if err != nil {
return nil, err
}
current := rewards.Rewards
historical, err := k.GetValidatorHistoricalRewards(ctx, valAddr, rewards.Period-1)
if err != nil {
return nil, err
}
if !historical.CumulativeRewardRatio.IsZero() {
rewardRatio := historical.CumulativeRewardRatio
var panicErr error
func() {
defer func() {
if r := recover(); r != nil {
panicErr = fmt.Errorf("deposit is too large: %v", r)
}
}()
rewardRatio.Add(current...)
}()

// Check if the deferred function caught a panic
if panicErr != nil {
return nil, fmt.Errorf("unable to deposit coins: %w", panicErr)
}
}
}

logger := k.Logger(ctx)
logger.Info(
"transferred from rewards to validator rewards pool",
Expand Down
Loading