diff --git a/PENDING.md b/PENDING.md index 07d9a50046c4..3207cce49d54 100644 --- a/PENDING.md +++ b/PENDING.md @@ -96,5 +96,6 @@ CLI flag. where validator is unexpectedly slashed throwing off test calculations * [\#3411] Include the `RequestInitChain.Time` in the block header init during `InitChain`. +* [\#3726] Cap(clip) reward to remaining coins in AllocateTokens. ### Tendermint diff --git a/types/dec_coin.go b/types/dec_coin.go index 538ea1c8c241..fe0dc9da7488 100644 --- a/types/dec_coin.go +++ b/types/dec_coin.go @@ -278,6 +278,20 @@ func (coins DecCoins) SafeSub(coinsB DecCoins) (DecCoins, bool) { return diff, diff.IsAnyNegative() } +// Trims any denom amount from coin which exceeds that of coinB, +// such that (coin.Cap(coinB)).IsLTE(coinB). +func (coins DecCoins) Cap(coinsB DecCoins) DecCoins { + res := make([]DecCoin, len(coins)) + for i, coin := range coins { + minCoin := DecCoin{ + Denom: coin.Denom, + Amount: MinDec(coin.Amount, coinsB.AmountOf(coin.Denom)), + } + res[i] = minCoin + } + return removeZeroDecCoins(res) +} + // IsAnyNegative returns true if there is at least one coin whose amount // is negative; returns false otherwise. It returns false if the DecCoins set // is empty too. diff --git a/types/dec_coin_test.go b/types/dec_coin_test.go index a5a0b7876de8..d5ddec2da5da 100644 --- a/types/dec_coin_test.go +++ b/types/dec_coin_test.go @@ -224,3 +224,35 @@ func TestDecCoinsString(t *testing.T) { require.Equal(t, tc.expected, out, "unexpected result for test case #%d, input: %v", i, tc.input) } } + +func TestDecCoinsCap(t *testing.T) { + testCases := []struct { + input1 string + input2 string + expectedResult string + }{ + {"", "", ""}, + {"1.0stake", "", ""}, + {"1.0stake", "1.0stake", "1.0stake"}, + {"", "1.0stake", ""}, + {"1.0stake", "", ""}, + {"2.0stake,1.0trope", "1.9stake", "1.9stake"}, + {"2.0stake,1.0trope", "2.1stake", "2.0stake"}, + {"2.0stake,1.0trope", "0.9trope", "0.9trope"}, + {"2.0stake,1.0trope", "1.9stake,0.9trope", "1.9stake,0.9trope"}, + {"2.0stake,1.0trope", "1.9stake,0.9trope,20.0other", "1.9stake,0.9trope"}, + {"2.0stake,1.0trope", "1.0other", ""}, + } + + for i, tc := range testCases { + in1, err := ParseDecCoins(tc.input1) + require.NoError(t, err, "unexpected parse error in %v", i) + in2, err := ParseDecCoins(tc.input2) + require.NoError(t, err, "unexpected parse error in %v", i) + exr, err := ParseDecCoins(tc.expectedResult) + require.NoError(t, err, "unexpected parse error in %v", i) + + require.True(t, in1.Cap(in2).IsEqual(exr), "in1.cap(in2) != exr in %v", i) + // require.Equal(t, tc.expectedResult, in1.Cap(in2).String(), "in1.cap(in2) != exr in %v", i) + } +} diff --git a/x/distribution/keeper/allocation.go b/x/distribution/keeper/allocation.go index 3f6c423c0291..f32e47d0256e 100644 --- a/x/distribution/keeper/allocation.go +++ b/x/distribution/keeper/allocation.go @@ -48,10 +48,11 @@ func (k Keeper) AllocateTokens(ctx sdk.Context, sumPrecommitPower, totalPower in for _, vote := range votes { validator := k.stakingKeeper.ValidatorByConsAddr(ctx, vote.Validator.Address) - // TODO likely we should only reward validators who actually signed the block. + // TODO consider microslashing for missing votes. // ref https://github.com/cosmos/cosmos-sdk/issues/2525#issuecomment-430838701 powerFraction := sdk.NewDec(vote.Validator.Power).QuoTruncate(sdk.NewDec(totalPower)) reward := feesCollected.MulDecTruncate(voteMultiplier).MulDecTruncate(powerFraction) + reward = reward.Cap(remaining) k.AllocateTokensToValidator(ctx, validator, reward) remaining = remaining.Sub(reward) }