Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Bug Fixes

* (enterprise/poa) [#26814](https://github.com/cosmos/cosmos-sdk/pull/26814) Do not panic in fee checkpointing when the rounded per-validator allocations exceed the module balance by dust; subtract safely and allocate only the denoms that have unallocated fees.
* (client/tx) [#26759](https://github.com/cosmos/cosmos-sdk/issues/26759) Populate the multisig bit array in simulation txs so `--gas auto` works for multisig senders.
* (blockstm) [#26772](https://github.com/cosmos/cosmos-sdk/pull/26772) Panic with a descriptive error when accessing an unregistered store instead of silently using store index zero.
* (x/genutil) [#26741](https://github.com/cosmos/cosmos-sdk/issues/26741) Preserve vote extension enable height when exporting genesis state.
Expand Down
16 changes: 13 additions & 3 deletions enterprise/poa/x/poa/keeper/distribution.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,21 @@ func (k *Keeper) getUnallocatedFees(ctx sdk.Context) (unallocated sdk.DecCoins,
return nil, err
}

// Calculate unallocated fees = fee_collector - total_allocated
unallocated = feeCollectorBalanceDec.Sub(totalAllocated)
// Calculate unallocated fees = fee_collector - total_allocated.
// The per-validator shares are rounded, so their sum can exceed the
// balance of a denom by a few units of dust. DecCoins.Sub panics on a
// negative result, so subtract safely and keep only the denoms that still
// have something to allocate.
diff, _ := feeCollectorBalanceDec.SafeSub(totalAllocated)
unallocated = sdk.DecCoins{}
for _, coin := range diff {
if coin.IsPositive() {
unallocated = append(unallocated, coin)
}
}

// If no unallocated fees, return zero
if unallocated.IsZero() || !unallocated.IsAllPositive() {
if unallocated.IsZero() {
return sdk.DecCoins{}, nil
}

Expand Down
43 changes: 43 additions & 0 deletions enterprise/poa/x/poa/keeper/distribution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1156,6 +1156,49 @@ func TestGetUnallocatedFees(t *testing.T) {
})
}

func TestGetUnallocatedFeesRoundingOvershoot(t *testing.T) {
// Six equal-power validators splitting 1 unit: each share rounds up to
// 0.166666666666666667, so the allocated total (1.000000000000000002)
// exceeds the module balance. The next checkpoint must not panic.
f := setupTest(t)
var validatorAddr string
for i := 1; i <= 6; i++ {
validatorAddr, _ = createValidator(t, f, i, 100)
}

fees := sdk.NewCoins(sdk.NewInt64Coin("stake", 1))
require.NoError(t, f.bankKeeper.MintCoins(f.ctx, poatypes.ModuleName, fees))
require.NoError(t, f.poaKeeper.checkpointAllValidators(f.ctx))

totalAllocated, err := f.poaKeeper.getTotalAllocated(f.ctx)
require.NoError(t, err)
require.True(t, totalAllocated.AmountOf("stake").GT(math.LegacyNewDec(1)))

// overshoot is treated as nothing left to allocate
unallocated, err := f.poaKeeper.getUnallocatedFees(f.ctx)
require.NoError(t, err)
require.True(t, unallocated.IsZero())

// new fees in another denom are still allocated despite the overshoot
require.NoError(t, f.bankKeeper.MintCoins(f.ctx, poatypes.ModuleName, sdk.NewCoins(sdk.NewInt64Coin("atom", 600))))
unallocated, err = f.poaKeeper.getUnallocatedFees(f.ctx)
require.NoError(t, err)
require.Equal(t, sdk.DecCoins{sdk.NewDecCoinFromDec("atom", math.LegacyNewDec(600))}, unallocated)

// second checkpoint before any new fees arrive
require.NoError(t, f.poaKeeper.checkpointAllValidators(f.ctx))

// query path
_, err = f.poaKeeper.WithdrawableFees(f.ctx, &poatypes.QueryWithdrawableFeesRequest{OperatorAddress: validatorAddr})
require.NoError(t, err)

// withdraw path
validatorAddrSdk, err := sdk.AccAddressFromBech32(validatorAddr)
require.NoError(t, err)
_, err = f.poaKeeper.WithdrawValidatorFees(f.ctx, validatorAddrSdk)
require.NoError(t, err)
}

func TestAdjustTotalAllocated(t *testing.T) {
t.Run("increases total allocated", func(t *testing.T) {
f := setupTest(t)
Expand Down