cosmos-sdk/x/staking/keeper/msg_server_test.go
Nguyễn Quang Vinh c8ededc504
fix: add delegator address to redelegate event (#25258)
Co-authored-by: Alex | Interchain Labs <alex@djinntek.world>
Co-authored-by: Alex | Interchain Labs <alex@cosmoslabs.io>
2025-09-02 17:00:03 +00:00

1126 lines
35 KiB
Go

package keeper_test
import (
"testing"
"time"
"go.uber.org/mock/gomock"
"cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/codec/address"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)
var (
PKS = simtestutil.CreateTestPubKeys(3)
Addr = sdk.AccAddress(PKS[0].Address())
ValAddr = sdk.ValAddress(Addr)
)
func (s *KeeperTestSuite) execExpectCalls() {
s.accountKeeper.EXPECT().AddressCodec().Return(address.NewBech32Codec("cosmos")).AnyTimes()
s.bankKeeper.EXPECT().DelegateCoinsFromAccountToModule(gomock.Any(), Addr, stakingtypes.NotBondedPoolName, gomock.Any()).AnyTimes()
}
func (s *KeeperTestSuite) TestMsgCreateValidator() {
ctx, msgServer := s.ctx, s.msgServer
require := s.Require()
s.execExpectCalls()
pk1 := ed25519.GenPrivKey().PubKey()
require.NotNil(pk1)
pubkey, err := codectypes.NewAnyWithValue(pk1)
require.NoError(err)
testCases := []struct {
name string
input *stakingtypes.MsgCreateValidator
expErr bool
expErrMsg string
}{
{
name: "empty description",
input: &stakingtypes.MsgCreateValidator{
Description: stakingtypes.Description{},
Commission: stakingtypes.CommissionRates{
Rate: math.LegacyNewDecWithPrec(5, 1),
MaxRate: math.LegacyNewDecWithPrec(5, 1),
MaxChangeRate: math.LegacyNewDec(0),
},
MinSelfDelegation: math.NewInt(1),
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Pubkey: pubkey,
Value: sdk.NewInt64Coin("stake", 10000),
},
expErr: true,
expErrMsg: "empty description",
},
{
name: "invalid validator address",
input: &stakingtypes.MsgCreateValidator{
Description: stakingtypes.Description{
Moniker: "NewValidator",
},
Commission: stakingtypes.CommissionRates{
Rate: math.LegacyNewDecWithPrec(5, 1),
MaxRate: math.LegacyNewDecWithPrec(5, 1),
MaxChangeRate: math.LegacyNewDec(0),
},
MinSelfDelegation: math.NewInt(1),
DelegatorAddress: Addr.String(),
ValidatorAddress: sdk.AccAddress([]byte("invalid")).String(),
Pubkey: pubkey,
Value: sdk.NewInt64Coin("stake", 10000),
},
expErr: true,
expErrMsg: "invalid validator address",
},
{
name: "empty validator pubkey",
input: &stakingtypes.MsgCreateValidator{
Description: stakingtypes.Description{
Moniker: "NewValidator",
},
Commission: stakingtypes.CommissionRates{
Rate: math.LegacyNewDecWithPrec(5, 1),
MaxRate: math.LegacyNewDecWithPrec(5, 1),
MaxChangeRate: math.LegacyNewDec(0),
},
MinSelfDelegation: math.NewInt(1),
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Pubkey: nil,
Value: sdk.NewInt64Coin("stake", 10000),
},
expErr: true,
expErrMsg: "empty validator public key",
},
{
name: "empty delegation amount",
input: &stakingtypes.MsgCreateValidator{
Description: stakingtypes.Description{
Moniker: "NewValidator",
},
Commission: stakingtypes.CommissionRates{
Rate: math.LegacyNewDecWithPrec(5, 1),
MaxRate: math.LegacyNewDecWithPrec(5, 1),
MaxChangeRate: math.LegacyNewDec(0),
},
MinSelfDelegation: math.NewInt(1),
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Pubkey: pubkey,
Value: sdk.NewInt64Coin("stake", 0),
},
expErr: true,
expErrMsg: "invalid delegation amount",
},
{
name: "nil delegation amount",
input: &stakingtypes.MsgCreateValidator{
Description: stakingtypes.Description{
Moniker: "NewValidator",
},
Commission: stakingtypes.CommissionRates{
Rate: math.LegacyNewDecWithPrec(5, 1),
MaxRate: math.LegacyNewDecWithPrec(5, 1),
MaxChangeRate: math.LegacyNewDec(0),
},
MinSelfDelegation: math.NewInt(1),
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Pubkey: pubkey,
Value: sdk.Coin{},
},
expErr: true,
expErrMsg: "invalid delegation amount",
},
{
name: "zero minimum self delegation",
input: &stakingtypes.MsgCreateValidator{
Description: stakingtypes.Description{
Moniker: "NewValidator",
},
Commission: stakingtypes.CommissionRates{
Rate: math.LegacyNewDecWithPrec(5, 1),
MaxRate: math.LegacyNewDecWithPrec(5, 1),
MaxChangeRate: math.LegacyNewDec(0),
},
MinSelfDelegation: math.NewInt(0),
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Pubkey: pubkey,
Value: sdk.NewInt64Coin("stake", 10000),
},
expErr: true,
expErrMsg: "minimum self delegation must be a positive integer",
},
{
name: "negative minimum self delegation",
input: &stakingtypes.MsgCreateValidator{
Description: stakingtypes.Description{
Moniker: "NewValidator",
},
Commission: stakingtypes.CommissionRates{
Rate: math.LegacyNewDecWithPrec(5, 1),
MaxRate: math.LegacyNewDecWithPrec(5, 1),
MaxChangeRate: math.LegacyNewDec(0),
},
MinSelfDelegation: math.NewInt(-1),
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Pubkey: pubkey,
Value: sdk.NewInt64Coin("stake", 10000),
},
expErr: true,
expErrMsg: "minimum self delegation must be a positive integer",
},
{
name: "delegation less than minimum self delegation",
input: &stakingtypes.MsgCreateValidator{
Description: stakingtypes.Description{
Moniker: "NewValidator",
},
Commission: stakingtypes.CommissionRates{
Rate: math.LegacyNewDecWithPrec(5, 1),
MaxRate: math.LegacyNewDecWithPrec(5, 1),
MaxChangeRate: math.LegacyNewDec(0),
},
MinSelfDelegation: math.NewInt(100),
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Pubkey: pubkey,
Value: sdk.NewInt64Coin("stake", 10),
},
expErr: true,
expErrMsg: "validator's self delegation must be greater than their minimum self delegation",
},
{
name: "valid msg",
input: &stakingtypes.MsgCreateValidator{
Description: stakingtypes.Description{
Moniker: "NewValidator",
Identity: "xyz",
Website: "xyz.com",
SecurityContact: "xyz@gmail.com",
Details: "details",
},
Commission: stakingtypes.CommissionRates{
Rate: math.LegacyNewDecWithPrec(5, 1),
MaxRate: math.LegacyNewDecWithPrec(5, 1),
MaxChangeRate: math.LegacyNewDec(0),
},
MinSelfDelegation: math.NewInt(1),
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Pubkey: pubkey,
Value: sdk.NewInt64Coin("stake", 10000),
},
expErr: false,
},
}
for _, tc := range testCases {
s.T().Run(tc.name, func(t *testing.T) {
_, err := msgServer.CreateValidator(ctx, tc.input)
if tc.expErr {
require.Error(err)
require.Contains(err.Error(), tc.expErrMsg)
} else {
require.NoError(err)
}
})
}
}
func (s *KeeperTestSuite) TestMsgEditValidator() {
ctx, msgServer := s.ctx, s.msgServer
require := s.Require()
s.execExpectCalls()
// create new context with updated block time
newCtx := ctx.WithBlockTime(ctx.BlockTime().AddDate(0, 0, 1))
pk := ed25519.GenPrivKey().PubKey()
require.NotNil(pk)
comm := stakingtypes.NewCommissionRates(math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0))
msg, err := stakingtypes.NewMsgCreateValidator(ValAddr.String(), pk, sdk.NewCoin("stake", math.NewInt(10)), stakingtypes.Description{Moniker: "NewVal"}, comm, math.OneInt())
require.NoError(err)
res, err := msgServer.CreateValidator(ctx, msg)
require.NoError(err)
require.NotNil(res)
newRate := math.LegacyZeroDec()
invalidRate := math.LegacyNewDec(2)
lowSelfDel := math.OneInt()
highSelfDel := math.NewInt(100)
negSelfDel := math.NewInt(-1)
newSelfDel := math.NewInt(3)
testCases := []struct {
name string
ctx sdk.Context
input *stakingtypes.MsgEditValidator
expErr bool
expErrMsg string
}{
{
name: "invalid validator",
ctx: newCtx,
input: &stakingtypes.MsgEditValidator{
Description: stakingtypes.Description{
Moniker: "TestValidator",
},
ValidatorAddress: sdk.AccAddress([]byte("invalid")).String(),
CommissionRate: &newRate,
MinSelfDelegation: &newSelfDel,
},
expErr: true,
expErrMsg: "invalid validator address",
},
{
name: "empty description",
ctx: newCtx,
input: &stakingtypes.MsgEditValidator{
Description: stakingtypes.Description{},
ValidatorAddress: ValAddr.String(),
CommissionRate: &newRate,
MinSelfDelegation: &newSelfDel,
},
expErr: true,
expErrMsg: "empty description",
},
{
name: "negative self delegation",
ctx: newCtx,
input: &stakingtypes.MsgEditValidator{
Description: stakingtypes.Description{
Moniker: "TestValidator",
},
ValidatorAddress: ValAddr.String(),
CommissionRate: &newRate,
MinSelfDelegation: &negSelfDel,
},
expErr: true,
expErrMsg: "minimum self delegation must be a positive integer",
},
{
name: "invalid commission rate",
ctx: newCtx,
input: &stakingtypes.MsgEditValidator{
Description: stakingtypes.Description{
Moniker: "TestValidator",
},
ValidatorAddress: ValAddr.String(),
CommissionRate: &invalidRate,
MinSelfDelegation: &newSelfDel,
},
expErr: true,
expErrMsg: "commission rate must be between 0 and 1 (inclusive)",
},
{
name: "validator does not exist",
ctx: newCtx,
input: &stakingtypes.MsgEditValidator{
Description: stakingtypes.Description{
Moniker: "TestValidator",
},
ValidatorAddress: sdk.ValAddress([]byte("val")).String(),
CommissionRate: &newRate,
MinSelfDelegation: &newSelfDel,
},
expErr: true,
expErrMsg: "validator does not exist",
},
{
name: "change commission rate in <24hrs",
ctx: ctx,
input: &stakingtypes.MsgEditValidator{
Description: stakingtypes.Description{
Moniker: "TestValidator",
},
ValidatorAddress: ValAddr.String(),
CommissionRate: &newRate,
MinSelfDelegation: &newSelfDel,
},
expErr: true,
expErrMsg: "commission cannot be changed more than once in 24h",
},
{
name: "minimum self delegation cannot decrease",
ctx: newCtx,
input: &stakingtypes.MsgEditValidator{
Description: stakingtypes.Description{
Moniker: "TestValidator",
},
ValidatorAddress: ValAddr.String(),
CommissionRate: &newRate,
MinSelfDelegation: &lowSelfDel,
},
expErr: true,
expErrMsg: "minimum self delegation cannot be decrease",
},
{
name: "validator self-delegation must be greater than min self delegation",
ctx: newCtx,
input: &stakingtypes.MsgEditValidator{
Description: stakingtypes.Description{
Moniker: "TestValidator",
},
ValidatorAddress: ValAddr.String(),
CommissionRate: &newRate,
MinSelfDelegation: &highSelfDel,
},
expErr: true,
expErrMsg: "validator's self delegation must be greater than their minimum self delegation",
},
{
name: "valid msg",
ctx: newCtx,
input: &stakingtypes.MsgEditValidator{
Description: stakingtypes.Description{
Moniker: "TestValidator",
Identity: "abc",
Website: "abc.com",
SecurityContact: "abc@gmail.com",
Details: "newDetails",
},
ValidatorAddress: ValAddr.String(),
CommissionRate: &newRate,
MinSelfDelegation: &newSelfDel,
},
expErr: false,
},
}
for _, tc := range testCases {
s.T().Run(tc.name, func(t *testing.T) {
_, err := msgServer.EditValidator(tc.ctx, tc.input)
if tc.expErr {
require.Error(err)
require.Contains(err.Error(), tc.expErrMsg)
} else {
require.NoError(err)
}
})
}
}
func (s *KeeperTestSuite) TestMsgDelegate() {
ctx, keeper, msgServer := s.ctx, s.stakingKeeper, s.msgServer
require := s.Require()
s.execExpectCalls()
pk := ed25519.GenPrivKey().PubKey()
require.NotNil(pk)
comm := stakingtypes.NewCommissionRates(math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0))
msg, err := stakingtypes.NewMsgCreateValidator(ValAddr.String(), pk, sdk.NewCoin("stake", math.NewInt(10)), stakingtypes.Description{Moniker: "NewVal"}, comm, math.OneInt())
require.NoError(err)
res, err := msgServer.CreateValidator(ctx, msg)
require.NoError(err)
require.NotNil(res)
testCases := []struct {
name string
input *stakingtypes.MsgDelegate
expErr bool
expErrMsg string
}{
{
name: "invalid validator",
input: &stakingtypes.MsgDelegate{
DelegatorAddress: Addr.String(),
ValidatorAddress: sdk.AccAddress([]byte("invalid")).String(),
Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))},
},
expErr: true,
expErrMsg: "invalid validator address",
},
{
name: "empty delegator",
input: &stakingtypes.MsgDelegate{
DelegatorAddress: "",
ValidatorAddress: ValAddr.String(),
Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))},
},
expErr: true,
expErrMsg: "invalid delegator address: empty address string is not allowed",
},
{
name: "invalid delegator",
input: &stakingtypes.MsgDelegate{
DelegatorAddress: "invalid",
ValidatorAddress: ValAddr.String(),
Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))},
},
expErr: true,
expErrMsg: "invalid delegator address: decoding bech32 failed",
},
{
name: "validator does not exist",
input: &stakingtypes.MsgDelegate{
DelegatorAddress: Addr.String(),
ValidatorAddress: sdk.ValAddress([]byte("val")).String(),
Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))},
},
expErr: true,
expErrMsg: "validator does not exist",
},
{
name: "zero amount",
input: &stakingtypes.MsgDelegate{
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(0))},
},
expErr: true,
expErrMsg: "invalid delegation amount",
},
{
name: "negative amount",
input: &stakingtypes.MsgDelegate{
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(-1))},
},
expErr: true,
expErrMsg: "invalid delegation amount",
},
{
name: "invalid BondDenom",
input: &stakingtypes.MsgDelegate{
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Amount: sdk.Coin{Denom: "test", Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))},
},
expErr: true,
expErrMsg: "invalid coin denomination",
},
{
name: "valid msg",
input: &stakingtypes.MsgDelegate{
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))},
},
expErr: false,
},
}
for _, tc := range testCases {
s.T().Run(tc.name, func(t *testing.T) {
_, err := msgServer.Delegate(ctx, tc.input)
if tc.expErr {
require.Error(err)
require.Contains(err.Error(), tc.expErrMsg)
} else {
require.NoError(err)
}
})
}
}
func (s *KeeperTestSuite) TestMsgBeginRedelegate() {
ctx, keeper, msgServer := s.ctx, s.stakingKeeper, s.msgServer
require := s.Require()
s.execExpectCalls()
srcValAddr := ValAddr
addr2 := sdk.AccAddress(PKS[1].Address())
dstValAddr := sdk.ValAddress(addr2)
pk := ed25519.GenPrivKey().PubKey()
require.NotNil(pk)
dstPk := ed25519.GenPrivKey().PubKey()
require.NotNil(dstPk)
comm := stakingtypes.NewCommissionRates(math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0))
amt := sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))}
msg, err := stakingtypes.NewMsgCreateValidator(srcValAddr.String(), pk, amt, stakingtypes.Description{Moniker: "NewVal"}, comm, math.OneInt())
require.NoError(err)
res, err := msgServer.CreateValidator(ctx, msg)
require.NoError(err)
require.NotNil(res)
s.bankKeeper.EXPECT().DelegateCoinsFromAccountToModule(gomock.Any(), addr2, stakingtypes.NotBondedPoolName, gomock.Any()).AnyTimes()
msg, err = stakingtypes.NewMsgCreateValidator(dstValAddr.String(), dstPk, amt, stakingtypes.Description{Moniker: "NewVal"}, comm, math.OneInt())
require.NoError(err)
res, err = msgServer.CreateValidator(ctx, msg)
require.NoError(err)
require.NotNil(res)
shares := math.LegacyNewDec(100)
del := stakingtypes.NewDelegation(Addr.String(), srcValAddr.String(), shares)
require.NoError(keeper.SetDelegation(ctx, del))
_, err = keeper.GetDelegation(ctx, Addr, srcValAddr)
require.NoError(err)
testCases := []struct {
name string
input *stakingtypes.MsgBeginRedelegate
expErr bool
expErrMsg string
}{
{
name: "invalid source validator",
input: &stakingtypes.MsgBeginRedelegate{
DelegatorAddress: Addr.String(),
ValidatorSrcAddress: sdk.AccAddress([]byte("invalid")).String(),
ValidatorDstAddress: dstValAddr.String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()),
},
expErr: true,
expErrMsg: "invalid source validator address",
},
{
name: "empty delegator",
input: &stakingtypes.MsgBeginRedelegate{
DelegatorAddress: "",
ValidatorSrcAddress: srcValAddr.String(),
ValidatorDstAddress: dstValAddr.String(),
Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))},
},
expErr: true,
expErrMsg: "invalid delegator address: empty address string is not allowed",
},
{
name: "invalid delegator",
input: &stakingtypes.MsgBeginRedelegate{
DelegatorAddress: "invalid",
ValidatorSrcAddress: srcValAddr.String(),
ValidatorDstAddress: dstValAddr.String(),
Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))},
},
expErr: true,
expErrMsg: "invalid delegator address: decoding bech32 failed: invalid bech32 string length 7",
},
{
name: "invalid destination validator",
input: &stakingtypes.MsgBeginRedelegate{
DelegatorAddress: Addr.String(),
ValidatorSrcAddress: srcValAddr.String(),
ValidatorDstAddress: sdk.AccAddress([]byte("invalid")).String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()),
},
expErr: true,
expErrMsg: "invalid destination validator address",
},
{
name: "validator does not exist",
input: &stakingtypes.MsgBeginRedelegate{
DelegatorAddress: Addr.String(),
ValidatorSrcAddress: sdk.ValAddress([]byte("invalid")).String(),
ValidatorDstAddress: dstValAddr.String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()),
},
expErr: true,
expErrMsg: "validator does not exist",
},
{
name: "self redelegation",
input: &stakingtypes.MsgBeginRedelegate{
DelegatorAddress: Addr.String(),
ValidatorSrcAddress: srcValAddr.String(),
ValidatorDstAddress: srcValAddr.String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()),
},
expErr: true,
expErrMsg: "cannot redelegate to the same validator",
},
{
name: "amount greater than delegated shares amount",
input: &stakingtypes.MsgBeginRedelegate{
DelegatorAddress: Addr.String(),
ValidatorSrcAddress: srcValAddr.String(),
ValidatorDstAddress: dstValAddr.String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(101)),
},
expErr: true,
expErrMsg: "invalid shares amount",
},
{
name: "zero amount",
input: &stakingtypes.MsgBeginRedelegate{
DelegatorAddress: Addr.String(),
ValidatorSrcAddress: srcValAddr.String(),
ValidatorDstAddress: dstValAddr.String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(0)),
},
expErr: true,
expErrMsg: "invalid shares amount",
},
{
name: "invalid coin denom",
input: &stakingtypes.MsgBeginRedelegate{
DelegatorAddress: Addr.String(),
ValidatorSrcAddress: srcValAddr.String(),
ValidatorDstAddress: dstValAddr.String(),
Amount: sdk.NewCoin("test", shares.RoundInt()),
},
expErr: true,
expErrMsg: "invalid coin denomination",
},
{
name: "valid msg",
input: &stakingtypes.MsgBeginRedelegate{
DelegatorAddress: Addr.String(),
ValidatorSrcAddress: srcValAddr.String(),
ValidatorDstAddress: dstValAddr.String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()),
},
expErr: false,
},
}
for _, tc := range testCases {
s.T().Run(tc.name, func(t *testing.T) {
_, err := msgServer.BeginRedelegate(ctx, tc.input)
if tc.expErr {
require.Error(err)
require.Contains(err.Error(), tc.expErrMsg)
} else {
require.NoError(err)
events := ctx.EventManager().Events()
delegator, found := events.GetAttributes("delegator")
s.Require().Equal(delegator[0].Value, tc.input.DelegatorAddress)
s.Require().Equal(true, found)
}
})
}
}
func (s *KeeperTestSuite) TestMsgUndelegate() {
ctx, keeper, msgServer := s.ctx, s.stakingKeeper, s.msgServer
require := s.Require()
s.execExpectCalls()
pk := ed25519.GenPrivKey().PubKey()
require.NotNil(pk)
comm := stakingtypes.NewCommissionRates(math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0))
amt := sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))}
msg, err := stakingtypes.NewMsgCreateValidator(ValAddr.String(), pk, amt, stakingtypes.Description{Moniker: "NewVal"}, comm, math.OneInt())
require.NoError(err)
res, err := msgServer.CreateValidator(ctx, msg)
require.NoError(err)
require.NotNil(res)
shares := math.LegacyNewDec(100)
del := stakingtypes.NewDelegation(Addr.String(), ValAddr.String(), shares)
require.NoError(keeper.SetDelegation(ctx, del))
_, err = keeper.GetDelegation(ctx, Addr, ValAddr)
require.NoError(err)
testCases := []struct {
name string
input *stakingtypes.MsgUndelegate
expErr bool
expErrMsg string
}{
{
name: "invalid validator",
input: &stakingtypes.MsgUndelegate{
DelegatorAddress: Addr.String(),
ValidatorAddress: sdk.AccAddress([]byte("invalid")).String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()),
},
expErr: true,
expErrMsg: "invalid validator address",
},
{
name: "empty delegator",
input: &stakingtypes.MsgUndelegate{
DelegatorAddress: "",
ValidatorAddress: ValAddr.String(),
Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: shares.RoundInt()},
},
expErr: true,
expErrMsg: "invalid delegator address: empty address string is not allowed",
},
{
name: "invalid delegator",
input: &stakingtypes.MsgUndelegate{
DelegatorAddress: "invalid",
ValidatorAddress: ValAddr.String(),
Amount: sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: shares.RoundInt()},
},
expErr: true,
expErrMsg: "invalid delegator address: decoding bech32 failed",
},
{
name: "validator does not exist",
input: &stakingtypes.MsgUndelegate{
DelegatorAddress: Addr.String(),
ValidatorAddress: sdk.ValAddress([]byte("invalid")).String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()),
},
expErr: true,
expErrMsg: "validator does not exist",
},
{
name: "amount greater than delegated shares amount",
input: &stakingtypes.MsgUndelegate{
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(101)),
},
expErr: true,
expErrMsg: "invalid shares amount",
},
{
name: "zero amount",
input: &stakingtypes.MsgUndelegate{
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(0)),
},
expErr: true,
expErrMsg: "invalid shares amount",
},
{
name: "invalid coin denom",
input: &stakingtypes.MsgUndelegate{
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Amount: sdk.NewCoin("test", shares.RoundInt()),
},
expErr: true,
expErrMsg: "invalid coin denomination",
},
{
name: "valid msg",
input: &stakingtypes.MsgUndelegate{
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()),
},
expErr: false,
},
}
for _, tc := range testCases {
s.T().Run(tc.name, func(t *testing.T) {
_, err := msgServer.Undelegate(ctx, tc.input)
if tc.expErr {
require.Error(err)
require.Contains(err.Error(), tc.expErrMsg)
} else {
require.NoError(err)
}
})
}
}
func (s *KeeperTestSuite) TestMsgCancelUnbondingDelegation() {
ctx, keeper, msgServer, ak := s.ctx, s.stakingKeeper, s.msgServer, s.accountKeeper
require := s.Require()
pk := ed25519.GenPrivKey().PubKey()
require.NotNil(pk)
comm := stakingtypes.NewCommissionRates(math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0))
amt := sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: keeper.TokensFromConsensusPower(s.ctx, int64(100))}
s.bankKeeper.EXPECT().DelegateCoinsFromAccountToModule(gomock.Any(), Addr, stakingtypes.NotBondedPoolName, gomock.Any()).AnyTimes()
msg, err := stakingtypes.NewMsgCreateValidator(ValAddr.String(), pk, amt, stakingtypes.Description{Moniker: "NewVal"}, comm, math.OneInt())
require.NoError(err)
res, err := msgServer.CreateValidator(ctx, msg)
require.NoError(err)
require.NotNil(res)
shares := math.LegacyNewDec(100)
del := stakingtypes.NewDelegation(Addr.String(), ValAddr.String(), shares)
require.NoError(keeper.SetDelegation(ctx, del))
resDel, err := keeper.GetDelegation(ctx, Addr, ValAddr)
require.NoError(err)
require.Equal(del, resDel)
ubd := stakingtypes.NewUnbondingDelegation(Addr, ValAddr, 10, ctx.BlockTime().Add(time.Minute*10), shares.RoundInt(), 0, keeper.ValidatorAddressCodec(), ak.AddressCodec())
require.NoError(keeper.SetUnbondingDelegation(ctx, ubd))
resUnbond, err := keeper.GetUnbondingDelegation(ctx, Addr, ValAddr)
require.NoError(err)
require.Equal(ubd, resUnbond)
testCases := []struct {
name string
input *stakingtypes.MsgCancelUnbondingDelegation
expErr bool
expErrMsg string
}{
{
name: "invalid validator",
input: &stakingtypes.MsgCancelUnbondingDelegation{
DelegatorAddress: Addr.String(),
ValidatorAddress: sdk.AccAddress([]byte("invalid")).String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()),
CreationHeight: 10,
},
expErr: true,
expErrMsg: "invalid validator address",
},
{
name: "empty delegator",
input: &stakingtypes.MsgCancelUnbondingDelegation{
DelegatorAddress: "",
ValidatorAddress: ValAddr.String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()),
CreationHeight: 10,
},
expErr: true,
expErrMsg: "invalid delegator address: empty address string is not allowed",
},
{
name: "invalid delegator",
input: &stakingtypes.MsgCancelUnbondingDelegation{
DelegatorAddress: "invalid",
ValidatorAddress: ValAddr.String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()),
CreationHeight: 10,
},
expErr: true,
expErrMsg: "invalid delegator address: decoding bech32 failed",
},
{
name: "entry not found at height",
input: &stakingtypes.MsgCancelUnbondingDelegation{
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()),
CreationHeight: 11,
},
expErr: true,
expErrMsg: "unbonding delegation entry is not found at block height",
},
{
name: "invalid height",
input: &stakingtypes.MsgCancelUnbondingDelegation{
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()),
CreationHeight: -1,
},
expErr: true,
expErrMsg: "invalid height",
},
{
name: "invalid coin",
input: &stakingtypes.MsgCancelUnbondingDelegation{
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Amount: sdk.NewCoin("test", shares.RoundInt()),
CreationHeight: 10,
},
expErr: true,
expErrMsg: "invalid coin denomination",
},
{
name: "validator does not exist",
input: &stakingtypes.MsgCancelUnbondingDelegation{
DelegatorAddress: Addr.String(),
ValidatorAddress: sdk.ValAddress([]byte("invalid")).String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()),
CreationHeight: 10,
},
expErr: true,
expErrMsg: "validator does not exist",
},
{
name: "amount is greater than balance",
input: &stakingtypes.MsgCancelUnbondingDelegation{
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(101)),
CreationHeight: 10,
},
expErr: true,
expErrMsg: "amount is greater than the unbonding delegation entry balance",
},
{
name: "zero amount",
input: &stakingtypes.MsgCancelUnbondingDelegation{
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(0)),
CreationHeight: 10,
},
expErr: true,
expErrMsg: "invalid amount",
},
{
name: "valid msg",
input: &stakingtypes.MsgCancelUnbondingDelegation{
DelegatorAddress: Addr.String(),
ValidatorAddress: ValAddr.String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, shares.RoundInt()),
CreationHeight: 10,
},
expErr: false,
},
}
for _, tc := range testCases {
s.T().Run(tc.name, func(t *testing.T) {
_, err := msgServer.CancelUnbondingDelegation(ctx, tc.input)
if tc.expErr {
require.Error(err)
require.Contains(err.Error(), tc.expErrMsg)
} else {
require.NoError(err)
}
})
}
}
func (s *KeeperTestSuite) TestMsgUpdateParams() {
ctx, keeper, msgServer := s.ctx, s.stakingKeeper, s.msgServer
require := s.Require()
testCases := []struct {
name string
input *stakingtypes.MsgUpdateParams
expErr bool
expErrMsg string
}{
{
name: "valid params",
input: &stakingtypes.MsgUpdateParams{
Authority: keeper.GetAuthority(),
Params: stakingtypes.DefaultParams(),
},
expErr: false,
},
{
name: "invalid authority",
input: &stakingtypes.MsgUpdateParams{
Authority: "invalid",
Params: stakingtypes.DefaultParams(),
},
expErr: true,
expErrMsg: "invalid authority",
},
{
name: "negative commission rate",
input: &stakingtypes.MsgUpdateParams{
Authority: keeper.GetAuthority(),
Params: stakingtypes.Params{
MinCommissionRate: math.LegacyNewDec(-10),
UnbondingTime: stakingtypes.DefaultUnbondingTime,
MaxValidators: stakingtypes.DefaultMaxValidators,
MaxEntries: stakingtypes.DefaultMaxEntries,
HistoricalEntries: stakingtypes.DefaultHistoricalEntries,
BondDenom: stakingtypes.BondStatusBonded,
},
},
expErr: true,
expErrMsg: "minimum commission rate cannot be negative",
},
{
name: "commission rate cannot be bigger than 100",
input: &stakingtypes.MsgUpdateParams{
Authority: keeper.GetAuthority(),
Params: stakingtypes.Params{
MinCommissionRate: math.LegacyNewDec(2),
UnbondingTime: stakingtypes.DefaultUnbondingTime,
MaxValidators: stakingtypes.DefaultMaxValidators,
MaxEntries: stakingtypes.DefaultMaxEntries,
HistoricalEntries: stakingtypes.DefaultHistoricalEntries,
BondDenom: stakingtypes.BondStatusBonded,
},
},
expErr: true,
expErrMsg: "minimum commission rate cannot be greater than 100%",
},
{
name: "invalid bond denom",
input: &stakingtypes.MsgUpdateParams{
Authority: keeper.GetAuthority(),
Params: stakingtypes.Params{
MinCommissionRate: stakingtypes.DefaultMinCommissionRate,
UnbondingTime: stakingtypes.DefaultUnbondingTime,
MaxValidators: stakingtypes.DefaultMaxValidators,
MaxEntries: stakingtypes.DefaultMaxEntries,
HistoricalEntries: stakingtypes.DefaultHistoricalEntries,
BondDenom: "",
},
},
expErr: true,
expErrMsg: "bond denom cannot be blank",
},
{
name: "max validators must be positive",
input: &stakingtypes.MsgUpdateParams{
Authority: keeper.GetAuthority(),
Params: stakingtypes.Params{
MinCommissionRate: stakingtypes.DefaultMinCommissionRate,
UnbondingTime: stakingtypes.DefaultUnbondingTime,
MaxValidators: 0,
MaxEntries: stakingtypes.DefaultMaxEntries,
HistoricalEntries: stakingtypes.DefaultHistoricalEntries,
BondDenom: stakingtypes.BondStatusBonded,
},
},
expErr: true,
expErrMsg: "max validators must be positive",
},
{
name: "max entries must be positive",
input: &stakingtypes.MsgUpdateParams{
Authority: keeper.GetAuthority(),
Params: stakingtypes.Params{
MinCommissionRate: stakingtypes.DefaultMinCommissionRate,
UnbondingTime: stakingtypes.DefaultUnbondingTime,
MaxValidators: stakingtypes.DefaultMaxValidators,
MaxEntries: 0,
HistoricalEntries: stakingtypes.DefaultHistoricalEntries,
BondDenom: stakingtypes.BondStatusBonded,
},
},
expErr: true,
expErrMsg: "max entries must be positive",
},
{
name: "negative unbounding time",
input: &stakingtypes.MsgUpdateParams{
Authority: keeper.GetAuthority(),
Params: stakingtypes.Params{
UnbondingTime: time.Hour * 24 * 7 * 3 * -1,
MaxEntries: stakingtypes.DefaultMaxEntries,
MaxValidators: stakingtypes.DefaultMaxValidators,
HistoricalEntries: stakingtypes.DefaultHistoricalEntries,
MinCommissionRate: stakingtypes.DefaultMinCommissionRate,
BondDenom: "denom",
},
},
expErr: true,
expErrMsg: "unbonding time must be positive",
},
}
for _, tc := range testCases {
s.T().Run(tc.name, func(t *testing.T) {
_, err := msgServer.UpdateParams(ctx, tc.input)
if tc.expErr {
require.Error(err)
require.Contains(err.Error(), tc.expErrMsg)
} else {
require.NoError(err)
}
})
}
}