diff --git a/.github/workflows/ci-default.yml b/.github/workflows/ci-default.yml index 1c4105b7..b1d68f52 100644 --- a/.github/workflows/ci-default.yml +++ b/.github/workflows/ci-default.yml @@ -33,37 +33,38 @@ jobs: run: make test - name: run e2e tests run: make docker-build test-e2e - validate-internal-testnet-genesis: - runs-on: ubuntu-latest - steps: - - name: checkout repo from current commit - uses: actions/checkout@v3 - - name: save version of kava that will be deployed if this pr is merged - id: kava-version - run: | - echo "KAVA_VERSION=$(cat ./ci/env/kava-internal-testnet/KAVA.VERSION)" >> $GITHUB_OUTPUT - - name: checkout repo from master - uses: actions/checkout@v3 - with: - ref: master - - name: checkout version of kava that will be deployed if this pr is merged - run: | - git pull -p - git checkout $KAVA_VERSION - env: - KAVA_VERSION: ${{ steps.kava-version.outputs.KAVA_VERSION }} - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: "1.20" - check-latest: true - cache: true - - name: build kava cli - run: make install - - name: checkout repo from current commit to validate current branch's genesis - uses: actions/checkout@v3 - - name: validate testnet genesis - run: kava validate-genesis ci/env/kava-internal-testnet/genesis.json + # this is only applicable for PRs based on master. Disabling for this release branch. + # validate-internal-testnet-genesis: + # runs-on: ubuntu-latest + # steps: + # - name: checkout repo from current commit + # uses: actions/checkout@v3 + # - name: save version of kava that will be deployed if this pr is merged + # id: kava-version + # run: | + # echo "KAVA_VERSION=$(cat ./ci/env/kava-internal-testnet/KAVA.VERSION)" >> $GITHUB_OUTPUT + # - name: checkout repo from master + # uses: actions/checkout@v3 + # with: + # ref: master + # - name: checkout version of kava that will be deployed if this pr is merged + # run: | + # git pull -p + # git checkout $KAVA_VERSION + # env: + # KAVA_VERSION: ${{ steps.kava-version.outputs.KAVA_VERSION }} + # - name: Set up Go + # uses: actions/setup-go@v3 + # with: + # go-version: "1.20" + # check-latest: true + # cache: true + # - name: build kava cli + # run: make install + # - name: checkout repo from current commit to validate current branch's genesis + # uses: actions/checkout@v3 + # - name: validate testnet genesis + # run: kava validate-genesis ci/env/kava-internal-testnet/genesis.json validate-protonet-genesis: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml index 378deadc..01b0b60c 100644 --- a/.github/workflows/ci-lint.yml +++ b/.github/workflows/ci-lint.yml @@ -14,3 +14,4 @@ jobs: with: github_token: ${{ secrets.github_token }} reporter: github-pr-review + golangci_lint_flags: --timeout 10m diff --git a/CHANGELOG.md b/CHANGELOG.md index 210be41e..e21cc7ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features - (metrics) [#1668] Adds non-state breaking x/metrics module for custom telemetry. - (metrics) [#1669] Add performance timing metrics to all Begin/EndBlockers +- (community) [#1751] Add `AnnualizedRewards` query endpoint ## [v0.24.0](https://github.com/Kava-Labs/kava/releases/tag/v0.24.0) @@ -274,6 +275,7 @@ the [changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.38.4/CHANGELOG.md). - [#257](https://github.com/Kava-Labs/kava/pulls/257) Include scripts to run large-scale simulations remotely using aws-batch +[#1751]: https://github.com/Kava-Labs/kava/pull/1751 [#1669]: https://github.com/Kava-Labs/kava/pull/1669 [#1668]: https://github.com/Kava-Labs/kava/pull/1668 [#1624]: https://github.com/Kava-Labs/kava/pull/1624 diff --git a/app/app.go b/app/app.go index 38177c4d..6c298ed3 100644 --- a/app/app.go +++ b/app/app.go @@ -641,14 +641,6 @@ func NewApp( &app.distrKeeper, ) - // x/community's deposit/withdraw to lend proposals depend on hard keeper. - app.communityKeeper = communitykeeper.NewKeeper( - app.accountKeeper, - app.bankKeeper, - &cdpKeeper, - app.distrKeeper, - &hardKeeper, - ) app.kavadistKeeper = kavadistkeeper.NewKeeper( appCodec, keys[kavadisttypes.StoreKey], @@ -669,6 +661,17 @@ func NewApp( authtypes.FeeCollectorName, ) + // x/community's deposit/withdraw to lend proposals depend on hard keeper. + app.communityKeeper = communitykeeper.NewKeeper( + app.accountKeeper, + app.bankKeeper, + &cdpKeeper, + app.distrKeeper, + &hardKeeper, + &app.mintKeeper, + app.stakingKeeper, + ) + app.incentiveKeeper = incentivekeeper.NewKeeper( appCodec, keys[incentivetypes.StoreKey], diff --git a/docs/core/proto-docs.md b/docs/core/proto-docs.md index 3eca9d59..b0fe3771 100644 --- a/docs/core/proto-docs.md +++ b/docs/core/proto-docs.md @@ -188,6 +188,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) - [QueryTotalBalanceRequest](#kava.community.v1beta1.QueryTotalBalanceRequest) @@ -2977,6 +2979,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 @@ -3043,6 +3070,7 @@ Query defines the gRPC querier service for x/community. | ----------- | ------------ | ------------- | ------------| ------- | -------- | | `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 53feab6b..cd066d68 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"; @@ -19,6 +20,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"; + } } // QueryBalanceRequest defines the request type for querying x/community balance. @@ -44,3 +51,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 20500794..bef2c2f8 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" "github.com/kava-labs/kava/x/community/types" ) @@ -45,3 +46,33 @@ func (s queryServer) TotalBalance( Pool: totalBalance, }, nil } + +// AnnualizedRewards calculates the annualized rewards for the chain. +// This method is backported from v0.25.x to allow for early migration. +func (s queryServer) AnnualizedRewards( + c context.Context, + req *types.QueryAnnualizedRewardsRequest, +) (*types.QueryAnnualizedRewardsResponse, error) { + ctx := sdk.UnwrapSDKContext(c) + + bondDenom := s.keeper.stakingKeeper.BondDenom(ctx) + + totalSupply := s.keeper.bankKeeper.GetSupply(ctx, bondDenom).Amount + totalBonded := s.keeper.stakingKeeper.TotalBondedTokens(ctx) + rewardsPerSecond := sdkmath.LegacyZeroDec() // always zero. this method is backported from v0.25.x to allow for early migration. + // 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 a40b0e76..369628a6 100644 --- a/x/community/keeper/grpc_query_test.go +++ b/x/community/keeper/grpc_query_test.go @@ -9,18 +9,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)) @@ -144,3 +146,105 @@ func (suite *grpcQueryTestSuite) TestGrpcQueryTotalBalance() { }) } } + +// backported from v0.25.x. Does not actually use `rewardsPerSec` because concept does not exist. +// 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() { + 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: "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"), + }, + } + + 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 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/keeper.go b/x/community/keeper/keeper.go index 1148d9d6..aafa496e 100644 --- a/x/community/keeper/keeper.go +++ b/x/community/keeper/keeper.go @@ -16,12 +16,22 @@ type Keeper struct { distrKeeper types.DistributionKeeper hardKeeper types.HardKeeper moduleAddress sdk.AccAddress + mintKeeper types.MintKeeper + stakingKeeper types.StakingKeeper legacyCommunityPoolAddress sdk.AccAddress } // NewKeeper creates a new community Keeper instance -func NewKeeper(ak types.AccountKeeper, bk types.BankKeeper, ck types.CdpKeeper, dk types.DistributionKeeper, hk types.HardKeeper) Keeper { +func NewKeeper( + ak types.AccountKeeper, + bk types.BankKeeper, + ck types.CdpKeeper, + dk types.DistributionKeeper, + hk types.HardKeeper, + mk types.MintKeeper, + sk types.StakingKeeper, +) Keeper { // ensure community module account is set addr := ak.GetModuleAddress(types.ModuleAccountName) if addr == nil { @@ -37,6 +47,8 @@ func NewKeeper(ak types.AccountKeeper, bk types.BankKeeper, ck types.CdpKeeper, cdpKeeper: ck, distrKeeper: dk, hardKeeper: hk, + mintKeeper: mk, + stakingKeeper: sk, moduleAddress: addr, legacyCommunityPoolAddress: legacyAddr, 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/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 f642dfe0..74920a27 100644 --- a/x/community/types/expected_keepers.go +++ b/x/community/types/expected_keepers.go @@ -1,8 +1,10 @@ package types import ( + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" ) // AccountKeeper defines the contract required for account APIs. @@ -17,6 +19,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 + + GetSupply(ctx sdk.Context, denom string) sdk.Coin } // CdpKeeper defines the contract needed to be fulfilled for cdp dependencies. @@ -36,4 +40,15 @@ type DistributionKeeper interface { DistributeFromFeePool(ctx sdk.Context, amount sdk.Coins, receiveAddr sdk.AccAddress) error FundCommunityPool(ctx sdk.Context, amount sdk.Coins, sender sdk.AccAddress) error GetFeePoolCommunityCoins(ctx sdk.Context) sdk.DecCoins + GetCommunityTax(ctx sdk.Context) sdk.Dec +} + +type MintKeeper interface { + GetMinter(ctx sdk.Context) (minter minttypes.Minter) +} + +// 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 5da1aae9..2de07ce5 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" @@ -197,11 +199,89 @@ 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{4} +} +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{5} +} +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((*QueryBalanceRequest)(nil), "kava.community.v1beta1.QueryBalanceRequest") 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() { @@ -209,33 +289,41 @@ func init() { } var fileDescriptor_f236f06c43149273 = []byte{ - // 411 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0x4f, 0x8e, 0xd3, 0x30, - 0x14, 0xc6, 0x93, 0x42, 0x41, 0x32, 0xac, 0x42, 0x41, 0x6d, 0x54, 0xa5, 0x10, 0x09, 0xb5, 0x52, - 0xa9, 0xdd, 0x3f, 0x37, 0x28, 0x70, 0x00, 0x2a, 0x56, 0x6c, 0x90, 0x13, 0xac, 0x10, 0x35, 0xf5, - 0x4b, 0x6b, 0xa7, 0x22, 0xdb, 0xee, 0x91, 0x90, 0xb8, 0x01, 0x4b, 0xce, 0xc0, 0x01, 0xba, 0xac, - 0xc4, 0x66, 0x56, 0x33, 0xa3, 0x76, 0x0e, 0x32, 0xb2, 0xe3, 0xa9, 0x3a, 0xa3, 0x74, 0xd4, 0x59, - 0xd9, 0xb2, 0xdf, 0xf7, 0x7d, 0x3f, 0xbf, 0x67, 0xe4, 0x4f, 0xe9, 0x92, 0x92, 0x10, 0x66, 0xb3, - 0x8c, 0xc7, 0x32, 0x27, 0xcb, 0x41, 0xc0, 0x24, 0x1d, 0x90, 0x79, 0xc6, 0x16, 0x39, 0x4e, 0x17, - 0x20, 0xc1, 0x79, 0xa5, 0x6a, 0xf0, 0xbe, 0x06, 0x9b, 0x1a, 0xd7, 0x0b, 0x41, 0xcc, 0x40, 0x90, - 0x80, 0x0a, 0xb6, 0x17, 0x86, 0x10, 0xf3, 0x42, 0xe7, 0xd6, 0x22, 0x88, 0x40, 0x6f, 0x89, 0xda, - 0x99, 0xd3, 0x66, 0x04, 0x10, 0x25, 0x8c, 0xd0, 0x34, 0x26, 0x94, 0x73, 0x90, 0x54, 0xc6, 0xc0, - 0x45, 0x71, 0xeb, 0xbf, 0x44, 0x2f, 0x3e, 0xa9, 0xe8, 0x31, 0x4d, 0x28, 0x0f, 0xd9, 0x84, 0xcd, - 0x33, 0x26, 0xa4, 0x9f, 0xa3, 0xda, 0xed, 0x63, 0x91, 0x02, 0x17, 0xcc, 0xa1, 0xa8, 0xaa, 0x02, - 0x45, 0xdd, 0x7e, 0xfd, 0xa8, 0xf3, 0x6c, 0xd8, 0xc0, 0x05, 0x12, 0x56, 0x48, 0x37, 0x9c, 0xf8, - 0x3d, 0xc4, 0x7c, 0xdc, 0x5f, 0x9f, 0xb7, 0xac, 0xbf, 0x17, 0xad, 0x4e, 0x14, 0xcb, 0xef, 0x59, - 0xa0, 0x9e, 0x43, 0x0c, 0x7f, 0xb1, 0xf4, 0xc4, 0xb7, 0x29, 0x91, 0x79, 0xca, 0x84, 0x16, 0x88, - 0x49, 0xe1, 0xec, 0xbb, 0xa8, 0xae, 0xa3, 0x3f, 0x83, 0xa4, 0xc9, 0x1d, 0xac, 0x95, 0x8d, 0x1a, - 0x25, 0x97, 0x06, 0x8e, 0xa1, 0xc7, 0x29, 0x40, 0x62, 0xd8, 0x9a, 0xa5, 0x6c, 0x1f, 0x58, 0xa8, - 0xf1, 0x46, 0x06, 0xaf, 0x7b, 0x02, 0x9e, 0xd1, 0x88, 0x89, 0xb6, 0x1f, 0xfe, 0xab, 0xa0, 0xaa, - 0x86, 0x70, 0x7e, 0xda, 0xe8, 0xa9, 0x81, 0x70, 0xba, 0xb8, 0x7c, 0x6a, 0xb8, 0xa4, 0xbd, 0xee, - 0xbb, 0xd3, 0x8a, 0x8b, 0x77, 0xf9, 0xed, 0xd5, 0xff, 0xab, 0xdf, 0x95, 0x37, 0x4e, 0x8b, 0x1c, - 0xf9, 0x3c, 0x81, 0x61, 0xf8, 0x63, 0xa3, 0xe7, 0x87, 0x9d, 0x71, 0xfa, 0xf7, 0xe6, 0x94, 0x74, - 0xd8, 0x1d, 0x3c, 0x40, 0x61, 0xf0, 0x7a, 0x1a, 0xaf, 0xed, 0xbc, 0x3d, 0x86, 0x27, 0x95, 0xea, - 0xab, 0x81, 0x1c, 0x7f, 0x5c, 0x6f, 0x3d, 0x7b, 0xb3, 0xf5, 0xec, 0xcb, 0xad, 0x67, 0xff, 0xda, - 0x79, 0xd6, 0x66, 0xe7, 0x59, 0x67, 0x3b, 0xcf, 0xfa, 0x72, 0x38, 0x0b, 0x65, 0xd5, 0x4b, 0x68, - 0x20, 0x0a, 0xd3, 0x1f, 0x07, 0xb6, 0x7a, 0x28, 0xc1, 0x13, 0xfd, 0x7f, 0x47, 0xd7, 0x01, 0x00, - 0x00, 0xff, 0xff, 0x67, 0x9d, 0xae, 0xd2, 0x51, 0x03, 0x00, 0x00, + // 537 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x94, 0x31, 0x6f, 0xd3, 0x40, + 0x14, 0xc7, 0x73, 0x94, 0x82, 0x38, 0x10, 0x08, 0x53, 0x50, 0x62, 0x8a, 0x5d, 0x2c, 0xa1, 0x46, + 0xb4, 0xf1, 0x35, 0xa9, 0x60, 0x27, 0x84, 0x8d, 0x05, 0x8b, 0xa9, 0x4b, 0x74, 0x76, 0x4e, 0xae, + 0x15, 0xe7, 0xce, 0xcd, 0x9d, 0x0b, 0x46, 0x4c, 0xdd, 0x91, 0x90, 0xf8, 0x06, 0x8c, 0xcc, 0x88, + 0xcf, 0xd0, 0xb1, 0x82, 0x05, 0x31, 0x14, 0x94, 0xf0, 0x15, 0xd8, 0x91, 0xcf, 0x2f, 0x51, 0xa0, + 0x76, 0xd5, 0x4e, 0xf6, 0xdd, 0x7b, 0xff, 0xf7, 0x7e, 0xcf, 0xef, 0x2f, 0x63, 0x67, 0x48, 0xf7, + 0x29, 0x09, 0xc4, 0x68, 0x94, 0xf2, 0x48, 0x65, 0x64, 0xbf, 0xed, 0x33, 0x45, 0xdb, 0x64, 0x2f, + 0x65, 0xe3, 0xcc, 0x4d, 0xc6, 0x42, 0x09, 0xe3, 0x4e, 0x9e, 0xe3, 0xce, 0x73, 0x5c, 0xc8, 0x31, + 0xad, 0x40, 0xc8, 0x91, 0x90, 0xc4, 0xa7, 0x92, 0xcd, 0x85, 0x81, 0x88, 0x78, 0xa1, 0x33, 0x1b, + 0x45, 0xbc, 0xaf, 0x4f, 0xa4, 0x38, 0x40, 0x68, 0x25, 0x14, 0xa1, 0x28, 0xee, 0xf3, 0x37, 0xb8, + 0x5d, 0x0d, 0x85, 0x08, 0x63, 0x46, 0x68, 0x12, 0x11, 0xca, 0xb9, 0x50, 0x54, 0x45, 0x82, 0x83, + 0xc6, 0xb9, 0x8d, 0x6f, 0xbd, 0xc8, 0xa9, 0xba, 0x34, 0xa6, 0x3c, 0x60, 0x1e, 0xdb, 0x4b, 0x99, + 0x54, 0x4e, 0x86, 0x57, 0xfe, 0xbd, 0x96, 0x89, 0xe0, 0x92, 0x19, 0x14, 0x2f, 0xe7, 0x2c, 0xb2, + 0x8e, 0xd6, 0x96, 0x9a, 0x57, 0x3b, 0x0d, 0x17, 0x00, 0x72, 0xda, 0xd9, 0x08, 0xee, 0x53, 0x11, + 0xf1, 0xee, 0xd6, 0xe1, 0xb1, 0x5d, 0xfb, 0xf4, 0xd3, 0x6e, 0x86, 0x91, 0xda, 0x4d, 0xfd, 0x7c, + 0x52, 0xa0, 0x85, 0x47, 0x4b, 0x0e, 0x86, 0x44, 0x65, 0x09, 0x93, 0x5a, 0x20, 0xbd, 0xa2, 0xb2, + 0x63, 0xe2, 0xba, 0x6e, 0xfd, 0x52, 0x28, 0x1a, 0xff, 0x87, 0x75, 0x80, 0x70, 0xa3, 0x24, 0x08, + 0x70, 0x0c, 0x5f, 0x4c, 0x84, 0x88, 0x81, 0x6d, 0xb5, 0x94, 0xad, 0xc7, 0x02, 0x8d, 0xb7, 0x0d, + 0x78, 0x1b, 0x67, 0xc0, 0x03, 0x8d, 0xf4, 0x74, 0x79, 0xc7, 0xc6, 0xf7, 0x34, 0xc3, 0x13, 0xce, + 0x53, 0x1a, 0x47, 0x6f, 0xd8, 0xc0, 0x63, 0xaf, 0xe8, 0x78, 0x20, 0x67, 0x94, 0x6f, 0xb1, 0x55, + 0x95, 0x00, 0xa4, 0x3b, 0xf8, 0x86, 0x54, 0x74, 0x18, 0xf1, 0xb0, 0x3f, 0x2e, 0x42, 0x75, 0xb4, + 0x86, 0x9a, 0x57, 0xba, 0xed, 0x1c, 0xeb, 0xc7, 0xb1, 0x7d, 0xb7, 0x80, 0x90, 0x83, 0xa1, 0x1b, + 0x09, 0x32, 0xa2, 0x6a, 0xd7, 0x7d, 0xce, 0x42, 0x1a, 0x64, 0x3d, 0x16, 0x7c, 0xfd, 0xdc, 0xc2, + 0x30, 0x5a, 0x8f, 0x05, 0xde, 0x75, 0xa8, 0x04, 0x3d, 0x3a, 0x7f, 0x96, 0xf0, 0xb2, 0x6e, 0x6f, + 0xbc, 0x43, 0xf8, 0x32, 0x7c, 0x23, 0x63, 0xc3, 0x2d, 0xf7, 0x9b, 0x5b, 0xb2, 0x7d, 0x73, 0xf3, + 0x6c, 0xc9, 0xc5, 0x30, 0xce, 0xfa, 0xc1, 0xb7, 0xdf, 0x1f, 0x2e, 0xdc, 0x37, 0x6c, 0x52, 0x61, + 0x7b, 0x1f, 0x18, 0x3e, 0x22, 0x7c, 0x6d, 0x71, 0x71, 0xc6, 0xd6, 0xa9, 0x7d, 0x4a, 0x0c, 0x60, + 0xb6, 0xcf, 0xa1, 0x00, 0xbc, 0x96, 0xc6, 0x5b, 0x37, 0x1e, 0x54, 0xe1, 0xa9, 0x5c, 0xd5, 0x9f, + 0x41, 0x7e, 0x41, 0xf8, 0xe6, 0x89, 0xc5, 0x19, 0x8f, 0x4e, 0xed, 0x5b, 0xe5, 0x04, 0xf3, 0xf1, + 0x79, 0x65, 0xc0, 0xdc, 0xd1, 0xcc, 0x9b, 0xc6, 0xc3, 0x2a, 0x66, 0x3a, 0x97, 0xce, 0x0c, 0xd4, + 0x7d, 0x76, 0x38, 0xb1, 0xd0, 0xd1, 0xc4, 0x42, 0xbf, 0x26, 0x16, 0x7a, 0x3f, 0xb5, 0x6a, 0x47, + 0x53, 0xab, 0xf6, 0x7d, 0x6a, 0xd5, 0x76, 0x16, 0x3d, 0x9e, 0xd7, 0x6b, 0xc5, 0xd4, 0x97, 0x45, + 0xe5, 0xd7, 0x0b, 0xb5, 0xb5, 0xd9, 0xfd, 0x4b, 0xfa, 0xbf, 0xb0, 0xfd, 0x37, 0x00, 0x00, 0xff, + 0xff, 0x67, 0x42, 0x2a, 0xb9, 0xc4, 0x04, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -255,6 +343,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 { @@ -283,6 +374,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 { // Balance queries the balance of all coins of x/community module. @@ -290,6 +390,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. @@ -302,6 +405,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) @@ -343,6 +449,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), @@ -355,6 +479,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", @@ -480,6 +608,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 @@ -539,6 +723,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 } @@ -813,6 +1017,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 9f649a31..4cc2d791 100644 --- a/x/community/types/query.pb.gw.go +++ b/x/community/types/query.pb.gw.go @@ -69,6 +69,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. @@ -121,6 +139,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 } @@ -202,6 +243,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 } @@ -209,10 +270,14 @@ 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 ( forward_Query_Balance_0 = runtime.ForwardResponseMessage forward_Query_TotalBalance_0 = runtime.ForwardResponseMessage + + forward_Query_AnnualizedRewards_0 = runtime.ForwardResponseMessage )