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
This commit is contained in:
Robert Pirtle 2023-10-24 12:24:21 -07:00 committed by GitHub
parent 1d36429fe3
commit 0efe7f2281
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 931 additions and 38 deletions

View File

@ -43,6 +43,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
- (community) [#1704] Add module params - (community) [#1704] Add module params
- (community) [#1706] Add disable inflation upgrade - (community) [#1706] Add disable inflation upgrade
- (community) [#1745] Enable params update via governance with `MsgUpdateParams` - (community) [#1745] Enable params update via governance with `MsgUpdateParams`
- (community) [#1751] Add `AnnualizedRewards` query endpoint
### Bug Fixes ### 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 large-scale simulations remotely using aws-batch
[#1752]: https://github.com/Kava-Labs/kava/pull/1752 [#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 [#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 [#1707]: https://github.com/Kava-Labs/kava/pull/1707
[#1706]: https://github.com/Kava-Labs/kava/pull/1706 [#1706]: https://github.com/Kava-Labs/kava/pull/1706
[#1704]: https://github.com/Kava-Labs/kava/pull/1704 [#1704]: https://github.com/Kava-Labs/kava/pull/1704

View File

@ -12991,6 +12991,52 @@ paths:
format: byte format: byte
tags: tags:
- Savings - 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: /kava/community/v1beta1/balance:
get: get:
summary: Balance queries the balance of all coins of x/community module. 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 and use when the disable inflation time is reached
description: Params defines the parameters of the community module. 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: kava.community.v1beta1.QueryBalanceResponse:
type: object type: object
properties: properties:

View File

@ -197,6 +197,8 @@
- [CommunityPoolLendWithdrawProposal](#kava.community.v1beta1.CommunityPoolLendWithdrawProposal) - [CommunityPoolLendWithdrawProposal](#kava.community.v1beta1.CommunityPoolLendWithdrawProposal)
- [kava/community/v1beta1/query.proto](#kava/community/v1beta1/query.proto) - [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) - [QueryBalanceRequest](#kava.community.v1beta1.QueryBalanceRequest)
- [QueryBalanceResponse](#kava.community.v1beta1.QueryBalanceResponse) - [QueryBalanceResponse](#kava.community.v1beta1.QueryBalanceResponse)
- [QueryParamsRequest](#kava.community.v1beta1.QueryParamsRequest) - [QueryParamsRequest](#kava.community.v1beta1.QueryParamsRequest)
@ -3087,6 +3089,31 @@ CommunityPoolLendWithdrawProposal withdraws a lend position back to the communit
<a name="kava.community.v1beta1.QueryAnnualizedRewardsRequest"></a>
### QueryAnnualizedRewardsRequest
QueryAnnualizedRewardsRequest defines the request type for querying the annualized rewards.
<a name="kava.community.v1beta1.QueryAnnualizedRewardsResponse"></a>
### 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 |
<a name="kava.community.v1beta1.QueryBalanceRequest"></a> <a name="kava.community.v1beta1.QueryBalanceRequest"></a>
### QueryBalanceRequest ### 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| | `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| | `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| | `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|
<!-- end services --> <!-- end services -->

View File

@ -2,6 +2,7 @@ syntax = "proto3";
package kava.community.v1beta1; package kava.community.v1beta1;
import "cosmos/base/v1beta1/coin.proto"; import "cosmos/base/v1beta1/coin.proto";
import "cosmos_proto/cosmos.proto";
import "gogoproto/gogo.proto"; import "gogoproto/gogo.proto";
import "google/api/annotations.proto"; import "google/api/annotations.proto";
import "kava/community/v1beta1/params.proto"; import "kava/community/v1beta1/params.proto";
@ -25,6 +26,12 @@ service Query {
rpc TotalBalance(QueryTotalBalanceRequest) returns (QueryTotalBalanceResponse) { rpc TotalBalance(QueryTotalBalanceRequest) returns (QueryTotalBalanceResponse) {
option (google.api.http).get = "/kava/community/v1beta1/total_balance"; 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. // QueryParams defines the request type for querying x/community params.
@ -59,3 +66,16 @@ message QueryTotalBalanceResponse {
(gogoproto.nullable) = false (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
];
}

View File

@ -3,6 +3,7 @@ package keeper
import ( import (
"context" "context"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
@ -62,3 +63,37 @@ func (s queryServer) TotalBalance(
Pool: totalBalance, Pool: totalBalance,
}, nil }, 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)
}

View File

@ -10,18 +10,20 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/suite" "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/keeper"
"github.com/kava-labs/kava/x/community/testutil"
"github.com/kava-labs/kava/x/community/types" "github.com/kava-labs/kava/x/community/types"
) )
type grpcQueryTestSuite struct { type grpcQueryTestSuite struct {
KeeperTestSuite testutil.Suite
queryClient types.QueryClient queryClient types.QueryClient
} }
func (suite *grpcQueryTestSuite) SetupTest() { func (suite *grpcQueryTestSuite) SetupTest() {
suite.KeeperTestSuite.SetupTest() suite.Suite.SetupTest()
queryHelper := baseapp.NewQueryServerTestHelper(suite.Ctx, suite.App.InterfaceRegistry()) queryHelper := baseapp.NewQueryServerTestHelper(suite.Ctx, suite.App.InterfaceRegistry())
types.RegisterQueryServer(queryHelper, keeper.NewQueryServerImpl(suite.Keeper)) 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
}

View File

@ -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)
}

View File

@ -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)
})
}
}

View File

@ -71,7 +71,7 @@ func (k Keeper) PayoutAccumulatedStakingRewards(ctx sdk.Context) {
k.SetStakingRewardsState(ctx, state) 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. // 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) { 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 // 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()) nanosecondsSinceLastPayout := sdkmath.LegacyNewDec(durationSinceLastPayout.Nanoseconds())
// We multiply by nanoseconds first, then divide by conversion to avoid loss of precision. // 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. // in normal operations.
accumulatedRewards := nanosecondsSinceLastPayout.Mul(stakingRewardsPerSecond).QuoInt64(nanosecondsInOneSecond) accumulatedRewards := nanosecondsSinceLastPayout.Mul(stakingRewardsPerSecond).QuoInt64(nanosecondsInOneSecond)
// Ensure we add any error from previous truncations // Ensure we add any error from previous truncations

View File

@ -28,9 +28,8 @@ func (suite *Suite) SetupTest() {
tApp := app.NewTestApp() tApp := app.NewTestApp()
ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()}) ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()})
tApp.InitializeFromGenesisStates() suite.App = tApp.InitializeFromGenesisStates()
suite.App = tApp
suite.Ctx = ctx suite.Ctx = ctx
suite.Keeper = tApp.GetCommunityKeeper() suite.Keeper = tApp.GetCommunityKeeper()
communityPoolAddress := tApp.GetAccountKeeper().GetModuleAddress(types.ModuleAccountName) communityPoolAddress := tApp.GetAccountKeeper().GetModuleAddress(types.ModuleAccountName)

View File

@ -1,6 +1,7 @@
package types package types
import ( import (
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/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 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 SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, 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. // 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) SetFeePool(ctx sdk.Context, feePool distrtypes.FeePool)
GetParams(ctx sdk.Context) distrtypes.Params GetParams(ctx sdk.Context) distrtypes.Params
SetParams(ctx sdk.Context, params distrtypes.Params) SetParams(ctx sdk.Context, params distrtypes.Params)
GetCommunityTax(ctx sdk.Context) sdk.Dec
} }
type MintKeeper interface { type MintKeeper interface {
GetParams(ctx sdk.Context) (params minttypes.Params) GetParams(ctx sdk.Context) (params minttypes.Params)
SetParams(ctx sdk.Context, params minttypes.Params) SetParams(ctx sdk.Context, params minttypes.Params)
GetMinter(ctx sdk.Context) (minter minttypes.Minter)
} }
type KavadistKeeper interface { type KavadistKeeper interface {
@ -60,4 +65,5 @@ type KavadistKeeper interface {
// StakingKeeper expected interface for the staking keeper // StakingKeeper expected interface for the staking keeper
type StakingKeeper interface { type StakingKeeper interface {
BondDenom(ctx sdk.Context) string BondDenom(ctx sdk.Context) string
TotalBondedTokens(ctx sdk.Context) sdkmath.Int
} }

View File

@ -5,7 +5,9 @@ package types
import ( import (
context "context" context "context"
cosmossdk_io_math "cosmossdk.io/math"
fmt "fmt" fmt "fmt"
_ "github.com/cosmos/cosmos-proto"
github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types"
types "github.com/cosmos/cosmos-sdk/types" types "github.com/cosmos/cosmos-sdk/types"
_ "github.com/gogo/protobuf/gogoproto" _ "github.com/gogo/protobuf/gogoproto"
@ -280,6 +282,82 @@ func (m *QueryTotalBalanceResponse) GetPool() github_com_cosmos_cosmos_sdk_types
return nil 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() { func init() {
proto.RegisterType((*QueryParamsRequest)(nil), "kava.community.v1beta1.QueryParamsRequest") proto.RegisterType((*QueryParamsRequest)(nil), "kava.community.v1beta1.QueryParamsRequest")
proto.RegisterType((*QueryParamsResponse)(nil), "kava.community.v1beta1.QueryParamsResponse") proto.RegisterType((*QueryParamsResponse)(nil), "kava.community.v1beta1.QueryParamsResponse")
@ -287,6 +365,8 @@ func init() {
proto.RegisterType((*QueryBalanceResponse)(nil), "kava.community.v1beta1.QueryBalanceResponse") proto.RegisterType((*QueryBalanceResponse)(nil), "kava.community.v1beta1.QueryBalanceResponse")
proto.RegisterType((*QueryTotalBalanceRequest)(nil), "kava.community.v1beta1.QueryTotalBalanceRequest") proto.RegisterType((*QueryTotalBalanceRequest)(nil), "kava.community.v1beta1.QueryTotalBalanceRequest")
proto.RegisterType((*QueryTotalBalanceResponse)(nil), "kava.community.v1beta1.QueryTotalBalanceResponse") 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() { func init() {
@ -294,37 +374,45 @@ func init() {
} }
var fileDescriptor_f236f06c43149273 = []byte{ var fileDescriptor_f236f06c43149273 = []byte{
// 478 bytes of a gzipped FileDescriptorProto // 606 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x93, 0x31, 0x6f, 0x13, 0x31, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x94, 0x31, 0x6f, 0xd3, 0x40,
0x14, 0xc7, 0x63, 0xda, 0x06, 0xc9, 0x65, 0x32, 0x01, 0xb5, 0xa7, 0xca, 0x29, 0x87, 0xa0, 0x11, 0x14, 0xc7, 0x63, 0x68, 0x8b, 0xb8, 0x22, 0x10, 0x47, 0x40, 0x8d, 0x29, 0x4e, 0x31, 0x82, 0x46,
0x25, 0xe7, 0x26, 0x5d, 0x99, 0x02, 0xec, 0x10, 0x98, 0x58, 0x90, 0xef, 0xb0, 0x8e, 0x53, 0x2f, 0x6d, 0x63, 0x37, 0xa9, 0x60, 0x62, 0x21, 0x84, 0x8d, 0x01, 0x0c, 0x53, 0x97, 0xe8, 0xec, 0x9c,
0xf7, 0xae, 0xb1, 0x53, 0x91, 0xb5, 0x1b, 0x03, 0x12, 0x12, 0xdf, 0x80, 0x91, 0x4f, 0x92, 0xb1, 0x5c, 0x2b, 0x8e, 0xcf, 0xcd, 0x5d, 0x0a, 0x41, 0x2c, 0x74, 0x63, 0x40, 0x42, 0xe2, 0x1b, 0x30,
0x12, 0x0b, 0x13, 0xa0, 0x84, 0x0f, 0x82, 0x6c, 0x3f, 0xa2, 0x04, 0x72, 0x51, 0x98, 0xce, 0x7a, 0x32, 0x23, 0x3e, 0x43, 0xc7, 0x0a, 0x16, 0xc4, 0x50, 0x50, 0xc2, 0x07, 0x41, 0x77, 0xf7, 0x12,
0x7e, 0xff, 0xf7, 0xff, 0xf9, 0x6f, 0x1f, 0x0d, 0xcf, 0xe4, 0x85, 0x14, 0x09, 0x0c, 0x06, 0xa3, 0x25, 0x34, 0x8e, 0xd2, 0x29, 0xf1, 0xbb, 0xf7, 0x7f, 0xff, 0xdf, 0xbd, 0xf7, 0x6c, 0x64, 0xb7,
0x22, 0x33, 0x63, 0x71, 0xd1, 0x89, 0x95, 0x91, 0x1d, 0x71, 0x3e, 0x52, 0xc3, 0x71, 0x54, 0x0e, 0xc8, 0x01, 0x71, 0x03, 0xd6, 0x6e, 0x77, 0x93, 0x48, 0xf4, 0xdc, 0x83, 0x8a, 0x4f, 0x05, 0xa9,
0xc1, 0x00, 0xbb, 0x6d, 0x7b, 0xa2, 0x79, 0x4f, 0x84, 0x3d, 0x01, 0x4f, 0x40, 0x0f, 0x40, 0x8b, 0xb8, 0xfb, 0x5d, 0xda, 0xe9, 0x39, 0x69, 0x87, 0x09, 0x86, 0x6f, 0xc8, 0x1c, 0x67, 0x94, 0xe3,
0x58, 0x6a, 0x35, 0x17, 0x26, 0x90, 0x15, 0x5e, 0x17, 0x34, 0x52, 0x48, 0xc1, 0x2d, 0x85, 0x5d, 0x40, 0x8e, 0x69, 0x05, 0x8c, 0xb7, 0x19, 0x77, 0x7d, 0xc2, 0xe9, 0x48, 0x18, 0xb0, 0x28, 0xd1,
0x61, 0xf5, 0x20, 0x05, 0x48, 0x73, 0x25, 0x64, 0x99, 0x09, 0x59, 0x14, 0x60, 0xa4, 0xc9, 0xa0, 0x3a, 0xb3, 0xa0, 0xcf, 0x1b, 0xea, 0xc9, 0xd5, 0x0f, 0x70, 0x94, 0x0f, 0x59, 0xc8, 0x74, 0x5c,
0xd0, 0xb8, 0x7b, 0xb7, 0x82, 0xa7, 0x94, 0x43, 0x39, 0xc0, 0xa6, 0xb0, 0x41, 0xd9, 0x73, 0xcb, 0xfe, 0x83, 0xe8, 0x6a, 0xc8, 0x58, 0x18, 0x53, 0x97, 0xa4, 0x91, 0x4b, 0x92, 0x84, 0x09, 0x22,
0xf7, 0xcc, 0x15, 0xfb, 0xea, 0x7c, 0xa4, 0xb4, 0x09, 0x5f, 0xd0, 0x9b, 0x4b, 0x55, 0x5d, 0x42, 0x22, 0x96, 0x0c, 0x35, 0x77, 0x32, 0x50, 0x53, 0xd2, 0x21, 0x6d, 0x48, 0xb2, 0xf3, 0x08, 0x3f,
0xa1, 0x15, 0x7b, 0x44, 0xeb, 0x5e, 0xbc, 0x47, 0x0e, 0x49, 0x6b, 0xb7, 0xcb, 0xa3, 0xd5, 0xc7, 0x97, 0xe8, 0xcf, 0x54, 0xd0, 0xa3, 0xfb, 0x5d, 0xca, 0x85, 0xfd, 0x02, 0x5d, 0x9b, 0x88, 0xf2,
0x89, 0xbc, 0xae, 0xb7, 0x3d, 0xf9, 0xde, 0xac, 0xf5, 0x51, 0x13, 0xde, 0xc2, 0xa1, 0x3d, 0x99, 0x94, 0x25, 0x9c, 0xe2, 0x87, 0x68, 0x49, 0x8b, 0x57, 0x8c, 0x35, 0xa3, 0xb4, 0x5c, 0xb5, 0x9c,
0xcb, 0x22, 0x51, 0x7f, 0xbc, 0xc6, 0xb4, 0xb1, 0x5c, 0x46, 0x33, 0x49, 0x77, 0x6c, 0x00, 0xd6, 0xe9, 0x37, 0x75, 0xb4, 0xae, 0xb6, 0x70, 0x74, 0x52, 0xcc, 0x79, 0xa0, 0xb1, 0xaf, 0x43, 0xd1,
0x6b, 0xab, 0xb5, 0xdb, 0xdd, 0x8f, 0x7c, 0x44, 0x91, 0x8d, 0x68, 0x6e, 0xf4, 0x18, 0xb2, 0xa2, 0x1a, 0x89, 0x49, 0x12, 0xd0, 0xa1, 0x57, 0x0f, 0xe5, 0x27, 0xc3, 0x60, 0x46, 0xd0, 0xa2, 0xec,
0x77, 0x62, 0x6d, 0xbe, 0xfc, 0x68, 0xb6, 0xd2, 0xcc, 0xbc, 0x1d, 0xc5, 0x96, 0x47, 0x60, 0x9e, 0x8d, 0xf4, 0x3a, 0x5f, 0x5a, 0xae, 0x16, 0x1c, 0x68, 0x88, 0xec, 0xde, 0xc8, 0xe8, 0x31, 0x8b,
0xfe, 0xd3, 0xd6, 0x6f, 0xce, 0x84, 0x19, 0x97, 0x4a, 0x3b, 0x81, 0xee, 0xfb, 0xc9, 0x61, 0x40, 0x92, 0xda, 0xb6, 0xb4, 0xf9, 0xf2, 0xbb, 0x58, 0x0a, 0x23, 0xb1, 0xd7, 0xf5, 0x25, 0x0f, 0x74,
0xf7, 0x9c, 0xf5, 0x4b, 0x30, 0x32, 0xff, 0x0b, 0xeb, 0x92, 0xd0, 0xfd, 0x15, 0x9b, 0x08, 0xa7, 0x0f, 0x7e, 0xca, 0xbc, 0xd9, 0x72, 0x45, 0x2f, 0xa5, 0x5c, 0x09, 0xb8, 0xa7, 0x2b, 0xdb, 0x26,
0xe8, 0x76, 0x09, 0x90, 0x23, 0xdb, 0xc1, 0x4a, 0xb6, 0x27, 0x2a, 0x71, 0x78, 0xa7, 0x88, 0x77, 0x5a, 0x51, 0xd6, 0x2f, 0x99, 0x20, 0xf1, 0x7f, 0x58, 0x87, 0x06, 0x2a, 0x4c, 0x39, 0x04, 0x38,
0xbc, 0x01, 0x1e, 0x6a, 0x74, 0xdf, 0x8d, 0xef, 0x4e, 0xb6, 0xe8, 0x8e, 0x83, 0x60, 0xef, 0x09, 0x8a, 0x16, 0x52, 0xc6, 0x62, 0x60, 0x5b, 0x9d, 0xca, 0x56, 0xa7, 0x81, 0xc2, 0xdb, 0x01, 0xbc,
0xad, 0xfb, 0x54, 0xd9, 0x83, 0xaa, 0xd4, 0xff, 0xbd, 0xc8, 0xe0, 0x78, 0xa3, 0x5e, 0x7f, 0xa8, 0xcd, 0x39, 0xf0, 0x40, 0xc3, 0x3d, 0x55, 0xde, 0x2e, 0xa2, 0x5b, 0x8a, 0xe1, 0x51, 0x92, 0x74,
0xf0, 0xfe, 0xe5, 0xd7, 0x5f, 0x9f, 0xae, 0x1d, 0x32, 0x2e, 0xd6, 0xbe, 0x1c, 0xf6, 0x81, 0xd0, 0x49, 0x1c, 0xbd, 0xa1, 0x4d, 0x8f, 0xbe, 0x22, 0x9d, 0xe6, 0x68, 0x50, 0x6f, 0x91, 0x95, 0x95,
0xeb, 0x18, 0x08, 0x5b, 0x6f, 0xb0, 0x9c, 0x69, 0xf0, 0x70, 0xb3, 0x66, 0xc4, 0x39, 0x72, 0x38, 0x00, 0xa4, 0xbb, 0xe8, 0x0a, 0x17, 0xa4, 0x15, 0x25, 0x61, 0xa3, 0xa3, 0x8f, 0xd4, 0xf0, 0x2e,
0x77, 0x58, 0xb3, 0x0a, 0x27, 0x46, 0x86, 0xcf, 0x84, 0xde, 0x58, 0xbc, 0x25, 0x76, 0xb2, 0xd6, 0xd6, 0x2a, 0x12, 0xeb, 0xd7, 0x49, 0xf1, 0xa6, 0x86, 0xe0, 0xcd, 0x96, 0x13, 0x31, 0xb7, 0x4d,
0x67, 0xc5, 0x6d, 0x07, 0x9d, 0xff, 0x50, 0x20, 0x5e, 0xdb, 0xe1, 0x1d, 0xb1, 0x7b, 0x55, 0x78, 0xc4, 0x9e, 0xf3, 0x94, 0x86, 0x24, 0xe8, 0xd5, 0x69, 0xf0, 0xfd, 0x6b, 0x19, 0xc1, 0xd5, 0xea,
0xc6, 0xaa, 0x5e, 0x23, 0x64, 0xef, 0xe9, 0x64, 0xca, 0xc9, 0xd5, 0x94, 0x93, 0x9f, 0x53, 0x4e, 0x34, 0xf0, 0x2e, 0x43, 0x25, 0xf0, 0xa8, 0xbe, 0x5b, 0x44, 0x8b, 0xca, 0x1e, 0xbf, 0x37, 0xd0,
0x3e, 0xce, 0x78, 0xed, 0x6a, 0xc6, 0x6b, 0xdf, 0x66, 0xbc, 0xf6, 0x6a, 0xf1, 0x5d, 0xd8, 0x51, 0x92, 0x1e, 0x3a, 0xde, 0xc8, 0x5a, 0x8a, 0xd3, 0x7b, 0x66, 0x6e, 0xce, 0x95, 0xab, 0x6f, 0x62,
0xed, 0x5c, 0xc6, 0xda, 0x0f, 0x7d, 0xb7, 0x30, 0xd6, 0x3d, 0x90, 0xb8, 0xee, 0x7e, 0xdb, 0xd3, 0xdf, 0x3b, 0xfc, 0xf1, 0xf7, 0xd3, 0xb9, 0x35, 0x6c, 0xb9, 0x33, 0x17, 0x1b, 0x7f, 0x30, 0xd0,
0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x62, 0x63, 0xf8, 0x10, 0x6d, 0x04, 0x00, 0x00, 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. // 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, // TotalBalance queries the balance of all coins, including x/distribution,
// x/community, and supplied balances. // x/community, and supplied balances.
TotalBalance(ctx context.Context, in *QueryTotalBalanceRequest, opts ...grpc.CallOption) (*QueryTotalBalanceResponse, error) 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 { type queryClient struct {
@ -383,6 +474,15 @@ func (c *queryClient) TotalBalance(ctx context.Context, in *QueryTotalBalanceReq
return out, nil 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. // QueryServer is the server API for Query service.
type QueryServer interface { type QueryServer interface {
// Params queires the module params. // Params queires the module params.
@ -392,6 +492,9 @@ type QueryServer interface {
// TotalBalance queries the balance of all coins, including x/distribution, // TotalBalance queries the balance of all coins, including x/distribution,
// x/community, and supplied balances. // x/community, and supplied balances.
TotalBalance(context.Context, *QueryTotalBalanceRequest) (*QueryTotalBalanceResponse, error) 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. // 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) { func (*UnimplementedQueryServer) TotalBalance(ctx context.Context, req *QueryTotalBalanceRequest) (*QueryTotalBalanceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method TotalBalance not implemented") 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) { func RegisterQueryServer(s grpc1.Server, srv QueryServer) {
s.RegisterService(&_Query_serviceDesc, srv) 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) 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{ var _Query_serviceDesc = grpc.ServiceDesc{
ServiceName: "kava.community.v1beta1.Query", ServiceName: "kava.community.v1beta1.Query",
HandlerType: (*QueryServer)(nil), HandlerType: (*QueryServer)(nil),
@ -482,6 +606,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{
MethodName: "TotalBalance", MethodName: "TotalBalance",
Handler: _Query_TotalBalance_Handler, Handler: _Query_TotalBalance_Handler,
}, },
{
MethodName: "AnnualizedRewards",
Handler: _Query_AnnualizedRewards_Handler,
},
}, },
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{},
Metadata: "kava/community/v1beta1/query.proto", Metadata: "kava/community/v1beta1/query.proto",
@ -663,6 +791,62 @@ func (m *QueryTotalBalanceResponse) MarshalToSizedBuffer(dAtA []byte) (int, erro
return len(dAtA) - i, nil 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 { func encodeVarintQuery(dAtA []byte, offset int, v uint64) int {
offset -= sovQuery(v) offset -= sovQuery(v)
base := offset base := offset
@ -742,6 +926,26 @@ func (m *QueryTotalBalanceResponse) Size() (n int) {
return n 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) { func sovQuery(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7 return (math_bits.Len64(x|1) + 6) / 7
} }
@ -1149,6 +1353,140 @@ func (m *QueryTotalBalanceResponse) Unmarshal(dAtA []byte) error {
} }
return nil 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) { func skipQuery(dAtA []byte) (n int, err error) {
l := len(dAtA) l := len(dAtA)
iNdEx := 0 iNdEx := 0

View File

@ -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". // RegisterQueryHandlerServer registers the http handlers for service Query to "mux".
// UnaryRPC :call QueryServer directly. // UnaryRPC :call QueryServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. // 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 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 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_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_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 ( var (
@ -280,4 +343,6 @@ var (
forward_Query_Balance_0 = runtime.ForwardResponseMessage forward_Query_Balance_0 = runtime.ForwardResponseMessage
forward_Query_TotalBalance_0 = runtime.ForwardResponseMessage forward_Query_TotalBalance_0 = runtime.ForwardResponseMessage
forward_Query_AnnualizedRewards_0 = runtime.ForwardResponseMessage
) )