Co-authored-by: Hieu Vu <72878483+hieuvubk@users.noreply.github.com> Co-authored-by: Ezequiel Raynaudo <raynaudo.ee@gmail.com>
393 lines
12 KiB
Go
393 lines
12 KiB
Go
package keeper_test
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"gotest.tools/v3/assert"
|
|
|
|
"cosmossdk.io/core/header"
|
|
"cosmossdk.io/math"
|
|
"cosmossdk.io/x/bank/testutil"
|
|
pooltypes "cosmossdk.io/x/protocolpool/types"
|
|
"cosmossdk.io/x/staking/keeper"
|
|
"cosmossdk.io/x/staking/types"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec/address"
|
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
|
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
)
|
|
|
|
func TestCancelUnbondingDelegation(t *testing.T) {
|
|
t.Parallel()
|
|
f := initFixture(t)
|
|
|
|
ctx := f.sdkCtx
|
|
msgServer := keeper.NewMsgServerImpl(f.stakingKeeper)
|
|
bondDenom, err := f.stakingKeeper.BondDenom(ctx)
|
|
assert.NilError(t, err)
|
|
|
|
// set the not bonded pool module account
|
|
notBondedPool := f.stakingKeeper.GetNotBondedPool(ctx)
|
|
startTokens := f.stakingKeeper.TokensFromConsensusPower(ctx, 5)
|
|
|
|
assert.NilError(t, testutil.FundModuleAccount(ctx, f.bankKeeper, notBondedPool.GetName(), sdk.NewCoins(sdk.NewCoin(bondDenom, startTokens))))
|
|
f.accountKeeper.SetModuleAccount(ctx, notBondedPool)
|
|
|
|
moduleBalance := f.bankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), bondDenom)
|
|
assert.DeepEqual(t, sdk.NewInt64Coin(bondDenom, startTokens.Int64()), moduleBalance)
|
|
|
|
// accounts
|
|
addrs := simtestutil.AddTestAddrsIncremental(f.bankKeeper, f.stakingKeeper, ctx, 2, math.NewInt(10000))
|
|
valAddr := sdk.ValAddress(addrs[0])
|
|
delegatorAddr := addrs[1]
|
|
|
|
// setup a new validator with bonded status
|
|
validator, err := types.NewValidator(valAddr.String(), PKs[0], types.NewDescription("Validator", "", "", "", ""))
|
|
validator.Status = types.Bonded
|
|
assert.NilError(t, err)
|
|
assert.NilError(t, f.stakingKeeper.SetValidator(ctx, validator))
|
|
|
|
validatorAddr, err := sdk.ValAddressFromBech32(validator.OperatorAddress)
|
|
assert.NilError(t, err)
|
|
|
|
// setting the ubd entry
|
|
unbondingAmount := sdk.NewInt64Coin(bondDenom, 5)
|
|
ubd := types.NewUnbondingDelegation(
|
|
delegatorAddr, validatorAddr, 10,
|
|
ctx.HeaderInfo().Time.Add(time.Minute*10),
|
|
unbondingAmount.Amount,
|
|
0,
|
|
address.NewBech32Codec("cosmosvaloper"), address.NewBech32Codec("cosmos"),
|
|
)
|
|
|
|
// set and retrieve a record
|
|
assert.NilError(t, f.stakingKeeper.SetUnbondingDelegation(ctx, ubd))
|
|
resUnbond, found := f.stakingKeeper.GetUnbondingDelegation(ctx, delegatorAddr, validatorAddr)
|
|
assert.Assert(t, found)
|
|
assert.DeepEqual(t, ubd, resUnbond)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
exceptErr bool
|
|
req types.MsgCancelUnbondingDelegation
|
|
expErrMsg string
|
|
}{
|
|
{
|
|
name: "entry not found at height",
|
|
exceptErr: true,
|
|
req: types.MsgCancelUnbondingDelegation{
|
|
DelegatorAddress: resUnbond.DelegatorAddress,
|
|
ValidatorAddress: resUnbond.ValidatorAddress,
|
|
Amount: sdk.NewCoin(bondDenom, math.NewInt(4)),
|
|
CreationHeight: 11,
|
|
},
|
|
expErrMsg: "unbonding delegation entry is not found at block height",
|
|
},
|
|
{
|
|
name: "invalid height",
|
|
exceptErr: true,
|
|
req: types.MsgCancelUnbondingDelegation{
|
|
DelegatorAddress: resUnbond.DelegatorAddress,
|
|
ValidatorAddress: resUnbond.ValidatorAddress,
|
|
Amount: sdk.NewCoin(bondDenom, math.NewInt(4)),
|
|
CreationHeight: 0,
|
|
},
|
|
expErrMsg: "invalid height",
|
|
},
|
|
{
|
|
name: "invalid coin",
|
|
exceptErr: true,
|
|
req: types.MsgCancelUnbondingDelegation{
|
|
DelegatorAddress: resUnbond.DelegatorAddress,
|
|
ValidatorAddress: resUnbond.ValidatorAddress,
|
|
Amount: sdk.NewCoin("dump_coin", math.NewInt(4)),
|
|
CreationHeight: 10,
|
|
},
|
|
expErrMsg: "invalid coin denomination",
|
|
},
|
|
{
|
|
name: "validator not exists",
|
|
exceptErr: true,
|
|
req: types.MsgCancelUnbondingDelegation{
|
|
DelegatorAddress: resUnbond.DelegatorAddress,
|
|
ValidatorAddress: sdk.ValAddress(sdk.AccAddress("asdsad")).String(),
|
|
Amount: unbondingAmount,
|
|
CreationHeight: 10,
|
|
},
|
|
expErrMsg: "validator does not exist",
|
|
},
|
|
{
|
|
name: "invalid delegator address",
|
|
exceptErr: true,
|
|
req: types.MsgCancelUnbondingDelegation{
|
|
DelegatorAddress: "invalid_delegator_addrtess",
|
|
ValidatorAddress: resUnbond.ValidatorAddress,
|
|
Amount: unbondingAmount,
|
|
CreationHeight: 0,
|
|
},
|
|
expErrMsg: "decoding bech32 failed",
|
|
},
|
|
{
|
|
name: "invalid amount",
|
|
exceptErr: true,
|
|
req: types.MsgCancelUnbondingDelegation{
|
|
DelegatorAddress: resUnbond.DelegatorAddress,
|
|
ValidatorAddress: resUnbond.ValidatorAddress,
|
|
Amount: unbondingAmount.Add(sdk.NewInt64Coin(bondDenom, 10)),
|
|
CreationHeight: 10,
|
|
},
|
|
expErrMsg: "amount is greater than the unbonding delegation entry balance",
|
|
},
|
|
{
|
|
name: "success",
|
|
exceptErr: false,
|
|
req: types.MsgCancelUnbondingDelegation{
|
|
DelegatorAddress: resUnbond.DelegatorAddress,
|
|
ValidatorAddress: resUnbond.ValidatorAddress,
|
|
Amount: unbondingAmount.Sub(sdk.NewInt64Coin(bondDenom, 1)),
|
|
CreationHeight: 10,
|
|
},
|
|
},
|
|
{
|
|
name: "success",
|
|
exceptErr: false,
|
|
req: types.MsgCancelUnbondingDelegation{
|
|
DelegatorAddress: resUnbond.DelegatorAddress,
|
|
ValidatorAddress: resUnbond.ValidatorAddress,
|
|
Amount: unbondingAmount.Sub(unbondingAmount.Sub(sdk.NewInt64Coin(bondDenom, 1))),
|
|
CreationHeight: 10,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
tc := tc
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := msgServer.CancelUnbondingDelegation(ctx, &tc.req)
|
|
if tc.exceptErr {
|
|
assert.ErrorContains(t, err, tc.expErrMsg)
|
|
} else {
|
|
assert.NilError(t, err)
|
|
balanceForNotBondedPool := f.bankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), bondDenom)
|
|
assert.DeepEqual(t, balanceForNotBondedPool, moduleBalance.Sub(tc.req.Amount))
|
|
moduleBalance = moduleBalance.Sub(tc.req.Amount)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRotateConsPubKey(t *testing.T) {
|
|
t.Parallel()
|
|
f := initFixture(t)
|
|
|
|
ctx := f.sdkCtx
|
|
stakingKeeper := f.stakingKeeper
|
|
bankKeeper := f.bankKeeper
|
|
accountKeeper := f.accountKeeper
|
|
|
|
msgServer := keeper.NewMsgServerImpl(stakingKeeper)
|
|
bondDenom, err := stakingKeeper.BondDenom(ctx)
|
|
assert.NilError(t, err)
|
|
|
|
params, err := stakingKeeper.Params.Get(ctx)
|
|
assert.NilError(t, err)
|
|
|
|
params.KeyRotationFee = sdk.NewInt64Coin(bondDenom, 10)
|
|
err = stakingKeeper.Params.Set(ctx, params)
|
|
assert.NilError(t, err)
|
|
|
|
addrs := simtestutil.AddTestAddrsIncremental(bankKeeper, stakingKeeper, ctx, 5, stakingKeeper.TokensFromConsensusPower(ctx, 100))
|
|
valAddrs := simtestutil.ConvertAddrsToValAddrs(addrs)
|
|
|
|
// create 5 validators
|
|
for i := 0; i < 5; i++ {
|
|
comm := types.NewCommissionRates(math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0))
|
|
acc := f.accountKeeper.NewAccountWithAddress(ctx, sdk.AccAddress(valAddrs[i]))
|
|
f.accountKeeper.SetAccount(ctx, acc)
|
|
msg, err := types.NewMsgCreateValidator(valAddrs[i].String(), PKs[i], sdk.NewCoin(sdk.DefaultBondDenom, stakingKeeper.TokensFromConsensusPower(ctx, 30)),
|
|
types.Description{Moniker: "NewVal"}, comm, math.OneInt())
|
|
assert.NilError(t, err)
|
|
_, err = msgServer.CreateValidator(ctx, msg)
|
|
assert.NilError(t, err)
|
|
}
|
|
|
|
// call endblocker to update the validator state
|
|
_, err = stakingKeeper.EndBlocker(ctx.WithBlockHeight(ctx.BlockHeader().Height + 1))
|
|
assert.NilError(t, err)
|
|
|
|
params, err = stakingKeeper.Params.Get(ctx)
|
|
assert.NilError(t, err)
|
|
|
|
validators, err := stakingKeeper.GetAllValidators(ctx)
|
|
assert.NilError(t, err)
|
|
assert.Equal(t, len(validators) >= 5, true)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
malleate func() sdk.Context
|
|
pass bool
|
|
validator string
|
|
newPubKey cryptotypes.PubKey
|
|
expErrMsg string
|
|
expHistoryObjs int
|
|
fees sdk.Coin
|
|
}{
|
|
{
|
|
name: "successful consensus pubkey rotation",
|
|
malleate: func() sdk.Context {
|
|
return ctx
|
|
},
|
|
validator: validators[0].GetOperator(),
|
|
newPubKey: PKs[499],
|
|
pass: true,
|
|
expHistoryObjs: 1,
|
|
fees: params.KeyRotationFee,
|
|
},
|
|
{
|
|
name: "non existing validator check",
|
|
malleate: func() sdk.Context {
|
|
return ctx
|
|
},
|
|
validator: sdk.ValAddress("non_existing_val").String(),
|
|
newPubKey: PKs[498],
|
|
pass: false,
|
|
expErrMsg: "validator does not exist",
|
|
},
|
|
{
|
|
name: "pubkey already associated with another validator",
|
|
malleate: func() sdk.Context {
|
|
return ctx
|
|
},
|
|
validator: validators[0].GetOperator(),
|
|
newPubKey: validators[1].ConsensusPubkey.GetCachedValue().(cryptotypes.PubKey),
|
|
pass: false,
|
|
expErrMsg: "validator already exist for this pubkey; must use new validator pubkey",
|
|
},
|
|
{
|
|
name: "consensus pubkey rotation limit check",
|
|
malleate: func() sdk.Context {
|
|
params, err := stakingKeeper.Params.Get(ctx)
|
|
assert.NilError(t, err)
|
|
|
|
params.KeyRotationFee = sdk.NewInt64Coin(bondDenom, 10)
|
|
err = stakingKeeper.Params.Set(ctx, params)
|
|
assert.NilError(t, err)
|
|
|
|
msg, err := types.NewMsgRotateConsPubKey(
|
|
validators[1].GetOperator(),
|
|
PKs[498],
|
|
)
|
|
assert.NilError(t, err)
|
|
_, err = msgServer.RotateConsPubKey(ctx, msg)
|
|
assert.NilError(t, err)
|
|
|
|
return ctx
|
|
},
|
|
validator: validators[1].GetOperator(),
|
|
newPubKey: PKs[497],
|
|
pass: false,
|
|
expErrMsg: "exceeding maximum consensus pubkey rotations within unbonding period",
|
|
},
|
|
{
|
|
name: "limit reached, but should rotate after the unbonding period",
|
|
malleate: func() sdk.Context {
|
|
params, err := stakingKeeper.Params.Get(ctx)
|
|
assert.NilError(t, err)
|
|
|
|
params.KeyRotationFee = sdk.NewInt64Coin(bondDenom, 10)
|
|
err = stakingKeeper.Params.Set(ctx, params)
|
|
assert.NilError(t, err)
|
|
|
|
msg, err := types.NewMsgRotateConsPubKey(
|
|
validators[3].GetOperator(),
|
|
PKs[495],
|
|
)
|
|
|
|
assert.NilError(t, err)
|
|
_, err = msgServer.RotateConsPubKey(ctx, msg)
|
|
assert.NilError(t, err)
|
|
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
|
|
|
|
// this shouldn't remove the existing keys from waiting queue since unbonding time isn't reached
|
|
_, err = stakingKeeper.EndBlocker(ctx)
|
|
assert.NilError(t, err)
|
|
|
|
msg, err = types.NewMsgRotateConsPubKey(
|
|
validators[3].GetOperator(),
|
|
PKs[494],
|
|
)
|
|
|
|
assert.NilError(t, err)
|
|
_, err = msgServer.RotateConsPubKey(ctx, msg)
|
|
assert.Error(t, err, "exceeding maximum consensus pubkey rotations within unbonding period")
|
|
|
|
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
|
|
|
|
newCtx := ctx.WithHeaderInfo(header.Info{Height: ctx.BlockHeight() + 1, Time: ctx.HeaderInfo().Time.Add(params.UnbondingTime)}).WithBlockHeight(ctx.BlockHeight() + 1)
|
|
// this should remove keys from waiting queue since unbonding time is reached
|
|
_, err = stakingKeeper.EndBlocker(newCtx)
|
|
assert.NilError(t, err)
|
|
|
|
return newCtx
|
|
},
|
|
validator: validators[3].GetOperator(),
|
|
newPubKey: PKs[494],
|
|
pass: true,
|
|
expErrMsg: "",
|
|
expHistoryObjs: 2,
|
|
fees: params.KeyRotationFee,
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
newCtx := testCase.malleate()
|
|
oldDistrBalance := bankKeeper.GetBalance(newCtx, accountKeeper.GetModuleAddress(pooltypes.ModuleName), bondDenom)
|
|
msg, err := types.NewMsgRotateConsPubKey(
|
|
testCase.validator,
|
|
testCase.newPubKey,
|
|
)
|
|
assert.NilError(t, err)
|
|
|
|
_, err = msgServer.RotateConsPubKey(newCtx, msg)
|
|
|
|
if testCase.pass {
|
|
assert.NilError(t, err)
|
|
|
|
_, err = stakingKeeper.EndBlocker(newCtx)
|
|
assert.NilError(t, err)
|
|
|
|
// rotation fee payment from sender to distrtypes
|
|
newDistrBalance := bankKeeper.GetBalance(newCtx, accountKeeper.GetModuleAddress(pooltypes.ModuleName), bondDenom)
|
|
assert.DeepEqual(t, newDistrBalance, oldDistrBalance.Add(testCase.fees))
|
|
|
|
valBytes, err := stakingKeeper.ValidatorAddressCodec().StringToBytes(testCase.validator)
|
|
assert.NilError(t, err)
|
|
|
|
// validator consensus pubkey update check
|
|
validator, err := stakingKeeper.GetValidator(newCtx, valBytes)
|
|
assert.NilError(t, err)
|
|
|
|
consAddr, err := validator.GetConsAddr()
|
|
assert.NilError(t, err)
|
|
assert.DeepEqual(t, consAddr, testCase.newPubKey.Address().Bytes())
|
|
|
|
// consensus rotation history set check
|
|
historyObjects, err := stakingKeeper.GetValidatorConsPubKeyRotationHistory(newCtx, valBytes)
|
|
assert.NilError(t, err)
|
|
assert.Equal(t, len(historyObjects), testCase.expHistoryObjs)
|
|
|
|
historyObjects, err = stakingKeeper.GetBlockConsPubKeyRotationHistory(newCtx)
|
|
assert.NilError(t, err)
|
|
assert.Equal(t, len(historyObjects), 1)
|
|
|
|
} else {
|
|
assert.ErrorContains(t, err, testCase.expErrMsg)
|
|
}
|
|
})
|
|
}
|
|
}
|