cosmos-sdk/tests/integration/v2/staking/slash_test.go

741 lines
33 KiB
Go

package staking
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/require"
"gotest.tools/v3/assert"
"cosmossdk.io/collections"
"cosmossdk.io/core/header"
"cosmossdk.io/math"
_ "cosmossdk.io/x/accounts"
banktestutil "cosmossdk.io/x/bank/testutil"
_ "cosmossdk.io/x/consensus"
_ "cosmossdk.io/x/distribution"
_ "cosmossdk.io/x/protocolpool"
_ "cosmossdk.io/x/slashing"
"cosmossdk.io/x/staking/keeper"
"cosmossdk.io/x/staking/testutil"
"cosmossdk.io/x/staking/types"
"github.com/cosmos/cosmos-sdk/codec/address"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/cosmos/cosmos-sdk/tests/integration/v2"
sdk "github.com/cosmos/cosmos-sdk/types"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
)
// bootstrapSlashTest creates 3 validators and bootstrap the app.
func bootstrapSlashTest(t *testing.T, power int64) (*fixture, []sdk.AccAddress, []sdk.ValAddress) {
t.Helper()
t.Parallel()
f := initFixture(t, false)
addrDels, addrVals := generateAddresses(f, 100)
amt := f.stakingKeeper.TokensFromConsensusPower(f.ctx, power)
bondDenom, err := f.stakingKeeper.BondDenom(f.ctx)
require.NoError(t, err)
totalSupply := sdk.NewCoins(sdk.NewCoin(bondDenom, amt.MulRaw(int64(len(addrDels)))))
notBondedPool := f.stakingKeeper.GetNotBondedPool(f.ctx)
assert.NilError(t, banktestutil.FundModuleAccount(f.ctx, f.bankKeeper, notBondedPool.GetName(), totalSupply))
f.accountKeeper.SetModuleAccount(f.ctx, notBondedPool)
numVals := int64(3)
bondedCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, amt.MulRaw(numVals)))
bondedPool := f.stakingKeeper.GetBondedPool(f.ctx)
// set bonded pool balance
f.accountKeeper.SetModuleAccount(f.ctx, bondedPool)
assert.NilError(t, banktestutil.FundModuleAccount(f.ctx, f.bankKeeper, bondedPool.GetName(), bondedCoins))
for i := int64(0); i < numVals; i++ {
validator := testutil.NewValidator(t, addrVals[i], PKs[i])
validator, _ = validator.AddTokensFromDel(amt)
validator, _ = keeper.TestingUpdateValidatorV2(f.stakingKeeper, f.ctx, validator, true)
assert.NilError(t, f.stakingKeeper.SetValidatorByConsAddr(f.ctx, validator))
}
return f, addrDels, addrVals
}
// tests slashUnbondingDelegation
func TestSlashUnbondingDelegation(t *testing.T) {
f, addrDels, addrVals := bootstrapSlashTest(t, 10)
fraction := math.LegacyNewDecWithPrec(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), math.NewInt(10), address.NewBech32Codec("cosmosvaloper"), address.NewBech32Codec("cosmos"))
assert.NilError(t, f.stakingKeeper.SetUnbondingDelegation(f.ctx, ubd))
// unbonding started prior to the infraction height, stakw didn't contribute
slashAmount, err := f.stakingKeeper.SlashUnbondingDelegation(f.ctx, ubd, 1, fraction)
assert.NilError(t, err)
assert.Assert(t, slashAmount.Equal(math.NewInt(0)))
// after the expiration time, no longer eligible for slashing
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Time: time.Unix(10, 0)})
assert.NilError(t, f.stakingKeeper.SetUnbondingDelegation(f.ctx, ubd))
slashAmount, err = f.stakingKeeper.SlashUnbondingDelegation(f.ctx, ubd, 0, fraction)
assert.NilError(t, err)
assert.Assert(t, slashAmount.Equal(math.NewInt(0)))
// test valid slash, before expiration timestamp and to which stake contributed
notBondedPool := f.stakingKeeper.GetNotBondedPool(f.ctx)
oldUnbondedPoolBalances := f.bankKeeper.GetAllBalances(f.ctx, notBondedPool.GetAddress())
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Time: time.Unix(0, 0)})
assert.NilError(t, f.stakingKeeper.SetUnbondingDelegation(f.ctx, ubd))
slashAmount, err = f.stakingKeeper.SlashUnbondingDelegation(f.ctx, ubd, 0, fraction)
assert.NilError(t, err)
assert.Assert(t, slashAmount.Equal(math.NewInt(5)))
ubd, found := f.stakingKeeper.GetUnbondingDelegation(f.ctx, addrDels[0], addrVals[0])
assert.Assert(t, found)
assert.Assert(t, len(ubd.Entries) == 1)
// initial balance unchanged
assert.DeepEqual(t, math.NewInt(10), ubd.Entries[0].InitialBalance)
// balance decreased
assert.DeepEqual(t, math.NewInt(5), ubd.Entries[0].Balance)
newUnbondedPoolBalances := f.bankKeeper.GetAllBalances(f.ctx, notBondedPool.GetAddress())
diffTokens := oldUnbondedPoolBalances.Sub(newUnbondedPoolBalances...)
bondDenom, err := f.stakingKeeper.BondDenom(f.ctx)
assert.NilError(t, err)
assert.Assert(t, diffTokens.AmountOf(bondDenom).Equal(math.NewInt(5)))
}
// tests slashRedelegation
func TestSlashRedelegation(t *testing.T) {
f, addrDels, addrVals := bootstrapSlashTest(t, 10)
fraction := math.LegacyNewDecWithPrec(5, 1)
bondDenom, err := f.stakingKeeper.BondDenom(f.ctx)
assert.NilError(t, err)
// add bonded tokens to pool for (re)delegations
startCoins := sdk.NewCoins(sdk.NewInt64Coin(bondDenom, 15))
bondedPool := f.stakingKeeper.GetBondedPool(f.ctx)
_ = f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
assert.NilError(t, banktestutil.FundModuleAccount(f.ctx, f.bankKeeper, bondedPool.GetName(), startCoins))
f.accountKeeper.SetModuleAccount(f.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), math.NewInt(10), math.LegacyNewDec(10), address.NewBech32Codec("cosmosvaloper"), address.NewBech32Codec("cosmos"))
assert.NilError(t, f.stakingKeeper.SetRedelegation(f.ctx, rd))
// set the associated delegation
del := types.NewDelegation(addrDels[0].String(), addrVals[1].String(), math.LegacyNewDec(10))
assert.NilError(t, f.stakingKeeper.SetDelegation(f.ctx, del))
// started redelegating prior to the current height, stake didn't contribute to infraction
validator, found := f.stakingKeeper.GetValidator(f.ctx, addrVals[1])
assert.Assert(t, found)
slashAmount, err := f.stakingKeeper.SlashRedelegation(f.ctx, validator, rd, 1, fraction)
assert.NilError(t, err)
assert.Assert(t, slashAmount.Equal(math.NewInt(0)))
// after the expiration time, no longer eligible for slashing
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Time: time.Unix(10, 0)})
assert.NilError(t, f.stakingKeeper.SetRedelegation(f.ctx, rd))
validator, found = f.stakingKeeper.GetValidator(f.ctx, addrVals[1])
assert.Assert(t, found)
slashAmount, err = f.stakingKeeper.SlashRedelegation(f.ctx, validator, rd, 0, fraction)
assert.NilError(t, err)
assert.Assert(t, slashAmount.Equal(math.NewInt(0)))
balances := f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
// test valid slash, before expiration timestamp and to which stake contributed
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Time: time.Unix(0, 0)})
assert.NilError(t, f.stakingKeeper.SetRedelegation(f.ctx, rd))
validator, found = f.stakingKeeper.GetValidator(f.ctx, addrVals[1])
assert.Assert(t, found)
slashAmount, err = f.stakingKeeper.SlashRedelegation(f.ctx, validator, rd, 0, fraction)
assert.NilError(t, err)
assert.Assert(t, slashAmount.Equal(math.NewInt(5)))
rd, found = f.stakingKeeper.Redelegations.Get(f.ctx, collections.Join3(addrDels[0].Bytes(), addrVals[0].Bytes(), addrVals[1].Bytes()))
assert.Assert(t, found)
assert.Assert(t, len(rd.Entries) == 1)
// end block
applyValidatorSetUpdates(t, f.ctx, f.stakingKeeper, 1)
// initialbalance unchanged
assert.DeepEqual(t, math.NewInt(10), rd.Entries[0].InitialBalance)
// shares decreased
del, found = f.stakingKeeper.Delegations.Get(f.ctx, collections.Join(addrDels[0], addrVals[1]))
assert.Assert(t, found)
assert.Equal(t, int64(5), del.Shares.RoundInt64())
// pool bonded tokens should decrease
burnedCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, slashAmount))
assert.DeepEqual(t, balances.Sub(burnedCoins...), f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress()))
}
// 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) {
f, _, _ := bootstrapSlashTest(t, 10)
consAddr := sdk.ConsAddress(PKs[0].Address())
fraction := math.LegacyNewDecWithPrec(5, 1)
bondedPool := f.stakingKeeper.GetBondedPool(f.ctx)
oldBondedPoolBalances := f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
_, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
assert.Assert(t, found)
_, err := f.stakingKeeper.Slash(f.ctx, consAddr, -2, 10, fraction)
assert.NilError(t, err)
// read updated state
validator, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
assert.Assert(t, found)
// end block
applyValidatorSetUpdates(t, f.ctx, f.stakingKeeper, 1)
valbz, err := f.stakingKeeper.ValidatorAddressCodec().StringToBytes(validator.GetOperator())
assert.NilError(t, err)
validator, found = f.stakingKeeper.GetValidator(f.ctx, valbz)
assert.Assert(t, found)
// power decreased
assert.Equal(t, int64(5), validator.GetConsensusPower(f.stakingKeeper.PowerReduction(f.ctx)))
bondDenom, err := f.stakingKeeper.BondDenom(f.ctx)
assert.NilError(t, err)
// pool bonded shares decreased
newBondedPoolBalances := f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
diffTokens := oldBondedPoolBalances.Sub(newBondedPoolBalances...).AmountOf(bondDenom)
assert.DeepEqual(t, f.stakingKeeper.TokensFromConsensusPower(f.ctx, 5).String(), diffTokens.String())
}
// tests Slash at the current height
func TestSlashValidatorAtCurrentHeight(t *testing.T) {
f, _, _ := bootstrapSlashTest(t, 10)
consAddr := sdk.ConsAddress(PKs[0].Address())
fraction := math.LegacyNewDecWithPrec(5, 1)
bondDenom, err := f.stakingKeeper.BondDenom(f.ctx)
assert.NilError(t, err)
bondedPool := f.stakingKeeper.GetBondedPool(f.ctx)
oldBondedPoolBalances := f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
_, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
assert.Assert(t, found)
_, err = f.stakingKeeper.Slash(f.ctx, consAddr, int64(f.app.LastBlockHeight()), 10, fraction)
assert.NilError(t, err)
// read updated state
validator, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
assert.Assert(t, found)
// end block
applyValidatorSetUpdates(t, f.ctx, f.stakingKeeper, 1)
valbz, err := f.stakingKeeper.ValidatorAddressCodec().StringToBytes(validator.GetOperator())
assert.NilError(t, err)
validator, found = f.stakingKeeper.GetValidator(f.ctx, valbz)
assert.Assert(t, found)
// power decreased
assert.Equal(t, int64(5), validator.GetConsensusPower(f.stakingKeeper.PowerReduction(f.ctx)))
// pool bonded shares decreased
newBondedPoolBalances := f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
diffTokens := oldBondedPoolBalances.Sub(newBondedPoolBalances...).AmountOf(bondDenom)
assert.DeepEqual(t, f.stakingKeeper.TokensFromConsensusPower(f.ctx, 5).String(), diffTokens.String())
}
// TestSlashWithUnbondingDelegation tests the slashing of a validator with an unbonding delegation.
// It sets up an environment with a validator and an unbonding delegation, and then performs slashing
// operations on the validator. The test verifies that the slashing correctly affects the unbonding
// delegation and the validator's power.
func TestSlashWithUnbondingDelegation(t *testing.T) {
f, addrDels, addrVals := bootstrapSlashTest(t, 10)
consAddr := sdk.ConsAddress(PKs[0].Address())
fraction := math.LegacyNewDecWithPrec(5, 1)
bondDenom, err := f.stakingKeeper.BondDenom(f.ctx)
assert.NilError(t, err)
// set an unbonding delegation with expiration timestamp beyond which the
// unbonding delegation shouldn't be slashed
ubdTokens := f.stakingKeeper.TokensFromConsensusPower(f.ctx, 4)
ubd := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 11, time.Unix(0, 0), ubdTokens, address.NewBech32Codec("cosmosvaloper"), address.NewBech32Codec("cosmos"))
assert.NilError(t, f.stakingKeeper.SetUnbondingDelegation(f.ctx, ubd))
// slash validator for the first time
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Height: 12})
bondedPool := f.stakingKeeper.GetBondedPool(f.ctx)
oldBondedPoolBalances := f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
_, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
assert.Assert(t, found)
_, err = f.stakingKeeper.Slash(f.ctx, consAddr, 10, 10, fraction)
assert.NilError(t, err)
// end block
applyValidatorSetUpdates(t, f.ctx, f.stakingKeeper, 1)
// read updating unbonding delegation
ubd, found = f.stakingKeeper.GetUnbondingDelegation(f.ctx, addrDels[0], addrVals[0])
assert.Assert(t, found)
assert.Assert(t, len(ubd.Entries) == 1)
// balance decreased
assert.DeepEqual(t, f.stakingKeeper.TokensFromConsensusPower(f.ctx, 2), ubd.Entries[0].Balance)
// bonded tokens burned
newBondedPoolBalances := f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
diffTokens := oldBondedPoolBalances.Sub(newBondedPoolBalances...).AmountOf(bondDenom)
assert.DeepEqual(t, f.stakingKeeper.TokensFromConsensusPower(f.ctx, 3), diffTokens)
// read updated validator
validator, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
assert.Assert(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
assert.Equal(t, int64(7), validator.GetConsensusPower(f.stakingKeeper.PowerReduction(f.ctx)))
// slash validator again
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Height: 13})
_, err = f.stakingKeeper.Slash(f.ctx, consAddr, 9, 10, fraction)
assert.NilError(t, err)
ubd, found = f.stakingKeeper.GetUnbondingDelegation(f.ctx, addrDels[0], addrVals[0])
assert.Assert(t, found)
assert.Assert(t, len(ubd.Entries) == 1)
// balance decreased again
assert.DeepEqual(t, math.NewInt(0), ubd.Entries[0].Balance)
// bonded tokens burned again
newBondedPoolBalances = f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
diffTokens = oldBondedPoolBalances.Sub(newBondedPoolBalances...).AmountOf(bondDenom)
assert.DeepEqual(t, f.stakingKeeper.TokensFromConsensusPower(f.ctx, 6), diffTokens)
// read updated validator
validator, found = f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
assert.Assert(t, found)
// power decreased by 3 again
assert.Equal(t, int64(4), validator.GetConsensusPower(f.stakingKeeper.PowerReduction(f.ctx)))
// 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 behavior, ref https://github.com/cosmos/cosmos-sdk/issues/1440
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Height: 13})
_, err = f.stakingKeeper.Slash(f.ctx, consAddr, 9, 10, fraction)
assert.NilError(t, err)
ubd, found = f.stakingKeeper.GetUnbondingDelegation(f.ctx, addrDels[0], addrVals[0])
assert.Assert(t, found)
assert.Assert(t, len(ubd.Entries) == 1)
// balance unchanged
assert.DeepEqual(t, math.NewInt(0), ubd.Entries[0].Balance)
// bonded tokens burned again
newBondedPoolBalances = f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
diffTokens = oldBondedPoolBalances.Sub(newBondedPoolBalances...).AmountOf(bondDenom)
assert.DeepEqual(t, f.stakingKeeper.TokensFromConsensusPower(f.ctx, 9), diffTokens)
// read updated validator
validator, found = f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
assert.Assert(t, found)
// power decreased by 3 again
assert.Equal(t, int64(1), validator.GetConsensusPower(f.stakingKeeper.PowerReduction(f.ctx)))
// 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 behavior, ref https://github.com/cosmos/cosmos-sdk/issues/1440
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Height: 13})
_, err = f.stakingKeeper.Slash(f.ctx, consAddr, 9, 10, fraction)
assert.NilError(t, err)
ubd, found = f.stakingKeeper.GetUnbondingDelegation(f.ctx, addrDels[0], addrVals[0])
assert.Assert(t, found)
assert.Assert(t, len(ubd.Entries) == 1)
// balance unchanged
assert.DeepEqual(t, math.NewInt(0), ubd.Entries[0].Balance)
// just 1 bonded token burned again since that's all the validator now has
newBondedPoolBalances = f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
diffTokens = oldBondedPoolBalances.Sub(newBondedPoolBalances...).AmountOf(bondDenom)
assert.DeepEqual(t, f.stakingKeeper.TokensFromConsensusPower(f.ctx, 10), diffTokens)
// apply TM updates
applyValidatorSetUpdates(t, f.ctx, f.stakingKeeper, -1)
// read updated validator
// power decreased by 1 again, validator is out of stake
// validator should be in unbonding period
validator, _ = f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
assert.Equal(t, validator.GetStatus(), sdk.Unbonding)
}
// tests Slash at a previous height with a redelegation
func TestSlashWithRedelegation(t *testing.T) {
f, addrDels, addrVals := bootstrapSlashTest(t, 10)
consAddr := sdk.ConsAddress(PKs[0].Address())
fraction := math.LegacyNewDecWithPrec(5, 1)
bondDenom, err := f.stakingKeeper.BondDenom(f.ctx)
assert.NilError(t, err)
// set a redelegation
rdTokens := f.stakingKeeper.TokensFromConsensusPower(f.ctx, 6)
rd := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 11, time.Unix(0, 0), rdTokens, math.LegacyNewDecFromInt(rdTokens), address.NewBech32Codec("cosmosvaloper"), address.NewBech32Codec("cosmos"))
assert.NilError(t, f.stakingKeeper.SetRedelegation(f.ctx, rd))
// set the associated delegation
del := types.NewDelegation(addrDels[0].String(), addrVals[1].String(), math.LegacyNewDecFromInt(rdTokens))
assert.NilError(t, f.stakingKeeper.SetDelegation(f.ctx, del))
// update bonded tokens
bondedPool := f.stakingKeeper.GetBondedPool(f.ctx)
notBondedPool := f.stakingKeeper.GetNotBondedPool(f.ctx)
rdCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, rdTokens.MulRaw(2)))
assert.NilError(t, banktestutil.FundModuleAccount(f.ctx, f.bankKeeper, bondedPool.GetName(), rdCoins))
f.accountKeeper.SetModuleAccount(f.ctx, bondedPool)
oldBonded := f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
oldNotBonded := f.bankKeeper.GetBalance(f.ctx, notBondedPool.GetAddress(), bondDenom).Amount
// slash validator
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Height: 12})
_, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
assert.Assert(t, found)
_, err = f.stakingKeeper.Slash(f.ctx, consAddr, 10, 10, fraction)
assert.NilError(t, err)
burnAmount := math.LegacyNewDecFromInt(f.stakingKeeper.TokensFromConsensusPower(f.ctx, 10)).Mul(fraction).TruncateInt()
bondedPool = f.stakingKeeper.GetBondedPool(f.ctx)
notBondedPool = f.stakingKeeper.GetNotBondedPool(f.ctx)
// burn bonded tokens from only from delegations
bondedPoolBalance := f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
assert.Assert(math.IntEq(t, oldBonded.Sub(burnAmount), bondedPoolBalance))
notBondedPoolBalance := f.bankKeeper.GetBalance(f.ctx, notBondedPool.GetAddress(), bondDenom).Amount
assert.Assert(math.IntEq(t, oldNotBonded, notBondedPoolBalance))
oldBonded = f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
// read updating redelegation
rd, found = f.stakingKeeper.Redelegations.Get(f.ctx, collections.Join3(addrDels[0].Bytes(), addrVals[0].Bytes(), addrVals[1].Bytes()))
assert.Assert(t, found)
assert.Assert(t, len(rd.Entries) == 1)
// read updated validator
validator, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
assert.Assert(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
assert.Equal(t, int64(8), validator.GetConsensusPower(f.stakingKeeper.PowerReduction(f.ctx)))
// slash the validator again
_, found = f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
assert.Assert(t, found)
_, err = f.stakingKeeper.Slash(f.ctx, consAddr, 10, 10, math.LegacyOneDec())
assert.NilError(t, err)
burnAmount = f.stakingKeeper.TokensFromConsensusPower(f.ctx, 7)
// read updated pool
bondedPool = f.stakingKeeper.GetBondedPool(f.ctx)
notBondedPool = f.stakingKeeper.GetNotBondedPool(f.ctx)
// seven bonded tokens burned
bondedPoolBalance = f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
assert.Assert(math.IntEq(t, oldBonded.Sub(burnAmount), bondedPoolBalance))
assert.Assert(math.IntEq(t, oldNotBonded, notBondedPoolBalance))
bondedPoolBalance = f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
assert.Assert(math.IntEq(t, oldBonded.Sub(burnAmount), bondedPoolBalance))
notBondedPoolBalance = f.bankKeeper.GetBalance(f.ctx, notBondedPool.GetAddress(), bondDenom).Amount
assert.Assert(math.IntEq(t, oldNotBonded, notBondedPoolBalance))
oldBonded = f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
// read updating redelegation
rd, found = f.stakingKeeper.Redelegations.Get(f.ctx, collections.Join3(addrDels[0].Bytes(), addrVals[0].Bytes(), addrVals[1].Bytes()))
assert.Assert(t, found)
assert.Assert(t, len(rd.Entries) == 1)
// read updated validator
validator, found = f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
assert.Assert(t, found)
// power decreased by 4
assert.Equal(t, int64(4), validator.GetConsensusPower(f.stakingKeeper.PowerReduction(f.ctx)))
// slash the validator again, by 100%
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Height: 12})
_, found = f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
assert.Assert(t, found)
_, err = f.stakingKeeper.Slash(f.ctx, consAddr, 10, 10, math.LegacyOneDec())
assert.NilError(t, err)
burnAmount = math.LegacyNewDecFromInt(f.stakingKeeper.TokensFromConsensusPower(f.ctx, 10)).Mul(math.LegacyOneDec()).TruncateInt()
burnAmount = burnAmount.Sub(math.LegacyOneDec().MulInt(rdTokens).TruncateInt())
// read updated pool
bondedPool = f.stakingKeeper.GetBondedPool(f.ctx)
notBondedPool = f.stakingKeeper.GetNotBondedPool(f.ctx)
bondedPoolBalance = f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
assert.Assert(math.IntEq(t, oldBonded.Sub(burnAmount), bondedPoolBalance))
notBondedPoolBalance = f.bankKeeper.GetBalance(f.ctx, notBondedPool.GetAddress(), bondDenom).Amount
assert.Assert(math.IntEq(t, oldNotBonded, notBondedPoolBalance))
oldBonded = f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
// read updating redelegation
rd, found = f.stakingKeeper.Redelegations.Get(f.ctx, collections.Join3(addrDels[0].Bytes(), addrVals[0].Bytes(), addrVals[1].Bytes()))
assert.Assert(t, found)
assert.Assert(t, len(rd.Entries) == 1)
// apply TM updates
applyValidatorSetUpdates(t, f.ctx, f.stakingKeeper, -1)
// read updated validator
// validator decreased to zero power, should be in unbonding period
validator, _ = f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
assert.Equal(t, validator.GetStatus(), sdk.Unbonding)
// slash the validator again, by 100%
// no stake remains to be slashed
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Height: 12})
// validator still in unbonding period
validator, _ = f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
assert.Equal(t, validator.GetStatus(), sdk.Unbonding)
_, err = f.stakingKeeper.Slash(f.ctx, consAddr, 10, 10, math.LegacyOneDec())
assert.NilError(t, err)
// read updated pool
bondedPool = f.stakingKeeper.GetBondedPool(f.ctx)
notBondedPool = f.stakingKeeper.GetNotBondedPool(f.ctx)
bondedPoolBalance = f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
assert.Assert(math.IntEq(t, oldBonded, bondedPoolBalance))
notBondedPoolBalance = f.bankKeeper.GetBalance(f.ctx, notBondedPool.GetAddress(), bondDenom).Amount
assert.Assert(math.IntEq(t, oldNotBonded, notBondedPoolBalance))
// read updating redelegation
rd, found = f.stakingKeeper.Redelegations.Get(f.ctx, collections.Join3(addrDels[0].Bytes(), addrVals[0].Bytes(), addrVals[1].Bytes()))
assert.Assert(t, found)
assert.Assert(t, len(rd.Entries) == 1)
// read updated validator
// power still zero, still in unbonding period
validator, _ = f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
assert.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) {
f, addrDels, addrVals := bootstrapSlashTest(t, 10)
fraction := math.LegacyNewDecWithPrec(5, 1)
bondDenom, err := f.stakingKeeper.BondDenom(f.ctx)
assert.NilError(t, err)
// set a redelegation with expiration timestamp beyond which the
// redelegation shouldn't be slashed
rdATokens := f.stakingKeeper.TokensFromConsensusPower(f.ctx, 6)
rdA := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 11, time.Unix(0, 0), rdATokens, math.LegacyNewDecFromInt(rdATokens), address.NewBech32Codec("cosmosvaloper"), address.NewBech32Codec("cosmos"))
assert.NilError(t, f.stakingKeeper.SetRedelegation(f.ctx, rdA))
// set the associated delegation
delA := types.NewDelegation(addrDels[0].String(), addrVals[1].String(), math.LegacyNewDecFromInt(rdATokens))
assert.NilError(t, f.stakingKeeper.SetDelegation(f.ctx, delA))
// set an unbonding delegation with expiration timestamp (beyond which the
// unbonding delegation shouldn't be slashed)
ubdATokens := f.stakingKeeper.TokensFromConsensusPower(f.ctx, 4)
ubdA := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 11,
time.Unix(0, 0), ubdATokens, address.NewBech32Codec("cosmosvaloper"), address.NewBech32Codec("cosmos"))
assert.NilError(t, f.stakingKeeper.SetUnbondingDelegation(f.ctx, ubdA))
bondedCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, rdATokens.MulRaw(2)))
notBondedCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, ubdATokens))
// update bonded tokens
bondedPool := f.stakingKeeper.GetBondedPool(f.ctx)
notBondedPool := f.stakingKeeper.GetNotBondedPool(f.ctx)
assert.NilError(t, banktestutil.FundModuleAccount(f.ctx, f.bankKeeper, bondedPool.GetName(), bondedCoins))
assert.NilError(t, banktestutil.FundModuleAccount(f.ctx, f.bankKeeper, notBondedPool.GetName(), notBondedCoins))
f.accountKeeper.SetModuleAccount(f.ctx, bondedPool)
f.accountKeeper.SetModuleAccount(f.ctx, notBondedPool)
oldBonded := f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
oldNotBonded := f.bankKeeper.GetBalance(f.ctx, notBondedPool.GetAddress(), bondDenom).Amount
// slash validator
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Height: 12})
_, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, sdk.GetConsAddress(PKs[0]))
assert.Assert(t, found)
consAddr0 := sdk.ConsAddress(PKs[0].Address())
_, err = f.stakingKeeper.Slash(f.ctx, consAddr0, 10, 10, fraction)
assert.NilError(t, err)
burnedNotBondedAmount := fraction.MulInt(ubdATokens).TruncateInt()
burnedBondAmount := math.LegacyNewDecFromInt(f.stakingKeeper.TokensFromConsensusPower(f.ctx, 10)).Mul(fraction).TruncateInt()
burnedBondAmount = burnedBondAmount.Sub(burnedNotBondedAmount)
// read updated pool
bondedPool = f.stakingKeeper.GetBondedPool(f.ctx)
notBondedPool = f.stakingKeeper.GetNotBondedPool(f.ctx)
bondedPoolBalance := f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
assert.Assert(math.IntEq(t, oldBonded.Sub(burnedBondAmount), bondedPoolBalance))
notBondedPoolBalance := f.bankKeeper.GetBalance(f.ctx, notBondedPool.GetAddress(), bondDenom).Amount
assert.Assert(math.IntEq(t, oldNotBonded.Sub(burnedNotBondedAmount), notBondedPoolBalance))
// read updating redelegation
rdA, found = f.stakingKeeper.Redelegations.Get(f.ctx, collections.Join3(addrDels[0].Bytes(), addrVals[0].Bytes(), addrVals[1].Bytes()))
assert.Assert(t, found)
assert.Assert(t, len(rdA.Entries) == 1)
// read updated validator
validator, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, sdk.GetConsAddress(PKs[0]))
assert.Assert(t, found)
// power not decreased, all stake was bonded since
assert.Equal(t, int64(10), validator.GetConsensusPower(f.stakingKeeper.PowerReduction(f.ctx)))
}
func TestSlashAmount(t *testing.T) {
f, _, _ := bootstrapSlashTest(t, 10)
consAddr := sdk.ConsAddress(PKs[0].Address())
fraction := math.LegacyNewDecWithPrec(5, 1)
burnedCoins, err := f.stakingKeeper.Slash(f.ctx, consAddr, int64(f.app.LastBlockHeight()), 10, fraction)
assert.NilError(t, err)
assert.Assert(t, burnedCoins.GT(math.ZeroInt()))
// test the case where the validator was not found, which should return no coins
_, addrVals := generateAddresses(f, 100)
noBurned, err := f.stakingKeeper.Slash(f.ctx, sdk.ConsAddress(addrVals[0]), int64(f.app.LastBlockHeight())+1, 10, fraction)
assert.NilError(t, err)
assert.Assert(t, math.NewInt(0).Equal(noBurned))
}
// TestFixAvoidFullSlashPenalty fixes the following issue: https://github.com/cosmos/cosmos-sdk/issues/20641
func TestFixAvoidFullSlashPenalty(t *testing.T) {
// setup
f := initFixture(t, false)
ctx := f.ctx
stakingMsgServer := keeper.NewMsgServerImpl(f.stakingKeeper)
bondDenom, err := f.stakingKeeper.BondDenom(ctx)
require.NoError(t, err)
// create 2 evil validators, controlled by attacker
evilValPubKey := secp256k1.GenPrivKey().PubKey()
evilValPubKey2 := secp256k1.GenPrivKey().PubKey()
// attacker user account
badtestAcc := sdk.AccAddress("addr1_______________")
// normal users who stakes on evilValAddr1
testAcc1 := sdk.AccAddress("addr2_______________")
testAcc2 := sdk.AccAddress("addr3_______________")
createAccount(t, ctx, f.accountKeeper, badtestAcc)
createAccount(t, ctx, f.accountKeeper, testAcc1)
createAccount(t, ctx, f.accountKeeper, testAcc2)
// fund all accounts
testCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, f.stakingKeeper.TokensFromConsensusPower(ctx, 1)))
require.NoError(t, banktestutil.FundAccount(ctx, f.bankKeeper, badtestAcc, testCoins))
require.NoError(t, banktestutil.FundAccount(ctx, f.bankKeeper, testAcc1, testCoins))
require.NoError(t, banktestutil.FundAccount(ctx, f.bankKeeper, testAcc2, testCoins))
// create evilValAddr1 for normal staking operations
evilValAddr1 := sdk.ValAddress(evilValPubKey.Address())
createAccount(t, ctx, f.accountKeeper, evilValAddr1.Bytes())
require.NoError(t, banktestutil.FundAccount(ctx, f.bankKeeper, sdk.AccAddress(evilValAddr1), testCoins))
createValMsg1, _ := types.NewMsgCreateValidator(
evilValAddr1.String(), evilValPubKey, testCoins[0], types.Description{Details: "test"}, types.NewCommissionRates(math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDec(0)), math.OneInt())
_, err = stakingMsgServer.CreateValidator(ctx, createValMsg1)
require.NoError(t, err)
// very small amount coin for evilValAddr2
smallCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, math.NewInt(1)))
// create evilValAddr2 to circumvent slashing
evilValAddr2 := sdk.ValAddress(evilValPubKey2.Address())
require.NoError(t, banktestutil.FundAccount(ctx, f.bankKeeper, sdk.AccAddress(evilValAddr2), smallCoins))
createValMsg3, _ := types.NewMsgCreateValidator(
evilValAddr2.String(), evilValPubKey2, smallCoins[0], types.Description{Details: "test"}, types.NewCommissionRates(math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDec(0)), math.OneInt())
createAccount(t, ctx, f.accountKeeper, evilValAddr2.Bytes())
_, err = stakingMsgServer.CreateValidator(ctx, createValMsg3)
require.NoError(t, err)
// next block
ctx = integration.SetHeaderInfo(ctx, header.Info{Height: int64(f.app.LastBlockHeight()) + 1})
_, state := f.app.Deliver(t, ctx, nil)
_, err = f.app.Commit(state)
require.NoError(t, err)
// all accs delegate to evilValAddr1
delMsg := types.NewMsgDelegate(badtestAcc.String(), evilValAddr1.String(), testCoins[0])
_, err = stakingMsgServer.Delegate(ctx, delMsg)
require.NoError(t, err)
delMsg = types.NewMsgDelegate(testAcc1.String(), evilValAddr1.String(), testCoins[0])
_, err = stakingMsgServer.Delegate(ctx, delMsg)
require.NoError(t, err)
delMsg = types.NewMsgDelegate(testAcc2.String(), evilValAddr1.String(), testCoins[0])
_, err = stakingMsgServer.Delegate(ctx, delMsg)
require.NoError(t, err)
// next block
_, state = f.app.Deliver(t, ctx, nil)
_, err = f.app.Commit(state)
require.NoError(t, err)
// 1. badtestAcc redelegates from evilValAddr1 to evilValAddr2
redelMsg := types.NewMsgBeginRedelegate(badtestAcc.String(), evilValAddr1.String(), evilValAddr2.String(), smallCoins[0])
_, err = stakingMsgServer.BeginRedelegate(ctx, redelMsg)
require.NoError(t, err)
// 2. evilValAddr2 undelegates its self-delegation and jail themselves
undelMsg := types.NewMsgUndelegate(sdk.AccAddress(evilValAddr2).String(), evilValAddr2.String(), smallCoins[0])
_, err = stakingMsgServer.Undelegate(ctx, undelMsg)
require.NoError(t, err)
// assert evilValAddr2 is jailed
evilVal2, err := f.stakingKeeper.GetValidator(ctx, evilValAddr2)
require.NoError(t, err)
require.True(t, evilVal2.Jailed)
// next block
_, state = f.app.Deliver(t, ctx, nil)
_, err = f.app.Commit(state)
require.NoError(t, err)
// evilValAddr1 is bad!
// lets slash evilValAddr1 with a 100% penalty
evilVal, err := f.stakingKeeper.GetValidator(ctx, evilValAddr1)
require.NoError(t, err)
evilValConsAddr, err := evilVal.GetConsAddr()
require.NoError(t, err)
evilPower := f.stakingKeeper.TokensToConsensusPower(ctx, evilVal.Tokens)
err = f.slashKeeper.Slash(ctx, evilValConsAddr, math.LegacyMustNewDecFromStr("1.0"), evilPower, 3)
require.NoError(t, err)
}
func createAccount(t *testing.T, ctx context.Context, k authkeeper.AccountKeeperI, addr sdk.AccAddress) {
t.Helper()
acc := k.NewAccountWithAddress(ctx, addr)
k.SetAccount(ctx, acc)
}