diff --git a/x/staking/keeper/old_slash_test.go b/x/staking/keeper/old_slash_test.go new file mode 100644 index 0000000000..f6b442dc3c --- /dev/null +++ b/x/staking/keeper/old_slash_test.go @@ -0,0 +1,579 @@ +package keeper + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// TODO integrate with test_common.go helper (CreateTestInput) +// setup helper function - creates two validators +func setupHelper(t *testing.T, power int64) (sdk.Context, Keeper, types.Params) { + // setup + ctx, _, _, keeper, _ := CreateTestInput(t, false, power) + params := keeper.GetParams(ctx) + numVals := int64(3) + amt := sdk.TokensFromConsensusPower(power) + bondedCoins := sdk.NewCoins(sdk.NewCoin(keeper.BondDenom(ctx), amt.MulRaw(numVals))) + + bondedPool := keeper.GetBondedPool(ctx) + require.NoError(t, keeper.bankKeeper.SetBalances(ctx, bondedPool.GetAddress(), bondedCoins)) + keeper.supplyKeeper.SetModuleAccount(ctx, bondedPool) + + // add numVals validators + for i := int64(0); i < numVals; i++ { + validator := types.NewValidator(addrVals[i], PKs[i], types.Description{}) + validator, _ = validator.AddTokensFromDel(amt) + validator = TestingUpdateValidator(keeper, ctx, validator, true) + keeper.SetValidatorByConsAddr(ctx, validator) + } + + return ctx, keeper, params +} + +//_________________________________________________________________________________ + +// tests slashUnbondingDelegation +func TestSlashUnbondingDelegation(t *testing.T) { + ctx, keeper, _ := setupHelper(t, 10) + fraction := sdk.NewDecWithPrec(5, 1) + + // set an unbonding delegation with expiration timestamp (beyond which the + // unbonding delegation shouldn't be slashed) + ubd := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 0, + time.Unix(5, 0), sdk.NewInt(10)) + + keeper.SetUnbondingDelegation(ctx, ubd) + + // unbonding started prior to the infraction height, stakw didn't contribute + slashAmount := keeper.slashUnbondingDelegation(ctx, ubd, 1, fraction) + require.Equal(t, int64(0), slashAmount.Int64()) + + // after the expiration time, no longer eligible for slashing + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(10, 0)}) + keeper.SetUnbondingDelegation(ctx, ubd) + slashAmount = keeper.slashUnbondingDelegation(ctx, ubd, 0, fraction) + require.Equal(t, int64(0), slashAmount.Int64()) + + // test valid slash, before expiration timestamp and to which stake contributed + notBondedPool := keeper.GetNotBondedPool(ctx) + oldUnbondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, notBondedPool.GetAddress()) + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(0, 0)}) + keeper.SetUnbondingDelegation(ctx, ubd) + slashAmount = keeper.slashUnbondingDelegation(ctx, ubd, 0, fraction) + require.Equal(t, int64(5), slashAmount.Int64()) + ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + require.Len(t, ubd.Entries, 1) + + // initial balance unchanged + require.Equal(t, sdk.NewInt(10), ubd.Entries[0].InitialBalance) + + // balance decreased + require.Equal(t, sdk.NewInt(5), ubd.Entries[0].Balance) + newUnbondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, notBondedPool.GetAddress()) + diffTokens := oldUnbondedPoolBalances.Sub(newUnbondedPoolBalances) + require.Equal(t, int64(5), diffTokens.AmountOf(keeper.BondDenom(ctx)).Int64()) +} + +// tests slashRedelegation +func TestSlashRedelegation(t *testing.T) { + ctx, keeper, _ := setupHelper(t, 10) + fraction := sdk.NewDecWithPrec(5, 1) + + // add bonded tokens to pool for (re)delegations + startCoins := sdk.NewCoins(sdk.NewInt64Coin(keeper.BondDenom(ctx), 15)) + bondedPool := keeper.GetBondedPool(ctx) + balances := keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) + + require.NoError(t, keeper.bankKeeper.SetBalances(ctx, bondedPool.GetAddress(), balances.Add(startCoins...))) + keeper.supplyKeeper.SetModuleAccount(ctx, bondedPool) + + // set a redelegation with an expiration timestamp beyond which the + // redelegation shouldn't be slashed + rd := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 0, + time.Unix(5, 0), sdk.NewInt(10), sdk.NewDec(10)) + + keeper.SetRedelegation(ctx, rd) + + // set the associated delegation + del := types.NewDelegation(addrDels[0], addrVals[1], sdk.NewDec(10)) + keeper.SetDelegation(ctx, del) + + // started redelegating prior to the current height, stake didn't contribute to infraction + validator, found := keeper.GetValidator(ctx, addrVals[1]) + require.True(t, found) + slashAmount := keeper.slashRedelegation(ctx, validator, rd, 1, fraction) + require.Equal(t, int64(0), slashAmount.Int64()) + + // after the expiration time, no longer eligible for slashing + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(10, 0)}) + keeper.SetRedelegation(ctx, rd) + validator, found = keeper.GetValidator(ctx, addrVals[1]) + require.True(t, found) + slashAmount = keeper.slashRedelegation(ctx, validator, rd, 0, fraction) + require.Equal(t, int64(0), slashAmount.Int64()) + + balances = keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) + + // test valid slash, before expiration timestamp and to which stake contributed + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(0, 0)}) + keeper.SetRedelegation(ctx, rd) + validator, found = keeper.GetValidator(ctx, addrVals[1]) + require.True(t, found) + slashAmount = keeper.slashRedelegation(ctx, validator, rd, 0, fraction) + require.Equal(t, int64(5), slashAmount.Int64()) + rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + require.Len(t, rd.Entries, 1) + + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + + // initialbalance unchanged + require.Equal(t, sdk.NewInt(10), rd.Entries[0].InitialBalance) + + // shares decreased + del, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[1]) + require.True(t, found) + require.Equal(t, int64(5), del.Shares.RoundInt64()) + + // pool bonded tokens should decrease + burnedCoins := sdk.NewCoins(sdk.NewCoin(keeper.BondDenom(ctx), slashAmount)) + require.Equal(t, balances.Sub(burnedCoins), keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress())) +} + +// tests Slash at a future height (must panic) +func TestSlashAtFutureHeight(t *testing.T) { + ctx, keeper, _ := setupHelper(t, 10) + consAddr := sdk.ConsAddress(PKs[0].Address()) + fraction := sdk.NewDecWithPrec(5, 1) + require.Panics(t, func() { keeper.Slash(ctx, consAddr, 1, 10, fraction) }) +} + +// test slash at a negative height +// this just represents pre-genesis and should have the same effect as slashing at height 0 +func TestSlashAtNegativeHeight(t *testing.T) { + ctx, keeper, _ := setupHelper(t, 10) + consAddr := sdk.ConsAddress(PKs[0].Address()) + fraction := sdk.NewDecWithPrec(5, 1) + + bondedPool := keeper.GetBondedPool(ctx) + oldBondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) + + validator, found := keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + keeper.Slash(ctx, consAddr, -2, 10, fraction) + + // read updated state + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates), "cons addr: %v, updates: %v", []byte(consAddr), updates) + + validator = keeper.mustGetValidator(ctx, validator.OperatorAddress) + // power decreased + require.Equal(t, int64(5), validator.GetConsensusPower()) + + // pool bonded shares decreased + newBondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) + diffTokens := oldBondedPoolBalances.Sub(newBondedPoolBalances).AmountOf(keeper.BondDenom(ctx)) + require.Equal(t, sdk.TokensFromConsensusPower(5).String(), diffTokens.String()) +} + +// tests Slash at the current height +func TestSlashValidatorAtCurrentHeight(t *testing.T) { + ctx, keeper, _ := setupHelper(t, 10) + consAddr := sdk.ConsAddress(PKs[0].Address()) + fraction := sdk.NewDecWithPrec(5, 1) + + bondedPool := keeper.GetBondedPool(ctx) + oldBondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) + + validator, found := keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + keeper.Slash(ctx, consAddr, ctx.BlockHeight(), 10, fraction) + + // read updated state + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates), "cons addr: %v, updates: %v", []byte(consAddr), updates) + + validator = keeper.mustGetValidator(ctx, validator.OperatorAddress) + // power decreased + require.Equal(t, int64(5), validator.GetConsensusPower()) + + // pool bonded shares decreased + newBondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) + diffTokens := oldBondedPoolBalances.Sub(newBondedPoolBalances).AmountOf(keeper.BondDenom(ctx)) + require.Equal(t, sdk.TokensFromConsensusPower(5).String(), diffTokens.String()) +} + +// tests Slash at a previous height with an unbonding delegation +func TestSlashWithUnbondingDelegation(t *testing.T) { + ctx, keeper, _ := setupHelper(t, 10) + consAddr := sdk.ConsAddress(PKs[0].Address()) + fraction := sdk.NewDecWithPrec(5, 1) + + // set an unbonding delegation with expiration timestamp beyond which the + // unbonding delegation shouldn't be slashed + ubdTokens := sdk.TokensFromConsensusPower(4) + ubd := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 11, + time.Unix(0, 0), ubdTokens) + keeper.SetUnbondingDelegation(ctx, ubd) + + // slash validator for the first time + ctx = ctx.WithBlockHeight(12) + bondedPool := keeper.GetBondedPool(ctx) + oldBondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) + + validator, found := keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + keeper.Slash(ctx, consAddr, 10, 10, fraction) + + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + + // read updating unbonding delegation + ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + require.Len(t, ubd.Entries, 1) + + // balance decreased + require.Equal(t, sdk.TokensFromConsensusPower(2), ubd.Entries[0].Balance) + + // bonded tokens burned + newBondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) + diffTokens := oldBondedPoolBalances.Sub(newBondedPoolBalances).AmountOf(keeper.BondDenom(ctx)) + require.Equal(t, sdk.TokensFromConsensusPower(3), diffTokens) + + // read updated validator + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + + // power decreased by 3 - 6 stake originally bonded at the time of infraction + // was still bonded at the time of discovery and was slashed by half, 4 stake + // bonded at the time of discovery hadn't been bonded at the time of infraction + // and wasn't slashed + require.Equal(t, int64(7), validator.GetConsensusPower()) + + // slash validator again + ctx = ctx.WithBlockHeight(13) + keeper.Slash(ctx, consAddr, 9, 10, fraction) + + ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + require.Len(t, ubd.Entries, 1) + + // balance decreased again + require.Equal(t, sdk.NewInt(0), ubd.Entries[0].Balance) + + // bonded tokens burned again + newBondedPoolBalances = keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) + diffTokens = oldBondedPoolBalances.Sub(newBondedPoolBalances).AmountOf(keeper.BondDenom(ctx)) + require.Equal(t, sdk.TokensFromConsensusPower(6), diffTokens) + + // read updated validator + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + + // power decreased by 3 again + require.Equal(t, int64(4), validator.GetConsensusPower()) + + // slash validator again + // all originally bonded stake has been slashed, so this will have no effect + // on the unbonding delegation, but it will slash stake bonded since the infraction + // this may not be the desirable behaviour, ref https://github.com/cosmos/cosmos-sdk/issues/1440 + ctx = ctx.WithBlockHeight(13) + keeper.Slash(ctx, consAddr, 9, 10, fraction) + + ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + require.Len(t, ubd.Entries, 1) + + // balance unchanged + require.Equal(t, sdk.NewInt(0), ubd.Entries[0].Balance) + + // bonded tokens burned again + newBondedPoolBalances = keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) + diffTokens = oldBondedPoolBalances.Sub(newBondedPoolBalances).AmountOf(keeper.BondDenom(ctx)) + require.Equal(t, sdk.TokensFromConsensusPower(9), diffTokens) + + // read updated validator + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + + // power decreased by 3 again + require.Equal(t, int64(1), validator.GetConsensusPower()) + + // slash validator again + // all originally bonded stake has been slashed, so this will have no effect + // on the unbonding delegation, but it will slash stake bonded since the infraction + // this may not be the desirable behaviour, ref https://github.com/cosmos/cosmos-sdk/issues/1440 + ctx = ctx.WithBlockHeight(13) + keeper.Slash(ctx, consAddr, 9, 10, fraction) + + ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + require.Len(t, ubd.Entries, 1) + + // balance unchanged + require.Equal(t, sdk.NewInt(0), ubd.Entries[0].Balance) + + // just 1 bonded token burned again since that's all the validator now has + newBondedPoolBalances = keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) + diffTokens = oldBondedPoolBalances.Sub(newBondedPoolBalances).AmountOf(keeper.BondDenom(ctx)) + require.Equal(t, sdk.TokensFromConsensusPower(10), diffTokens) + + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + + // read updated validator + // power decreased by 1 again, validator is out of stake + // validator should be in unbonding period + validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) +} + +// tests Slash at a previous height with a redelegation +func TestSlashWithRedelegation(t *testing.T) { + ctx, keeper, _ := setupHelper(t, 10) + consAddr := sdk.ConsAddress(PKs[0].Address()) + fraction := sdk.NewDecWithPrec(5, 1) + bondDenom := keeper.BondDenom(ctx) + + // set a redelegation + rdTokens := sdk.TokensFromConsensusPower(6) + rd := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 11, + time.Unix(0, 0), rdTokens, rdTokens.ToDec()) + keeper.SetRedelegation(ctx, rd) + + // set the associated delegation + del := types.NewDelegation(addrDels[0], addrVals[1], rdTokens.ToDec()) + keeper.SetDelegation(ctx, del) + + // update bonded tokens + bondedPool := keeper.GetBondedPool(ctx) + notBondedPool := keeper.GetNotBondedPool(ctx) + rdCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, rdTokens.MulRaw(2))) + + balances := keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) + err := keeper.bankKeeper.SetBalances(ctx, bondedPool.GetAddress(), balances.Add(rdCoins...)) + require.NoError(t, err) + + keeper.supplyKeeper.SetModuleAccount(ctx, bondedPool) + + oldBonded := keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount + oldNotBonded := keeper.bankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), bondDenom).Amount + + // slash validator + ctx = ctx.WithBlockHeight(12) + validator, found := keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + + require.NotPanics(t, func() { keeper.Slash(ctx, consAddr, 10, 10, fraction) }) + burnAmount := sdk.TokensFromConsensusPower(10).ToDec().Mul(fraction).TruncateInt() + + bondedPool = keeper.GetBondedPool(ctx) + notBondedPool = keeper.GetNotBondedPool(ctx) + + // burn bonded tokens from only from delegations + bondedPoolBalance := keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount + require.True(sdk.IntEq(t, oldBonded.Sub(burnAmount), bondedPoolBalance)) + + notBondedPoolBalance := keeper.bankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), bondDenom).Amount + require.True(sdk.IntEq(t, oldNotBonded, notBondedPoolBalance)) + oldBonded = keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount + + // read updating redelegation + rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + require.Len(t, rd.Entries, 1) + // read updated validator + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + // power decreased by 2 - 4 stake originally bonded at the time of infraction + // was still bonded at the time of discovery and was slashed by half, 4 stake + // bonded at the time of discovery hadn't been bonded at the time of infraction + // and wasn't slashed + require.Equal(t, int64(8), validator.GetConsensusPower()) + + // slash the validator again + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + + require.NotPanics(t, func() { keeper.Slash(ctx, consAddr, 10, 10, sdk.OneDec()) }) + burnAmount = sdk.TokensFromConsensusPower(7) + + // read updated pool + bondedPool = keeper.GetBondedPool(ctx) + notBondedPool = keeper.GetNotBondedPool(ctx) + + // seven bonded tokens burned + bondedPoolBalance = keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount + require.True(sdk.IntEq(t, oldBonded.Sub(burnAmount), bondedPoolBalance)) + require.True(sdk.IntEq(t, oldNotBonded, notBondedPoolBalance)) + + bondedPoolBalance = keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount + require.True(sdk.IntEq(t, oldBonded.Sub(burnAmount), bondedPoolBalance)) + + notBondedPoolBalance = keeper.bankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), bondDenom).Amount + require.True(sdk.IntEq(t, oldNotBonded, notBondedPoolBalance)) + oldBonded = keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount + + // read updating redelegation + rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + require.Len(t, rd.Entries, 1) + // read updated validator + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + // power decreased by 4 + require.Equal(t, int64(4), validator.GetConsensusPower()) + + // slash the validator again, by 100% + ctx = ctx.WithBlockHeight(12) + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + + require.NotPanics(t, func() { keeper.Slash(ctx, consAddr, 10, 10, sdk.OneDec()) }) + + burnAmount = sdk.TokensFromConsensusPower(10).ToDec().Mul(sdk.OneDec()).TruncateInt() + burnAmount = burnAmount.Sub(sdk.OneDec().MulInt(rdTokens).TruncateInt()) + + // read updated pool + bondedPool = keeper.GetBondedPool(ctx) + notBondedPool = keeper.GetNotBondedPool(ctx) + + bondedPoolBalance = keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount + require.True(sdk.IntEq(t, oldBonded.Sub(burnAmount), bondedPoolBalance)) + notBondedPoolBalance = keeper.bankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), bondDenom).Amount + require.True(sdk.IntEq(t, oldNotBonded, notBondedPoolBalance)) + oldBonded = keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount + + // read updating redelegation + rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + require.Len(t, rd.Entries, 1) + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + // read updated validator + // validator decreased to zero power, should be in unbonding period + validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) + + // slash the validator again, by 100% + // no stake remains to be slashed + ctx = ctx.WithBlockHeight(12) + // validator still in unbonding period + validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) + + require.NotPanics(t, func() { keeper.Slash(ctx, consAddr, 10, 10, sdk.OneDec()) }) + + // read updated pool + bondedPool = keeper.GetBondedPool(ctx) + notBondedPool = keeper.GetNotBondedPool(ctx) + + bondedPoolBalance = keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount + require.True(sdk.IntEq(t, oldBonded, bondedPoolBalance)) + notBondedPoolBalance = keeper.bankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), bondDenom).Amount + require.True(sdk.IntEq(t, oldNotBonded, notBondedPoolBalance)) + + // read updating redelegation + rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + require.Len(t, rd.Entries, 1) + // read updated validator + // power still zero, still in unbonding period + validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) +} + +// tests Slash at a previous height with both an unbonding delegation and a redelegation +func TestSlashBoth(t *testing.T) { + ctx, keeper, _ := setupHelper(t, 10) + fraction := sdk.NewDecWithPrec(5, 1) + bondDenom := keeper.BondDenom(ctx) + + // set a redelegation with expiration timestamp beyond which the + // redelegation shouldn't be slashed + rdATokens := sdk.TokensFromConsensusPower(6) + rdA := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 11, + time.Unix(0, 0), rdATokens, + rdATokens.ToDec()) + keeper.SetRedelegation(ctx, rdA) + + // set the associated delegation + delA := types.NewDelegation(addrDels[0], addrVals[1], rdATokens.ToDec()) + keeper.SetDelegation(ctx, delA) + + // set an unbonding delegation with expiration timestamp (beyond which the + // unbonding delegation shouldn't be slashed) + ubdATokens := sdk.TokensFromConsensusPower(4) + ubdA := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 11, + time.Unix(0, 0), ubdATokens) + keeper.SetUnbondingDelegation(ctx, ubdA) + + bondedCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, rdATokens.MulRaw(2))) + notBondedCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, ubdATokens)) + + // update bonded tokens + bondedPool := keeper.GetBondedPool(ctx) + notBondedPool := keeper.GetNotBondedPool(ctx) + + bondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) + require.NoError(t, keeper.bankKeeper.SetBalances(ctx, bondedPool.GetAddress(), bondedPoolBalances.Add(bondedCoins...))) + + notBondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, notBondedPool.GetAddress()) + require.NoError(t, keeper.bankKeeper.SetBalances(ctx, notBondedPool.GetAddress(), notBondedPoolBalances.Add(notBondedCoins...))) + + keeper.supplyKeeper.SetModuleAccount(ctx, bondedPool) + keeper.supplyKeeper.SetModuleAccount(ctx, notBondedPool) + + oldBonded := keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount + oldNotBonded := keeper.bankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), bondDenom).Amount + // slash validator + ctx = ctx.WithBlockHeight(12) + validator, found := keeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(PKs[0])) + require.True(t, found) + consAddr0 := sdk.ConsAddress(PKs[0].Address()) + keeper.Slash(ctx, consAddr0, 10, 10, fraction) + + burnedNotBondedAmount := fraction.MulInt(ubdATokens).TruncateInt() + burnedBondAmount := sdk.TokensFromConsensusPower(10).ToDec().Mul(fraction).TruncateInt() + burnedBondAmount = burnedBondAmount.Sub(burnedNotBondedAmount) + + // read updated pool + bondedPool = keeper.GetBondedPool(ctx) + notBondedPool = keeper.GetNotBondedPool(ctx) + + bondedPoolBalance := keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount + require.True(sdk.IntEq(t, oldBonded.Sub(burnedBondAmount), bondedPoolBalance)) + + notBondedPoolBalance := keeper.bankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), bondDenom).Amount + require.True(sdk.IntEq(t, oldNotBonded.Sub(burnedNotBondedAmount), notBondedPoolBalance)) + + // read updating redelegation + rdA, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + require.Len(t, rdA.Entries, 1) + // read updated validator + validator, found = keeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(PKs[0])) + require.True(t, found) + // power not decreased, all stake was bonded since + require.Equal(t, int64(10), validator.GetConsensusPower()) +} diff --git a/x/staking/keeper/slash_test.go b/x/staking/keeper/slash_test.go index 2127cf51e1..3f88942a5c 100644 --- a/x/staking/keeper/slash_test.go +++ b/x/staking/keeper/slash_test.go @@ -1,604 +1,66 @@ -package keeper +package keeper_test import ( "testing" - "time" - - "github.com/stretchr/testify/require" - - abci "github.com/tendermint/tendermint/abci/types" + "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/keeper" "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/require" ) -// TODO integrate with test_common.go helper (CreateTestInput) -// setup helper function - creates two validators -func setupHelper(t *testing.T, power int64) (sdk.Context, Keeper, types.Params) { - // setup - ctx, _, _, keeper, _ := CreateTestInput(t, false, power) - params := keeper.GetParams(ctx) +// initConfig creates 3 validators and bootstrap the app. +func initConfig(t *testing.T) (*simapp.SimApp, sdk.Context, []sdk.ValAddress) { + _, app, ctx := getBaseSimappWithCustomKeeper() + + addrDels := simapp.AddTestAddrsIncremental(app, ctx, 3, sdk.NewInt(10000)) + addrVals := simapp.ConvertAddrsToValAddrs(addrDels) + numVals := int64(3) - amt := sdk.TokensFromConsensusPower(power) - bondedCoins := sdk.NewCoins(sdk.NewCoin(keeper.BondDenom(ctx), amt.MulRaw(numVals))) + amt := sdk.TokensFromConsensusPower(10) + bondedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), amt.MulRaw(numVals))) - bondedPool := keeper.GetBondedPool(ctx) - require.NoError(t, keeper.bankKeeper.SetBalances(ctx, bondedPool.GetAddress(), bondedCoins)) - keeper.supplyKeeper.SetModuleAccount(ctx, bondedPool) + notBondedPool := app.StakingKeeper.GetNotBondedPool(ctx) + err := app.BankKeeper.SetBalances(ctx, notBondedPool.GetAddress(), bondedCoins) + require.NoError(t, err) + app.SupplyKeeper.SetModuleAccount(ctx, notBondedPool) + + bondedPool := app.StakingKeeper.GetBondedPool(ctx) + err = app.BankKeeper.SetBalances(ctx, bondedPool.GetAddress(), bondedCoins) + require.NoError(t, err) + app.SupplyKeeper.SetModuleAccount(ctx, bondedPool) - // add numVals validators for i := int64(0); i < numVals; i++ { validator := types.NewValidator(addrVals[i], PKs[i], types.Description{}) validator, _ = validator.AddTokensFromDel(amt) - validator = TestingUpdateValidator(keeper, ctx, validator, true) - keeper.SetValidatorByConsAddr(ctx, validator) + validator = keeper.TestingUpdateValidator(app.StakingKeeper, ctx, validator, true) + app.StakingKeeper.SetValidatorByConsAddr(ctx, validator) } - return ctx, keeper, params + return app, ctx, addrVals } -//_________________________________________________________________________________ - // tests Jail, Unjail func TestRevocation(t *testing.T) { - // setup - ctx, keeper, _ := setupHelper(t, 10) - addr := addrVals[0] + app, ctx, addrVals := initConfig(t) + consAddr := sdk.ConsAddress(PKs[0].Address()) // initial state - val, found := keeper.GetValidator(ctx, addr) + val, found := app.StakingKeeper.GetValidator(ctx, addrVals[0]) require.True(t, found) require.False(t, val.IsJailed()) // test jail - keeper.Jail(ctx, consAddr) - val, found = keeper.GetValidator(ctx, addr) + app.StakingKeeper.Jail(ctx, consAddr) + val, found = app.StakingKeeper.GetValidator(ctx, addrVals[0]) require.True(t, found) require.True(t, val.IsJailed()) // test unjail - keeper.Unjail(ctx, consAddr) - val, found = keeper.GetValidator(ctx, addr) + app.StakingKeeper.Unjail(ctx, consAddr) + val, found = app.StakingKeeper.GetValidator(ctx, addrVals[0]) require.True(t, found) require.False(t, val.IsJailed()) } - -// tests slashUnbondingDelegation -func TestSlashUnbondingDelegation(t *testing.T) { - ctx, keeper, _ := setupHelper(t, 10) - fraction := sdk.NewDecWithPrec(5, 1) - - // set an unbonding delegation with expiration timestamp (beyond which the - // unbonding delegation shouldn't be slashed) - ubd := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 0, - time.Unix(5, 0), sdk.NewInt(10)) - - keeper.SetUnbondingDelegation(ctx, ubd) - - // unbonding started prior to the infraction height, stakw didn't contribute - slashAmount := keeper.slashUnbondingDelegation(ctx, ubd, 1, fraction) - require.Equal(t, int64(0), slashAmount.Int64()) - - // after the expiration time, no longer eligible for slashing - ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(10, 0)}) - keeper.SetUnbondingDelegation(ctx, ubd) - slashAmount = keeper.slashUnbondingDelegation(ctx, ubd, 0, fraction) - require.Equal(t, int64(0), slashAmount.Int64()) - - // test valid slash, before expiration timestamp and to which stake contributed - notBondedPool := keeper.GetNotBondedPool(ctx) - oldUnbondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, notBondedPool.GetAddress()) - ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(0, 0)}) - keeper.SetUnbondingDelegation(ctx, ubd) - slashAmount = keeper.slashUnbondingDelegation(ctx, ubd, 0, fraction) - require.Equal(t, int64(5), slashAmount.Int64()) - ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) - require.True(t, found) - require.Len(t, ubd.Entries, 1) - - // initial balance unchanged - require.Equal(t, sdk.NewInt(10), ubd.Entries[0].InitialBalance) - - // balance decreased - require.Equal(t, sdk.NewInt(5), ubd.Entries[0].Balance) - newUnbondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, notBondedPool.GetAddress()) - diffTokens := oldUnbondedPoolBalances.Sub(newUnbondedPoolBalances) - require.Equal(t, int64(5), diffTokens.AmountOf(keeper.BondDenom(ctx)).Int64()) -} - -// tests slashRedelegation -func TestSlashRedelegation(t *testing.T) { - ctx, keeper, _ := setupHelper(t, 10) - fraction := sdk.NewDecWithPrec(5, 1) - - // add bonded tokens to pool for (re)delegations - startCoins := sdk.NewCoins(sdk.NewInt64Coin(keeper.BondDenom(ctx), 15)) - bondedPool := keeper.GetBondedPool(ctx) - balances := keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) - - require.NoError(t, keeper.bankKeeper.SetBalances(ctx, bondedPool.GetAddress(), balances.Add(startCoins...))) - keeper.supplyKeeper.SetModuleAccount(ctx, bondedPool) - - // set a redelegation with an expiration timestamp beyond which the - // redelegation shouldn't be slashed - rd := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 0, - time.Unix(5, 0), sdk.NewInt(10), sdk.NewDec(10)) - - keeper.SetRedelegation(ctx, rd) - - // set the associated delegation - del := types.NewDelegation(addrDels[0], addrVals[1], sdk.NewDec(10)) - keeper.SetDelegation(ctx, del) - - // started redelegating prior to the current height, stake didn't contribute to infraction - validator, found := keeper.GetValidator(ctx, addrVals[1]) - require.True(t, found) - slashAmount := keeper.slashRedelegation(ctx, validator, rd, 1, fraction) - require.Equal(t, int64(0), slashAmount.Int64()) - - // after the expiration time, no longer eligible for slashing - ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(10, 0)}) - keeper.SetRedelegation(ctx, rd) - validator, found = keeper.GetValidator(ctx, addrVals[1]) - require.True(t, found) - slashAmount = keeper.slashRedelegation(ctx, validator, rd, 0, fraction) - require.Equal(t, int64(0), slashAmount.Int64()) - - balances = keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) - - // test valid slash, before expiration timestamp and to which stake contributed - ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(0, 0)}) - keeper.SetRedelegation(ctx, rd) - validator, found = keeper.GetValidator(ctx, addrVals[1]) - require.True(t, found) - slashAmount = keeper.slashRedelegation(ctx, validator, rd, 0, fraction) - require.Equal(t, int64(5), slashAmount.Int64()) - rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) - require.True(t, found) - require.Len(t, rd.Entries, 1) - - // end block - updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) - require.Equal(t, 1, len(updates)) - - // initialbalance unchanged - require.Equal(t, sdk.NewInt(10), rd.Entries[0].InitialBalance) - - // shares decreased - del, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[1]) - require.True(t, found) - require.Equal(t, int64(5), del.Shares.RoundInt64()) - - // pool bonded tokens should decrease - burnedCoins := sdk.NewCoins(sdk.NewCoin(keeper.BondDenom(ctx), slashAmount)) - require.Equal(t, balances.Sub(burnedCoins), keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress())) -} - -// tests Slash at a future height (must panic) -func TestSlashAtFutureHeight(t *testing.T) { - ctx, keeper, _ := setupHelper(t, 10) - consAddr := sdk.ConsAddress(PKs[0].Address()) - fraction := sdk.NewDecWithPrec(5, 1) - require.Panics(t, func() { keeper.Slash(ctx, consAddr, 1, 10, fraction) }) -} - -// test slash at a negative height -// this just represents pre-genesis and should have the same effect as slashing at height 0 -func TestSlashAtNegativeHeight(t *testing.T) { - ctx, keeper, _ := setupHelper(t, 10) - consAddr := sdk.ConsAddress(PKs[0].Address()) - fraction := sdk.NewDecWithPrec(5, 1) - - bondedPool := keeper.GetBondedPool(ctx) - oldBondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) - - validator, found := keeper.GetValidatorByConsAddr(ctx, consAddr) - require.True(t, found) - keeper.Slash(ctx, consAddr, -2, 10, fraction) - - // read updated state - validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.True(t, found) - - // end block - updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) - require.Equal(t, 1, len(updates), "cons addr: %v, updates: %v", []byte(consAddr), updates) - - validator = keeper.mustGetValidator(ctx, validator.OperatorAddress) - // power decreased - require.Equal(t, int64(5), validator.GetConsensusPower()) - - // pool bonded shares decreased - newBondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) - diffTokens := oldBondedPoolBalances.Sub(newBondedPoolBalances).AmountOf(keeper.BondDenom(ctx)) - require.Equal(t, sdk.TokensFromConsensusPower(5).String(), diffTokens.String()) -} - -// tests Slash at the current height -func TestSlashValidatorAtCurrentHeight(t *testing.T) { - ctx, keeper, _ := setupHelper(t, 10) - consAddr := sdk.ConsAddress(PKs[0].Address()) - fraction := sdk.NewDecWithPrec(5, 1) - - bondedPool := keeper.GetBondedPool(ctx) - oldBondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) - - validator, found := keeper.GetValidatorByConsAddr(ctx, consAddr) - require.True(t, found) - keeper.Slash(ctx, consAddr, ctx.BlockHeight(), 10, fraction) - - // read updated state - validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.True(t, found) - - // end block - updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) - require.Equal(t, 1, len(updates), "cons addr: %v, updates: %v", []byte(consAddr), updates) - - validator = keeper.mustGetValidator(ctx, validator.OperatorAddress) - // power decreased - require.Equal(t, int64(5), validator.GetConsensusPower()) - - // pool bonded shares decreased - newBondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) - diffTokens := oldBondedPoolBalances.Sub(newBondedPoolBalances).AmountOf(keeper.BondDenom(ctx)) - require.Equal(t, sdk.TokensFromConsensusPower(5).String(), diffTokens.String()) -} - -// tests Slash at a previous height with an unbonding delegation -func TestSlashWithUnbondingDelegation(t *testing.T) { - ctx, keeper, _ := setupHelper(t, 10) - consAddr := sdk.ConsAddress(PKs[0].Address()) - fraction := sdk.NewDecWithPrec(5, 1) - - // set an unbonding delegation with expiration timestamp beyond which the - // unbonding delegation shouldn't be slashed - ubdTokens := sdk.TokensFromConsensusPower(4) - ubd := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 11, - time.Unix(0, 0), ubdTokens) - keeper.SetUnbondingDelegation(ctx, ubd) - - // slash validator for the first time - ctx = ctx.WithBlockHeight(12) - bondedPool := keeper.GetBondedPool(ctx) - oldBondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) - - validator, found := keeper.GetValidatorByConsAddr(ctx, consAddr) - require.True(t, found) - keeper.Slash(ctx, consAddr, 10, 10, fraction) - - // end block - updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) - require.Equal(t, 1, len(updates)) - - // read updating unbonding delegation - ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) - require.True(t, found) - require.Len(t, ubd.Entries, 1) - - // balance decreased - require.Equal(t, sdk.TokensFromConsensusPower(2), ubd.Entries[0].Balance) - - // bonded tokens burned - newBondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) - diffTokens := oldBondedPoolBalances.Sub(newBondedPoolBalances).AmountOf(keeper.BondDenom(ctx)) - require.Equal(t, sdk.TokensFromConsensusPower(3), diffTokens) - - // read updated validator - validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.True(t, found) - - // power decreased by 3 - 6 stake originally bonded at the time of infraction - // was still bonded at the time of discovery and was slashed by half, 4 stake - // bonded at the time of discovery hadn't been bonded at the time of infraction - // and wasn't slashed - require.Equal(t, int64(7), validator.GetConsensusPower()) - - // slash validator again - ctx = ctx.WithBlockHeight(13) - keeper.Slash(ctx, consAddr, 9, 10, fraction) - - ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) - require.True(t, found) - require.Len(t, ubd.Entries, 1) - - // balance decreased again - require.Equal(t, sdk.NewInt(0), ubd.Entries[0].Balance) - - // bonded tokens burned again - newBondedPoolBalances = keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) - diffTokens = oldBondedPoolBalances.Sub(newBondedPoolBalances).AmountOf(keeper.BondDenom(ctx)) - require.Equal(t, sdk.TokensFromConsensusPower(6), diffTokens) - - // read updated validator - validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.True(t, found) - - // power decreased by 3 again - require.Equal(t, int64(4), validator.GetConsensusPower()) - - // slash validator again - // all originally bonded stake has been slashed, so this will have no effect - // on the unbonding delegation, but it will slash stake bonded since the infraction - // this may not be the desirable behaviour, ref https://github.com/cosmos/cosmos-sdk/issues/1440 - ctx = ctx.WithBlockHeight(13) - keeper.Slash(ctx, consAddr, 9, 10, fraction) - - ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) - require.True(t, found) - require.Len(t, ubd.Entries, 1) - - // balance unchanged - require.Equal(t, sdk.NewInt(0), ubd.Entries[0].Balance) - - // bonded tokens burned again - newBondedPoolBalances = keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) - diffTokens = oldBondedPoolBalances.Sub(newBondedPoolBalances).AmountOf(keeper.BondDenom(ctx)) - require.Equal(t, sdk.TokensFromConsensusPower(9), diffTokens) - - // read updated validator - validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.True(t, found) - - // power decreased by 3 again - require.Equal(t, int64(1), validator.GetConsensusPower()) - - // slash validator again - // all originally bonded stake has been slashed, so this will have no effect - // on the unbonding delegation, but it will slash stake bonded since the infraction - // this may not be the desirable behaviour, ref https://github.com/cosmos/cosmos-sdk/issues/1440 - ctx = ctx.WithBlockHeight(13) - keeper.Slash(ctx, consAddr, 9, 10, fraction) - - ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) - require.True(t, found) - require.Len(t, ubd.Entries, 1) - - // balance unchanged - require.Equal(t, sdk.NewInt(0), ubd.Entries[0].Balance) - - // just 1 bonded token burned again since that's all the validator now has - newBondedPoolBalances = keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) - diffTokens = oldBondedPoolBalances.Sub(newBondedPoolBalances).AmountOf(keeper.BondDenom(ctx)) - require.Equal(t, sdk.TokensFromConsensusPower(10), diffTokens) - - // apply TM updates - keeper.ApplyAndReturnValidatorSetUpdates(ctx) - - // read updated validator - // power decreased by 1 again, validator is out of stake - // validator should be in unbonding period - validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.Equal(t, validator.GetStatus(), sdk.Unbonding) -} - -// tests Slash at a previous height with a redelegation -func TestSlashWithRedelegation(t *testing.T) { - ctx, keeper, _ := setupHelper(t, 10) - consAddr := sdk.ConsAddress(PKs[0].Address()) - fraction := sdk.NewDecWithPrec(5, 1) - bondDenom := keeper.BondDenom(ctx) - - // set a redelegation - rdTokens := sdk.TokensFromConsensusPower(6) - rd := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 11, - time.Unix(0, 0), rdTokens, rdTokens.ToDec()) - keeper.SetRedelegation(ctx, rd) - - // set the associated delegation - del := types.NewDelegation(addrDels[0], addrVals[1], rdTokens.ToDec()) - keeper.SetDelegation(ctx, del) - - // update bonded tokens - bondedPool := keeper.GetBondedPool(ctx) - notBondedPool := keeper.GetNotBondedPool(ctx) - rdCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, rdTokens.MulRaw(2))) - - balances := keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) - err := keeper.bankKeeper.SetBalances(ctx, bondedPool.GetAddress(), balances.Add(rdCoins...)) - require.NoError(t, err) - - keeper.supplyKeeper.SetModuleAccount(ctx, bondedPool) - - oldBonded := keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount - oldNotBonded := keeper.bankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), bondDenom).Amount - - // slash validator - ctx = ctx.WithBlockHeight(12) - validator, found := keeper.GetValidatorByConsAddr(ctx, consAddr) - require.True(t, found) - - require.NotPanics(t, func() { keeper.Slash(ctx, consAddr, 10, 10, fraction) }) - burnAmount := sdk.TokensFromConsensusPower(10).ToDec().Mul(fraction).TruncateInt() - - bondedPool = keeper.GetBondedPool(ctx) - notBondedPool = keeper.GetNotBondedPool(ctx) - - // burn bonded tokens from only from delegations - bondedPoolBalance := keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount - require.True(sdk.IntEq(t, oldBonded.Sub(burnAmount), bondedPoolBalance)) - - notBondedPoolBalance := keeper.bankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), bondDenom).Amount - require.True(sdk.IntEq(t, oldNotBonded, notBondedPoolBalance)) - oldBonded = keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount - - // read updating redelegation - rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) - require.True(t, found) - require.Len(t, rd.Entries, 1) - // read updated validator - validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.True(t, found) - // power decreased by 2 - 4 stake originally bonded at the time of infraction - // was still bonded at the time of discovery and was slashed by half, 4 stake - // bonded at the time of discovery hadn't been bonded at the time of infraction - // and wasn't slashed - require.Equal(t, int64(8), validator.GetConsensusPower()) - - // slash the validator again - validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.True(t, found) - - require.NotPanics(t, func() { keeper.Slash(ctx, consAddr, 10, 10, sdk.OneDec()) }) - burnAmount = sdk.TokensFromConsensusPower(7) - - // read updated pool - bondedPool = keeper.GetBondedPool(ctx) - notBondedPool = keeper.GetNotBondedPool(ctx) - - // seven bonded tokens burned - bondedPoolBalance = keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount - require.True(sdk.IntEq(t, oldBonded.Sub(burnAmount), bondedPoolBalance)) - require.True(sdk.IntEq(t, oldNotBonded, notBondedPoolBalance)) - - bondedPoolBalance = keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount - require.True(sdk.IntEq(t, oldBonded.Sub(burnAmount), bondedPoolBalance)) - - notBondedPoolBalance = keeper.bankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), bondDenom).Amount - require.True(sdk.IntEq(t, oldNotBonded, notBondedPoolBalance)) - oldBonded = keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount - - // read updating redelegation - rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) - require.True(t, found) - require.Len(t, rd.Entries, 1) - // read updated validator - validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.True(t, found) - // power decreased by 4 - require.Equal(t, int64(4), validator.GetConsensusPower()) - - // slash the validator again, by 100% - ctx = ctx.WithBlockHeight(12) - validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.True(t, found) - - require.NotPanics(t, func() { keeper.Slash(ctx, consAddr, 10, 10, sdk.OneDec()) }) - - burnAmount = sdk.TokensFromConsensusPower(10).ToDec().Mul(sdk.OneDec()).TruncateInt() - burnAmount = burnAmount.Sub(sdk.OneDec().MulInt(rdTokens).TruncateInt()) - - // read updated pool - bondedPool = keeper.GetBondedPool(ctx) - notBondedPool = keeper.GetNotBondedPool(ctx) - - bondedPoolBalance = keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount - require.True(sdk.IntEq(t, oldBonded.Sub(burnAmount), bondedPoolBalance)) - notBondedPoolBalance = keeper.bankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), bondDenom).Amount - require.True(sdk.IntEq(t, oldNotBonded, notBondedPoolBalance)) - oldBonded = keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount - - // read updating redelegation - rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) - require.True(t, found) - require.Len(t, rd.Entries, 1) - // apply TM updates - keeper.ApplyAndReturnValidatorSetUpdates(ctx) - // read updated validator - // validator decreased to zero power, should be in unbonding period - validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.Equal(t, validator.GetStatus(), sdk.Unbonding) - - // slash the validator again, by 100% - // no stake remains to be slashed - ctx = ctx.WithBlockHeight(12) - // validator still in unbonding period - validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.Equal(t, validator.GetStatus(), sdk.Unbonding) - - require.NotPanics(t, func() { keeper.Slash(ctx, consAddr, 10, 10, sdk.OneDec()) }) - - // read updated pool - bondedPool = keeper.GetBondedPool(ctx) - notBondedPool = keeper.GetNotBondedPool(ctx) - - bondedPoolBalance = keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount - require.True(sdk.IntEq(t, oldBonded, bondedPoolBalance)) - notBondedPoolBalance = keeper.bankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), bondDenom).Amount - require.True(sdk.IntEq(t, oldNotBonded, notBondedPoolBalance)) - - // read updating redelegation - rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) - require.True(t, found) - require.Len(t, rd.Entries, 1) - // read updated validator - // power still zero, still in unbonding period - validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) - require.Equal(t, validator.GetStatus(), sdk.Unbonding) -} - -// tests Slash at a previous height with both an unbonding delegation and a redelegation -func TestSlashBoth(t *testing.T) { - ctx, keeper, _ := setupHelper(t, 10) - fraction := sdk.NewDecWithPrec(5, 1) - bondDenom := keeper.BondDenom(ctx) - - // set a redelegation with expiration timestamp beyond which the - // redelegation shouldn't be slashed - rdATokens := sdk.TokensFromConsensusPower(6) - rdA := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 11, - time.Unix(0, 0), rdATokens, - rdATokens.ToDec()) - keeper.SetRedelegation(ctx, rdA) - - // set the associated delegation - delA := types.NewDelegation(addrDels[0], addrVals[1], rdATokens.ToDec()) - keeper.SetDelegation(ctx, delA) - - // set an unbonding delegation with expiration timestamp (beyond which the - // unbonding delegation shouldn't be slashed) - ubdATokens := sdk.TokensFromConsensusPower(4) - ubdA := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 11, - time.Unix(0, 0), ubdATokens) - keeper.SetUnbondingDelegation(ctx, ubdA) - - bondedCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, rdATokens.MulRaw(2))) - notBondedCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, ubdATokens)) - - // update bonded tokens - bondedPool := keeper.GetBondedPool(ctx) - notBondedPool := keeper.GetNotBondedPool(ctx) - - bondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, bondedPool.GetAddress()) - require.NoError(t, keeper.bankKeeper.SetBalances(ctx, bondedPool.GetAddress(), bondedPoolBalances.Add(bondedCoins...))) - - notBondedPoolBalances := keeper.bankKeeper.GetAllBalances(ctx, notBondedPool.GetAddress()) - require.NoError(t, keeper.bankKeeper.SetBalances(ctx, notBondedPool.GetAddress(), notBondedPoolBalances.Add(notBondedCoins...))) - - keeper.supplyKeeper.SetModuleAccount(ctx, bondedPool) - keeper.supplyKeeper.SetModuleAccount(ctx, notBondedPool) - - oldBonded := keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount - oldNotBonded := keeper.bankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), bondDenom).Amount - // slash validator - ctx = ctx.WithBlockHeight(12) - validator, found := keeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(PKs[0])) - require.True(t, found) - consAddr0 := sdk.ConsAddress(PKs[0].Address()) - keeper.Slash(ctx, consAddr0, 10, 10, fraction) - - burnedNotBondedAmount := fraction.MulInt(ubdATokens).TruncateInt() - burnedBondAmount := sdk.TokensFromConsensusPower(10).ToDec().Mul(fraction).TruncateInt() - burnedBondAmount = burnedBondAmount.Sub(burnedNotBondedAmount) - - // read updated pool - bondedPool = keeper.GetBondedPool(ctx) - notBondedPool = keeper.GetNotBondedPool(ctx) - - bondedPoolBalance := keeper.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom).Amount - require.True(sdk.IntEq(t, oldBonded.Sub(burnedBondAmount), bondedPoolBalance)) - - notBondedPoolBalance := keeper.bankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), bondDenom).Amount - require.True(sdk.IntEq(t, oldNotBonded.Sub(burnedNotBondedAmount), notBondedPoolBalance)) - - // read updating redelegation - rdA, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) - require.True(t, found) - require.Len(t, rdA.Entries, 1) - // read updated validator - validator, found = keeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(PKs[0])) - require.True(t, found) - // power not decreased, all stake was bonded since - require.Equal(t, int64(10), validator.GetConsensusPower()) -}