diff --git a/x/kavamint/keeper/grpc_query.go b/x/kavamint/keeper/grpc_query.go index ba910c98..84df3e50 100644 --- a/x/kavamint/keeper/grpc_query.go +++ b/x/kavamint/keeper/grpc_query.go @@ -3,7 +3,12 @@ package keeper import ( "context" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + sdk "github.com/cosmos/cosmos-sdk/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + "github.com/kava-labs/kava/x/kavamint/types" ) @@ -24,3 +29,57 @@ func (k Keeper) Inflation(c context.Context, _ *types.QueryInflationRequest) (*t return &types.QueryInflationResponse{Inflation: inflation}, nil } + +// MintQueryServer implements cosmos sdk's x/mint querier. +// x/mint was removed from kava, but the standard inflation endpoint is still registered +// for easier third party integration and backwards compatibility. +type MintQueryServer struct { + keeper Keeper +} + +// NewMintQueryServer returns a service that implements x/mint's QueryServer +func NewMintQueryServer(kavamintKeeper Keeper) MintQueryServer { + return MintQueryServer{kavamintKeeper} +} + +var _ minttypes.QueryServer = MintQueryServer{} + +// Params is not implemented. There is no mint module. +func (MintQueryServer) Params( + _ context.Context, _ *minttypes.QueryParamsRequest, +) (*minttypes.QueryParamsResponse, error) { + return nil, status.Error(codes.Unimplemented, "x/mint has been replaced by x/kavamint") +} + +// Inflation returns an adjusted inflation rate. +// The `/cosmos/mint/v1beta1/inflation` endpoint is used by third parties to calculate staking APY. +// The usual staking APY calculation takes the inflation and determines the portion of it devoted +// to staking rewards after adjusting for the bonded ratio and x/distribution community_tax. +// staking_apy = (inflation - community_tax) * total_supply / total_bonded +// Staking APY is not set directly via the x/kavamint staking_rewards_apy param. +// This endpoint returns the inflation that makes the above calculation equal to the param: +// inflation = staking_apy * total_bonded / total_supply +// NOTE: assumes x/distribution community_tax = 0 +func (mq MintQueryServer) Inflation( + c context.Context, _ *minttypes.QueryInflationRequest, +) (*minttypes.QueryInflationResponse, error) { + ctx := sdk.UnwrapSDKContext(c) + + stakingApy := mq.keeper.GetParams(ctx).StakingRewardsApy + totalBonded := mq.keeper.TotalBondedTokens(ctx) + totalSupply := mq.keeper.TotalSupply(ctx) + + // inflation = staking_apy * total_bonded / total_supply + inflation := stakingApy.MulInt(totalBonded).QuoInt(totalSupply) + + return &minttypes.QueryInflationResponse{ + Inflation: inflation, + }, nil +} + +// AnnualProvisions is not implemented. +func (MintQueryServer) AnnualProvisions( + _ context.Context, _ *minttypes.QueryAnnualProvisionsRequest, +) (*minttypes.QueryAnnualProvisionsResponse, error) { + return nil, status.Error(codes.Unimplemented, "x/mint has been replaced by x/kavamint") +} diff --git a/x/kavamint/keeper/grpc_query_test.go b/x/kavamint/keeper/grpc_query_test.go index ae81e639..b88864e2 100644 --- a/x/kavamint/keeper/grpc_query_test.go +++ b/x/kavamint/keeper/grpc_query_test.go @@ -5,11 +5,15 @@ import ( "testing" "github.com/stretchr/testify/suite" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/kava-labs/kava/x/kavamint/keeper" "github.com/kava-labs/kava/x/kavamint/testutil" "github.com/kava-labs/kava/x/kavamint/types" ) @@ -17,7 +21,8 @@ import ( type grpcQueryTestSuite struct { testutil.KavamintTestSuite - queryClient types.QueryClient + queryClient types.QueryClient + mintQueryClient minttypes.QueryClient } func (suite *grpcQueryTestSuite) SetupTest() { @@ -26,6 +31,10 @@ func (suite *grpcQueryTestSuite) SetupTest() { queryHelper := baseapp.NewQueryServerTestHelper(suite.Ctx, suite.App.InterfaceRegistry()) types.RegisterQueryServer(queryHelper, suite.Keeper) suite.queryClient = types.NewQueryClient(queryHelper) + + mintQueryServer := keeper.NewMintQueryServer(suite.Keeper) + minttypes.RegisterQueryServer(queryHelper, mintQueryServer) + suite.mintQueryClient = minttypes.NewQueryClient(queryHelper) } func TestGRPCQueryTestSuite(t *testing.T) { @@ -116,6 +125,35 @@ func (suite *grpcQueryTestSuite) TestGRPCInflationQuery() { suite.Require().NoError(err) suite.Require().Equal(inflation.Inflation, kavamintKeeper.CumulativeInflation(ctx)) suite.Require().Equal(inflation.Inflation, tc.expectedInflation) + + // ensure overridden x/mint query for inflation returns the adjusted inflation + // the adjusted inflation is the inflation that returns the correct staking apy for the + // standard staking apy calculation used by third parties: + // staking_apy = (inflation - community_tax) * total_supply / total_bonded + // => inflation = staking_apy * total_bonded / total_supply + expectedAdjustedInflation := tc.stakingApy.Mul(tc.bondedRatio) + mintQ, err := suite.mintQueryClient.Inflation( + context.Background(), + &minttypes.QueryInflationRequest{}, + ) + suite.NoError(err) + suite.Equal(expectedAdjustedInflation, mintQ.Inflation) }) } } + +func (suite *grpcQueryTestSuite) TestUnimplementedMintQueries() { + suite.SetupTest() + + suite.Run("Params is unimplemented", func() { + _, err := suite.mintQueryClient.Params(context.Background(), nil) + suite.Error(err) + suite.Equal(codes.Unimplemented, status.Code(err)) + }) + + suite.Run("AnnualProvisions is unimplemented", func() { + _, err := suite.mintQueryClient.AnnualProvisions(context.Background(), nil) + suite.Error(err) + suite.Equal(codes.Unimplemented, status.Code(err)) + }) +} diff --git a/x/kavamint/module.go b/x/kavamint/module.go index 58d6c255..9d8f9667 100644 --- a/x/kavamint/module.go +++ b/x/kavamint/module.go @@ -15,6 +15,8 @@ import ( cdctypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + "github.com/kava-labs/kava/x/kavamint/client/cli" "github.com/kava-labs/kava/x/kavamint/keeper" "github.com/kava-labs/kava/x/kavamint/types" @@ -66,6 +68,8 @@ func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Rout func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) + // add x/mint query handler for better 3rd party compatibility + minttypes.RegisterQueryHandlerClient(context.Background(), mux, minttypes.NewQueryClient(clientCtx)) } // GetTxCmd returns no root tx command for the kavamint module. @@ -118,6 +122,11 @@ func (am AppModule) LegacyQuerierHandler(legacyQuerierCdc *codec.LegacyAmino) sd // module-specific gRPC queries. func (am AppModule) RegisterServices(cfg module.Configurator) { types.RegisterQueryServer(cfg.QueryServer(), am.keeper) + + // register x/mint query server. + // x/mint was replaced by this module but we still want 3rd parties to be able to + // request the original sdk endpoints. + minttypes.RegisterQueryServer(cfg.QueryServer(), keeper.NewMintQueryServer(am.keeper)) } // InitGenesis performs genesis initialization for the kavamint module. It returns