diff --git a/simapp/app.go b/simapp/app.go index 69b4d1f94d8d..f8386e42e5e0 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -315,7 +315,7 @@ func NewSimApp( encodingConfig.TxConfig, ), auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts), - vesting.NewAppModule(app.AccountKeeper, app.BankKeeper, app.DistrKeeper), + vesting.NewAppModule(app.AccountKeeper, app.BankKeeper, app.DistrKeeper, app.StakingKeeper), bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper), capability.NewAppModule(appCodec, *app.CapabilityKeeper), crisis.NewAppModule(&app.CrisisKeeper, skipGenesisInvariants), diff --git a/x/auth/vesting/handler.go b/x/auth/vesting/handler.go index 53764e718d52..cd48e43c9d9d 100644 --- a/x/auth/vesting/handler.go +++ b/x/auth/vesting/handler.go @@ -8,8 +8,13 @@ import ( ) // NewHandler returns a handler for x/auth message types. -func NewHandler(ak keeper.AccountKeeper, bk types.BankKeeper, dk types.DistrKeeper) sdk.Handler { - msgServer := NewMsgServerImpl(ak, bk, dk) +func NewHandler( + ak keeper.AccountKeeper, + bk types.BankKeeper, + dk types.DistrKeeper, + sk types.StakingKeeper, +) sdk.Handler { + msgServer := NewMsgServerImpl(ak, bk, dk, sk) return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { ctx = ctx.WithEventManager(sdk.NewEventManager()) diff --git a/x/auth/vesting/handler_test.go b/x/auth/vesting/handler_test.go index 6f0db79276b5..f832fa372e84 100644 --- a/x/auth/vesting/handler_test.go +++ b/x/auth/vesting/handler_test.go @@ -9,6 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cosmos/cosmos-sdk/x/auth/vesting" "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" @@ -25,7 +26,12 @@ func (suite *HandlerTestSuite) SetupTest() { checkTx := false app := simapp.Setup(checkTx) - suite.handler = vesting.NewHandler(app.AccountKeeper, app.BankKeeper, app.DistrKeeper) + suite.handler = vesting.NewHandler( + app.AccountKeeper, + app.BankKeeper, + app.DistrKeeper, + app.StakingKeeper, + ) suite.app = app } @@ -101,6 +107,8 @@ func (suite *HandlerTestSuite) TestMsgDonateVestingToken() { addr2 := sdk.AccAddress([]byte("addr2_______________")) addr3 := sdk.AccAddress([]byte("addr3_______________")) + valAddr := sdk.ValAddress([]byte("validator___________")) + acc1 := suite.app.AccountKeeper.NewAccountWithAddress(ctx, addr1) suite.app.AccountKeeper.SetAccount(ctx, acc1) suite.Require().NoError(simapp.FundAccount(suite.app.BankKeeper, ctx, addr1, balances)) @@ -110,6 +118,11 @@ func (suite *HandlerTestSuite) TestMsgDonateVestingToken() { ) acc2.DelegatedVesting = balances suite.app.AccountKeeper.SetAccount(ctx, acc2) + suite.app.StakingKeeper.SetDelegation(ctx, stakingtypes.Delegation{ + DelegatorAddress: addr2.String(), + ValidatorAddress: valAddr.String(), + Shares: sdk.OneDec(), + }) suite.Require().NoError(simapp.FundAccount(suite.app.BankKeeper, ctx, addr2, balances)) acc3 := types.NewPermanentLockedAccount( diff --git a/x/auth/vesting/module.go b/x/auth/vesting/module.go index b735d0198a06..51a96d71d3e7 100644 --- a/x/auth/vesting/module.go +++ b/x/auth/vesting/module.go @@ -80,14 +80,21 @@ type AppModule struct { accountKeeper keeper.AccountKeeper bankKeeper types.BankKeeper distrKeeper types.DistrKeeper + stakingKeeper types.StakingKeeper } -func NewAppModule(ak keeper.AccountKeeper, bk types.BankKeeper, dk types.DistrKeeper) AppModule { +func NewAppModule( + ak keeper.AccountKeeper, + bk types.BankKeeper, + dk types.DistrKeeper, + sk types.StakingKeeper, +) AppModule { return AppModule{ AppModuleBasic: AppModuleBasic{}, accountKeeper: ak, bankKeeper: bk, distrKeeper: dk, + stakingKeeper: sk, } } @@ -96,7 +103,7 @@ func (AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} // Route returns the module's message router and handler. func (am AppModule) Route() sdk.Route { - return sdk.NewRoute(types.RouterKey, NewHandler(am.accountKeeper, am.bankKeeper, am.distrKeeper)) + return sdk.NewRoute(types.RouterKey, NewHandler(am.accountKeeper, am.bankKeeper, am.distrKeeper, am.stakingKeeper)) } // QuerierRoute returns an empty string as the module contains no query @@ -105,7 +112,15 @@ func (AppModule) QuerierRoute() string { return "" } // RegisterServices registers module services. func (am AppModule) RegisterServices(cfg module.Configurator) { - types.RegisterMsgServer(cfg.MsgServer(), NewMsgServerImpl(am.accountKeeper, am.bankKeeper, am.distrKeeper)) + types.RegisterMsgServer( + cfg.MsgServer(), + NewMsgServerImpl( + am.accountKeeper, + am.bankKeeper, + am.distrKeeper, + am.stakingKeeper, + ), + ) } // LegacyQuerierHandler performs a no-op. diff --git a/x/auth/vesting/msg_server.go b/x/auth/vesting/msg_server.go index 90a38b5c5746..98794a5cdb44 100644 --- a/x/auth/vesting/msg_server.go +++ b/x/auth/vesting/msg_server.go @@ -19,12 +19,18 @@ type msgServer struct { keeper.AccountKeeper types.BankKeeper types.DistrKeeper + types.StakingKeeper } // NewMsgServerImpl returns an implementation of the vesting MsgServer interface, // wrapping the corresponding AccountKeeper and BankKeeper. -func NewMsgServerImpl(k keeper.AccountKeeper, bk types.BankKeeper, dk types.DistrKeeper) types.MsgServer { - return &msgServer{AccountKeeper: k, BankKeeper: bk, DistrKeeper: dk} +func NewMsgServerImpl( + k keeper.AccountKeeper, + bk types.BankKeeper, + dk types.DistrKeeper, + sk types.StakingKeeper, +) types.MsgServer { + return &msgServer{AccountKeeper: k, BankKeeper: bk, DistrKeeper: dk, StakingKeeper: sk} } var _ types.MsgServer = msgServer{} @@ -166,6 +172,7 @@ func (s msgServer) DonateAllVestingTokens(goCtx context.Context, msg *types.MsgD ak := s.AccountKeeper dk := s.DistrKeeper + sk := s.StakingKeeper from, err := sdk.AccAddressFromBech32(msg.FromAddress) if err != nil { @@ -177,15 +184,18 @@ func (s msgServer) DonateAllVestingTokens(goCtx context.Context, msg *types.MsgD return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "account %s not exists", msg.FromAddress) } + // check whether an account has any type of staking entry + if len(sk.GetDelegatorDelegations(ctx, acc.GetAddress(), 1)) != 0 || + len(sk.GetUnbondingDelegations(ctx, acc.GetAddress(), 1)) != 0 || + len(sk.GetRedelegations(ctx, acc.GetAddress(), 1)) != 0 { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "account %s has staking entry", msg.FromAddress) + } + vestingAcc, ok := acc.(exported.VestingAccount) if !ok { return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "account %s is not vesting account", msg.FromAddress) } - if !vestingAcc.GetDelegatedVesting().IsZero() { - return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "account %s has delegated vesting tokens", msg.FromAddress) - } - vestingCoins := vestingAcc.GetVestingCoins(ctx.BlockTime()) if vestingCoins.IsZero() { return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "account %s has no vesting tokens", msg.FromAddress) diff --git a/x/auth/vesting/types/expected_keepers.go b/x/auth/vesting/types/expected_keepers.go index 2b179712eef4..a799cd5e7387 100644 --- a/x/auth/vesting/types/expected_keepers.go +++ b/x/auth/vesting/types/expected_keepers.go @@ -2,6 +2,7 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) // BankKeeper defines the expected interface contract the vesting module requires @@ -16,3 +17,10 @@ type BankKeeper interface { type DistrKeeper interface { FundCommunityPool(ctx sdk.Context, amount sdk.Coins, sender sdk.AccAddress) error } + +// StakingKeeper defines the exported interface for staking keeper +type StakingKeeper interface { + GetDelegatorDelegations(ctx sdk.Context, delegator sdk.AccAddress, maxRetrieve uint16) (delegations []stakingtypes.Delegation) + GetUnbondingDelegations(ctx sdk.Context, delegator sdk.AccAddress, maxRetrieve uint16) (unbondingDelegations []stakingtypes.UnbondingDelegation) + GetRedelegations(ctx sdk.Context, delegator sdk.AccAddress, maxRetrieve uint16) (redelegations []stakingtypes.Redelegation) +}