package keeper_test import ( "testing" "cosmossdk.io/math" "github.com/stretchr/testify/require" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" banktestutil "github.com/cosmos/cosmos-sdk/x/bank/testutil" "github.com/cosmos/cosmos-sdk/x/distribution/keeper" "github.com/cosmos/cosmos-sdk/x/distribution/testutil" "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" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) func TestCalculateRewardsBasic(t *testing.T) { var ( bankKeeper bankkeeper.Keeper distrKeeper keeper.Keeper stakingKeeper *stakingkeeper.Keeper ) app, err := simtestutil.Setup(testutil.AppConfig, &bankKeeper, &distrKeeper, &stakingKeeper, ) require.NoError(t, err) ctx := app.BaseApp.NewContext(false, tmproto.Header{}) distrKeeper.DeleteAllValidatorHistoricalRewards(ctx) tstaking := stakingtestutil.NewHelper(t, ctx, stakingKeeper) addr := simtestutil.AddTestAddrs(bankKeeper, stakingKeeper, ctx, 2, sdk.NewInt(1000)) valAddrs := simtestutil.ConvertAddrsToValAddrs(addr) // create validator with 50% commission tstaking.Commission = stakingtypes.NewCommissionRates(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), math.LegacyNewDec(0)) tstaking.CreateValidator(valAddrs[0], valConsPk0, sdk.NewInt(100), true) // end block to bond validator and start new block staking.EndBlocker(ctx, stakingKeeper) ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) tstaking.Ctx = ctx // fetch validator and delegation val := stakingKeeper.Validator(ctx, valAddrs[0]) del := stakingKeeper.Delegation(ctx, sdk.AccAddress(valAddrs[0]), valAddrs[0]) // 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, valAddrs[0]).Commission) } func TestCalculateRewardsAfterSlash(t *testing.T) { var ( bankKeeper bankkeeper.Keeper distrKeeper keeper.Keeper stakingKeeper *stakingkeeper.Keeper ) app, err := simtestutil.Setup(testutil.AppConfig, &bankKeeper, &distrKeeper, &stakingKeeper, ) require.NoError(t, err) ctx := app.BaseApp.NewContext(false, tmproto.Header{}) addr := simtestutil.AddTestAddrs(bankKeeper, stakingKeeper, ctx, 2, sdk.NewInt(100000000)) valAddrs := simtestutil.ConvertAddrsToValAddrs(addr) tstaking := stakingtestutil.NewHelper(t, ctx, stakingKeeper) // create validator with 50% commission tstaking.Commission = stakingtypes.NewCommissionRates(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), math.LegacyNewDec(0)) valPower := int64(100) tstaking.CreateValidatorWithValPower(valAddrs[0], valConsPk0, valPower, true) // end block to bond validator staking.EndBlocker(ctx, stakingKeeper) // next block ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) // fetch validator and delegation val := stakingKeeper.Validator(ctx, valAddrs[0]) del := stakingKeeper.Delegation(ctx, sdk.AccAddress(valAddrs[0]), valAddrs[0]) // 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% stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower, sdk.NewDecWithPrec(5, 1), stakingtypes.Infraction_INFRACTION_UNSPECIFIED) // retrieve validator val = stakingKeeper.Validator(ctx, valAddrs[0]) // increase block height ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3) // allocate some rewards initial := stakingKeeper.TokensFromConsensusPower(ctx, 10) 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, valAddrs[0]).Commission) } func TestCalculateRewardsAfterManySlashes(t *testing.T) { var ( bankKeeper bankkeeper.Keeper distrKeeper keeper.Keeper stakingKeeper *stakingkeeper.Keeper ) app, err := simtestutil.Setup(testutil.AppConfig, &bankKeeper, &distrKeeper, &stakingKeeper, ) require.NoError(t, err) ctx := app.BaseApp.NewContext(false, tmproto.Header{}) tstaking := stakingtestutil.NewHelper(t, ctx, stakingKeeper) addr := simtestutil.AddTestAddrs(bankKeeper, stakingKeeper, ctx, 2, sdk.NewInt(100000000)) valAddrs := simtestutil.ConvertAddrsToValAddrs(addr) // create validator with 50% commission valPower := int64(100) tstaking.Commission = stakingtypes.NewCommissionRates(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), math.LegacyNewDec(0)) tstaking.CreateValidatorWithValPower(valAddrs[0], valConsPk0, valPower, true) // end block to bond validator staking.EndBlocker(ctx, stakingKeeper) // next block ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) // fetch validator and delegation val := stakingKeeper.Validator(ctx, valAddrs[0]) del := stakingKeeper.Delegation(ctx, sdk.AccAddress(valAddrs[0]), valAddrs[0]) // 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% stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower, sdk.NewDecWithPrec(5, 1), stakingtypes.Infraction_INFRACTION_UNSPECIFIED) // fetch the validator again val = stakingKeeper.Validator(ctx, valAddrs[0]) // increase block height ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3) // allocate some rewards initial := stakingKeeper.TokensFromConsensusPower(ctx, 10) tokens := sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: sdk.NewDecFromInt(initial)}} distrKeeper.AllocateTokensToValidator(ctx, val, tokens) // slash the validator by 50% again stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower/2, sdk.NewDecWithPrec(5, 1), stakingtypes.Infraction_INFRACTION_UNSPECIFIED) // fetch the validator again val = stakingKeeper.Validator(ctx, valAddrs[0]) // 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, valAddrs[0]).Commission) } func TestCalculateRewardsMultiDelegator(t *testing.T) { var ( bankKeeper bankkeeper.Keeper distrKeeper keeper.Keeper stakingKeeper *stakingkeeper.Keeper ) app, err := simtestutil.Setup(testutil.AppConfig, &bankKeeper, &distrKeeper, &stakingKeeper, ) require.NoError(t, err) ctx := app.BaseApp.NewContext(false, tmproto.Header{}) tstaking := stakingtestutil.NewHelper(t, ctx, stakingKeeper) addr := simtestutil.AddTestAddrs(bankKeeper, stakingKeeper, ctx, 2, sdk.NewInt(100000000)) valAddrs := simtestutil.ConvertAddrsToValAddrs(addr) // create validator with 50% commission tstaking.Commission = stakingtypes.NewCommissionRates(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), math.LegacyNewDec(0)) tstaking.CreateValidator(valAddrs[0], valConsPk0, sdk.NewInt(100), true) // end block to bond validator staking.EndBlocker(ctx, stakingKeeper) // next block ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) // fetch validator and delegation val := stakingKeeper.Validator(ctx, valAddrs[0]) del1 := stakingKeeper.Delegation(ctx, sdk.AccAddress(valAddrs[0]), valAddrs[0]) // allocate some rewards initial := int64(20) tokens := sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: math.LegacyNewDec(initial)}} distrKeeper.AllocateTokensToValidator(ctx, val, tokens) // second delegation tstaking.Ctx = ctx tstaking.Delegate(sdk.AccAddress(valAddrs[1]), valAddrs[0], sdk.NewInt(100)) del2 := stakingKeeper.Delegation(ctx, sdk.AccAddress(valAddrs[1]), valAddrs[0]) // fetch updated validator val = stakingKeeper.Validator(ctx, valAddrs[0]) // end block staking.EndBlocker(ctx, stakingKeeper) // 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, del1, endingPeriod) // rewards for del1 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, del2, 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, valAddrs[0]).Commission) } func TestWithdrawDelegationRewardsBasic(t *testing.T) { var ( accountKeeper authkeeper.AccountKeeper bankKeeper bankkeeper.Keeper distrKeeper keeper.Keeper stakingKeeper *stakingkeeper.Keeper ) app, err := simtestutil.Setup(testutil.AppConfig, &accountKeeper, &bankKeeper, &distrKeeper, &stakingKeeper, ) require.NoError(t, err) ctx := app.BaseApp.NewContext(false, tmproto.Header{}) distrKeeper.DeleteAllValidatorHistoricalRewards(ctx) balancePower := int64(1000) balanceTokens := stakingKeeper.TokensFromConsensusPower(ctx, balancePower) addr := simtestutil.AddTestAddrs(bankKeeper, stakingKeeper, ctx, 1, sdk.NewInt(1000000000)) valAddrs := simtestutil.ConvertAddrsToValAddrs(addr) tstaking := stakingtestutil.NewHelper(t, ctx, stakingKeeper) // set module account coins distrAcc := distrKeeper.GetDistributionAccount(ctx) require.NoError(t, banktestutil.FundModuleAccount(bankKeeper, ctx, distrAcc.GetName(), sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, balanceTokens)))) accountKeeper.SetModuleAccount(ctx, distrAcc) // create validator with 50% commission power := int64(100) tstaking.Commission = stakingtypes.NewCommissionRates(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), math.LegacyNewDec(0)) valTokens := tstaking.CreateValidatorWithValPower(valAddrs[0], valConsPk0, power, true) // assert correct initial balance expTokens := balanceTokens.Sub(valTokens) require.Equal(t, sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, expTokens)}, bankKeeper.GetAllBalances(ctx, sdk.AccAddress(valAddrs[0])), ) // end block to bond validator staking.EndBlocker(ctx, stakingKeeper) // next block ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) // fetch validator and delegation val := stakingKeeper.Validator(ctx, valAddrs[0]) // allocate some rewards initial := stakingKeeper.TokensFromConsensusPower(ctx, 10) 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 _, err = distrKeeper.WithdrawDelegationRewards(ctx, sdk.AccAddress(valAddrs[0]), valAddrs[0]) require.Nil(t, err) // historical count should still be 2 (added one record, cleared one) require.Equal(t, uint64(2), distrKeeper.GetValidatorHistoricalReferenceCount(ctx)) // assert correct balance exp := balanceTokens.Sub(valTokens).Add(initial.QuoRaw(2)) require.Equal(t, sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, exp)}, bankKeeper.GetAllBalances(ctx, sdk.AccAddress(valAddrs[0])), ) // withdraw commission _, err = distrKeeper.WithdrawValidatorCommission(ctx, valAddrs[0]) require.Nil(t, err) // assert correct balance exp = balanceTokens.Sub(valTokens).Add(initial) require.Equal(t, sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, exp)}, bankKeeper.GetAllBalances(ctx, sdk.AccAddress(valAddrs[0])), ) } func TestCalculateRewardsAfterManySlashesInSameBlock(t *testing.T) { var ( bankKeeper bankkeeper.Keeper distrKeeper keeper.Keeper stakingKeeper *stakingkeeper.Keeper ) app, err := simtestutil.Setup(testutil.AppConfig, &bankKeeper, &distrKeeper, &stakingKeeper, ) require.NoError(t, err) ctx := app.BaseApp.NewContext(false, tmproto.Header{}) addr := simtestutil.AddTestAddrs(bankKeeper, stakingKeeper, ctx, 1, sdk.NewInt(1000000000)) valAddrs := simtestutil.ConvertAddrsToValAddrs(addr) tstaking := stakingtestutil.NewHelper(t, ctx, stakingKeeper) // create validator with 50% commission valPower := int64(100) tstaking.Commission = stakingtypes.NewCommissionRates(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), math.LegacyNewDec(0)) tstaking.CreateValidatorWithValPower(valAddrs[0], valConsPk0, valPower, true) // end block to bond validator staking.EndBlocker(ctx, stakingKeeper) // next block ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) // fetch validator and delegation val := stakingKeeper.Validator(ctx, valAddrs[0]) del := stakingKeeper.Delegation(ctx, sdk.AccAddress(valAddrs[0]), valAddrs[0]) // 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(stakingKeeper.TokensFromConsensusPower(ctx, 10)) tokens := sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: initial}} distrKeeper.AllocateTokensToValidator(ctx, val, tokens) // slash the validator by 50% stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower, sdk.NewDecWithPrec(5, 1), stakingtypes.Infraction_INFRACTION_UNSPECIFIED) // slash the validator by 50% again stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower/2, sdk.NewDecWithPrec(5, 1), stakingtypes.Infraction_INFRACTION_UNSPECIFIED) // fetch the validator again val = stakingKeeper.Validator(ctx, valAddrs[0]) // 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, valAddrs[0]).Commission) } func TestCalculateRewardsMultiDelegatorMultiSlash(t *testing.T) { var ( bankKeeper bankkeeper.Keeper distrKeeper keeper.Keeper stakingKeeper *stakingkeeper.Keeper ) app, err := simtestutil.Setup(testutil.AppConfig, &bankKeeper, &distrKeeper, &stakingKeeper, ) require.NoError(t, err) ctx := app.BaseApp.NewContext(false, tmproto.Header{}) tstaking := stakingtestutil.NewHelper(t, ctx, stakingKeeper) addr := simtestutil.AddTestAddrs(bankKeeper, stakingKeeper, ctx, 2, sdk.NewInt(1000000000)) valAddrs := simtestutil.ConvertAddrsToValAddrs(addr) // create validator with 50% commission tstaking.Commission = stakingtypes.NewCommissionRates(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), math.LegacyNewDec(0)) valPower := int64(100) tstaking.CreateValidatorWithValPower(valAddrs[0], valConsPk0, valPower, true) // end block to bond validator staking.EndBlocker(ctx, stakingKeeper) // next block ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) // fetch validator and delegation val := stakingKeeper.Validator(ctx, valAddrs[0]) del1 := stakingKeeper.Delegation(ctx, sdk.AccAddress(valAddrs[0]), valAddrs[0]) // allocate some rewards initial := sdk.NewDecFromInt(stakingKeeper.TokensFromConsensusPower(ctx, 30)) tokens := sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: initial}} distrKeeper.AllocateTokensToValidator(ctx, val, tokens) // slash the validator ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3) stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower, sdk.NewDecWithPrec(5, 1), stakingtypes.Infraction_INFRACTION_UNSPECIFIED) ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3) // second delegation tstaking.DelegateWithPower(sdk.AccAddress(valAddrs[1]), valAddrs[0], 100) del2 := stakingKeeper.Delegation(ctx, sdk.AccAddress(valAddrs[1]), valAddrs[0]) // end block staking.EndBlocker(ctx, stakingKeeper) // 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) stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower, sdk.NewDecWithPrec(5, 1), stakingtypes.Infraction_INFRACTION_UNSPECIFIED) ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3) // fetch updated validator val = stakingKeeper.Validator(ctx, valAddrs[0]) // end period endingPeriod := distrKeeper.IncrementValidatorPeriod(ctx, val) // calculate delegation rewards for del1 rewards := distrKeeper.CalculateDelegationRewards(ctx, val, del1, 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, valAddrs[0]).Commission) } func TestCalculateRewardsMultiDelegatorMultWithdraw(t *testing.T) { var ( accountKeeper authkeeper.AccountKeeper bankKeeper bankkeeper.Keeper distrKeeper keeper.Keeper stakingKeeper *stakingkeeper.Keeper ) app, err := simtestutil.Setup(testutil.AppConfig, &accountKeeper, &bankKeeper, &distrKeeper, &stakingKeeper, ) require.NoError(t, err) ctx := app.BaseApp.NewContext(false, tmproto.Header{}) distrKeeper.DeleteAllValidatorHistoricalRewards(ctx) tstaking := stakingtestutil.NewHelper(t, ctx, stakingKeeper) addr := simtestutil.AddTestAddrs(bankKeeper, stakingKeeper, ctx, 2, sdk.NewInt(1000000000)) valAddrs := simtestutil.ConvertAddrsToValAddrs(addr) initial := int64(20) // set module account coins distrAcc := distrKeeper.GetDistributionAccount(ctx) require.NoError(t, banktestutil.FundModuleAccount(bankKeeper, ctx, distrAcc.GetName(), sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(1000))))) accountKeeper.SetModuleAccount(ctx, distrAcc) tokens := sdk.DecCoins{sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, math.LegacyNewDec(initial))} // create validator with 50% commission tstaking.Commission = stakingtypes.NewCommissionRates(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), math.LegacyNewDec(0)) tstaking.CreateValidator(valAddrs[0], valConsPk0, sdk.NewInt(100), true) // end block to bond validator staking.EndBlocker(ctx, stakingKeeper) // next block ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) // fetch validator and delegation val := stakingKeeper.Validator(ctx, valAddrs[0]) del1 := stakingKeeper.Delegation(ctx, sdk.AccAddress(valAddrs[0]), valAddrs[0]) // allocate some rewards distrKeeper.AllocateTokensToValidator(ctx, val, tokens) // historical count should be 2 (validator init, delegation init) require.Equal(t, uint64(2), distrKeeper.GetValidatorHistoricalReferenceCount(ctx)) // second delegation tstaking.Delegate(sdk.AccAddress(valAddrs[1]), valAddrs[0], sdk.NewInt(100)) // historical count should be 3 (second delegation init) require.Equal(t, uint64(3), distrKeeper.GetValidatorHistoricalReferenceCount(ctx)) // fetch updated validator val = stakingKeeper.Validator(ctx, valAddrs[0]) del2 := stakingKeeper.Delegation(ctx, sdk.AccAddress(valAddrs[1]), valAddrs[0]) // end block staking.EndBlocker(ctx, stakingKeeper) // next block ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) // allocate some more rewards distrKeeper.AllocateTokensToValidator(ctx, val, tokens) // first delegator withdraws _, err = distrKeeper.WithdrawDelegationRewards(ctx, sdk.AccAddress(valAddrs[0]), valAddrs[0]) require.NoError(t, err) // second delegator withdraws _, err = distrKeeper.WithdrawDelegationRewards(ctx, sdk.AccAddress(valAddrs[1]), valAddrs[0]) require.NoError(t, err) // historical count should be 3 (validator init + two delegations) require.Equal(t, uint64(3), distrKeeper.GetValidatorHistoricalReferenceCount(ctx)) // validator withdraws commission _, err = distrKeeper.WithdrawValidatorCommission(ctx, valAddrs[0]) require.NoError(t, err) // end period endingPeriod := distrKeeper.IncrementValidatorPeriod(ctx, val) // calculate delegation rewards for del1 rewards := distrKeeper.CalculateDelegationRewards(ctx, val, del1, 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, valAddrs[0]).Commission.IsZero()) // next block ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) // allocate some more rewards distrKeeper.AllocateTokensToValidator(ctx, val, tokens) // first delegator withdraws again _, err = distrKeeper.WithdrawDelegationRewards(ctx, sdk.AccAddress(valAddrs[0]), valAddrs[0]) require.NoError(t, err) // end period endingPeriod = distrKeeper.IncrementValidatorPeriod(ctx, val) // calculate delegation rewards for del1 rewards = distrKeeper.CalculateDelegationRewards(ctx, val, del1, 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, valAddrs[0]).Commission) // next block ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) // allocate some more rewards distrKeeper.AllocateTokensToValidator(ctx, val, tokens) // withdraw commission _, err = distrKeeper.WithdrawValidatorCommission(ctx, valAddrs[0]) require.NoError(t, err) // end period endingPeriod = distrKeeper.IncrementValidatorPeriod(ctx, val) // calculate delegation rewards for del1 rewards = distrKeeper.CalculateDelegationRewards(ctx, val, del1, 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, valAddrs[0]).Commission.IsZero()) } func Test100PercentCommissionReward(t *testing.T) { var ( accountKeeper authkeeper.AccountKeeper bankKeeper bankkeeper.Keeper distrKeeper keeper.Keeper stakingKeeper *stakingkeeper.Keeper ) app, err := simtestutil.Setup(testutil.AppConfig, &accountKeeper, &bankKeeper, &distrKeeper, &stakingKeeper, ) require.NoError(t, err) ctx := app.BaseApp.NewContext(false, tmproto.Header{}) tstaking := stakingtestutil.NewHelper(t, ctx, stakingKeeper) addr := simtestutil.AddTestAddrs(bankKeeper, stakingKeeper, ctx, 2, sdk.NewInt(1000000000)) valAddrs := simtestutil.ConvertAddrsToValAddrs(addr) initial := int64(20) // set module account coins distrAcc := distrKeeper.GetDistributionAccount(ctx) require.NoError(t, banktestutil.FundModuleAccount(bankKeeper, ctx, distrAcc.GetName(), sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(1000))))) accountKeeper.SetModuleAccount(ctx, distrAcc) tokens := sdk.DecCoins{sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, math.LegacyNewDec(initial))} // create validator with 100% commission tstaking.Commission = stakingtypes.NewCommissionRates(sdk.NewDecWithPrec(10, 1), sdk.NewDecWithPrec(10, 1), math.LegacyNewDec(0)) tstaking.CreateValidator(valAddrs[0], valConsPk0, sdk.NewInt(100), true) stakingKeeper.Delegation(ctx, sdk.AccAddress(valAddrs[0]), valAddrs[0]) // end block to bond validator staking.EndBlocker(ctx, stakingKeeper) // next block ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) // fetch validator val := stakingKeeper.Validator(ctx, valAddrs[0]) // allocate some rewards distrKeeper.AllocateTokensToValidator(ctx, val, tokens) // end block staking.EndBlocker(ctx, stakingKeeper) // 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(valAddrs[0]), valAddrs[0]) 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) }