cosmos-sdk/x/stake/keeper/slash_test.go

481 lines
17 KiB
Go

package keeper
import (
"testing"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/stake/types"
abci "github.com/tendermint/tendermint/abci/types"
)
// setup helper function
// creates two validators
func setupHelper(t *testing.T, amt int64) (sdk.Context, Keeper, types.Params) {
// setup
ctx, _, keeper := CreateTestInput(t, false, amt)
params := keeper.GetParams(ctx)
pool := keeper.GetPool(ctx)
numVals := 3
pool.LooseTokens = amt * int64(numVals)
// add numVals validators
for i := 0; i < numVals; i++ {
validator := types.NewValidator(addrVals[i], PKs[i], types.Description{})
validator, pool, _ = validator.AddTokensFromDel(pool, amt)
keeper.SetPool(ctx, pool)
keeper.UpdateValidator(ctx, validator)
keeper.SetValidatorByPubKeyIndex(ctx, validator)
}
return ctx, keeper, params
}
// tests Revoke, Unrevoke
func TestRevocation(t *testing.T) {
// setup
ctx, keeper, _ := setupHelper(t, 10)
addr := addrVals[0]
pk := PKs[0]
// initial state
val, found := keeper.GetValidator(ctx, addr)
require.True(t, found)
require.False(t, val.GetRevoked())
// test revoke
keeper.Revoke(ctx, pk)
val, found = keeper.GetValidator(ctx, addr)
require.True(t, found)
require.True(t, val.GetRevoked())
// test unrevoke
keeper.Unrevoke(ctx, pk)
val, found = keeper.GetValidator(ctx, addr)
require.True(t, found)
require.False(t, val.GetRevoked())
}
// tests slashUnbondingDelegation
func TestSlashUnbondingDelegation(t *testing.T) {
ctx, keeper, params := setupHelper(t, 10)
fraction := sdk.NewRat(1, 2)
// set an unbonding delegation
ubd := types.UnbondingDelegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[0],
CreationHeight: 0,
// expiration timestamp (beyond which the unbonding delegation shouldn't be slashed)
MinTime: 0,
InitialBalance: sdk.NewCoin(params.BondDenom, 10),
Balance: sdk.NewCoin(params.BondDenom, 10),
}
keeper.SetUnbondingDelegation(ctx, ubd)
// unbonding started prior to the infraction height, stake 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: int64(10)})
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
oldPool := keeper.GetPool(ctx)
ctx = ctx.WithBlockHeader(abci.Header{Time: int64(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)
// initialbalance unchanged
require.Equal(t, sdk.NewCoin(params.BondDenom, 10), ubd.InitialBalance)
// balance decreased
require.Equal(t, sdk.NewCoin(params.BondDenom, 5), ubd.Balance)
newPool := keeper.GetPool(ctx)
require.Equal(t, int64(5), oldPool.LooseTokens-newPool.LooseTokens)
}
// tests slashRedelegation
func TestSlashRedelegation(t *testing.T) {
ctx, keeper, params := setupHelper(t, 10)
fraction := sdk.NewRat(1, 2)
// set a redelegation
rd := types.Redelegation{
DelegatorAddr: addrDels[0],
ValidatorSrcAddr: addrVals[0],
ValidatorDstAddr: addrVals[1],
CreationHeight: 0,
// expiration timestamp (beyond which the redelegation shouldn't be slashed)
MinTime: 0,
SharesSrc: sdk.NewRat(10),
SharesDst: sdk.NewRat(10),
InitialBalance: sdk.NewCoin(params.BondDenom, 10),
Balance: sdk.NewCoin(params.BondDenom, 10),
}
keeper.SetRedelegation(ctx, rd)
// set the associated delegation
del := types.Delegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[1],
Shares: sdk.NewRat(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: int64(10)})
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())
// test valid slash, before expiration timestamp and to which stake contributed
oldPool := keeper.GetPool(ctx)
ctx = ctx.WithBlockHeader(abci.Header{Time: int64(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)
// initialbalance unchanged
require.Equal(t, sdk.NewCoin(params.BondDenom, 10), rd.InitialBalance)
// balance decreased
require.Equal(t, sdk.NewCoin(params.BondDenom, 5), rd.Balance)
// 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 decreased
newPool := keeper.GetPool(ctx)
require.Equal(t, int64(5), oldPool.BondedTokens-newPool.BondedTokens)
}
// tests Slash at a future height (must panic)
func TestSlashAtFutureHeight(t *testing.T) {
ctx, keeper, _ := setupHelper(t, 10)
pk := PKs[0]
fraction := sdk.NewRat(1, 2)
require.Panics(t, func() { keeper.Slash(ctx, pk, 1, 10, fraction) })
}
// tests Slash at the current height
func TestSlashAtCurrentHeight(t *testing.T) {
ctx, keeper, _ := setupHelper(t, 10)
pk := PKs[0]
fraction := sdk.NewRat(1, 2)
oldPool := keeper.GetPool(ctx)
validator, found := keeper.GetValidatorByPubKey(ctx, pk)
require.True(t, found)
keeper.Slash(ctx, pk, ctx.BlockHeight(), 10, fraction)
// read updated state
validator, found = keeper.GetValidatorByPubKey(ctx, pk)
require.True(t, found)
newPool := keeper.GetPool(ctx)
// power decreased
require.Equal(t, sdk.NewRat(5), validator.GetPower())
// pool bonded shares decreased
require.Equal(t, sdk.NewRat(5).RoundInt64(), oldPool.BondedShares.Sub(newPool.BondedShares).RoundInt64())
}
// tests Slash at a previous height with an unbonding delegation
func TestSlashWithUnbondingDelegation(t *testing.T) {
ctx, keeper, params := setupHelper(t, 10)
pk := PKs[0]
fraction := sdk.NewRat(1, 2)
// set an unbonding delegation
ubd := types.UnbondingDelegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[0],
CreationHeight: 11,
// expiration timestamp (beyond which the unbonding delegation shouldn't be slashed)
MinTime: 0,
InitialBalance: sdk.NewCoin(params.BondDenom, 4),
Balance: sdk.NewCoin(params.BondDenom, 4),
}
keeper.SetUnbondingDelegation(ctx, ubd)
// slash validator for the first time
ctx = ctx.WithBlockHeight(12)
oldPool := keeper.GetPool(ctx)
validator, found := keeper.GetValidatorByPubKey(ctx, pk)
require.True(t, found)
keeper.Slash(ctx, pk, 10, 10, fraction)
// read updating unbonding delegation
ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
require.True(t, found)
// balance decreased
require.Equal(t, sdk.NewInt(2), ubd.Balance.Amount)
// read updated pool
newPool := keeper.GetPool(ctx)
// bonded tokens burned
require.Equal(t, int64(3), oldPool.BondedTokens-newPool.BondedTokens)
// read updated validator
validator, found = keeper.GetValidatorByPubKey(ctx, pk)
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, sdk.NewRat(7), validator.GetPower())
// slash validator again
ctx = ctx.WithBlockHeight(13)
keeper.Slash(ctx, pk, 9, 10, fraction)
ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
require.True(t, found)
// balance decreased again
require.Equal(t, sdk.NewInt(0), ubd.Balance.Amount)
// read updated pool
newPool = keeper.GetPool(ctx)
// bonded tokens burned again
require.Equal(t, int64(6), oldPool.BondedTokens-newPool.BondedTokens)
// read updated validator
validator, found = keeper.GetValidatorByPubKey(ctx, pk)
require.True(t, found)
// power decreased by 3 again
require.Equal(t, sdk.NewRat(4), validator.GetPower())
// 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, pk, 9, 10, fraction)
ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
require.True(t, found)
// balance unchanged
require.Equal(t, sdk.NewInt(0), ubd.Balance.Amount)
// read updated pool
newPool = keeper.GetPool(ctx)
// bonded tokens burned again
require.Equal(t, int64(9), oldPool.BondedTokens-newPool.BondedTokens)
// read updated validator
validator, found = keeper.GetValidatorByPubKey(ctx, pk)
require.True(t, found)
// power decreased by 3 again
require.Equal(t, sdk.NewRat(1), validator.GetPower())
// 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, pk, 9, 10, fraction)
ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
require.True(t, found)
// balance unchanged
require.Equal(t, sdk.NewInt(0), ubd.Balance.Amount)
// read updated pool
newPool = keeper.GetPool(ctx)
// just 1 bonded token burned again since that's all the validator now has
require.Equal(t, int64(10), oldPool.BondedTokens-newPool.BondedTokens)
// read updated validator
// power decreased by 1 again, validator is out of stake
// ergo validator should have been removed from the store
_, found = keeper.GetValidatorByPubKey(ctx, pk)
require.False(t, found)
}
// tests Slash at a previous height with a redelegation
func TestSlashWithRedelegation(t *testing.T) {
ctx, keeper, params := setupHelper(t, 10)
pk := PKs[0]
fraction := sdk.NewRat(1, 2)
// set a redelegation
rd := types.Redelegation{
DelegatorAddr: addrDels[0],
ValidatorSrcAddr: addrVals[0],
ValidatorDstAddr: addrVals[1],
CreationHeight: 11,
MinTime: 0,
SharesSrc: sdk.NewRat(6),
SharesDst: sdk.NewRat(6),
InitialBalance: sdk.NewCoin(params.BondDenom, 6),
Balance: sdk.NewCoin(params.BondDenom, 6),
}
keeper.SetRedelegation(ctx, rd)
// set the associated delegation
del := types.Delegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[1],
Shares: sdk.NewRat(6),
}
keeper.SetDelegation(ctx, del)
// slash validator
ctx = ctx.WithBlockHeight(12)
oldPool := keeper.GetPool(ctx)
validator, found := keeper.GetValidatorByPubKey(ctx, pk)
require.True(t, found)
keeper.Slash(ctx, pk, 10, 10, fraction)
// read updating redelegation
rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
require.True(t, found)
// balance decreased
require.Equal(t, sdk.NewInt(3), rd.Balance.Amount)
// read updated pool
newPool := keeper.GetPool(ctx)
// bonded tokens burned
require.Equal(t, int64(5), oldPool.BondedTokens-newPool.BondedTokens)
// read updated validator
validator, found = keeper.GetValidatorByPubKey(ctx, pk)
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, sdk.NewRat(8), validator.GetPower())
// slash the validator again
ctx = ctx.WithBlockHeight(12)
validator, found = keeper.GetValidatorByPubKey(ctx, pk)
require.True(t, found)
keeper.Slash(ctx, pk, 10, 10, sdk.NewRat(3, 4))
// read updating redelegation
rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
require.True(t, found)
// balance decreased, now zero
require.Equal(t, sdk.NewInt(0), rd.Balance.Amount)
// read updated pool
newPool = keeper.GetPool(ctx)
// 7 bonded tokens burned
require.Equal(t, int64(12), oldPool.BondedTokens-newPool.BondedTokens)
// read updated validator
validator, found = keeper.GetValidatorByPubKey(ctx, pk)
require.True(t, found)
// power decreased by 4
require.Equal(t, sdk.NewRat(4), validator.GetPower())
// slash the validator again, by 100%
ctx = ctx.WithBlockHeight(12)
validator, found = keeper.GetValidatorByPubKey(ctx, pk)
require.True(t, found)
keeper.Slash(ctx, pk, 10, 10, sdk.OneRat())
// read updating redelegation
rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
require.True(t, found)
// balance still zero
require.Equal(t, sdk.NewInt(0), rd.Balance.Amount)
// read updated pool
newPool = keeper.GetPool(ctx)
// four more bonded tokens burned
require.Equal(t, int64(16), oldPool.BondedTokens-newPool.BondedTokens)
// read updated validator
// validator decreased to zero power, should have been removed from the store
_, found = keeper.GetValidatorByPubKey(ctx, pk)
require.False(t, found)
// slash the validator again, by 100%
// no stake remains to be slashed
ctx = ctx.WithBlockHeight(12)
// validator no longer in the store
_, found = keeper.GetValidatorByPubKey(ctx, pk)
require.False(t, found)
keeper.Slash(ctx, pk, 10, 10, sdk.OneRat())
// read updating redelegation
rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
require.True(t, found)
// balance still zero
require.Equal(t, sdk.NewInt(0), rd.Balance.Amount)
// read updated pool
newPool = keeper.GetPool(ctx)
// no more bonded tokens burned
require.Equal(t, int64(16), oldPool.BondedTokens-newPool.BondedTokens)
// read updated validator
// power still zero, still not in the store
_, found = keeper.GetValidatorByPubKey(ctx, pk)
require.False(t, found)
}
// tests Slash at a previous height with both an unbonding delegation and a redelegation
func TestSlashBoth(t *testing.T) {
ctx, keeper, params := setupHelper(t, 10)
fraction := sdk.NewRat(1, 2)
// set a redelegation
rdA := types.Redelegation{
DelegatorAddr: addrDels[0],
ValidatorSrcAddr: addrVals[0],
ValidatorDstAddr: addrVals[1],
CreationHeight: 11,
// expiration timestamp (beyond which the redelegation shouldn't be slashed)
MinTime: 0,
SharesSrc: sdk.NewRat(6),
SharesDst: sdk.NewRat(6),
InitialBalance: sdk.NewCoin(params.BondDenom, 6),
Balance: sdk.NewCoin(params.BondDenom, 6),
}
keeper.SetRedelegation(ctx, rdA)
// set the associated delegation
delA := types.Delegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[1],
Shares: sdk.NewRat(6),
}
keeper.SetDelegation(ctx, delA)
// set an unbonding delegation
ubdA := types.UnbondingDelegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[0],
CreationHeight: 11,
// expiration timestamp (beyond which the unbonding delegation shouldn't be slashed)
MinTime: 0,
InitialBalance: sdk.NewCoin(params.BondDenom, 4),
Balance: sdk.NewCoin(params.BondDenom, 4),
}
keeper.SetUnbondingDelegation(ctx, ubdA)
// slash validator
ctx = ctx.WithBlockHeight(12)
oldPool := keeper.GetPool(ctx)
validator, found := keeper.GetValidatorByPubKey(ctx, PKs[0])
require.True(t, found)
keeper.Slash(ctx, PKs[0], 10, 10, fraction)
// read updating redelegation
rdA, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
require.True(t, found)
// balance decreased
require.Equal(t, sdk.NewInt(3), rdA.Balance.Amount)
// read updated pool
newPool := keeper.GetPool(ctx)
// loose tokens burned
require.Equal(t, int64(2), oldPool.LooseTokens-newPool.LooseTokens)
// bonded tokens burned
require.Equal(t, int64(3), oldPool.BondedTokens-newPool.BondedTokens)
// read updated validator
validator, found = keeper.GetValidatorByPubKey(ctx, PKs[0])
require.True(t, found)
// power not decreased, all stake was bonded since
require.Equal(t, sdk.NewRat(10), validator.GetPower())
}