diff --git a/x/auth/testutil/app_config.go b/x/auth/testutil/app_config.go index 26409bfb1a39..0af9d7764ff2 100644 --- a/x/auth/testutil/app_config.go +++ b/x/auth/testutil/app_config.go @@ -6,6 +6,7 @@ import ( _ "github.com/cosmos/cosmos-sdk/x/auth/vesting" _ "github.com/cosmos/cosmos-sdk/x/bank" _ "github.com/cosmos/cosmos-sdk/x/consensus" + _ "github.com/cosmos/cosmos-sdk/x/distribution" _ "github.com/cosmos/cosmos-sdk/x/feegrant/module" _ "github.com/cosmos/cosmos-sdk/x/genutil" _ "github.com/cosmos/cosmos-sdk/x/params" @@ -16,6 +17,7 @@ import ( vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" consensustypes "github.com/cosmos/cosmos-sdk/x/consensus/types" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/cosmos/cosmos-sdk/x/feegrant" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" @@ -27,6 +29,7 @@ import ( authmodulev1 "cosmossdk.io/api/cosmos/auth/module/v1" bankmodulev1 "cosmossdk.io/api/cosmos/bank/module/v1" consensusmodulev1 "cosmossdk.io/api/cosmos/consensus/module/v1" + distributionv1 "cosmossdk.io/api/cosmos/distribution/module/v1" feegrantmodulev1 "cosmossdk.io/api/cosmos/feegrant/module/v1" genutilmodulev1 "cosmossdk.io/api/cosmos/genutil/module/v1" paramsmodulev1 "cosmossdk.io/api/cosmos/params/module/v1" @@ -45,6 +48,7 @@ var AppConfig = appconfig.Compose(&appv1alpha1.Config{ stakingtypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName, + distributiontypes.ModuleName, genutiltypes.ModuleName, feegrant.ModuleName, paramstypes.ModuleName, @@ -55,6 +59,7 @@ var AppConfig = appconfig.Compose(&appv1alpha1.Config{ stakingtypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName, + distributiontypes.ModuleName, genutiltypes.ModuleName, feegrant.ModuleName, paramstypes.ModuleName, @@ -64,6 +69,7 @@ var AppConfig = appconfig.Compose(&appv1alpha1.Config{ InitGenesis: []string{ authtypes.ModuleName, banktypes.ModuleName, + distributiontypes.ModuleName, stakingtypes.ModuleName, genutiltypes.ModuleName, feegrant.ModuleName, @@ -84,6 +90,7 @@ var AppConfig = appconfig.Compose(&appv1alpha1.Config{ {Account: stakingtypes.NotBondedPoolName, Permissions: []string{authtypes.Burner, stakingtypes.ModuleName}}, {Account: "multiple permissions account", Permissions: []string{authtypes.Minter, authtypes.Burner, stakingtypes.ModuleName}}, // dummy permissions {Account: "random permission", Permissions: []string{"random"}}, + {Account: distributiontypes.ModuleName, Permissions: []string{authtypes.Minter, authtypes.Burner}}, }, }), }, @@ -115,10 +122,13 @@ var AppConfig = appconfig.Compose(&appv1alpha1.Config{ Name: genutiltypes.ModuleName, Config: appconfig.WrapAny(&genutilmodulev1.Module{}), }, - { Name: feegrant.ModuleName, Config: appconfig.WrapAny(&feegrantmodulev1.Module{}), }, + { + Name: distributiontypes.ModuleName, + Config: appconfig.WrapAny(&distributionv1.Module{}), + }, }, }) diff --git a/x/auth/vesting/handler_test.go b/x/auth/vesting/handler_test.go new file mode 100644 index 000000000000..e3b8e5dfa1e0 --- /dev/null +++ b/x/auth/vesting/handler_test.go @@ -0,0 +1,237 @@ +package vesting_test + +import ( + "github.com/cosmos/cosmos-sdk/runtime" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + "github.com/cosmos/cosmos-sdk/x/auth/keeper" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + "github.com/cosmos/cosmos-sdk/x/bank/testutil" + distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + "testing" + "time" + + cometproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/stretchr/testify/suite" + + 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" + + authtestutil "github.com/cosmos/cosmos-sdk/x/auth/testutil" + "github.com/cosmos/cosmos-sdk/x/auth/vesting" + "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" +) + +type HandlerTestSuite struct { + suite.Suite + + handler sdk.Handler + app *runtime.App + accountKeeper keeper.AccountKeeper + bankKeeper bankkeeper.Keeper + distrKeeper distrkeeper.Keeper + stakingKeeper *stakingkeeper.Keeper +} + +func (suite *HandlerTestSuite) SetupTest() { + app, err := simtestutil.SetupAtGenesis(authtestutil.AppConfig, &suite.accountKeeper, &suite.bankKeeper, &suite.distrKeeper, &suite.stakingKeeper) + suite.Require().NoError(err) + + suite.handler = vesting.NewHandler( + suite.accountKeeper, + suite.bankKeeper, + suite.distrKeeper, + suite.stakingKeeper, + ) + suite.app = app + +} + +func (suite *HandlerTestSuite) TestMsgCreateVestingAccount() { + ctx := suite.app.BaseApp.NewContext(false, cometproto.Header{Height: suite.app.LastBlockHeight() + 1}) + + balances := sdk.NewCoins(sdk.NewInt64Coin("test", 1000)) + addr1 := sdk.AccAddress([]byte("addr1_______________")) + addr2 := sdk.AccAddress([]byte("addr2_______________")) + addr3 := sdk.AccAddress([]byte("addr3_______________")) + + acc1 := suite.accountKeeper.NewAccountWithAddress(ctx, addr1) + suite.accountKeeper.SetAccount(ctx, acc1) + suite.Require().NoError(testutil.FundAccount(suite.bankKeeper, ctx, addr1, balances)) + + testCases := []struct { + name string + msg *types.MsgCreateVestingAccount + expectErr bool + }{ + { + name: "create delayed vesting account", + msg: types.NewMsgCreateVestingAccount(addr1, addr2, sdk.NewCoins(sdk.NewInt64Coin("test", 100)), ctx.BlockTime().Unix()+10000, true), + expectErr: false, + }, + { + name: "create continuous vesting account", + msg: types.NewMsgCreateVestingAccount(addr1, addr3, sdk.NewCoins(sdk.NewInt64Coin("test", 100)), ctx.BlockTime().Unix()+10000, false), + expectErr: false, + }, + { + name: "continuous vesting account already exists", + msg: types.NewMsgCreateVestingAccount(addr1, addr3, sdk.NewCoins(sdk.NewInt64Coin("test", 100)), ctx.BlockTime().Unix()+10000, false), + expectErr: true, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + res, err := suite.handler(ctx, tc.msg) + if tc.expectErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + suite.Require().NotNil(res) + + toAddr, err := sdk.AccAddressFromBech32(tc.msg.ToAddress) + suite.Require().NoError(err) + accI := suite.accountKeeper.GetAccount(ctx, toAddr) + suite.Require().NotNil(accI) + + if tc.msg.Delayed { + acc, ok := accI.(*types.DelayedVestingAccount) + suite.Require().True(ok) + suite.Require().Equal(tc.msg.Amount, acc.GetVestingCoins(ctx.BlockTime())) + } else { + acc, ok := accI.(*types.ContinuousVestingAccount) + suite.Require().True(ok) + suite.Require().Equal(tc.msg.Amount, acc.GetVestingCoins(ctx.BlockTime())) + } + } + }) + } +} + +func (suite *HandlerTestSuite) TestMsgDonateVestingToken() { + ctx := suite.app.BaseApp.NewContext(false, cometproto.Header{Height: suite.app.LastBlockHeight() + 1}) + + prevCommunityFund := suite.distrKeeper.GetFeePool(ctx).CommunityPool + + balances := sdk.NewCoins(sdk.NewInt64Coin("test", 1000)) + addr1 := sdk.AccAddress([]byte("addr1_______________")) + addr2 := sdk.AccAddress([]byte("addr2_______________")) + addr3 := sdk.AccAddress([]byte("addr3_______________")) + addr4 := sdk.AccAddress([]byte("addr4_______________")) + + valAddr := sdk.ValAddress([]byte("validator___________")) + suite.stakingKeeper.SetValidator(ctx, stakingtypes.Validator{ + OperatorAddress: valAddr.String(), + ConsensusPubkey: nil, + Jailed: false, + Status: 0, + Tokens: sdk.NewInt(2), + DelegatorShares: sdk.MustNewDecFromStr("1.1"), + Description: stakingtypes.Description{}, + UnbondingHeight: 0, + UnbondingTime: time.Time{}, + Commission: stakingtypes.Commission{}, + MinSelfDelegation: sdk.NewInt(1), + }) + + acc1 := suite.accountKeeper.NewAccountWithAddress(ctx, addr1) + suite.accountKeeper.SetAccount(ctx, acc1) + suite.Require().NoError(testutil.FundAccount(suite.bankKeeper, ctx, addr1, balances)) + + acc2 := types.NewPermanentLockedAccount( + suite.accountKeeper.NewAccountWithAddress(ctx, addr2).(*authtypes.BaseAccount), balances, + ) + acc2.DelegatedVesting = balances + suite.accountKeeper.SetAccount(ctx, acc2) + suite.stakingKeeper.SetDelegation(ctx, stakingtypes.Delegation{ + DelegatorAddress: addr2.String(), + ValidatorAddress: valAddr.String(), + Shares: sdk.OneDec(), + }) + suite.Require().NoError(testutil.FundAccount(suite.bankKeeper, ctx, addr2, balances)) + + acc3 := types.NewPermanentLockedAccount( + suite.accountKeeper.NewAccountWithAddress(ctx, addr3).(*authtypes.BaseAccount), balances, + ) + suite.accountKeeper.SetAccount(ctx, acc3) + suite.Require().NoError(testutil.FundAccount(suite.bankKeeper, ctx, addr3, balances)) + + acc4 := types.NewPermanentLockedAccount( + suite.accountKeeper.NewAccountWithAddress(ctx, addr4).(*authtypes.BaseAccount), balances, + ) + acc4.DelegatedVesting = balances + suite.accountKeeper.SetAccount(ctx, acc4) + suite.stakingKeeper.SetDelegation(ctx, stakingtypes.Delegation{ + DelegatorAddress: addr4.String(), + ValidatorAddress: valAddr.String(), + Shares: sdk.MustNewDecFromStr("0.1"), + }) + suite.Require().NoError(testutil.FundAccount(suite.bankKeeper, ctx, addr4, balances)) + + testCases := []struct { + name string + msg *types.MsgDonateAllVestingTokens + expectErr bool + }{ + { + name: "donate from normal account", + msg: types.NewMsgDonateAllVestingTokens(addr1), + expectErr: true, + }, + { + name: "donate from vesting account with delegated vesting", + msg: types.NewMsgDonateAllVestingTokens(addr2), + expectErr: true, + }, + { + name: "donate from vesting account", + msg: types.NewMsgDonateAllVestingTokens(addr3), + expectErr: false, + }, + { + name: "donate from vesting account with dust delegation", + msg: types.NewMsgDonateAllVestingTokens(addr4), + expectErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + // Rollback context after every test case + ctx, _ := ctx.CacheContext() + res, err := suite.handler(ctx, tc.msg) + if tc.expectErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + suite.Require().NotNil(res) + + feePool := suite.distrKeeper.GetFeePool(ctx).CommunityPool.Sub(prevCommunityFund) + communityFund, _ := feePool.TruncateDecimal() + suite.Require().Equal(balances, communityFund) + + fromAddr, err := sdk.AccAddressFromBech32(tc.msg.FromAddress) + suite.Require().NoError(err) + accI := suite.accountKeeper.GetAccount(ctx, fromAddr) + suite.Require().NotNil(accI) + _, ok := accI.(*authtypes.BaseAccount) + suite.Require().True(ok) + balance := suite.bankKeeper.GetAllBalances(ctx, fromAddr) + suite.Require().Empty(balance) + + _, broken := stakingkeeper.DelegatorSharesInvariant(suite.stakingKeeper)(ctx) + suite.Require().False(broken) + } + }) + } +} + +func TestHandlerTestSuite(t *testing.T) { + suite.Run(t, new(HandlerTestSuite)) +}