From 0efe7f2281d63c37ff08cbe4edc5d9ee9493d4bf Mon Sep 17 00:00:00 2001 From: Robert Pirtle Date: Tue, 24 Oct 2023 12:24:21 -0700 Subject: [PATCH] feat(community): add AnnualizedRewards grpc query (#1751) * add annualized_reward query proto * use sdkmath.LegacyDec to match RPS param... * add AnnualizedRewards grpc query * add changelog entry * simplify calculation & expand test cases --- CHANGELOG.md | 4 +- client/docs/swagger-ui/swagger.yaml | 57 ++++ docs/core/proto-docs.md | 28 ++ proto/kava/community/v1beta1/query.proto | 20 ++ x/community/keeper/grpc_query.go | 35 ++ x/community/keeper/grpc_query_test.go | 131 +++++++- x/community/keeper/rewards.go | 27 ++ x/community/keeper/rewards_test.go | 189 +++++++++++ x/community/keeper/staking.go | 4 +- x/community/testutil/main.go | 3 +- x/community/types/expected_keepers.go | 6 + x/community/types/query.pb.go | 400 +++++++++++++++++++++-- x/community/types/query.pb.gw.go | 65 ++++ 13 files changed, 931 insertions(+), 38 deletions(-) create mode 100644 x/community/keeper/rewards.go create mode 100644 x/community/keeper/rewards_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index e8faef14..4cf82f7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ - (community) [#1704] Add module params - (community) [#1706] Add disable inflation upgrade - (community) [#1745] Enable params update via governance with `MsgUpdateParams` +- (community) [#1751] Add `AnnualizedRewards` query endpoint ### Bug Fixes @@ -296,8 +297,9 @@ the [changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.38.4/CHANGELOG.md). large-scale simulations remotely using aws-batch [#1752]: https://github.com/Kava-Labs/kava/pull/1752 -[#1729]: https://github.com/Kava-Labs/kava/pull/1729 +[#1751]: https://github.com/Kava-Labs/kava/pull/1751 [#1745]: https://github.com/Kava-Labs/kava/pull/1745 +[#1729]: https://github.com/Kava-Labs/kava/pull/1729 [#1707]: https://github.com/Kava-Labs/kava/pull/1707 [#1706]: https://github.com/Kava-Labs/kava/pull/1706 [#1704]: https://github.com/Kava-Labs/kava/pull/1704 diff --git a/client/docs/swagger-ui/swagger.yaml b/client/docs/swagger-ui/swagger.yaml index 8d6bdf6f..f74931ab 100644 --- a/client/docs/swagger-ui/swagger.yaml +++ b/client/docs/swagger-ui/swagger.yaml @@ -12991,6 +12991,52 @@ paths: format: byte tags: - Savings + /kava/community/v1beta1/annualized_rewards: + get: + summary: >- + AnnualizedRewards calculates and returns the current annualized reward + percentages, + + like staking rewards, for the chain. + operationId: CommunityAnnualizedRewards + responses: + '200': + description: A successful response. + schema: + type: object + properties: + staking_rewards: + type: string + title: >- + staking_rewards is the calculated annualized staking rewards + percentage rate + description: >- + QueryAnnualizedRewardsResponse defines the response type for + querying the annualized rewards. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + tags: + - Community /kava/community/v1beta1/balance: get: summary: Balance queries the balance of all coins of x/community module. @@ -57043,6 +57089,17 @@ definitions: and use when the disable inflation time is reached description: Params defines the parameters of the community module. + kava.community.v1beta1.QueryAnnualizedRewardsResponse: + type: object + properties: + staking_rewards: + type: string + title: >- + staking_rewards is the calculated annualized staking rewards + percentage rate + description: >- + QueryAnnualizedRewardsResponse defines the response type for querying the + annualized rewards. kava.community.v1beta1.QueryBalanceResponse: type: object properties: diff --git a/docs/core/proto-docs.md b/docs/core/proto-docs.md index f1e8d197..e9a3ab15 100644 --- a/docs/core/proto-docs.md +++ b/docs/core/proto-docs.md @@ -197,6 +197,8 @@ - [CommunityPoolLendWithdrawProposal](#kava.community.v1beta1.CommunityPoolLendWithdrawProposal) - [kava/community/v1beta1/query.proto](#kava/community/v1beta1/query.proto) + - [QueryAnnualizedRewardsRequest](#kava.community.v1beta1.QueryAnnualizedRewardsRequest) + - [QueryAnnualizedRewardsResponse](#kava.community.v1beta1.QueryAnnualizedRewardsResponse) - [QueryBalanceRequest](#kava.community.v1beta1.QueryBalanceRequest) - [QueryBalanceResponse](#kava.community.v1beta1.QueryBalanceResponse) - [QueryParamsRequest](#kava.community.v1beta1.QueryParamsRequest) @@ -3087,6 +3089,31 @@ CommunityPoolLendWithdrawProposal withdraws a lend position back to the communit + + +### QueryAnnualizedRewardsRequest +QueryAnnualizedRewardsRequest defines the request type for querying the annualized rewards. + + + + + + + + +### QueryAnnualizedRewardsResponse +QueryAnnualizedRewardsResponse defines the response type for querying the annualized rewards. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `staking_rewards` | [string](#string) | | staking_rewards is the calculated annualized staking rewards percentage rate | + + + + + + ### QueryBalanceRequest @@ -3179,6 +3206,7 @@ Query defines the gRPC querier service for x/community. | `Params` | [QueryParamsRequest](#kava.community.v1beta1.QueryParamsRequest) | [QueryParamsResponse](#kava.community.v1beta1.QueryParamsResponse) | Params queires the module params. | GET|/kava/community/v1beta1/params| | `Balance` | [QueryBalanceRequest](#kava.community.v1beta1.QueryBalanceRequest) | [QueryBalanceResponse](#kava.community.v1beta1.QueryBalanceResponse) | Balance queries the balance of all coins of x/community module. | GET|/kava/community/v1beta1/balance| | `TotalBalance` | [QueryTotalBalanceRequest](#kava.community.v1beta1.QueryTotalBalanceRequest) | [QueryTotalBalanceResponse](#kava.community.v1beta1.QueryTotalBalanceResponse) | TotalBalance queries the balance of all coins, including x/distribution, x/community, and supplied balances. | GET|/kava/community/v1beta1/total_balance| +| `AnnualizedRewards` | [QueryAnnualizedRewardsRequest](#kava.community.v1beta1.QueryAnnualizedRewardsRequest) | [QueryAnnualizedRewardsResponse](#kava.community.v1beta1.QueryAnnualizedRewardsResponse) | AnnualizedRewards calculates and returns the current annualized reward percentages, like staking rewards, for the chain. | GET|/kava/community/v1beta1/annualized_rewards| diff --git a/proto/kava/community/v1beta1/query.proto b/proto/kava/community/v1beta1/query.proto index 715fc031..b21bb059 100644 --- a/proto/kava/community/v1beta1/query.proto +++ b/proto/kava/community/v1beta1/query.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package kava.community.v1beta1; import "cosmos/base/v1beta1/coin.proto"; +import "cosmos_proto/cosmos.proto"; import "gogoproto/gogo.proto"; import "google/api/annotations.proto"; import "kava/community/v1beta1/params.proto"; @@ -25,6 +26,12 @@ service Query { rpc TotalBalance(QueryTotalBalanceRequest) returns (QueryTotalBalanceResponse) { option (google.api.http).get = "/kava/community/v1beta1/total_balance"; } + + // AnnualizedRewards calculates and returns the current annualized reward percentages, + // like staking rewards, for the chain. + rpc AnnualizedRewards(QueryAnnualizedRewardsRequest) returns (QueryAnnualizedRewardsResponse) { + option (google.api.http).get = "/kava/community/v1beta1/annualized_rewards"; + } } // QueryParams defines the request type for querying x/community params. @@ -59,3 +66,16 @@ message QueryTotalBalanceResponse { (gogoproto.nullable) = false ]; } + +// QueryAnnualizedRewardsRequest defines the request type for querying the annualized rewards. +message QueryAnnualizedRewardsRequest {} + +// QueryAnnualizedRewardsResponse defines the response type for querying the annualized rewards. +message QueryAnnualizedRewardsResponse { + // staking_rewards is the calculated annualized staking rewards percentage rate + string staking_rewards = 1 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; +} diff --git a/x/community/keeper/grpc_query.go b/x/community/keeper/grpc_query.go index 908cbfa9..44d58e6c 100644 --- a/x/community/keeper/grpc_query.go +++ b/x/community/keeper/grpc_query.go @@ -3,6 +3,7 @@ package keeper import ( "context" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -62,3 +63,37 @@ func (s queryServer) TotalBalance( Pool: totalBalance, }, nil } + +// AnnualizedRewards calculates the annualized rewards for the chain. +func (s queryServer) AnnualizedRewards( + c context.Context, + req *types.QueryAnnualizedRewardsRequest, +) (*types.QueryAnnualizedRewardsResponse, error) { + ctx := sdk.UnwrapSDKContext(c) + + // staking rewards come from one of two sources depending on if inflation is enabled or not. + // at any given time, only one source will contribute to the staking rewards. the other will be zero. + // this method adds both sources together so it is accurate in both cases. + + params := s.keeper.mustGetParams(ctx) + bondDenom := s.keeper.stakingKeeper.BondDenom(ctx) + + totalSupply := s.keeper.bankKeeper.GetSupply(ctx, bondDenom).Amount + totalBonded := s.keeper.stakingKeeper.TotalBondedTokens(ctx) + rewardsPerSecond := params.StakingRewardsPerSecond + // need to convert these from sdk.Dec to sdkmath.LegacyDec + inflationRate := convertDecToLegacyDec(s.keeper.mintKeeper.GetMinter(ctx).Inflation) + communityTax := convertDecToLegacyDec(s.keeper.distrKeeper.GetCommunityTax(ctx)) + + return &types.QueryAnnualizedRewardsResponse{ + StakingRewards: CalculateStakingAnnualPercentage(totalSupply, totalBonded, inflationRate, communityTax, rewardsPerSecond), + }, nil +} + +// convertDecToLegacyDec is a helper method for converting between new and old Dec types +// current version of cosmos-sdk in this repo uses sdk.Dec +// this module uses sdkmath.LegacyDec in its parameters +// TODO: remove me after upgrade to cosmos-sdk v50 (LegacyDec is everywhere) +func convertDecToLegacyDec(in sdk.Dec) sdkmath.LegacyDec { + return sdkmath.LegacyNewDecFromBigIntWithPrec(in.BigInt(), sdk.Precision) +} diff --git a/x/community/keeper/grpc_query_test.go b/x/community/keeper/grpc_query_test.go index c8ba5544..6a4b9b28 100644 --- a/x/community/keeper/grpc_query_test.go +++ b/x/community/keeper/grpc_query_test.go @@ -10,18 +10,20 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/suite" + "github.com/kava-labs/kava/app" "github.com/kava-labs/kava/x/community/keeper" + "github.com/kava-labs/kava/x/community/testutil" "github.com/kava-labs/kava/x/community/types" ) type grpcQueryTestSuite struct { - KeeperTestSuite + testutil.Suite queryClient types.QueryClient } func (suite *grpcQueryTestSuite) SetupTest() { - suite.KeeperTestSuite.SetupTest() + suite.Suite.SetupTest() queryHelper := baseapp.NewQueryServerTestHelper(suite.Ctx, suite.App.InterfaceRegistry()) types.RegisterQueryServer(queryHelper, keeper.NewQueryServerImpl(suite.Keeper)) @@ -163,3 +165,128 @@ func (suite *grpcQueryTestSuite) TestGrpcQueryTotalBalance() { }) } } + +// NOTE: this test makes use of the fact that there is always an initial 1e6 bonded tokens +// To adjust the bonded ratio, it adjusts the total supply by minting tokens. +func (suite *grpcQueryTestSuite) TestGrpcQueryAnnualizedRewards() { + bondedTokens := sdkmath.NewInt(1e6) + testCases := []struct { + name string + bondedRatio sdk.Dec + inflation sdk.Dec + rewardsPerSec sdkmath.LegacyDec + communityTax sdk.Dec + expectedRate sdkmath.LegacyDec + }{ + { + name: "sanity check: no inflation, no rewards => 0%", + bondedRatio: sdk.MustNewDecFromStr("0.3456"), + inflation: sdk.ZeroDec(), + rewardsPerSec: sdkmath.LegacyZeroDec(), + expectedRate: sdkmath.LegacyZeroDec(), + }, + { + name: "inflation sanity check: 100% inflation, 100% bonded => 100%", + bondedRatio: sdk.OneDec(), + inflation: sdk.OneDec(), + rewardsPerSec: sdkmath.LegacyZeroDec(), + expectedRate: sdkmath.LegacyOneDec(), + }, + { + name: "inflation sanity check: 100% community tax => 0%", + bondedRatio: sdk.OneDec(), + inflation: sdk.OneDec(), + communityTax: sdk.OneDec(), + rewardsPerSec: sdkmath.LegacyZeroDec(), + expectedRate: sdkmath.LegacyZeroDec(), + }, + { + name: "rewards per second sanity check: (totalBonded/SecondsPerYear) rps => 100%", + bondedRatio: sdk.OneDec(), // bonded tokens are constant in this test. ratio has no affect. + inflation: sdk.ZeroDec(), + rewardsPerSec: sdkmath.LegacyNewDecFromInt(bondedTokens).QuoInt(sdkmath.NewInt(keeper.SecondsPerYear)), + // expect ~100% + expectedRate: sdkmath.LegacyMustNewDecFromStr("0.999999999999999984"), + }, + { + name: "inflation enabled: realistic example", + bondedRatio: sdk.MustNewDecFromStr("0.148"), + inflation: sdk.MustNewDecFromStr("0.595"), + communityTax: sdk.MustNewDecFromStr("0.9495"), + rewardsPerSec: sdkmath.LegacyZeroDec(), + // expect ~20.23% + expectedRate: sdkmath.LegacyMustNewDecFromStr("0.203023625910000000"), + }, + { + name: "inflation disabled: simple example", + bondedRatio: sdk.OneDec(), // bonded tokens are constant in this test. ratio has no affect. + inflation: sdk.ZeroDec(), + rewardsPerSec: sdkmath.LegacyMustNewDecFromStr("0.01"), + // 1e6 bonded tokens => seconds per year / bonded tokens = 31.536 + // expect 31.536% + expectedRate: sdkmath.LegacyMustNewDecFromStr("0.31536"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + // set inflation + mk := suite.App.GetMintKeeper() + minter := mk.GetMinter(suite.Ctx) + minter.Inflation = tc.inflation + mk.SetMinter(suite.Ctx, minter) + + // set community tax + communityTax := sdk.ZeroDec() + if !tc.communityTax.IsNil() { + communityTax = tc.communityTax + } + dk := suite.App.GetDistrKeeper() + distParams := dk.GetParams(suite.Ctx) + distParams.CommunityTax = communityTax + dk.SetParams(suite.Ctx, distParams) + + // set staking rewards per second + ck := suite.App.GetCommunityKeeper() + commParams, _ := ck.GetParams(suite.Ctx) + commParams.StakingRewardsPerSecond = tc.rewardsPerSec + ck.SetParams(suite.Ctx, commParams) + + // set bonded tokens + suite.adjustBondedRatio(tc.bondedRatio) + + // query for annualized rewards + res, err := suite.queryClient.AnnualizedRewards(suite.Ctx, &types.QueryAnnualizedRewardsRequest{}) + // verify results match expected + suite.Require().NoError(err) + suite.Equal(tc.expectedRate, res.StakingRewards) + }) + } +} + +// adjustBondRatio changes the ratio of bonded coins +// it leverages the fact that there is a constant number of bonded tokens +// and adjusts the total supply to make change the bonded ratio. +// returns the new total supply of the bond denom +func (suite *grpcQueryTestSuite) adjustBondedRatio(desiredRatio sdk.Dec) sdkmath.Int { + // from the InitGenesis validator + bondedTokens := sdkmath.NewInt(1e6) + bondDenom := suite.App.GetStakingKeeper().BondDenom(suite.Ctx) + + // first, burn all non-delegated coins (bonded ratio = 100%) + suite.App.DeleteGenesisValidatorCoins(suite.T(), suite.Ctx) + + if desiredRatio.Equal(sdk.OneDec()) { + return bondedTokens + } + + // mint new tokens to adjust the bond ratio + newTotalSupply := sdk.NewDecFromInt(bondedTokens).Quo(desiredRatio).TruncateInt() + coinsToMint := newTotalSupply.Sub(bondedTokens) + err := suite.App.FundAccount(suite.Ctx, app.RandomAddress(), sdk.NewCoins(sdk.NewCoin(bondDenom, coinsToMint))) + suite.Require().NoError(err) + + return newTotalSupply +} diff --git a/x/community/keeper/rewards.go b/x/community/keeper/rewards.go new file mode 100644 index 00000000..1f9198a3 --- /dev/null +++ b/x/community/keeper/rewards.go @@ -0,0 +1,27 @@ +package keeper + +import ( + sdkmath "cosmossdk.io/math" +) + +const SecondsPerYear = 365 * 24 * 3600 + +// CalculateStakingAnnualPercentage returns the annualized staking reward rate. +// It assumes that staking comes from one of two sources depending on if inflation is enabled or not. +func CalculateStakingAnnualPercentage(totalSupply, totalBonded sdkmath.Int, inflationRate, communityTax, rewardsPerSecond sdkmath.LegacyDec) sdkmath.LegacyDec { + // no rewards are given if no tokens are bonded, in addition avoid division by zero + if totalBonded.IsZero() { + return sdkmath.LegacyZeroDec() + } + + // the percent of inflationRate * totalSupply tokens that are distributed to stakers + percentInflationDistributedToStakers := sdkmath.LegacyOneDec().Sub(communityTax) + + // the total amount of tokens distributed to stakers in a year + amountGivenPerYear := inflationRate. + MulInt(totalSupply).Mul(percentInflationDistributedToStakers). // portion provided by inflation via mint & distribution modules + Add(rewardsPerSecond.Mul(sdkmath.LegacyNewDec(SecondsPerYear))) // portion provided by community module + + // divide by total bonded tokens to get the percent return + return amountGivenPerYear.QuoInt(totalBonded) +} diff --git a/x/community/keeper/rewards_test.go b/x/community/keeper/rewards_test.go new file mode 100644 index 00000000..8aed1f76 --- /dev/null +++ b/x/community/keeper/rewards_test.go @@ -0,0 +1,189 @@ +package keeper_test + +import ( + "math/big" + "testing" + + sdkmath "cosmossdk.io/math" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/x/community/keeper" +) + +func TestStakingRewardsCalculator(t *testing.T) { + hugeInflation := new(big.Int).Exp(big.NewInt(2), big.NewInt(205), nil) + hugeRewardsPerSec := new(big.Int).Exp(big.NewInt(2), big.NewInt(230), nil) + + testCases := []struct { + name string + totalSupply sdkmath.Int + totalBonded sdkmath.Int + inflation sdkmath.LegacyDec + communityTax sdkmath.LegacyDec + perSecReward sdkmath.LegacyDec + expectedRate sdkmath.LegacyDec + }{ + { + name: "no inflation, no rewards per sec -> 0%", + totalSupply: sdkmath.ZeroInt(), + totalBonded: sdkmath.ZeroInt(), + inflation: sdkmath.LegacyZeroDec(), + communityTax: sdkmath.LegacyZeroDec(), + perSecReward: sdkmath.LegacyZeroDec(), + expectedRate: sdkmath.LegacyZeroDec(), + }, + // + // + // inflation-only + // + // + { + name: "inflation only: no bonded tokens -> 0%", + totalSupply: sdk.NewInt(42), + totalBonded: sdkmath.ZeroInt(), + inflation: sdkmath.LegacyOneDec(), + communityTax: sdkmath.LegacyZeroDec(), + perSecReward: sdkmath.LegacyZeroDec(), + expectedRate: sdkmath.LegacyZeroDec(), + }, + { + name: "inflation only: 0% inflation -> 0%", + totalSupply: sdk.NewInt(123), + totalBonded: sdkmath.NewInt(45), + inflation: sdkmath.LegacyZeroDec(), + communityTax: sdkmath.LegacyZeroDec(), + perSecReward: sdkmath.LegacyZeroDec(), + expectedRate: sdkmath.LegacyZeroDec(), + }, + { + name: "inflation only: 100% bonded w/ 100% inflation -> 100%", + totalSupply: sdk.NewInt(42), + totalBonded: sdk.NewInt(42), + inflation: sdkmath.LegacyOneDec(), + communityTax: sdkmath.LegacyZeroDec(), + perSecReward: sdkmath.LegacyZeroDec(), + expectedRate: sdkmath.LegacyOneDec(), + }, + { + name: "inflation only: 100% community tax -> 0%", + totalSupply: sdk.NewInt(123), + totalBonded: sdkmath.NewInt(45), + inflation: sdkmath.LegacyMustNewDecFromStr("0.853"), + communityTax: sdkmath.LegacyOneDec(), + perSecReward: sdkmath.LegacyZeroDec(), + expectedRate: sdkmath.LegacyZeroDec(), + }, + { + name: "inflation only: Oct 2023 case", + totalSupply: sdk.NewInt(857570000e6), + totalBonded: sdk.NewInt(127680000e6), + inflation: sdkmath.LegacyMustNewDecFromStr("0.595"), + communityTax: sdkmath.LegacyMustNewDecFromStr("0.9495"), + perSecReward: sdkmath.LegacyZeroDec(), + // expect 20.18% staking reward + expectedRate: sdkmath.LegacyMustNewDecFromStr("0.201815746984649122"), // verified manually + }, + { + name: "inflation only: low inflation", + totalSupply: sdk.NewInt(857570000e6), + totalBonded: sdk.NewInt(127680000e6), + inflation: sdkmath.LegacyMustNewDecFromStr("0.0000000001"), + communityTax: sdkmath.LegacyMustNewDecFromStr("0.9495"), + perSecReward: sdkmath.LegacyZeroDec(), + expectedRate: sdkmath.LegacyMustNewDecFromStr("0.000000000033918612"), // verified manually, rounded would be 0.000000000033918613 + }, + { + name: "inflation only: absurdly high inflation", + totalSupply: sdk.NewInt(857570000e6), + totalBonded: sdk.NewInt(127680000e6), + inflation: sdkmath.LegacyNewDecFromBigInt(hugeInflation), // 2^205. a higher exponent than this overflows. + communityTax: sdkmath.LegacyMustNewDecFromStr("0.9495"), + perSecReward: sdkmath.LegacyZeroDec(), + // https://www.wolframalpha.com/input?i=%282%5E205%29+*+%281+-+0.9495%29+*+%28857570000e6+%2F127680000e6%29 + expectedRate: sdkmath.LegacyMustNewDecFromStr("17441635052648297161685283657196753398188161373334495592570113.113824561403508771"), // verified manually, would round up + }, + // + // + // rewards-only + // + // + { + name: "rps only: no bonded tokens -> 0%", + totalSupply: sdk.NewInt(42), + totalBonded: sdkmath.ZeroInt(), + inflation: sdkmath.LegacyZeroDec(), + communityTax: sdkmath.LegacyZeroDec(), + perSecReward: sdkmath.LegacyMustNewDecFromStr("1234567.123456"), + expectedRate: sdkmath.LegacyZeroDec(), + }, + { + name: "rps only: rps = total bonded / seconds in year -> basically 100%", + totalSupply: sdk.NewInt(12345), + totalBonded: sdkmath.NewInt(1234), + inflation: sdkmath.LegacyZeroDec(), + communityTax: sdkmath.LegacyZeroDec(), + perSecReward: sdkmath.LegacyNewDec(1234).Quo(sdkmath.LegacyNewDec(keeper.SecondsPerYear)), + expectedRate: sdkmath.LegacyMustNewDecFromStr("0.999999999999987228"), // <-- for 6-decimal token, this is negligible rounding + }, + { + name: "rps only: 10M kava / year rewards", + totalSupply: sdk.NewInt(870950000e6), + totalBonded: sdkmath.NewInt(130380000e6), + inflation: sdkmath.LegacyZeroDec(), + communityTax: sdkmath.LegacyZeroDec(), + perSecReward: sdkmath.LegacyMustNewDecFromStr("317097.919837645865043125"), // 10 million kava per year + expectedRate: sdkmath.LegacyMustNewDecFromStr("0.076698880196349133"), // verified manually + }, + { + name: "rps only: 25M kava / year rewards", + totalSupply: sdk.NewInt(870950000e6), + totalBonded: sdkmath.NewInt(130380000e6), + inflation: sdkmath.LegacyZeroDec(), + communityTax: sdkmath.LegacyZeroDec(), + perSecReward: sdkmath.LegacyMustNewDecFromStr("792744.799594114662607813"), // 25 million kava per year + expectedRate: sdkmath.LegacyMustNewDecFromStr("0.191747200490872833"), // verified manually + }, + { + name: "rps only: too much kava / year rewards", + totalSupply: sdk.NewInt(870950000e6), + totalBonded: sdkmath.NewInt(130380000e6), + inflation: sdkmath.LegacyZeroDec(), + communityTax: sdkmath.LegacyZeroDec(), + perSecReward: sdkmath.LegacyNewDecFromBigInt(hugeRewardsPerSec), // 2^230. a higher exponent than this overflows. + // https://www.wolframalpha.com/input?i=%28%28365+*+24+*+3600%29+%2F+130380000e6%29+*+%282%5E230%29 + expectedRate: sdkmath.LegacyMustNewDecFromStr("417344440850566075319340506352140425426634017001007267992800590.431305795858260469"), // verified manually + }, + { + name: "rps only: low kava / year rewards", + totalSupply: sdk.NewInt(870950000e6), + totalBonded: sdkmath.NewInt(130380000e6), + inflation: sdkmath.LegacyZeroDec(), + communityTax: sdkmath.LegacyZeroDec(), + perSecReward: sdkmath.LegacyMustNewDecFromStr("0.1"), + expectedRate: sdkmath.LegacyMustNewDecFromStr("0.000000024187758858"), // verified manually, rounded would be 0.000000024187758859 + }, + { + name: "rps only: 1 ukava / year rewards", + totalSupply: sdk.NewInt(870950000e6), + totalBonded: sdkmath.NewInt(130380000e6), + inflation: sdkmath.LegacyZeroDec(), + communityTax: sdkmath.LegacyZeroDec(), + perSecReward: sdkmath.LegacyMustNewDecFromStr("0.000000031709791984"), // 1 ukava per year + expectedRate: sdkmath.LegacyMustNewDecFromStr("0.000000000000007669"), // verified manually, rounded would be 0.000000000000007670 + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + rewardRate := keeper.CalculateStakingAnnualPercentage( + tc.totalSupply, + tc.totalBonded, + tc.inflation, + tc.communityTax, + tc.perSecReward) + require.Equal(t, tc.expectedRate, rewardRate) + }) + } +} diff --git a/x/community/keeper/staking.go b/x/community/keeper/staking.go index c645faa5..52101a11 100644 --- a/x/community/keeper/staking.go +++ b/x/community/keeper/staking.go @@ -71,7 +71,7 @@ func (k Keeper) PayoutAccumulatedStakingRewards(ctx sdk.Context) { k.SetStakingRewardsState(ctx, state) } -// calculateStakingRewards takees the currentBlockTime, state of last accumulation, rewards per second, and the community pool balance +// calculateStakingRewards takes the currentBlockTime, state of last accumulation, rewards per second, and the community pool balance // in order to calculate the total payout since the last accumulation time. It returns the truncated payout amount and the truncation error. func calculateStakingRewards(currentBlockTime, lastAccumulationTime time.Time, lastTruncationError, stakingRewardsPerSecond, communityPoolBalance sdkmath.LegacyDec) (sdkmath.Int, sdkmath.LegacyDec) { // we get the duration since we last accumulated, then use nanoseconds for full precision available @@ -79,7 +79,7 @@ func calculateStakingRewards(currentBlockTime, lastAccumulationTime time.Time, l nanosecondsSinceLastPayout := sdkmath.LegacyNewDec(durationSinceLastPayout.Nanoseconds()) // We multiply by nanoseconds first, then divide by conversion to avoid loss of precision. - // This multiplicaiton is also tested against very large values so we are safe from overflow + // This multiplication is also tested against very large values so we are safe from overflow // in normal operations. accumulatedRewards := nanosecondsSinceLastPayout.Mul(stakingRewardsPerSecond).QuoInt64(nanosecondsInOneSecond) // Ensure we add any error from previous truncations diff --git a/x/community/testutil/main.go b/x/community/testutil/main.go index c9ec69fd..b802e9dc 100644 --- a/x/community/testutil/main.go +++ b/x/community/testutil/main.go @@ -28,9 +28,8 @@ func (suite *Suite) SetupTest() { tApp := app.NewTestApp() ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()}) - tApp.InitializeFromGenesisStates() + suite.App = tApp.InitializeFromGenesisStates() - suite.App = tApp suite.Ctx = ctx suite.Keeper = tApp.GetCommunityKeeper() communityPoolAddress := tApp.GetAccountKeeper().GetModuleAddress(types.ModuleAccountName) diff --git a/x/community/types/expected_keepers.go b/x/community/types/expected_keepers.go index d4ca0024..c6f5845e 100644 --- a/x/community/types/expected_keepers.go +++ b/x/community/types/expected_keepers.go @@ -1,6 +1,7 @@ package types import ( + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" @@ -22,6 +23,8 @@ type BankKeeper interface { SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error + + GetSupply(ctx sdk.Context, denom string) sdk.Coin } // CdpKeeper defines the contract needed to be fulfilled for cdp dependencies. @@ -45,11 +48,13 @@ type DistributionKeeper interface { SetFeePool(ctx sdk.Context, feePool distrtypes.FeePool) GetParams(ctx sdk.Context) distrtypes.Params SetParams(ctx sdk.Context, params distrtypes.Params) + GetCommunityTax(ctx sdk.Context) sdk.Dec } type MintKeeper interface { GetParams(ctx sdk.Context) (params minttypes.Params) SetParams(ctx sdk.Context, params minttypes.Params) + GetMinter(ctx sdk.Context) (minter minttypes.Minter) } type KavadistKeeper interface { @@ -60,4 +65,5 @@ type KavadistKeeper interface { // StakingKeeper expected interface for the staking keeper type StakingKeeper interface { BondDenom(ctx sdk.Context) string + TotalBondedTokens(ctx sdk.Context) sdkmath.Int } diff --git a/x/community/types/query.pb.go b/x/community/types/query.pb.go index 4c4ce652..61d4993f 100644 --- a/x/community/types/query.pb.go +++ b/x/community/types/query.pb.go @@ -5,7 +5,9 @@ package types import ( context "context" + cosmossdk_io_math "cosmossdk.io/math" fmt "fmt" + _ "github.com/cosmos/cosmos-proto" github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" types "github.com/cosmos/cosmos-sdk/types" _ "github.com/gogo/protobuf/gogoproto" @@ -280,6 +282,82 @@ func (m *QueryTotalBalanceResponse) GetPool() github_com_cosmos_cosmos_sdk_types return nil } +// QueryAnnualizedRewardsRequest defines the request type for querying the annualized rewards. +type QueryAnnualizedRewardsRequest struct { +} + +func (m *QueryAnnualizedRewardsRequest) Reset() { *m = QueryAnnualizedRewardsRequest{} } +func (m *QueryAnnualizedRewardsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryAnnualizedRewardsRequest) ProtoMessage() {} +func (*QueryAnnualizedRewardsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_f236f06c43149273, []int{6} +} +func (m *QueryAnnualizedRewardsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryAnnualizedRewardsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryAnnualizedRewardsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryAnnualizedRewardsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryAnnualizedRewardsRequest.Merge(m, src) +} +func (m *QueryAnnualizedRewardsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryAnnualizedRewardsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryAnnualizedRewardsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryAnnualizedRewardsRequest proto.InternalMessageInfo + +// QueryAnnualizedRewardsResponse defines the response type for querying the annualized rewards. +type QueryAnnualizedRewardsResponse struct { + // staking_rewards is the calculated annualized staking rewards percentage rate + StakingRewards cosmossdk_io_math.LegacyDec `protobuf:"bytes,1,opt,name=staking_rewards,json=stakingRewards,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"staking_rewards"` +} + +func (m *QueryAnnualizedRewardsResponse) Reset() { *m = QueryAnnualizedRewardsResponse{} } +func (m *QueryAnnualizedRewardsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryAnnualizedRewardsResponse) ProtoMessage() {} +func (*QueryAnnualizedRewardsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_f236f06c43149273, []int{7} +} +func (m *QueryAnnualizedRewardsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryAnnualizedRewardsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryAnnualizedRewardsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryAnnualizedRewardsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryAnnualizedRewardsResponse.Merge(m, src) +} +func (m *QueryAnnualizedRewardsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryAnnualizedRewardsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryAnnualizedRewardsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryAnnualizedRewardsResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*QueryParamsRequest)(nil), "kava.community.v1beta1.QueryParamsRequest") proto.RegisterType((*QueryParamsResponse)(nil), "kava.community.v1beta1.QueryParamsResponse") @@ -287,6 +365,8 @@ func init() { proto.RegisterType((*QueryBalanceResponse)(nil), "kava.community.v1beta1.QueryBalanceResponse") proto.RegisterType((*QueryTotalBalanceRequest)(nil), "kava.community.v1beta1.QueryTotalBalanceRequest") proto.RegisterType((*QueryTotalBalanceResponse)(nil), "kava.community.v1beta1.QueryTotalBalanceResponse") + proto.RegisterType((*QueryAnnualizedRewardsRequest)(nil), "kava.community.v1beta1.QueryAnnualizedRewardsRequest") + proto.RegisterType((*QueryAnnualizedRewardsResponse)(nil), "kava.community.v1beta1.QueryAnnualizedRewardsResponse") } func init() { @@ -294,37 +374,45 @@ func init() { } var fileDescriptor_f236f06c43149273 = []byte{ - // 478 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x93, 0x31, 0x6f, 0x13, 0x31, - 0x14, 0xc7, 0x63, 0xda, 0x06, 0xc9, 0x65, 0x32, 0x01, 0xb5, 0xa7, 0xca, 0x29, 0x87, 0xa0, 0x11, - 0x25, 0xe7, 0x26, 0x5d, 0x99, 0x02, 0xec, 0x10, 0x98, 0x58, 0x90, 0xef, 0xb0, 0x8e, 0x53, 0x2f, - 0xf7, 0xae, 0xb1, 0x53, 0x91, 0xb5, 0x1b, 0x03, 0x12, 0x12, 0xdf, 0x80, 0x91, 0x4f, 0x92, 0xb1, - 0x12, 0x0b, 0x13, 0xa0, 0x84, 0x0f, 0x82, 0x6c, 0x3f, 0xa2, 0x04, 0x72, 0x51, 0x98, 0xce, 0x7a, - 0x7e, 0xff, 0xf7, 0xff, 0xf9, 0x6f, 0x1f, 0x0d, 0xcf, 0xe4, 0x85, 0x14, 0x09, 0x0c, 0x06, 0xa3, - 0x22, 0x33, 0x63, 0x71, 0xd1, 0x89, 0x95, 0x91, 0x1d, 0x71, 0x3e, 0x52, 0xc3, 0x71, 0x54, 0x0e, - 0xc1, 0x00, 0xbb, 0x6d, 0x7b, 0xa2, 0x79, 0x4f, 0x84, 0x3d, 0x01, 0x4f, 0x40, 0x0f, 0x40, 0x8b, - 0x58, 0x6a, 0x35, 0x17, 0x26, 0x90, 0x15, 0x5e, 0x17, 0x34, 0x52, 0x48, 0xc1, 0x2d, 0x85, 0x5d, - 0x61, 0xf5, 0x20, 0x05, 0x48, 0x73, 0x25, 0x64, 0x99, 0x09, 0x59, 0x14, 0x60, 0xa4, 0xc9, 0xa0, - 0xd0, 0xb8, 0x7b, 0xb7, 0x82, 0xa7, 0x94, 0x43, 0x39, 0xc0, 0xa6, 0xb0, 0x41, 0xd9, 0x73, 0xcb, - 0xf7, 0xcc, 0x15, 0xfb, 0xea, 0x7c, 0xa4, 0xb4, 0x09, 0x5f, 0xd0, 0x9b, 0x4b, 0x55, 0x5d, 0x42, - 0xa1, 0x15, 0x7b, 0x44, 0xeb, 0x5e, 0xbc, 0x47, 0x0e, 0x49, 0x6b, 0xb7, 0xcb, 0xa3, 0xd5, 0xc7, - 0x89, 0xbc, 0xae, 0xb7, 0x3d, 0xf9, 0xde, 0xac, 0xf5, 0x51, 0x13, 0xde, 0xc2, 0xa1, 0x3d, 0x99, - 0xcb, 0x22, 0x51, 0x7f, 0xbc, 0xc6, 0xb4, 0xb1, 0x5c, 0x46, 0x33, 0x49, 0x77, 0x6c, 0x00, 0xd6, - 0x6b, 0xab, 0xb5, 0xdb, 0xdd, 0x8f, 0x7c, 0x44, 0x91, 0x8d, 0x68, 0x6e, 0xf4, 0x18, 0xb2, 0xa2, - 0x77, 0x62, 0x6d, 0xbe, 0xfc, 0x68, 0xb6, 0xd2, 0xcc, 0xbc, 0x1d, 0xc5, 0x96, 0x47, 0x60, 0x9e, - 0xfe, 0xd3, 0xd6, 0x6f, 0xce, 0x84, 0x19, 0x97, 0x4a, 0x3b, 0x81, 0xee, 0xfb, 0xc9, 0x61, 0x40, - 0xf7, 0x9c, 0xf5, 0x4b, 0x30, 0x32, 0xff, 0x0b, 0xeb, 0x92, 0xd0, 0xfd, 0x15, 0x9b, 0x08, 0xa7, - 0xe8, 0x76, 0x09, 0x90, 0x23, 0xdb, 0xc1, 0x4a, 0xb6, 0x27, 0x2a, 0x71, 0x78, 0xa7, 0x88, 0x77, - 0xbc, 0x01, 0x1e, 0x6a, 0x74, 0xdf, 0x8d, 0xef, 0x4e, 0xb6, 0xe8, 0x8e, 0x83, 0x60, 0xef, 0x09, - 0xad, 0xfb, 0x54, 0xd9, 0x83, 0xaa, 0xd4, 0xff, 0xbd, 0xc8, 0xe0, 0x78, 0xa3, 0x5e, 0x7f, 0xa8, - 0xf0, 0xfe, 0xe5, 0xd7, 0x5f, 0x9f, 0xae, 0x1d, 0x32, 0x2e, 0xd6, 0xbe, 0x1c, 0xf6, 0x81, 0xd0, - 0xeb, 0x18, 0x08, 0x5b, 0x6f, 0xb0, 0x9c, 0x69, 0xf0, 0x70, 0xb3, 0x66, 0xc4, 0x39, 0x72, 0x38, - 0x77, 0x58, 0xb3, 0x0a, 0x27, 0x46, 0x86, 0xcf, 0x84, 0xde, 0x58, 0xbc, 0x25, 0x76, 0xb2, 0xd6, - 0x67, 0xc5, 0x6d, 0x07, 0x9d, 0xff, 0x50, 0x20, 0x5e, 0xdb, 0xe1, 0x1d, 0xb1, 0x7b, 0x55, 0x78, - 0xc6, 0xaa, 0x5e, 0x23, 0x64, 0xef, 0xe9, 0x64, 0xca, 0xc9, 0xd5, 0x94, 0x93, 0x9f, 0x53, 0x4e, - 0x3e, 0xce, 0x78, 0xed, 0x6a, 0xc6, 0x6b, 0xdf, 0x66, 0xbc, 0xf6, 0x6a, 0xf1, 0x5d, 0xd8, 0x51, - 0xed, 0x5c, 0xc6, 0xda, 0x0f, 0x7d, 0xb7, 0x30, 0xd6, 0x3d, 0x90, 0xb8, 0xee, 0x7e, 0xdb, 0xd3, - 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x62, 0x63, 0xf8, 0x10, 0x6d, 0x04, 0x00, 0x00, + // 606 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x94, 0x31, 0x6f, 0xd3, 0x40, + 0x14, 0xc7, 0x63, 0x68, 0x8b, 0xb8, 0x22, 0x10, 0x47, 0x40, 0x8d, 0x29, 0x4e, 0x31, 0x82, 0x46, + 0x6d, 0x63, 0x37, 0xa9, 0x60, 0x62, 0x21, 0x84, 0x8d, 0x01, 0x0c, 0x53, 0x97, 0xe8, 0xec, 0x9c, + 0x5c, 0x2b, 0x8e, 0xcf, 0xcd, 0x5d, 0x0a, 0x41, 0x2c, 0x74, 0x63, 0x40, 0x42, 0xe2, 0x1b, 0x30, + 0x32, 0x23, 0x3e, 0x43, 0xc7, 0x0a, 0x16, 0xc4, 0x50, 0x50, 0xc2, 0x07, 0x41, 0x77, 0xf7, 0x12, + 0x25, 0x34, 0x8e, 0xd2, 0x29, 0xf1, 0xbb, 0xf7, 0x7f, 0xff, 0xdf, 0xbd, 0xf7, 0x6c, 0x64, 0xb7, + 0xc8, 0x01, 0x71, 0x03, 0xd6, 0x6e, 0x77, 0x93, 0x48, 0xf4, 0xdc, 0x83, 0x8a, 0x4f, 0x05, 0xa9, + 0xb8, 0xfb, 0x5d, 0xda, 0xe9, 0x39, 0x69, 0x87, 0x09, 0x86, 0x6f, 0xc8, 0x1c, 0x67, 0x94, 0xe3, + 0x40, 0x8e, 0x69, 0x05, 0x8c, 0xb7, 0x19, 0x77, 0x7d, 0xc2, 0xe9, 0x48, 0x18, 0xb0, 0x28, 0xd1, + 0x3a, 0xb3, 0xa0, 0xcf, 0x1b, 0xea, 0xc9, 0xd5, 0x0f, 0x70, 0x94, 0x0f, 0x59, 0xc8, 0x74, 0x5c, + 0xfe, 0x83, 0xe8, 0x6a, 0xc8, 0x58, 0x18, 0x53, 0x97, 0xa4, 0x91, 0x4b, 0x92, 0x84, 0x09, 0x22, + 0x22, 0x96, 0x0c, 0x35, 0x77, 0x32, 0x50, 0x53, 0xd2, 0x21, 0x6d, 0x48, 0xb2, 0xf3, 0x08, 0x3f, + 0x97, 0xe8, 0xcf, 0x54, 0xd0, 0xa3, 0xfb, 0x5d, 0xca, 0x85, 0xfd, 0x02, 0x5d, 0x9b, 0x88, 0xf2, + 0x94, 0x25, 0x9c, 0xe2, 0x87, 0x68, 0x49, 0x8b, 0x57, 0x8c, 0x35, 0xa3, 0xb4, 0x5c, 0xb5, 0x9c, + 0xe9, 0x37, 0x75, 0xb4, 0xae, 0xb6, 0x70, 0x74, 0x52, 0xcc, 0x79, 0xa0, 0xb1, 0xaf, 0x43, 0xd1, + 0x1a, 0x89, 0x49, 0x12, 0xd0, 0xa1, 0x57, 0x0f, 0xe5, 0x27, 0xc3, 0x60, 0x46, 0xd0, 0xa2, 0xec, + 0x8d, 0xf4, 0x3a, 0x5f, 0x5a, 0xae, 0x16, 0x1c, 0x68, 0x88, 0xec, 0xde, 0xc8, 0xe8, 0x31, 0x8b, + 0x92, 0xda, 0xb6, 0xb4, 0xf9, 0xf2, 0xbb, 0x58, 0x0a, 0x23, 0xb1, 0xd7, 0xf5, 0x25, 0x0f, 0x74, + 0x0f, 0x7e, 0xca, 0xbc, 0xd9, 0x72, 0x45, 0x2f, 0xa5, 0x5c, 0x09, 0xb8, 0xa7, 0x2b, 0xdb, 0x26, + 0x5a, 0x51, 0xd6, 0x2f, 0x99, 0x20, 0xf1, 0x7f, 0x58, 0x87, 0x06, 0x2a, 0x4c, 0x39, 0x04, 0x38, + 0x8a, 0x16, 0x52, 0xc6, 0x62, 0x60, 0x5b, 0x9d, 0xca, 0x56, 0xa7, 0x81, 0xc2, 0xdb, 0x01, 0xbc, + 0xcd, 0x39, 0xf0, 0x40, 0xc3, 0x3d, 0x55, 0xde, 0x2e, 0xa2, 0x5b, 0x8a, 0xe1, 0x51, 0x92, 0x74, + 0x49, 0x1c, 0xbd, 0xa1, 0x4d, 0x8f, 0xbe, 0x22, 0x9d, 0xe6, 0x68, 0x50, 0x6f, 0x91, 0x95, 0x95, + 0x00, 0xa4, 0xbb, 0xe8, 0x0a, 0x17, 0xa4, 0x15, 0x25, 0x61, 0xa3, 0xa3, 0x8f, 0xd4, 0xf0, 0x2e, + 0xd6, 0x2a, 0x12, 0xeb, 0xd7, 0x49, 0xf1, 0xa6, 0x86, 0xe0, 0xcd, 0x96, 0x13, 0x31, 0xb7, 0x4d, + 0xc4, 0x9e, 0xf3, 0x94, 0x86, 0x24, 0xe8, 0xd5, 0x69, 0xf0, 0xfd, 0x6b, 0x19, 0xc1, 0xd5, 0xea, + 0x34, 0xf0, 0x2e, 0x43, 0x25, 0xf0, 0xa8, 0xbe, 0x5b, 0x44, 0x8b, 0xca, 0x1e, 0xbf, 0x37, 0xd0, + 0x92, 0x1e, 0x3a, 0xde, 0xc8, 0x5a, 0x8a, 0xd3, 0x7b, 0x66, 0x6e, 0xce, 0x95, 0xab, 0x6f, 0x62, + 0xdf, 0x3b, 0xfc, 0xf1, 0xf7, 0xd3, 0xb9, 0x35, 0x6c, 0xb9, 0x33, 0x17, 0x1b, 0x7f, 0x30, 0xd0, + 0x05, 0x98, 0x17, 0x9e, 0x6d, 0x30, 0x39, 0x72, 0x73, 0x6b, 0xbe, 0x64, 0xc0, 0x59, 0x57, 0x38, + 0xb7, 0x71, 0x31, 0x0b, 0xc7, 0x07, 0x86, 0xcf, 0x06, 0xba, 0x34, 0xbe, 0x44, 0x78, 0x7b, 0xa6, + 0xcf, 0x94, 0x65, 0x34, 0x2b, 0x67, 0x50, 0x00, 0x5e, 0x59, 0xe1, 0xad, 0xe3, 0xbb, 0x59, 0x78, + 0x42, 0xaa, 0x1a, 0x43, 0xc8, 0x6f, 0x06, 0xba, 0x7a, 0x6a, 0x89, 0xf0, 0xfd, 0x99, 0xbe, 0x59, + 0x5b, 0x69, 0x3e, 0x38, 0xab, 0x0c, 0x98, 0xab, 0x8a, 0x79, 0x0b, 0x6f, 0x64, 0x31, 0x93, 0x91, + 0x74, 0xb8, 0xcc, 0xb5, 0x27, 0x47, 0x7d, 0xcb, 0x38, 0xee, 0x5b, 0xc6, 0x9f, 0xbe, 0x65, 0x7c, + 0x1c, 0x58, 0xb9, 0xe3, 0x81, 0x95, 0xfb, 0x39, 0xb0, 0x72, 0xbb, 0xe3, 0xef, 0x9b, 0xac, 0x57, + 0x8e, 0x89, 0xcf, 0x75, 0xe5, 0xd7, 0x63, 0xb5, 0xd5, 0x8b, 0xe7, 0x2f, 0xa9, 0xcf, 0xe1, 0xce, + 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x2e, 0xb3, 0x12, 0x9f, 0xe0, 0x05, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -346,6 +434,9 @@ type QueryClient interface { // TotalBalance queries the balance of all coins, including x/distribution, // x/community, and supplied balances. TotalBalance(ctx context.Context, in *QueryTotalBalanceRequest, opts ...grpc.CallOption) (*QueryTotalBalanceResponse, error) + // AnnualizedRewards calculates and returns the current annualized reward percentages, + // like staking rewards, for the chain. + AnnualizedRewards(ctx context.Context, in *QueryAnnualizedRewardsRequest, opts ...grpc.CallOption) (*QueryAnnualizedRewardsResponse, error) } type queryClient struct { @@ -383,6 +474,15 @@ func (c *queryClient) TotalBalance(ctx context.Context, in *QueryTotalBalanceReq return out, nil } +func (c *queryClient) AnnualizedRewards(ctx context.Context, in *QueryAnnualizedRewardsRequest, opts ...grpc.CallOption) (*QueryAnnualizedRewardsResponse, error) { + out := new(QueryAnnualizedRewardsResponse) + err := c.cc.Invoke(ctx, "/kava.community.v1beta1.Query/AnnualizedRewards", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { // Params queires the module params. @@ -392,6 +492,9 @@ type QueryServer interface { // TotalBalance queries the balance of all coins, including x/distribution, // x/community, and supplied balances. TotalBalance(context.Context, *QueryTotalBalanceRequest) (*QueryTotalBalanceResponse, error) + // AnnualizedRewards calculates and returns the current annualized reward percentages, + // like staking rewards, for the chain. + AnnualizedRewards(context.Context, *QueryAnnualizedRewardsRequest) (*QueryAnnualizedRewardsResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -407,6 +510,9 @@ func (*UnimplementedQueryServer) Balance(ctx context.Context, req *QueryBalanceR func (*UnimplementedQueryServer) TotalBalance(ctx context.Context, req *QueryTotalBalanceRequest) (*QueryTotalBalanceResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method TotalBalance not implemented") } +func (*UnimplementedQueryServer) AnnualizedRewards(ctx context.Context, req *QueryAnnualizedRewardsRequest) (*QueryAnnualizedRewardsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AnnualizedRewards not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -466,6 +572,24 @@ func _Query_TotalBalance_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } +func _Query_AnnualizedRewards_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryAnnualizedRewardsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).AnnualizedRewards(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/kava.community.v1beta1.Query/AnnualizedRewards", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).AnnualizedRewards(ctx, req.(*QueryAnnualizedRewardsRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "kava.community.v1beta1.Query", HandlerType: (*QueryServer)(nil), @@ -482,6 +606,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "TotalBalance", Handler: _Query_TotalBalance_Handler, }, + { + MethodName: "AnnualizedRewards", + Handler: _Query_AnnualizedRewards_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "kava/community/v1beta1/query.proto", @@ -663,6 +791,62 @@ func (m *QueryTotalBalanceResponse) MarshalToSizedBuffer(dAtA []byte) (int, erro return len(dAtA) - i, nil } +func (m *QueryAnnualizedRewardsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryAnnualizedRewardsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryAnnualizedRewardsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryAnnualizedRewardsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryAnnualizedRewardsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryAnnualizedRewardsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.StakingRewards.Size() + i -= size + if _, err := m.StakingRewards.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { offset -= sovQuery(v) base := offset @@ -742,6 +926,26 @@ func (m *QueryTotalBalanceResponse) Size() (n int) { return n } +func (m *QueryAnnualizedRewardsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryAnnualizedRewardsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.StakingRewards.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -1149,6 +1353,140 @@ func (m *QueryTotalBalanceResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryAnnualizedRewardsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryAnnualizedRewardsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryAnnualizedRewardsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryAnnualizedRewardsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryAnnualizedRewardsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryAnnualizedRewardsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingRewards", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.StakingRewards.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/community/types/query.pb.gw.go b/x/community/types/query.pb.gw.go index 29ffc3f2..289b1988 100644 --- a/x/community/types/query.pb.gw.go +++ b/x/community/types/query.pb.gw.go @@ -87,6 +87,24 @@ func local_request_Query_TotalBalance_0(ctx context.Context, marshaler runtime.M } +func request_Query_AnnualizedRewards_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryAnnualizedRewardsRequest + var metadata runtime.ServerMetadata + + msg, err := client.AnnualizedRewards(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_AnnualizedRewards_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryAnnualizedRewardsRequest + var metadata runtime.ServerMetadata + + msg, err := server.AnnualizedRewards(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -162,6 +180,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_AnnualizedRewards_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_AnnualizedRewards_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_AnnualizedRewards_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -263,6 +304,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_AnnualizedRewards_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_AnnualizedRewards_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_AnnualizedRewards_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -272,6 +333,8 @@ var ( pattern_Query_Balance_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"kava", "community", "v1beta1", "balance"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_TotalBalance_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"kava", "community", "v1beta1", "total_balance"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_AnnualizedRewards_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"kava", "community", "v1beta1", "annualized_rewards"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( @@ -280,4 +343,6 @@ var ( forward_Query_Balance_0 = runtime.ForwardResponseMessage forward_Query_TotalBalance_0 = runtime.ForwardResponseMessage + + forward_Query_AnnualizedRewards_0 = runtime.ForwardResponseMessage )