From ad0d1f80c8bacdddb2aea07f941e186a5e085c82 Mon Sep 17 00:00:00 2001 From: Robert Pirtle Date: Mon, 19 Dec 2022 13:50:43 -0800 Subject: [PATCH] Override original community-pool query service (#1430) * initial version * override x/distribution community-pool query * test subset of original x/distribution queries Co-authored-by: rhuairahrighairigh --- app/app.go | 25 ++- app/query/distribution/expected_keepers.go | 10 ++ app/query/distribution/query.go | 38 +++++ app/query/distribution/query_test.go | 170 +++++++++++++++++++++ 4 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 app/query/distribution/expected_keepers.go create mode 100644 app/query/distribution/query.go create mode 100644 app/query/distribution/query_test.go diff --git a/app/app.go b/app/app.go index 15829a17..fd7a9a17 100644 --- a/app/app.go +++ b/app/app.go @@ -90,6 +90,7 @@ import ( "github.com/kava-labs/kava/app/ante" kavaparams "github.com/kava-labs/kava/app/params" + kavadistrquery "github.com/kava-labs/kava/app/query/distribution" "github.com/kava-labs/kava/x/auction" auctionkeeper "github.com/kava-labs/kava/x/auction/keeper" auctiontypes "github.com/kava-labs/kava/x/auction/types" @@ -898,7 +899,7 @@ func NewApp( app.mm.RegisterRoutes(app.Router(), app.QueryRouter(), encodingConfig.Amino) app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter()) - app.mm.RegisterServices(app.configurator) + app.RegisterServices(app.configurator) // RegisterUpgradeHandlers is used for registering any on-chain upgrades. // It needs to be called after `app.mm` and `app.configurator` are set. @@ -971,6 +972,28 @@ func NewApp( return app } +func (app *App) RegisterServices(cfg module.Configurator) { + // Register services from all unmodified modules + for _, module := range app.mm.Modules { + // skip registration of distribution services + if module.Name() == distrtypes.ModuleName { + continue + } + module.RegisterServices(cfg) + } + + // register ditribution services except query server + distrtypes.RegisterMsgServer(cfg.MsgServer(), distrkeeper.NewMsgServerImpl(app.distrKeeper)) + cfg.RegisterMigration( + distrtypes.ModuleName, + 1, + distrkeeper.NewMigrator(app.distrKeeper).Migrate1to2, + ) + + // register fake distribution query server + distrtypes.RegisterQueryServer(cfg.QueryServer(), kavadistrquery.NewQueryServer(app.distrKeeper, app.communityKeeper)) +} + // BeginBlocker contains app specific logic for the BeginBlock abci call. func (app *App) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { return app.mm.BeginBlock(ctx, req) diff --git a/app/query/distribution/expected_keepers.go b/app/query/distribution/expected_keepers.go new file mode 100644 index 00000000..ea8a77fd --- /dev/null +++ b/app/query/distribution/expected_keepers.go @@ -0,0 +1,10 @@ +package distribution + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// CommunityKeeper defines the contract needed to be fulfilled for community module dependencies. +type CommunityKeeper interface { + GetModuleAccountBalance(sdk.Context) sdk.Coins +} diff --git a/app/query/distribution/query.go b/app/query/distribution/query.go new file mode 100644 index 00000000..1312c5f4 --- /dev/null +++ b/app/query/distribution/query.go @@ -0,0 +1,38 @@ +package distribution + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +var _ distrtypes.QueryServer = &queryServer{} + +type queryServer struct { + distrkeeper.Keeper + + communityKeeper CommunityKeeper +} + +// NewQueryServer returns a grpc query server for the distribution module. +// It forwards most requests to the distribution keeper, except for the community pool request +// which is mapped to the kava community module. +func NewQueryServer(distrKeeper distrkeeper.Keeper, commKeeper CommunityKeeper) distrtypes.QueryServer { + return &queryServer{ + Keeper: distrKeeper, + communityKeeper: commKeeper, + } +} + +// CommunityPool queries the kava community module +// The original community pool, which is a separately accounted for portion of x/auth's fee pool +// is replaces with the x/community module account. +// TODO: implement legacy community pool balance query in x/community +// To query the original community pool, including historical values, use x/community's LegacyCommunityPoolBalance +func (q queryServer) CommunityPool(c context.Context, req *distrtypes.QueryCommunityPoolRequest) (*distrtypes.QueryCommunityPoolResponse, error) { + ctx := sdk.UnwrapSDKContext(c) + balance := q.communityKeeper.GetModuleAccountBalance(ctx) + return &distrtypes.QueryCommunityPoolResponse{Pool: sdk.NewDecCoinsFromCoins(balance...)}, nil +} diff --git a/app/query/distribution/query_test.go b/app/query/distribution/query_test.go new file mode 100644 index 00000000..8f030531 --- /dev/null +++ b/app/query/distribution/query_test.go @@ -0,0 +1,170 @@ +package distribution_test + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" + + "github.com/kava-labs/kava/app" + distrquery "github.com/kava-labs/kava/app/query/distribution" + communitytypes "github.com/kava-labs/kava/x/community/types" +) + +type queryTestSuite struct { + suite.Suite + + App app.TestApp + Ctx sdk.Context + + queryClient distrtypes.QueryClient + communityPoolAddress sdk.AccAddress +} + +func (suite *queryTestSuite) SetupTest() { + app.SetSDKConfig() + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()}) + + tApp.InitializeFromGenesisStates() + + suite.App = tApp + suite.Ctx = ctx + + queryHelper := baseapp.NewQueryServerTestHelper(suite.Ctx, suite.App.InterfaceRegistry()) + distrtypes.RegisterQueryServer(queryHelper, distrquery.NewQueryServer( + tApp.GetDistrKeeper(), + tApp.GetCommunityKeeper(), + )) + suite.queryClient = distrtypes.NewQueryClient(queryHelper) + suite.communityPoolAddress = tApp.GetAccountKeeper().GetModuleAddress(communitytypes.ModuleAccountName) +} + +func TestGRPQueryTestSuite(t *testing.T) { + suite.Run(t, new(queryTestSuite)) +} + +func (suite queryTestSuite) FundCommunityPool(amt sdk.Coins) { + err := suite.App.FundModuleAccount(suite.Ctx, communitytypes.ModuleAccountName, amt) + suite.NoError(err) +} + +func (suite queryTestSuite) CheckCommunityPoolBalance(expected sdk.Coins, result sdk.DecCoins) { + actual := suite.App.GetBankKeeper().GetAllBalances(suite.Ctx, suite.communityPoolAddress) + // check that account was properly funded + suite.Equal(expected, actual, "unexpected community pool balance") + + // transform the expected values to DecCoins to compare with result + decCoins := sdk.NewDecCoinsFromCoins(expected...) + suite.True(decCoins.IsEqual(result), "unexpected community pool query response") +} + +func (suite *queryTestSuite) Test_CommunityPoolOverride() { + singleDenom := sdk.NewCoins(sdk.NewInt64Coin("ukava", 1e10)) + multiDenom := singleDenom.Add(sdk.NewInt64Coin("other-denom", 1e9)) + + testCases := []struct { + name string + funds sdk.Coins + }{ + {"single denom", singleDenom}, + {"multiple denoms", multiDenom}, + {"no balance", sdk.NewCoins()}, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("queries the correct balance - %s", tc.name), func() { + suite.SetupTest() + + // init community pool funds + if !tc.funds.IsZero() { + suite.FundCommunityPool(tc.funds) + } + + // query the overridden endpoint + balance, err := suite.queryClient.CommunityPool( + context.Background(), + &distrtypes.QueryCommunityPoolRequest{}, + ) + suite.NoError(err) + suite.CheckCommunityPoolBalance(tc.funds, balance.Pool) + }) + } +} + +// modified from sdk test of distribution querier +func (suite *queryTestSuite) Test_OGDistributionQueries() { + suite.SetupTest() + + ctx, distrKeeper, stakingKeeper := suite.Ctx, suite.App.GetDistrKeeper(), suite.App.GetStakingKeeper() + + // setup a validator + addr := app.RandomAddress() + valAddr := sdk.ValAddress(addr) + bondDenom := stakingKeeper.BondDenom(ctx) + + // test param queries + params := distrtypes.Params{ + CommunityTax: sdk.ZeroDec(), + BaseProposerReward: sdk.NewDecWithPrec(2, 1), + BonusProposerReward: sdk.NewDecWithPrec(1, 1), + WithdrawAddrEnabled: true, + } + distrKeeper.SetParams(ctx, params) + r1, err := suite.queryClient.Params(context.Background(), &distrtypes.QueryParamsRequest{}) + suite.NoError(err) + suite.Equal(params, r1.Params) + + // test outstanding rewards query + outstandingRewards := sdk.NewDecCoins(sdk.NewInt64DecCoin(bondDenom, 100), sdk.NewInt64DecCoin("other", 10)) + distrKeeper.SetValidatorOutstandingRewards(ctx, valAddr, distrtypes.ValidatorOutstandingRewards{Rewards: outstandingRewards}) + r2, err := suite.queryClient.ValidatorOutstandingRewards(context.Background(), &distrtypes.QueryValidatorOutstandingRewardsRequest{ValidatorAddress: valAddr.String()}) + suite.NoError(err) + suite.Equal(outstandingRewards, r2.Rewards.Rewards) + + // test validator commission query + commission := sdk.DecCoins{{Denom: "token1", Amount: sdk.NewDec(4)}, {Denom: "token2", Amount: sdk.NewDec(2)}} + distrKeeper.SetValidatorAccumulatedCommission(ctx, valAddr, distrtypes.ValidatorAccumulatedCommission{Commission: commission}) + r3, err := suite.queryClient.ValidatorCommission(context.Background(), &distrtypes.QueryValidatorCommissionRequest{ValidatorAddress: valAddr.String()}) + suite.NoError(err) + suite.Equal(commission, r3.Commission.Commission) + + // test delegator's total rewards query + r4, err := suite.queryClient.DelegationTotalRewards(context.Background(), &distrtypes.QueryDelegationTotalRewardsRequest{DelegatorAddress: addr.String()}) + suite.NoError(err) + suite.Equal(&distrtypes.QueryDelegationTotalRewardsResponse{}, r4) + + // test validator slashes query with height range + slashOne := distrtypes.NewValidatorSlashEvent(3, sdk.NewDecWithPrec(5, 1)) + slashTwo := distrtypes.NewValidatorSlashEvent(7, sdk.NewDecWithPrec(6, 1)) + distrKeeper.SetValidatorSlashEvent(ctx, valAddr, 3, 0, slashOne) + distrKeeper.SetValidatorSlashEvent(ctx, valAddr, 7, 0, slashTwo) + slashes := suite.getQueriedValidatorSlashes(valAddr, 0, 2) + suite.Equal(0, len(slashes)) + slashes = suite.getQueriedValidatorSlashes(valAddr, 0, 5) + suite.Equal([]distrtypes.ValidatorSlashEvent{slashOne}, slashes) + slashes = suite.getQueriedValidatorSlashes(valAddr, 0, 10) + suite.Equal([]distrtypes.ValidatorSlashEvent{slashOne, slashTwo}, slashes) + + // non-zero delegator reward queries are not tested here. +} + +func (suite *queryTestSuite) getQueriedValidatorSlashes(validatorAddr sdk.ValAddress, startHeight uint64, endHeight uint64) (slashes []distrtypes.ValidatorSlashEvent) { + result, err := suite.queryClient.ValidatorSlashes( + context.Background(), + &distrtypes.QueryValidatorSlashesRequest{ + ValidatorAddress: validatorAddr.String(), + StartingHeight: startHeight, + EndingHeight: endHeight, + }, + ) + suite.NoError(err) + return result.Slashes +}