package keeper_test import ( "context" "fmt" "testing" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" "gotest.tools/v3/assert" "cosmossdk.io/collections" "cosmossdk.io/core/appmodule" "cosmossdk.io/core/comet" "cosmossdk.io/log" "cosmossdk.io/math" storetypes "cosmossdk.io/store/types" "cosmossdk.io/x/auth" authkeeper "cosmossdk.io/x/auth/keeper" authsims "cosmossdk.io/x/auth/simulation" authtestutil "cosmossdk.io/x/auth/testutil" authtypes "cosmossdk.io/x/auth/types" "cosmossdk.io/x/bank" bankkeeper "cosmossdk.io/x/bank/keeper" banktypes "cosmossdk.io/x/bank/types" consensustypes "cosmossdk.io/x/consensus/types" "cosmossdk.io/x/distribution" distrkeeper "cosmossdk.io/x/distribution/keeper" distrtypes "cosmossdk.io/x/distribution/types" "cosmossdk.io/x/protocolpool" poolkeeper "cosmossdk.io/x/protocolpool/keeper" pooltypes "cosmossdk.io/x/protocolpool/types" "cosmossdk.io/x/staking" stakingkeeper "cosmossdk.io/x/staking/keeper" stakingtestutil "cosmossdk.io/x/staking/testutil" stakingtypes "cosmossdk.io/x/staking/types" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" addresscodec "github.com/cosmos/cosmos-sdk/codec/address" codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" "github.com/cosmos/cosmos-sdk/runtime" "github.com/cosmos/cosmos-sdk/testutil/integration" sdk "github.com/cosmos/cosmos-sdk/types" moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" ) var ( emptyDelAddr sdk.AccAddress emptyValAddr sdk.ValAddress ) type fixture struct { app *integration.App sdkCtx sdk.Context cdc codec.Codec keys map[string]*storetypes.KVStoreKey queryClient distrtypes.QueryClient accountKeeper authkeeper.AccountKeeper bankKeeper bankkeeper.Keeper distrKeeper distrkeeper.Keeper stakingKeeper *stakingkeeper.Keeper poolKeeper poolkeeper.Keeper addr sdk.AccAddress valAddr sdk.ValAddress } func initFixture(t *testing.T) *fixture { t.Helper() keys := storetypes.NewKVStoreKeys( authtypes.StoreKey, banktypes.StoreKey, distrtypes.StoreKey, pooltypes.StoreKey, stakingtypes.StoreKey, consensustypes.StoreKey, ) encodingCfg := moduletestutil.MakeTestEncodingConfig(codectestutil.CodecOptions{}, auth.AppModule{}, bank.AppModule{}) cdc := encodingCfg.Codec logger := log.NewTestLogger(t) cms := integration.CreateMultiStore(keys, logger) newCtx := sdk.NewContext(cms, true, logger) authority := authtypes.NewModuleAddress("gov") maccPerms := map[string][]string{ pooltypes.ModuleName: {}, pooltypes.StreamAccount: {}, pooltypes.ProtocolPoolDistrAccount: {}, distrtypes.ModuleName: {authtypes.Minter}, stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, } // gomock initializations ctrl := gomock.NewController(t) acctsModKeeper := authtestutil.NewMockAccountsModKeeper(ctrl) accNum := uint64(0) acctsModKeeper.EXPECT().NextAccountNumber(gomock.Any()).AnyTimes().DoAndReturn(func(ctx context.Context) (uint64, error) { currentNum := accNum accNum++ return currentNum, nil }) accountKeeper := authkeeper.NewAccountKeeper( runtime.NewEnvironment(runtime.NewKVStoreService(keys[authtypes.StoreKey]), log.NewNopLogger()), cdc, authtypes.ProtoBaseAccount, acctsModKeeper, maccPerms, addresscodec.NewBech32Codec(sdk.Bech32MainPrefix), sdk.Bech32MainPrefix, authority.String(), ) blockedAddresses := map[string]bool{ accountKeeper.GetAuthority(): false, } bankKeeper := bankkeeper.NewBaseKeeper( runtime.NewEnvironment(runtime.NewKVStoreService(keys[banktypes.StoreKey]), log.NewNopLogger()), cdc, accountKeeper, blockedAddresses, authority.String(), ) assert.NilError(t, bankKeeper.SetParams(newCtx, banktypes.DefaultParams())) msgRouter := baseapp.NewMsgServiceRouter() grpcRouter := baseapp.NewGRPCQueryRouter() cometService := runtime.NewContextAwareCometInfoService() stakingKeeper := stakingkeeper.NewKeeper(cdc, runtime.NewEnvironment(runtime.NewKVStoreService(keys[stakingtypes.StoreKey]), log.NewNopLogger(), runtime.EnvWithQueryRouterService(grpcRouter), runtime.EnvWithMsgRouterService(msgRouter)), accountKeeper, bankKeeper, authority.String(), addresscodec.NewBech32Codec(sdk.Bech32PrefixValAddr), addresscodec.NewBech32Codec(sdk.Bech32PrefixConsAddr), cometService) require.NoError(t, stakingKeeper.Params.Set(newCtx, stakingtypes.DefaultParams())) poolKeeper := poolkeeper.NewKeeper(cdc, runtime.NewEnvironment(runtime.NewKVStoreService(keys[pooltypes.StoreKey]), log.NewNopLogger()), accountKeeper, bankKeeper, stakingKeeper, authority.String()) distrKeeper := distrkeeper.NewKeeper( cdc, runtime.NewEnvironment(runtime.NewKVStoreService(keys[distrtypes.StoreKey]), logger), accountKeeper, bankKeeper, stakingKeeper, cometService, distrtypes.ModuleName, authority.String(), ) authModule := auth.NewAppModule(cdc, accountKeeper, acctsModKeeper, authsims.RandomGenesisAccounts) bankModule := bank.NewAppModule(cdc, bankKeeper, accountKeeper) stakingModule := staking.NewAppModule(cdc, stakingKeeper, accountKeeper, bankKeeper) distrModule := distribution.NewAppModule(cdc, distrKeeper, accountKeeper, bankKeeper, stakingKeeper) poolModule := protocolpool.NewAppModule(cdc, poolKeeper, accountKeeper, bankKeeper) addr := sdk.AccAddress(PKS[0].Address()) valAddr := sdk.ValAddress(addr) valConsAddr := sdk.ConsAddress(valConsPk0.Address()) // set proposer and vote infos ctx := newCtx.WithProposer(valConsAddr).WithCometInfo(comet.Info{ LastCommit: comet.CommitInfo{ Votes: []comet.VoteInfo{ { Validator: comet.Validator{ Address: valAddr, Power: 100, }, BlockIDFlag: comet.BlockIDFlagCommit, }, }, }, ProposerAddress: valConsAddr, }) integrationApp := integration.NewIntegrationApp(ctx, logger, keys, cdc, encodingCfg.InterfaceRegistry.SigningContext().AddressCodec(), encodingCfg.InterfaceRegistry.SigningContext().ValidatorAddressCodec(), map[string]appmodule.AppModule{ authtypes.ModuleName: authModule, banktypes.ModuleName: bankModule, stakingtypes.ModuleName: stakingModule, distrtypes.ModuleName: distrModule, pooltypes.ModuleName: poolModule, }, msgRouter, grpcRouter, ) sdkCtx := sdk.UnwrapSDKContext(integrationApp.Context()) // Register MsgServer and QueryServer distrtypes.RegisterMsgServer(integrationApp.MsgServiceRouter(), distrkeeper.NewMsgServerImpl(distrKeeper)) distrtypes.RegisterQueryServer(integrationApp.QueryHelper(), distrkeeper.NewQuerier(distrKeeper)) qr := integrationApp.QueryHelper() distrQueryClient := distrtypes.NewQueryClient(qr) return &fixture{ app: integrationApp, sdkCtx: sdkCtx, cdc: cdc, keys: keys, accountKeeper: accountKeeper, bankKeeper: bankKeeper, distrKeeper: distrKeeper, stakingKeeper: stakingKeeper, poolKeeper: poolKeeper, addr: addr, valAddr: valAddr, queryClient: distrQueryClient, } } func TestMsgWithdrawDelegatorReward(t *testing.T) { t.Parallel() f := initFixture(t) err := f.distrKeeper.FeePool.Set(f.sdkCtx, distrtypes.FeePool{ CommunityPool: sdk.NewDecCoins(sdk.DecCoin{Denom: "stake", Amount: math.LegacyNewDec(10000)}), }) require.NoError(t, err) require.NoError(t, f.distrKeeper.Params.Set(f.sdkCtx, distrtypes.DefaultParams())) delAddr := sdk.AccAddress(PKS[1].Address()) valCommission := sdk.DecCoins{ sdk.NewDecCoinFromDec("mytoken", math.LegacyNewDec(5).Quo(math.LegacyNewDec(4))), sdk.NewDecCoinFromDec("stake", math.LegacyNewDec(3).Quo(math.LegacyNewDec(2))), } // setup staking validator validator, err := stakingtypes.NewValidator(f.valAddr.String(), PKS[0], stakingtypes.Description{}) assert.NilError(t, err) commission := stakingtypes.NewCommission(math.LegacyZeroDec(), math.LegacyOneDec(), math.LegacyOneDec()) validator, err = validator.SetInitialCommission(commission) assert.NilError(t, err) validator.DelegatorShares = math.LegacyNewDec(100) validator.Tokens = math.NewInt(1000000) assert.NilError(t, f.stakingKeeper.SetValidator(f.sdkCtx, validator)) // set module account coins initTokens := f.stakingKeeper.TokensFromConsensusPower(f.sdkCtx, int64(1000)) err = f.bankKeeper.MintCoins(f.sdkCtx, distrtypes.ModuleName, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initTokens))) require.NoError(t, err) // send funds to val addr err = f.bankKeeper.SendCoinsFromModuleToAccount(f.sdkCtx, distrtypes.ModuleName, sdk.AccAddress(f.valAddr), sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initTokens))) require.NoError(t, err) initBalance := f.bankKeeper.GetAllBalances(f.sdkCtx, delAddr) // setup delegation delTokens := sdk.TokensFromConsensusPower(2, sdk.DefaultPowerReduction) validator, issuedShares := validator.AddTokensFromDel(delTokens) valBz, err := f.stakingKeeper.ValidatorAddressCodec().StringToBytes(validator.GetOperator()) require.NoError(t, err) delegation := stakingtypes.NewDelegation(delAddr.String(), validator.GetOperator(), issuedShares) require.NoError(t, f.stakingKeeper.SetDelegation(f.sdkCtx, delegation)) require.NoError(t, f.distrKeeper.DelegatorStartingInfo.Set(f.sdkCtx, collections.Join(sdk.ValAddress(valBz), delAddr), distrtypes.NewDelegatorStartingInfo(2, math.LegacyOneDec(), 20))) // setup validator rewards decCoins := sdk.DecCoins{sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, math.LegacyOneDec())} historicalRewards := distrtypes.NewValidatorHistoricalRewards(decCoins, 2) err = f.distrKeeper.ValidatorHistoricalRewards.Set(f.sdkCtx, collections.Join(sdk.ValAddress(valBz), uint64(2)), historicalRewards) require.NoError(t, err) // setup current rewards and outstanding rewards currentRewards := distrtypes.NewValidatorCurrentRewards(decCoins, 3) err = f.distrKeeper.ValidatorCurrentRewards.Set(f.sdkCtx, f.valAddr, currentRewards) require.NoError(t, err) err = f.distrKeeper.ValidatorOutstandingRewards.Set(f.sdkCtx, f.valAddr, distrtypes.ValidatorOutstandingRewards{Rewards: valCommission}) require.NoError(t, err) testCases := []struct { name string msg *distrtypes.MsgWithdrawDelegatorReward expErr bool expErrMsg string }{ { name: "empty delegator address", msg: &distrtypes.MsgWithdrawDelegatorReward{ DelegatorAddress: emptyDelAddr.String(), ValidatorAddress: f.valAddr.String(), }, expErr: true, expErrMsg: "invalid delegator address", }, { name: "empty validator address", msg: &distrtypes.MsgWithdrawDelegatorReward{ DelegatorAddress: delAddr.String(), ValidatorAddress: emptyValAddr.String(), }, expErr: true, expErrMsg: "invalid validator address", }, { name: "both empty addresses", msg: &distrtypes.MsgWithdrawDelegatorReward{ DelegatorAddress: emptyDelAddr.String(), ValidatorAddress: emptyValAddr.String(), }, expErr: true, expErrMsg: "invalid validator address", }, { name: "delegator with no delegations", msg: &distrtypes.MsgWithdrawDelegatorReward{ DelegatorAddress: sdk.AccAddress([]byte("invalid")).String(), ValidatorAddress: f.valAddr.String(), }, expErr: true, expErrMsg: "not found", }, { name: "validator with no delegations", msg: &distrtypes.MsgWithdrawDelegatorReward{ DelegatorAddress: delAddr.String(), ValidatorAddress: sdk.ValAddress(sdk.AccAddress(PKS[2].Address())).String(), }, expErr: true, expErrMsg: "validator does not exist", }, { name: "valid msg", msg: &distrtypes.MsgWithdrawDelegatorReward{ DelegatorAddress: delAddr.String(), ValidatorAddress: f.valAddr.String(), }, expErr: false, }, } height := f.app.LastBlockHeight() for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { res, err := f.app.RunMsg( tc.msg, integration.WithAutomaticFinalizeBlock(), integration.WithAutomaticCommit(), ) height++ if f.app.LastBlockHeight() != height { panic(fmt.Errorf("expected block height to be %d, got %d", height, f.app.LastBlockHeight())) } if tc.expErr { assert.ErrorContains(t, err, tc.expErrMsg) } else { assert.NilError(t, err) assert.Assert(t, res != nil) // check the result result := distrtypes.MsgWithdrawDelegatorRewardResponse{} err := f.cdc.Unmarshal(res.Value, &result) assert.NilError(t, err) // check current balance is greater than initial balance curBalance := f.bankKeeper.GetAllBalances(f.sdkCtx, sdk.AccAddress(f.valAddr)) assert.Assert(t, initBalance.IsAllLTE(curBalance)) } var previousTotalPower int64 for _, vote := range f.sdkCtx.CometInfo().LastCommit.Votes { previousTotalPower += vote.Validator.Power } assert.Equal(t, previousTotalPower, int64(100)) }) } } func TestMsgSetWithdrawAddress(t *testing.T) { t.Parallel() f := initFixture(t) require.NoError(t, f.distrKeeper.Params.Set(f.sdkCtx, distrtypes.DefaultParams())) delAddr := sdk.AccAddress(PKS[0].Address()) withdrawAddr := sdk.AccAddress(PKS[1].Address()) testCases := []struct { name string preRun func() msg *distrtypes.MsgSetWithdrawAddress expErr bool expErrMsg string }{ { name: "empty delegator address", preRun: func() { params, _ := f.distrKeeper.Params.Get(f.sdkCtx) params.WithdrawAddrEnabled = true assert.NilError(t, f.distrKeeper.Params.Set(f.sdkCtx, params)) }, msg: &distrtypes.MsgSetWithdrawAddress{ DelegatorAddress: emptyDelAddr.String(), WithdrawAddress: withdrawAddr.String(), }, expErr: true, expErrMsg: "invalid delegator address", }, { name: "empty withdraw address", preRun: func() { params, _ := f.distrKeeper.Params.Get(f.sdkCtx) params.WithdrawAddrEnabled = true assert.NilError(t, f.distrKeeper.Params.Set(f.sdkCtx, params)) }, msg: &distrtypes.MsgSetWithdrawAddress{ DelegatorAddress: delAddr.String(), WithdrawAddress: emptyDelAddr.String(), }, expErr: true, expErrMsg: "invalid withdraw address", }, { name: "both empty addresses", preRun: func() { params, _ := f.distrKeeper.Params.Get(f.sdkCtx) params.WithdrawAddrEnabled = true assert.NilError(t, f.distrKeeper.Params.Set(f.sdkCtx, params)) }, msg: &distrtypes.MsgSetWithdrawAddress{ DelegatorAddress: emptyDelAddr.String(), WithdrawAddress: emptyDelAddr.String(), }, expErr: true, expErrMsg: "invalid delegator address", }, { name: "withdraw address disabled", preRun: func() { params, _ := f.distrKeeper.Params.Get(f.sdkCtx) params.WithdrawAddrEnabled = false assert.NilError(t, f.distrKeeper.Params.Set(f.sdkCtx, params)) }, msg: &distrtypes.MsgSetWithdrawAddress{ DelegatorAddress: delAddr.String(), WithdrawAddress: withdrawAddr.String(), }, expErr: true, expErrMsg: "set withdraw address disabled", }, { name: "valid msg with same delegator and withdraw address", preRun: func() { params, _ := f.distrKeeper.Params.Get(f.sdkCtx) params.WithdrawAddrEnabled = true assert.NilError(t, f.distrKeeper.Params.Set(f.sdkCtx, params)) }, msg: &distrtypes.MsgSetWithdrawAddress{ DelegatorAddress: delAddr.String(), WithdrawAddress: delAddr.String(), }, expErr: false, }, { name: "valid msg", preRun: func() { params, _ := f.distrKeeper.Params.Get(f.sdkCtx) params.WithdrawAddrEnabled = true assert.NilError(t, f.distrKeeper.Params.Set(f.sdkCtx, params)) }, msg: &distrtypes.MsgSetWithdrawAddress{ DelegatorAddress: delAddr.String(), WithdrawAddress: withdrawAddr.String(), }, expErr: false, }, } for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { tc.preRun() res, err := f.app.RunMsg( tc.msg, integration.WithAutomaticFinalizeBlock(), integration.WithAutomaticCommit(), ) if tc.expErr { assert.ErrorContains(t, err, tc.expErrMsg) // query the delegator withdraw address addr, _ := f.distrKeeper.GetDelegatorWithdrawAddr(f.sdkCtx, delAddr) assert.DeepEqual(t, addr, delAddr) } else { assert.NilError(t, err) assert.Assert(t, res != nil) // check the result result := distrtypes.MsgSetWithdrawAddressResponse{} err = f.cdc.Unmarshal(res.Value, &result) assert.NilError(t, err) // query the delegator withdraw address addr, _ := f.distrKeeper.GetDelegatorWithdrawAddr(f.sdkCtx, delAddr) assert.DeepEqual(t, addr.String(), tc.msg.WithdrawAddress) } }) } } func TestMsgWithdrawValidatorCommission(t *testing.T) { t.Parallel() f := initFixture(t) valCommission := sdk.DecCoins{ sdk.NewDecCoinFromDec("mytoken", math.LegacyNewDec(5).Quo(math.LegacyNewDec(4))), sdk.NewDecCoinFromDec("stake", math.LegacyNewDec(3).Quo(math.LegacyNewDec(2))), } // set module account coins initTokens := f.stakingKeeper.TokensFromConsensusPower(f.sdkCtx, int64(1000)) err := f.bankKeeper.MintCoins(f.sdkCtx, distrtypes.ModuleName, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initTokens))) require.NoError(t, err) // send funds to val addr err = f.bankKeeper.SendCoinsFromModuleToAccount(f.sdkCtx, distrtypes.ModuleName, sdk.AccAddress(f.valAddr), sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initTokens))) require.NoError(t, err) coins := sdk.NewCoins(sdk.NewCoin("mytoken", math.NewInt(2)), sdk.NewCoin("stake", math.NewInt(2))) err = f.bankKeeper.MintCoins(f.sdkCtx, distrtypes.ModuleName, coins) require.NoError(t, err) // check initial balance balance := f.bankKeeper.GetAllBalances(f.sdkCtx, sdk.AccAddress(f.valAddr)) expTokens := f.stakingKeeper.TokensFromConsensusPower(f.sdkCtx, 1000) expCoins := sdk.NewCoins(sdk.NewCoin("stake", expTokens)) assert.DeepEqual(t, expCoins, balance) // set outstanding rewards err = f.distrKeeper.ValidatorOutstandingRewards.Set(f.sdkCtx, f.valAddr, distrtypes.ValidatorOutstandingRewards{Rewards: valCommission}) require.NoError(t, err) // set commission err = f.distrKeeper.ValidatorsAccumulatedCommission.Set(f.sdkCtx, f.valAddr, distrtypes.ValidatorAccumulatedCommission{Commission: valCommission}) require.NoError(t, err) testCases := []struct { name string msg *distrtypes.MsgWithdrawValidatorCommission expErr bool expErrMsg string }{ { name: "empty validator address", msg: &distrtypes.MsgWithdrawValidatorCommission{ ValidatorAddress: emptyValAddr.String(), }, expErr: true, expErrMsg: "invalid validator address", }, { name: "validator with no commission", msg: &distrtypes.MsgWithdrawValidatorCommission{ ValidatorAddress: sdk.ValAddress([]byte("addr1_______________")).String(), }, expErr: true, expErrMsg: "no validator commission to withdraw", }, { name: "valid msg", msg: &distrtypes.MsgWithdrawValidatorCommission{ ValidatorAddress: f.valAddr.String(), }, expErr: false, }, } for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { res, err := f.app.RunMsg( tc.msg, integration.WithAutomaticFinalizeBlock(), integration.WithAutomaticCommit(), ) if tc.expErr { assert.ErrorContains(t, err, tc.expErrMsg) } else { assert.NilError(t, err) assert.Assert(t, res != nil) // check the result result := distrtypes.MsgWithdrawValidatorCommissionResponse{} err = f.cdc.Unmarshal(res.Value, &result) assert.NilError(t, err) // check balance increase balance = f.bankKeeper.GetAllBalances(f.sdkCtx, sdk.AccAddress(f.valAddr)) assert.DeepEqual(t, sdk.NewCoins( sdk.NewCoin("mytoken", math.NewInt(1)), sdk.NewCoin("stake", expTokens.AddRaw(1)), ), balance) // check remainder remainder, err := f.distrKeeper.ValidatorsAccumulatedCommission.Get(f.sdkCtx, f.valAddr) require.NoError(t, err) assert.DeepEqual(t, sdk.DecCoins{ sdk.NewDecCoinFromDec("mytoken", math.LegacyNewDec(1).Quo(math.LegacyNewDec(4))), sdk.NewDecCoinFromDec("stake", math.LegacyNewDec(1).Quo(math.LegacyNewDec(2))), }, remainder.Commission) } }) } } func TestMsgFundCommunityPool(t *testing.T) { t.Parallel() f := initFixture(t) addr := sdk.AccAddress(PKS[0].Address()) addr2 := sdk.AccAddress(PKS[1].Address()) amount := sdk.NewCoins(sdk.NewInt64Coin("stake", 100)) poolAcc := f.accountKeeper.GetModuleAccount(f.sdkCtx, pooltypes.ModuleName) // check that the pool account balance is empty assert.Assert(t, f.bankKeeper.GetAllBalances(f.sdkCtx, poolAcc.GetAddress()).Empty()) // fund the account by minting and sending amount from distribution module to addr err := f.bankKeeper.MintCoins(f.sdkCtx, distrtypes.ModuleName, amount) assert.NilError(t, err) err = f.bankKeeper.SendCoinsFromModuleToAccount(f.sdkCtx, distrtypes.ModuleName, addr, amount) assert.NilError(t, err) testCases := []struct { name string msg *distrtypes.MsgFundCommunityPool //nolint:staticcheck // we're using a deprecated call expErr bool expErrMsg string }{ { name: "no depositor address", msg: &distrtypes.MsgFundCommunityPool{ //nolint:staticcheck // we're using a deprecated call Amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(100))), Depositor: emptyDelAddr.String(), }, expErr: true, expErrMsg: "invalid depositor address", }, { name: "invalid coin", msg: &distrtypes.MsgFundCommunityPool{ //nolint:staticcheck // we're using a deprecated call Amount: sdk.Coins{sdk.NewInt64Coin("stake", 10), sdk.NewInt64Coin("stake", 10)}, Depositor: addr.String(), }, expErr: true, expErrMsg: "10stake,10stake: invalid coins", }, { name: "depositor address with no funds", msg: &distrtypes.MsgFundCommunityPool{ //nolint:staticcheck // we're using a deprecated call Amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(100))), Depositor: addr2.String(), }, expErr: true, expErrMsg: "insufficient funds", }, { name: "valid message", msg: &distrtypes.MsgFundCommunityPool{ //nolint:staticcheck // we're using a deprecated call Amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(100))), Depositor: addr.String(), }, expErr: false, }, } for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { res, err := f.app.RunMsg( tc.msg, integration.WithAutomaticFinalizeBlock(), integration.WithAutomaticCommit(), ) if tc.expErr { assert.ErrorContains(t, err, tc.expErrMsg) } else { assert.NilError(t, err) assert.Assert(t, res != nil) // check the result result := distrtypes.MsgFundCommunityPool{} //nolint:staticcheck // we're using a deprecated call err = f.cdc.Unmarshal(res.Value, &result) assert.NilError(t, err) // query the community pool funds poolBal := f.bankKeeper.GetAllBalances(f.sdkCtx, poolAcc.GetAddress()) assert.Assert(t, poolBal.Equal(amount)) assert.Assert(t, f.bankKeeper.GetAllBalances(f.sdkCtx, addr).Empty()) } }) } } func TestMsgUpdateParams(t *testing.T) { t.Parallel() f := initFixture(t) // default params communityTax := math.LegacyNewDecWithPrec(2, 2) // 2% withdrawAddrEnabled := true testCases := []struct { name string msg *distrtypes.MsgUpdateParams expErr bool expErrMsg string }{ { name: "invalid authority", msg: &distrtypes.MsgUpdateParams{ Authority: "invalid", Params: distrtypes.Params{ CommunityTax: math.LegacyNewDecWithPrec(2, 0), WithdrawAddrEnabled: withdrawAddrEnabled, BaseProposerReward: math.LegacyZeroDec(), BonusProposerReward: math.LegacyZeroDec(), }, }, expErr: true, expErrMsg: "invalid authority", }, { name: "community tax is nil", msg: &distrtypes.MsgUpdateParams{ Authority: f.distrKeeper.GetAuthority(), Params: distrtypes.Params{ CommunityTax: math.LegacyDec{}, WithdrawAddrEnabled: withdrawAddrEnabled, BaseProposerReward: math.LegacyZeroDec(), BonusProposerReward: math.LegacyZeroDec(), }, }, expErr: true, expErrMsg: "community tax must be not nil", }, { name: "community tax > 1", msg: &distrtypes.MsgUpdateParams{ Authority: f.distrKeeper.GetAuthority(), Params: distrtypes.Params{ CommunityTax: math.LegacyNewDecWithPrec(2, 0), WithdrawAddrEnabled: withdrawAddrEnabled, BaseProposerReward: math.LegacyZeroDec(), BonusProposerReward: math.LegacyZeroDec(), }, }, expErr: true, expErrMsg: "community tax too large: 2.000000000000000000", }, { name: "negative community tax", msg: &distrtypes.MsgUpdateParams{ Authority: f.distrKeeper.GetAuthority(), Params: distrtypes.Params{ CommunityTax: math.LegacyNewDecWithPrec(-2, 1), WithdrawAddrEnabled: withdrawAddrEnabled, BaseProposerReward: math.LegacyZeroDec(), BonusProposerReward: math.LegacyZeroDec(), }, }, expErr: true, expErrMsg: "community tax must be positive: -0.200000000000000000", }, { name: "base proposer reward set", msg: &distrtypes.MsgUpdateParams{ Authority: f.distrKeeper.GetAuthority(), Params: distrtypes.Params{ CommunityTax: communityTax, BaseProposerReward: math.LegacyNewDecWithPrec(1, 2), BonusProposerReward: math.LegacyZeroDec(), WithdrawAddrEnabled: withdrawAddrEnabled, }, }, expErr: true, expErrMsg: "cannot update base or bonus proposer reward because these are deprecated fields", }, { name: "bonus proposer reward set", msg: &distrtypes.MsgUpdateParams{ Authority: f.distrKeeper.GetAuthority(), Params: distrtypes.Params{ CommunityTax: communityTax, BaseProposerReward: math.LegacyZeroDec(), BonusProposerReward: math.LegacyNewDecWithPrec(1, 2), WithdrawAddrEnabled: withdrawAddrEnabled, }, }, expErr: true, expErrMsg: "cannot update base or bonus proposer reward because these are deprecated fields", }, { name: "all good", msg: &distrtypes.MsgUpdateParams{ Authority: f.distrKeeper.GetAuthority(), Params: distrtypes.Params{ CommunityTax: communityTax, BaseProposerReward: math.LegacyZeroDec(), BonusProposerReward: math.LegacyZeroDec(), WithdrawAddrEnabled: withdrawAddrEnabled, }, }, expErr: false, }, } for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { res, err := f.app.RunMsg( tc.msg, integration.WithAutomaticFinalizeBlock(), integration.WithAutomaticCommit(), ) if tc.expErr { assert.ErrorContains(t, err, tc.expErrMsg) } else { assert.NilError(t, err) assert.Assert(t, res != nil) // check the result result := distrtypes.MsgUpdateParams{} err = f.cdc.Unmarshal(res.Value, &result) assert.NilError(t, err) // query the params and verify it has been updated params, _ := f.distrKeeper.Params.Get(f.sdkCtx) assert.DeepEqual(t, distrtypes.DefaultParams(), params) } }) } } func TestMsgCommunityPoolSpend(t *testing.T) { t.Parallel() f := initFixture(t) initTokens := f.stakingKeeper.TokensFromConsensusPower(f.sdkCtx, int64(100)) err := f.bankKeeper.MintCoins(f.sdkCtx, distrtypes.ModuleName, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initTokens))) require.NoError(t, err) // fund pool module account amount := sdk.NewCoins(sdk.NewInt64Coin("stake", 100)) poolAcc := f.accountKeeper.GetModuleAccount(f.sdkCtx, pooltypes.ModuleName) err = f.bankKeeper.SendCoinsFromModuleToModule(f.sdkCtx, distrtypes.ModuleName, poolAcc.GetName(), amount) require.NoError(t, err) // query the community pool to verify it has been updated with balance poolBal := f.bankKeeper.GetAllBalances(f.sdkCtx, poolAcc.GetAddress()) assert.Assert(t, poolBal.Equal(amount)) recipient := sdk.AccAddress([]byte("addr1")) testCases := []struct { name string msg *distrtypes.MsgCommunityPoolSpend //nolint:staticcheck // we're using a deprecated call expErr bool expErrMsg string }{ { name: "invalid authority", msg: &distrtypes.MsgCommunityPoolSpend{ //nolint:staticcheck // we're using a deprecated call Authority: "invalid", Recipient: recipient.String(), Amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(100))), }, expErr: true, expErrMsg: "invalid authority", }, { name: "invalid recipient", msg: &distrtypes.MsgCommunityPoolSpend{ //nolint:staticcheck // we're using a deprecated call Authority: f.distrKeeper.GetAuthority(), Recipient: "invalid", Amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(100))), }, expErr: true, expErrMsg: "decoding bech32 failed", }, { name: "valid message", msg: &distrtypes.MsgCommunityPoolSpend{ //nolint:staticcheck // we're using a deprecated call Authority: f.distrKeeper.GetAuthority(), Recipient: recipient.String(), Amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(100))), }, expErr: false, }, } for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { res, err := f.app.RunMsg( tc.msg, integration.WithAutomaticFinalizeBlock(), integration.WithAutomaticCommit(), ) if tc.expErr { assert.ErrorContains(t, err, tc.expErrMsg) } else { assert.NilError(t, err) assert.Assert(t, res != nil) // check the result result := distrtypes.MsgCommunityPoolSpend{} //nolint:staticcheck // we're using a deprecated call err = f.cdc.Unmarshal(res.Value, &result) assert.NilError(t, err) // query the community pool to verify it has been updated poolBal := f.bankKeeper.GetAllBalances(f.sdkCtx, poolAcc.GetAddress()) assert.Assert(t, poolBal.Empty()) } }) } } func TestMsgDepositValidatorRewardsPool(t *testing.T) { t.Parallel() f := initFixture(t) require.NoError(t, f.distrKeeper.Params.Set(f.sdkCtx, distrtypes.DefaultParams())) err := f.distrKeeper.FeePool.Set(f.sdkCtx, distrtypes.FeePool{ CommunityPool: sdk.NewDecCoins(sdk.DecCoin{Denom: "stake", Amount: math.LegacyNewDec(100)}), }) require.NoError(t, err) initTokens := f.stakingKeeper.TokensFromConsensusPower(f.sdkCtx, int64(10000)) require.NoError(t, f.bankKeeper.MintCoins(f.sdkCtx, distrtypes.ModuleName, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initTokens)))) // Set default staking params require.NoError(t, f.stakingKeeper.Params.Set(f.sdkCtx, stakingtypes.DefaultParams())) addr := sdk.AccAddress("addr") addr1 := sdk.AccAddress(PKS[0].Address()) valAddr1 := sdk.ValAddress(addr1) // send funds to val addr tokens := f.stakingKeeper.TokensFromConsensusPower(f.sdkCtx, int64(1000)) err = f.bankKeeper.SendCoinsFromModuleToAccount(f.sdkCtx, distrtypes.ModuleName, sdk.AccAddress(valAddr1), sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, tokens))) require.NoError(t, err) // send funds from module to addr to perform DepositValidatorRewardsPool err = f.bankKeeper.SendCoinsFromModuleToAccount(f.sdkCtx, distrtypes.ModuleName, addr, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, tokens))) f.accountKeeper.SetAccount(f.sdkCtx, f.accountKeeper.NewAccountWithAddress(f.sdkCtx, sdk.AccAddress(valAddr1))) require.NoError(t, err) tstaking := stakingtestutil.NewHelper(t, f.sdkCtx, f.stakingKeeper) tstaking.Commission = stakingtypes.NewCommissionRates(math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDec(0)) tstaking.CreateValidator(valAddr1, valConsPk0, math.NewInt(100), true) // mint a non-staking token and send to an account amt := sdk.NewCoins(sdk.NewInt64Coin("foo", 500)) require.NoError(t, f.bankKeeper.MintCoins(f.sdkCtx, distrtypes.ModuleName, amt)) require.NoError(t, f.bankKeeper.SendCoinsFromModuleToAccount(f.sdkCtx, distrtypes.ModuleName, addr, amt)) bondDenom, err := f.stakingKeeper.BondDenom(f.sdkCtx) require.NoError(t, err) testCases := []struct { name string msg *distrtypes.MsgDepositValidatorRewardsPool expErr bool expErrMsg string }{ { name: "happy path (staking token)", msg: &distrtypes.MsgDepositValidatorRewardsPool{ Depositor: addr.String(), ValidatorAddress: valAddr1.String(), Amount: sdk.NewCoins(sdk.NewCoin(bondDenom, math.NewInt(100))), }, }, { name: "happy path (non-staking token)", msg: &distrtypes.MsgDepositValidatorRewardsPool{ Depositor: addr.String(), ValidatorAddress: valAddr1.String(), Amount: amt, }, }, { name: "invalid validator", msg: &distrtypes.MsgDepositValidatorRewardsPool{ Depositor: addr.String(), ValidatorAddress: sdk.ValAddress([]byte("addr1_______________")).String(), Amount: sdk.NewCoins(sdk.NewCoin(bondDenom, math.NewInt(100))), }, expErr: true, expErrMsg: "validator does not exist", }, } for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { res, err := f.app.RunMsg( tc.msg, integration.WithAutomaticFinalizeBlock(), integration.WithAutomaticCommit(), ) if tc.expErr { assert.ErrorContains(t, err, tc.expErrMsg) } else { assert.NilError(t, err) assert.Assert(t, res != nil) // check the result result := distrtypes.MsgDepositValidatorRewardsPoolResponse{} err = f.cdc.Unmarshal(res.Value, &result) assert.NilError(t, err) val, err := sdk.ValAddressFromBech32(tc.msg.ValidatorAddress) assert.NilError(t, err) // check validator outstanding rewards outstandingRewards, err := f.distrKeeper.ValidatorOutstandingRewards.Get(f.sdkCtx, val) assert.NilError(t, err) for _, c := range tc.msg.Amount { x := outstandingRewards.Rewards.AmountOf(c.Denom) assert.DeepEqual(t, x, math.LegacyNewDecFromInt(c.Amount)) } } }) } }