cosmos-sdk/x/distribution/keeper/delegation_test.go

977 lines
36 KiB
Go

package keeper_test
import (
"testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/testutil"
sdk "github.com/cosmos/cosmos-sdk/types"
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/distribution/keeper"
distrtestutil "github.com/cosmos/cosmos-sdk/x/distribution/testutil"
disttypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)
func TestCalculateRewardsBasic(t *testing.T) {
ctrl := gomock.NewController(t)
key := sdk.NewKVStoreKey(disttypes.StoreKey)
testCtx := testutil.DefaultContextWithDB(t, key, sdk.NewTransientStoreKey("transient_test"))
encCfg := moduletestutil.MakeTestEncodingConfig(distribution.AppModuleBasic{})
ctx := testCtx.Ctx.WithBlockHeader(tmproto.Header{Height: 1})
bankKeeper := distrtestutil.NewMockBankKeeper(ctrl)
stakingKeeper := distrtestutil.NewMockStakingKeeper(ctrl)
accountKeeper := distrtestutil.NewMockAccountKeeper(ctrl)
accountKeeper.EXPECT().GetModuleAddress("distribution").Return(distrAcc.GetAddress())
distrKeeper := keeper.NewKeeper(
encCfg.Codec,
key,
accountKeeper,
bankKeeper,
stakingKeeper,
"fee_collector",
authtypes.NewModuleAddress("gov").String(),
)
// reset fee pool
distrKeeper.SetFeePool(ctx, disttypes.InitialFeePool())
distrKeeper.SetParams(ctx, disttypes.DefaultParams())
// create validator with 50% commission
valAddr := sdk.ValAddress(valConsAddr0)
addr := sdk.AccAddress(valAddr)
val, err := distrtestutil.CreateValidator(valConsPk0, sdk.NewInt(1000))
require.NoError(t, err)
val.Commission = stakingtypes.NewCommission(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), math.LegacyNewDec(0))
// delegation mock
del := stakingtypes.NewDelegation(addr, valAddr, val.DelegatorShares)
stakingKeeper.EXPECT().Validator(gomock.Any(), valAddr).Return(val).Times(3)
stakingKeeper.EXPECT().Delegation(gomock.Any(), addr, valAddr).Return(del)
// run the necessary hooks manually (given that we are not running an actual staking module)
err = distrtestutil.CallCreateValidatorHooks(ctx, distrKeeper, addr, valAddr)
require.NoError(t, err)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// historical count should be 2 (once for validator init, once for delegation init)
require.Equal(t, uint64(2), distrKeeper.GetValidatorHistoricalReferenceCount(ctx))
// end period
endingPeriod := distrKeeper.IncrementValidatorPeriod(ctx, val)
// historical count should be 2 still
require.Equal(t, uint64(2), distrKeeper.GetValidatorHistoricalReferenceCount(ctx))
// calculate delegation rewards
rewards := distrKeeper.CalculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards should be zero
require.True(t, rewards.IsZero())
// allocate some rewards
initial := int64(10)
tokens := sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: math.LegacyNewDec(initial)}}
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
// end period
endingPeriod = distrKeeper.IncrementValidatorPeriod(ctx, val)
// calculate delegation rewards
rewards = distrKeeper.CalculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards should be half the tokens
require.Equal(t, sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: math.LegacyNewDec(initial / 2)}}, rewards)
// commission should be the other half
require.Equal(t, sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: math.LegacyNewDec(initial / 2)}}, distrKeeper.GetValidatorAccumulatedCommission(ctx, valAddr).Commission)
}
func TestCalculateRewardsAfterSlash(t *testing.T) {
ctrl := gomock.NewController(t)
key := sdk.NewKVStoreKey(disttypes.StoreKey)
testCtx := testutil.DefaultContextWithDB(t, key, sdk.NewTransientStoreKey("transient_test"))
encCfg := moduletestutil.MakeTestEncodingConfig(distribution.AppModuleBasic{})
ctx := testCtx.Ctx.WithBlockHeader(tmproto.Header{Height: 1})
bankKeeper := distrtestutil.NewMockBankKeeper(ctrl)
stakingKeeper := distrtestutil.NewMockStakingKeeper(ctrl)
accountKeeper := distrtestutil.NewMockAccountKeeper(ctrl)
accountKeeper.EXPECT().GetModuleAddress("distribution").Return(distrAcc.GetAddress())
distrKeeper := keeper.NewKeeper(
encCfg.Codec,
key,
accountKeeper,
bankKeeper,
stakingKeeper,
"fee_collector",
authtypes.NewModuleAddress("gov").String(),
)
// reset fee pool
distrKeeper.SetFeePool(ctx, disttypes.InitialFeePool())
distrKeeper.SetParams(ctx, disttypes.DefaultParams())
// create validator with 50% commission
valAddr := sdk.ValAddress(valConsAddr0)
addr := sdk.AccAddress(valAddr)
valPower := int64(100)
stake := sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction)
val, err := distrtestutil.CreateValidator(valConsPk0, stake)
require.NoError(t, err)
val.Commission = stakingtypes.NewCommission(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), math.LegacyNewDec(0))
del := stakingtypes.NewDelegation(addr, valAddr, val.DelegatorShares)
// set mock calls
stakingKeeper.EXPECT().Validator(gomock.Any(), valAddr).Return(val).Times(4)
stakingKeeper.EXPECT().Delegation(gomock.Any(), addr, valAddr).Return(del)
// run the necessary hooks manually (given that we are not running an actual staking module)
err = distrtestutil.CallCreateValidatorHooks(ctx, distrKeeper, addr, valAddr)
require.NoError(t, err)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// end period
endingPeriod := distrKeeper.IncrementValidatorPeriod(ctx, val)
// calculate delegation rewards
rewards := distrKeeper.CalculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards should be zero
require.True(t, rewards.IsZero())
// start out block height
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
// slash the validator by 50% (simulated with manual calls; we assume the validator is bonded)
slashedTokens := distrtestutil.SlashValidator(
ctx,
valConsAddr0,
ctx.BlockHeight(),
valPower,
sdk.NewDecWithPrec(5, 1),
&val,
&distrKeeper,
)
require.True(t, slashedTokens.IsPositive(), "expected positive slashed tokens, got: %s", slashedTokens)
// increase block height
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
// allocate some rewards
initial := sdk.TokensFromConsensusPower(10, sdk.DefaultPowerReduction)
tokens := sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: sdk.NewDecFromInt(initial)}}
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
// end period
endingPeriod = distrKeeper.IncrementValidatorPeriod(ctx, val)
// calculate delegation rewards
rewards = distrKeeper.CalculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards should be half the tokens
require.Equal(t, sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: sdk.NewDecFromInt(initial.QuoRaw(2))}}, rewards)
// commission should be the other half
require.Equal(t, sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: sdk.NewDecFromInt(initial.QuoRaw(2))}},
distrKeeper.GetValidatorAccumulatedCommission(ctx, valAddr).Commission)
}
func TestCalculateRewardsAfterManySlashes(t *testing.T) {
ctrl := gomock.NewController(t)
key := sdk.NewKVStoreKey(disttypes.StoreKey)
testCtx := testutil.DefaultContextWithDB(t, key, sdk.NewTransientStoreKey("transient_test"))
encCfg := moduletestutil.MakeTestEncodingConfig(distribution.AppModuleBasic{})
ctx := testCtx.Ctx.WithBlockHeader(tmproto.Header{Height: 1})
bankKeeper := distrtestutil.NewMockBankKeeper(ctrl)
stakingKeeper := distrtestutil.NewMockStakingKeeper(ctrl)
accountKeeper := distrtestutil.NewMockAccountKeeper(ctrl)
accountKeeper.EXPECT().GetModuleAddress("distribution").Return(distrAcc.GetAddress())
distrKeeper := keeper.NewKeeper(
encCfg.Codec,
key,
accountKeeper,
bankKeeper,
stakingKeeper,
"fee_collector",
authtypes.NewModuleAddress("gov").String(),
)
// reset fee pool
distrKeeper.SetFeePool(ctx, disttypes.InitialFeePool())
distrKeeper.SetParams(ctx, disttypes.DefaultParams())
// create validator with 50% commission
valAddr := sdk.ValAddress(valConsAddr0)
addr := sdk.AccAddress(valAddr)
valPower := int64(100)
stake := sdk.TokensFromConsensusPower(valPower, sdk.DefaultPowerReduction)
val, err := distrtestutil.CreateValidator(valConsPk0, stake)
require.NoError(t, err)
val.Commission = stakingtypes.NewCommission(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), math.LegacyNewDec(0))
// delegation mocks
del := stakingtypes.NewDelegation(addr, valAddr, val.DelegatorShares)
stakingKeeper.EXPECT().Validator(gomock.Any(), valAddr).Return(val).Times(4)
stakingKeeper.EXPECT().Delegation(gomock.Any(), addr, valAddr).Return(del)
// run the necessary hooks manually (given that we are not running an actual staking module)
err = distrtestutil.CallCreateValidatorHooks(ctx, distrKeeper, addr, valAddr)
require.NoError(t, err)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// end period
endingPeriod := distrKeeper.IncrementValidatorPeriod(ctx, val)
// calculate delegation rewards
rewards := distrKeeper.CalculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards should be zero
require.True(t, rewards.IsZero())
// start out block height
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
// slash the validator by 50% (simulated with manual calls; we assume the validator is bonded)
slashedTokens := distrtestutil.SlashValidator(
ctx,
valConsAddr0,
ctx.BlockHeight(),
valPower,
sdk.NewDecWithPrec(5, 1),
&val,
&distrKeeper,
)
require.True(t, slashedTokens.IsPositive(), "expected positive slashed tokens, got: %s", slashedTokens)
// expect a call for the next slash with the updated validator
stakingKeeper.EXPECT().Validator(gomock.Any(), valAddr).Return(val).Times(1)
// increase block height
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
// allocate some rewards
initial := sdk.TokensFromConsensusPower(10, sdk.DefaultPowerReduction)
tokens := sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: sdk.NewDecFromInt(initial)}}
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
// slash the validator by 50% again
slashedTokens = distrtestutil.SlashValidator(
ctx,
valConsAddr0,
ctx.BlockHeight(),
valPower/2,
sdk.NewDecWithPrec(2, 1),
&val,
&distrKeeper,
)
require.True(t, slashedTokens.IsPositive(), "expected positive slashed tokens, got: %s", slashedTokens)
// increase block height
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
// allocate some more rewards
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
// end period
endingPeriod = distrKeeper.IncrementValidatorPeriod(ctx, val)
// calculate delegation rewards
rewards = distrKeeper.CalculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards should be half the tokens
require.Equal(t, sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: sdk.NewDecFromInt(initial)}}, rewards)
// commission should be the other half
require.Equal(t, sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: sdk.NewDecFromInt(initial)}},
distrKeeper.GetValidatorAccumulatedCommission(ctx, valAddr).Commission)
}
func TestCalculateRewardsMultiDelegator(t *testing.T) {
ctrl := gomock.NewController(t)
key := sdk.NewKVStoreKey(disttypes.StoreKey)
testCtx := testutil.DefaultContextWithDB(t, key, sdk.NewTransientStoreKey("transient_test"))
encCfg := moduletestutil.MakeTestEncodingConfig(distribution.AppModuleBasic{})
ctx := testCtx.Ctx.WithBlockHeader(tmproto.Header{Height: 1})
bankKeeper := distrtestutil.NewMockBankKeeper(ctrl)
stakingKeeper := distrtestutil.NewMockStakingKeeper(ctrl)
accountKeeper := distrtestutil.NewMockAccountKeeper(ctrl)
accountKeeper.EXPECT().GetModuleAddress("distribution").Return(distrAcc.GetAddress())
distrKeeper := keeper.NewKeeper(
encCfg.Codec,
key,
accountKeeper,
bankKeeper,
stakingKeeper,
"fee_collector",
authtypes.NewModuleAddress("gov").String(),
)
// reset fee pool
distrKeeper.SetFeePool(ctx, disttypes.InitialFeePool())
distrKeeper.SetParams(ctx, disttypes.DefaultParams())
// create validator with 50% commission
valAddr := sdk.ValAddress(valConsAddr0)
addr0 := sdk.AccAddress(valAddr)
val, err := distrtestutil.CreateValidator(valConsPk0, math.NewInt(100))
require.NoError(t, err)
val.Commission = stakingtypes.NewCommission(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), math.LegacyNewDec(0))
del0 := stakingtypes.NewDelegation(addr0, valAddr, val.DelegatorShares)
// set mock calls
stakingKeeper.EXPECT().Validator(gomock.Any(), valAddr).Return(val).Times(4)
stakingKeeper.EXPECT().Delegation(gomock.Any(), addr0, valAddr).Return(del0).Times(1)
// run the necessary hooks manually (given that we are not running an actual staking module)
err = distrtestutil.CallCreateValidatorHooks(ctx, distrKeeper, addr0, valAddr)
require.NoError(t, err)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// allocate some rewards
initial := int64(20)
tokens := sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: math.LegacyNewDec(initial)}}
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
// second delegation
addr1 := sdk.AccAddress(valConsAddr1)
_, del1, err := distrtestutil.Delegate(ctx, distrKeeper, addr1, &val, sdk.NewInt(100), nil)
require.NoError(t, err)
stakingKeeper.EXPECT().Delegation(gomock.Any(), addr1, valAddr).Return(del1)
stakingKeeper.EXPECT().Validator(gomock.Any(), valAddr).Return(val).Times(1)
// call necessary hooks to update a delegation
err = distrKeeper.Hooks().AfterDelegationModified(ctx, addr1, valAddr)
require.NoError(t, err)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// allocate some more rewards
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
// end period
endingPeriod := distrKeeper.IncrementValidatorPeriod(ctx, val)
// calculate delegation rewards for del1
rewards := distrKeeper.CalculateDelegationRewards(ctx, val, del0, endingPeriod)
// rewards for del0 should be 3/4 initial
require.Equal(t, sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: math.LegacyNewDec(initial * 3 / 4)}}, rewards)
// calculate delegation rewards for del2
rewards = distrKeeper.CalculateDelegationRewards(ctx, val, del1, endingPeriod)
// rewards for del2 should be 1/4 initial
require.Equal(t, sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: math.LegacyNewDec(initial * 1 / 4)}}, rewards)
// commission should be equal to initial (50% twice)
require.Equal(t, sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: math.LegacyNewDec(initial)}}, distrKeeper.GetValidatorAccumulatedCommission(ctx, valAddr).Commission)
}
func TestWithdrawDelegationRewardsBasic(t *testing.T) {
ctrl := gomock.NewController(t)
key := sdk.NewKVStoreKey(disttypes.StoreKey)
testCtx := testutil.DefaultContextWithDB(t, key, sdk.NewTransientStoreKey("transient_test"))
encCfg := moduletestutil.MakeTestEncodingConfig(distribution.AppModuleBasic{})
ctx := testCtx.Ctx.WithBlockHeader(tmproto.Header{Height: 1})
bankKeeper := distrtestutil.NewMockBankKeeper(ctrl)
stakingKeeper := distrtestutil.NewMockStakingKeeper(ctrl)
accountKeeper := distrtestutil.NewMockAccountKeeper(ctrl)
accountKeeper.EXPECT().GetModuleAddress("distribution").Return(distrAcc.GetAddress())
distrKeeper := keeper.NewKeeper(
encCfg.Codec,
key,
accountKeeper,
bankKeeper,
stakingKeeper,
"fee_collector",
authtypes.NewModuleAddress("gov").String(),
)
// reset fee pool
distrKeeper.SetFeePool(ctx, disttypes.InitialFeePool())
distrKeeper.SetParams(ctx, disttypes.DefaultParams())
// create validator with 50% commission
valAddr := sdk.ValAddress(valConsAddr0)
addr := sdk.AccAddress(valAddr)
val, err := distrtestutil.CreateValidator(valConsPk0, math.NewInt(100))
require.NoError(t, err)
val.Commission = stakingtypes.NewCommission(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), math.LegacyNewDec(0))
// delegation mock
del := stakingtypes.NewDelegation(addr, valAddr, val.DelegatorShares)
stakingKeeper.EXPECT().Validator(gomock.Any(), valAddr).Return(val).Times(5)
stakingKeeper.EXPECT().Delegation(gomock.Any(), addr, valAddr).Return(del).Times(3)
// run the necessary hooks manually (given that we are not running an actual staking module)
err = distrtestutil.CallCreateValidatorHooks(ctx, distrKeeper, addr, valAddr)
require.NoError(t, err)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// allocate some rewards
initial := sdk.TokensFromConsensusPower(10, sdk.DefaultPowerReduction)
tokens := sdk.DecCoins{sdk.NewDecCoin(sdk.DefaultBondDenom, initial)}
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
// historical count should be 2 (initial + latest for delegation)
require.Equal(t, uint64(2), distrKeeper.GetValidatorHistoricalReferenceCount(ctx))
// withdraw rewards (the bank keeper should be called with the right amount of tokens to transfer)
expRewards := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, initial.QuoRaw(2))}
bankKeeper.EXPECT().SendCoinsFromModuleToAccount(ctx, disttypes.ModuleName, addr, expRewards)
_, err = distrKeeper.WithdrawDelegationRewards(ctx, sdk.AccAddress(valAddr), valAddr)
require.Nil(t, err)
// historical count should still be 2 (added one record, cleared one)
require.Equal(t, uint64(2), distrKeeper.GetValidatorHistoricalReferenceCount(ctx))
// withdraw commission (the bank keeper should be called with the right amount of tokens to transfer)
expCommission := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, initial.QuoRaw(2))}
bankKeeper.EXPECT().SendCoinsFromModuleToAccount(ctx, disttypes.ModuleName, addr, expCommission)
_, err = distrKeeper.WithdrawValidatorCommission(ctx, valAddr)
require.Nil(t, err)
}
func TestCalculateRewardsAfterManySlashesInSameBlock(t *testing.T) {
ctrl := gomock.NewController(t)
key := sdk.NewKVStoreKey(disttypes.StoreKey)
testCtx := testutil.DefaultContextWithDB(t, key, sdk.NewTransientStoreKey("transient_test"))
encCfg := moduletestutil.MakeTestEncodingConfig(distribution.AppModuleBasic{})
ctx := testCtx.Ctx.WithBlockHeader(tmproto.Header{Height: 1})
bankKeeper := distrtestutil.NewMockBankKeeper(ctrl)
stakingKeeper := distrtestutil.NewMockStakingKeeper(ctrl)
accountKeeper := distrtestutil.NewMockAccountKeeper(ctrl)
accountKeeper.EXPECT().GetModuleAddress("distribution").Return(distrAcc.GetAddress())
distrKeeper := keeper.NewKeeper(
encCfg.Codec,
key,
accountKeeper,
bankKeeper,
stakingKeeper,
"fee_collector",
authtypes.NewModuleAddress("gov").String(),
)
// reset fee pool
distrKeeper.SetFeePool(ctx, disttypes.InitialFeePool())
distrKeeper.SetParams(ctx, disttypes.DefaultParams())
// create validator with 50% commission
valAddr := sdk.ValAddress(valConsAddr0)
addr := sdk.AccAddress(valAddr)
val, err := distrtestutil.CreateValidator(valConsPk0, math.NewInt(100))
require.NoError(t, err)
val.Commission = stakingtypes.NewCommission(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), math.LegacyNewDec(0))
// delegation mock
del := stakingtypes.NewDelegation(addr, valAddr, val.DelegatorShares)
stakingKeeper.EXPECT().Validator(gomock.Any(), valAddr).Return(val).Times(5)
stakingKeeper.EXPECT().Delegation(gomock.Any(), addr, valAddr).Return(del)
// run the necessary hooks manually (given that we are not running an actual staking module)
err = distrtestutil.CallCreateValidatorHooks(ctx, distrKeeper, addr, valAddr)
require.NoError(t, err)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// end period
endingPeriod := distrKeeper.IncrementValidatorPeriod(ctx, val)
// calculate delegation rewards
rewards := distrKeeper.CalculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards should be zero
require.True(t, rewards.IsZero())
// start out block height
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
// allocate some rewards
initial := sdk.NewDecFromInt(sdk.TokensFromConsensusPower(10, sdk.DefaultPowerReduction))
tokens := sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: initial}}
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
valPower := int64(100)
// slash the validator by 50% (simulated with manual calls; we assume the validator is bonded)
distrtestutil.SlashValidator(
ctx,
valConsAddr0,
ctx.BlockHeight(),
valPower,
sdk.NewDecWithPrec(5, 1),
&val,
&distrKeeper,
)
// slash the validator by 50% again
// stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower/2, sdk.NewDecWithPrec(5, 1))
distrtestutil.SlashValidator(
ctx,
valConsAddr0,
ctx.BlockHeight(),
valPower/2,
sdk.NewDecWithPrec(5, 1),
&val,
&distrKeeper,
)
// increase block height
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
// allocate some more rewards
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
// end period
endingPeriod = distrKeeper.IncrementValidatorPeriod(ctx, val)
// calculate delegation rewards
rewards = distrKeeper.CalculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards should be half the tokens
require.Equal(t, sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: initial}}, rewards)
// commission should be the other half
require.Equal(t, sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: initial}}, distrKeeper.GetValidatorAccumulatedCommission(ctx, valAddr).Commission)
}
func TestCalculateRewardsMultiDelegatorMultiSlash(t *testing.T) {
ctrl := gomock.NewController(t)
key := sdk.NewKVStoreKey(disttypes.StoreKey)
testCtx := testutil.DefaultContextWithDB(t, key, sdk.NewTransientStoreKey("transient_test"))
encCfg := moduletestutil.MakeTestEncodingConfig(distribution.AppModuleBasic{})
ctx := testCtx.Ctx.WithBlockHeader(tmproto.Header{Height: 1})
bankKeeper := distrtestutil.NewMockBankKeeper(ctrl)
stakingKeeper := distrtestutil.NewMockStakingKeeper(ctrl)
accountKeeper := distrtestutil.NewMockAccountKeeper(ctrl)
accountKeeper.EXPECT().GetModuleAddress("distribution").Return(distrAcc.GetAddress())
distrKeeper := keeper.NewKeeper(
encCfg.Codec,
key,
accountKeeper,
bankKeeper,
stakingKeeper,
"fee_collector",
authtypes.NewModuleAddress("gov").String(),
)
// reset fee pool
distrKeeper.SetFeePool(ctx, disttypes.InitialFeePool())
distrKeeper.SetParams(ctx, disttypes.DefaultParams())
valPower := int64(100)
// create validator with 50% commission
valAddr := sdk.ValAddress(valConsAddr0)
addr := sdk.AccAddress(valAddr)
val, err := distrtestutil.CreateValidator(valConsPk0, sdk.TokensFromConsensusPower(valPower, sdk.DefaultPowerReduction))
require.NoError(t, err)
val.Commission = stakingtypes.NewCommission(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), math.LegacyNewDec(0))
// validator and delegation mocks
del := stakingtypes.NewDelegation(addr, valAddr, val.DelegatorShares)
stakingKeeper.EXPECT().Validator(gomock.Any(), valAddr).Return(val).Times(3)
stakingKeeper.EXPECT().Delegation(gomock.Any(), addr, valAddr).Return(del)
// run the necessary hooks manually (given that we are not running an actual staking module)
err = distrtestutil.CallCreateValidatorHooks(ctx, distrKeeper, addr, valAddr)
require.NoError(t, err)
stakingKeeper.EXPECT().Validator(gomock.Any(), valAddr).Return(val).Times(2)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// allocate some rewards
initial := sdk.NewDecFromInt(sdk.TokensFromConsensusPower(30, sdk.DefaultPowerReduction))
tokens := sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: initial}}
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
// slash the validator
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
distrtestutil.SlashValidator(
ctx,
valConsAddr0,
ctx.BlockHeight(),
valPower,
sdk.NewDecWithPrec(5, 1),
&val,
&distrKeeper,
)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
// update validator mock
stakingKeeper.EXPECT().Validator(gomock.Any(), valAddr).Return(val).Times(1)
// second delegation
_, del2, err := distrtestutil.Delegate(
ctx,
distrKeeper,
sdk.AccAddress(valConsAddr1),
&val,
sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction),
nil,
)
require.NoError(t, err)
// new delegation mock and update validator mock
stakingKeeper.EXPECT().Delegation(gomock.Any(), sdk.AccAddress(valConsAddr1), valAddr).Return(del2)
stakingKeeper.EXPECT().Validator(gomock.Any(), valAddr).Return(val).Times(1)
// call necessary hooks to update a delegation
err = distrKeeper.Hooks().AfterDelegationModified(ctx, sdk.AccAddress(valConsAddr1), valAddr)
require.NoError(t, err)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// allocate some more rewards
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
// slash the validator again
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
distrtestutil.SlashValidator(
ctx,
valConsAddr0,
ctx.BlockHeight(),
valPower,
sdk.NewDecWithPrec(5, 1),
&val,
&distrKeeper,
)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
// end period
endingPeriod := distrKeeper.IncrementValidatorPeriod(ctx, val)
// calculate delegation rewards for del1
rewards := distrKeeper.CalculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards for del1 should be 2/3 initial (half initial first period, 1/6 initial second period)
require.Equal(t, sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: initial.QuoInt64(2).Add(initial.QuoInt64(6))}}, rewards)
// calculate delegation rewards for del2
rewards = distrKeeper.CalculateDelegationRewards(ctx, val, del2, endingPeriod)
// rewards for del2 should be initial / 3
require.Equal(t, sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: initial.QuoInt64(3)}}, rewards)
// commission should be equal to initial (twice 50% commission, unaffected by slashing)
require.Equal(t, sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: initial}}, distrKeeper.GetValidatorAccumulatedCommission(ctx, valAddr).Commission)
}
func TestCalculateRewardsMultiDelegatorMultWithdraw(t *testing.T) {
ctrl := gomock.NewController(t)
key := sdk.NewKVStoreKey(disttypes.StoreKey)
testCtx := testutil.DefaultContextWithDB(t, key, sdk.NewTransientStoreKey("transient_test"))
encCfg := moduletestutil.MakeTestEncodingConfig(distribution.AppModuleBasic{})
ctx := testCtx.Ctx.WithBlockHeader(tmproto.Header{Height: 1})
bankKeeper := distrtestutil.NewMockBankKeeper(ctrl)
stakingKeeper := distrtestutil.NewMockStakingKeeper(ctrl)
accountKeeper := distrtestutil.NewMockAccountKeeper(ctrl)
accountKeeper.EXPECT().GetModuleAddress("distribution").Return(distrAcc.GetAddress())
distrKeeper := keeper.NewKeeper(
encCfg.Codec,
key,
accountKeeper,
bankKeeper,
stakingKeeper,
"fee_collector",
authtypes.NewModuleAddress("gov").String(),
)
// reset fee pool
distrKeeper.SetFeePool(ctx, disttypes.InitialFeePool())
distrKeeper.SetParams(ctx, disttypes.DefaultParams())
// create validator with 50% commission
valAddr := sdk.ValAddress(valConsAddr0)
addr := sdk.AccAddress(valAddr)
val, err := distrtestutil.CreateValidator(valConsPk0, sdk.NewInt(100))
require.NoError(t, err)
val.Commission = stakingtypes.NewCommission(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), math.LegacyNewDec(0))
// validator and delegation mocks
del := stakingtypes.NewDelegation(addr, valAddr, val.DelegatorShares)
stakingKeeper.EXPECT().Validator(gomock.Any(), valAddr).Return(val).Times(3)
stakingKeeper.EXPECT().Delegation(gomock.Any(), addr, valAddr).Return(del).Times(5)
// run the necessary hooks manually (given that we are not running an actual staking module)
err = distrtestutil.CallCreateValidatorHooks(ctx, distrKeeper, addr, valAddr)
require.NoError(t, err)
stakingKeeper.EXPECT().Validator(gomock.Any(), valAddr).Return(val).Times(2)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// allocate some rewards
initial := int64(20)
tokens := sdk.DecCoins{sdk.NewDecCoin(sdk.DefaultBondDenom, math.NewInt(initial))}
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
// historical count should be 2 (validator init, delegation init)
require.Equal(t, uint64(2), distrKeeper.GetValidatorHistoricalReferenceCount(ctx))
// second delegation
_, del2, err := distrtestutil.Delegate(
ctx,
distrKeeper,
sdk.AccAddress(valConsAddr1),
&val,
sdk.NewInt(100),
nil,
)
require.NoError(t, err)
// new delegation mock and update validator mock
stakingKeeper.EXPECT().Delegation(gomock.Any(), sdk.AccAddress(valConsAddr1), valAddr).Return(del2).Times(3)
stakingKeeper.EXPECT().Validator(gomock.Any(), valAddr).Return(val).Times(6)
// call necessary hooks to update a delegation
err = distrKeeper.Hooks().AfterDelegationModified(ctx, sdk.AccAddress(valConsAddr1), valAddr)
require.NoError(t, err)
// historical count should be 3 (second delegation init)
require.Equal(t, uint64(3), distrKeeper.GetValidatorHistoricalReferenceCount(ctx))
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// allocate some more rewards
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
// first delegator withdraws
expRewards := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(initial*3/4))}
bankKeeper.EXPECT().SendCoinsFromModuleToAccount(ctx, disttypes.ModuleName, addr, expRewards)
_, err = distrKeeper.WithdrawDelegationRewards(ctx, addr, valAddr)
require.NoError(t, err)
// second delegator withdraws
expRewards = sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(initial*1/4))}
bankKeeper.EXPECT().SendCoinsFromModuleToAccount(ctx, disttypes.ModuleName, sdk.AccAddress(valConsAddr1), expRewards)
_, err = distrKeeper.WithdrawDelegationRewards(ctx, sdk.AccAddress(valConsAddr1), valAddr)
require.NoError(t, err)
// historical count should be 3 (validator init + two delegations)
require.Equal(t, uint64(3), distrKeeper.GetValidatorHistoricalReferenceCount(ctx))
// validator withdraws commission
expCommission := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(initial))}
bankKeeper.EXPECT().SendCoinsFromModuleToAccount(ctx, disttypes.ModuleName, addr, expCommission)
_, err = distrKeeper.WithdrawValidatorCommission(ctx, valAddr)
require.NoError(t, err)
// end period
endingPeriod := distrKeeper.IncrementValidatorPeriod(ctx, val)
// calculate delegation rewards for del1
rewards := distrKeeper.CalculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards for del1 should be zero
require.True(t, rewards.IsZero())
// calculate delegation rewards for del2
rewards = distrKeeper.CalculateDelegationRewards(ctx, val, del2, endingPeriod)
// rewards for del2 should be zero
require.True(t, rewards.IsZero())
// commission should be zero
require.True(t, distrKeeper.GetValidatorAccumulatedCommission(ctx, valAddr).Commission.IsZero())
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// allocate some more rewards
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
// first delegator withdraws again
expCommission = sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(initial*1/4))}
bankKeeper.EXPECT().SendCoinsFromModuleToAccount(ctx, disttypes.ModuleName, addr, expCommission)
_, err = distrKeeper.WithdrawDelegationRewards(ctx, addr, valAddr)
require.NoError(t, err)
// end period
endingPeriod = distrKeeper.IncrementValidatorPeriod(ctx, val)
// calculate delegation rewards for del1
rewards = distrKeeper.CalculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards for del1 should be zero
require.True(t, rewards.IsZero())
// calculate delegation rewards for del2
rewards = distrKeeper.CalculateDelegationRewards(ctx, val, del2, endingPeriod)
// rewards for del2 should be 1/4 initial
require.Equal(t, sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: math.LegacyNewDec(initial / 4)}}, rewards)
// commission should be half initial
require.Equal(t, sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: math.LegacyNewDec(initial / 2)}}, distrKeeper.GetValidatorAccumulatedCommission(ctx, valAddr).Commission)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// allocate some more rewards
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
// withdraw commission
expCommission = sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(initial))}
bankKeeper.EXPECT().SendCoinsFromModuleToAccount(ctx, disttypes.ModuleName, addr, expCommission)
_, err = distrKeeper.WithdrawValidatorCommission(ctx, valAddr)
require.NoError(t, err)
// end period
endingPeriod = distrKeeper.IncrementValidatorPeriod(ctx, val)
// calculate delegation rewards for del1
rewards = distrKeeper.CalculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards for del1 should be 1/4 initial
require.Equal(t, sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: math.LegacyNewDec(initial / 4)}}, rewards)
// calculate delegation rewards for del2
rewards = distrKeeper.CalculateDelegationRewards(ctx, val, del2, endingPeriod)
// rewards for del2 should be 1/2 initial
require.Equal(t, sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: math.LegacyNewDec(initial / 2)}}, rewards)
// commission should be zero
require.True(t, distrKeeper.GetValidatorAccumulatedCommission(ctx, valAddr).Commission.IsZero())
}
func Test100PercentCommissionReward(t *testing.T) {
ctrl := gomock.NewController(t)
key := sdk.NewKVStoreKey(disttypes.StoreKey)
testCtx := testutil.DefaultContextWithDB(t, key, sdk.NewTransientStoreKey("transient_test"))
encCfg := moduletestutil.MakeTestEncodingConfig(distribution.AppModuleBasic{})
ctx := testCtx.Ctx.WithBlockHeader(tmproto.Header{Height: 1})
bankKeeper := distrtestutil.NewMockBankKeeper(ctrl)
stakingKeeper := distrtestutil.NewMockStakingKeeper(ctrl)
accountKeeper := distrtestutil.NewMockAccountKeeper(ctrl)
accountKeeper.EXPECT().GetModuleAddress("distribution").Return(distrAcc.GetAddress())
distrKeeper := keeper.NewKeeper(
encCfg.Codec,
key,
accountKeeper,
bankKeeper,
stakingKeeper,
"fee_collector",
authtypes.NewModuleAddress("gov").String(),
)
// reset fee pool
distrKeeper.SetFeePool(ctx, disttypes.InitialFeePool())
distrKeeper.SetParams(ctx, disttypes.DefaultParams())
// create validator with 50% commission
valAddr := sdk.ValAddress(valConsAddr0)
addr := sdk.AccAddress(valAddr)
val, err := distrtestutil.CreateValidator(valConsPk0, sdk.NewInt(100))
require.NoError(t, err)
val.Commission = stakingtypes.NewCommission(sdk.NewDecWithPrec(10, 1), sdk.NewDecWithPrec(10, 1), math.LegacyNewDec(0))
// validator and delegation mocks
del := stakingtypes.NewDelegation(addr, valAddr, val.DelegatorShares)
stakingKeeper.EXPECT().Validator(gomock.Any(), valAddr).Return(val).Times(3)
stakingKeeper.EXPECT().Delegation(gomock.Any(), addr, valAddr).Return(del).Times(3)
// run the necessary hooks manually (given that we are not running an actual staking module)
err = distrtestutil.CallCreateValidatorHooks(ctx, distrKeeper, addr, valAddr)
require.NoError(t, err)
stakingKeeper.EXPECT().Validator(gomock.Any(), valAddr).Return(val).Times(2)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// allocate some rewards
initial := int64(20)
tokens := sdk.DecCoins{sdk.NewDecCoin(sdk.DefaultBondDenom, math.NewInt(initial))}
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// allocate some rewards
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// allocate some more rewards
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// allocate some more rewards
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
rewards, err := distrKeeper.WithdrawDelegationRewards(ctx, sdk.AccAddress(addr), valAddr)
require.NoError(t, err)
zeroRewards := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, math.ZeroInt())}
require.True(t, rewards.IsEqual(zeroRewards))
events := ctx.EventManager().Events()
lastEvent := events[len(events)-1]
var hasValue bool
for _, attr := range lastEvent.Attributes {
if attr.Key == "amount" && attr.Value == "0stake" {
hasValue = true
}
}
require.True(t, hasValue)
}