Add /incentive/apy query endpoint (#1328)

* Add /incentive/apy query endpoint

* Add APY calculation using usd value

* Fix maths for adding bkava incentive rewards

Signed-off-by: drklee3 <derrick@dlee.dev>

* Fix apy calculation with community tax, remove infra tax

* Fix pricefeed market mismatches, fix division by zero

* Remove unused kavadist expected keeper

Signed-off-by: drklee3 <derrick@dlee.dev>
This commit is contained in:
Derrick Lee 2022-10-05 11:39:50 -07:00 committed by GitHub
parent 4c879bc4fb
commit be7242d86d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 571 additions and 13 deletions

View File

@ -633,6 +633,9 @@ func NewApp(
&savingsKeeper, &savingsKeeper,
&app.liquidKeeper, &app.liquidKeeper,
&earnKeeper, &earnKeeper,
app.mintKeeper,
app.distrKeeper,
app.pricefeedKeeper,
) )
app.routerKeeper = routerkeeper.NewKeeper( app.routerKeeper = routerkeeper.NewKeeper(
&app.earnKeeper, &app.earnKeeper,

View File

@ -19,6 +19,7 @@ func registerQueryRoutes(cliCtx client.Context, r *mux.Router) {
r.HandleFunc(fmt.Sprintf("/%s/rewards", types.ModuleName), queryRewardsHandlerFn(cliCtx)).Methods("GET") r.HandleFunc(fmt.Sprintf("/%s/rewards", types.ModuleName), queryRewardsHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/%s/parameters", types.ModuleName), queryParamsHandlerFn(cliCtx)).Methods("GET") r.HandleFunc(fmt.Sprintf("/%s/parameters", types.ModuleName), queryParamsHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/%s/reward-factors", types.ModuleName), queryRewardFactorsHandlerFn(cliCtx)).Methods("GET") r.HandleFunc(fmt.Sprintf("/%s/reward-factors", types.ModuleName), queryRewardFactorsHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/%s/apy", types.ModuleName), queryAPYsHandlerFn(cliCtx)).Methods("GET")
} }
func queryRewardsHandlerFn(cliCtx client.Context) http.HandlerFunc { func queryRewardsHandlerFn(cliCtx client.Context) http.HandlerFunc {
@ -273,3 +274,23 @@ func executeAllRewardQueries(w http.ResponseWriter, cliCtx client.Context, param
rest.PostProcessResponse(w, cliCtx, resBz) rest.PostProcessResponse(w, cliCtx, resBz)
} }
func queryAPYsHandlerFn(cliCtx client.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok {
return
}
route := fmt.Sprintf("custom/%s/%s", types.ModuleName, types.QueryGetAPYs)
res, height, err := cliCtx.QueryWithData(route, nil)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
cliCtx = cliCtx.WithHeight(height)
rest.PostProcessResponse(w, cliCtx, res)
}
}

View File

@ -24,6 +24,11 @@ type Keeper struct {
savingsKeeper types.SavingsKeeper savingsKeeper types.SavingsKeeper
liquidKeeper types.LiquidKeeper liquidKeeper types.LiquidKeeper
earnKeeper types.EarnKeeper earnKeeper types.EarnKeeper
// Keepers used for APY queries
mintKeeper types.MintKeeper
distrKeeper types.DistrKeeper
pricefeedKeeper types.PricefeedKeeper
} }
// NewKeeper creates a new keeper // NewKeeper creates a new keeper
@ -31,6 +36,7 @@ func NewKeeper(
cdc codec.Codec, key sdk.StoreKey, paramstore types.ParamSubspace, bk types.BankKeeper, cdc codec.Codec, key sdk.StoreKey, paramstore types.ParamSubspace, bk types.BankKeeper,
cdpk types.CdpKeeper, hk types.HardKeeper, ak types.AccountKeeper, stk types.StakingKeeper, cdpk types.CdpKeeper, hk types.HardKeeper, ak types.AccountKeeper, stk types.StakingKeeper,
swpk types.SwapKeeper, svk types.SavingsKeeper, lqk types.LiquidKeeper, ek types.EarnKeeper, swpk types.SwapKeeper, svk types.SavingsKeeper, lqk types.LiquidKeeper, ek types.EarnKeeper,
mk types.MintKeeper, dk types.DistrKeeper, pfk types.PricefeedKeeper,
) Keeper { ) Keeper {
if !paramstore.HasKeyTable() { if !paramstore.HasKeyTable() {
paramstore = paramstore.WithKeyTable(types.ParamKeyTable()) paramstore = paramstore.WithKeyTable(types.ParamKeyTable())
@ -49,6 +55,9 @@ func NewKeeper(
savingsKeeper: svk, savingsKeeper: svk,
liquidKeeper: lqk, liquidKeeper: lqk,
earnKeeper: ek, earnKeeper: ek,
mintKeeper: mk,
distrKeeper: dk,
pricefeedKeeper: pfk,
} }
} }

View File

@ -1,13 +1,21 @@
package keeper package keeper
import ( import (
"fmt"
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
earntypes "github.com/kava-labs/kava/x/earn/types"
"github.com/kava-labs/kava/x/incentive/types" "github.com/kava-labs/kava/x/incentive/types"
liquidtypes "github.com/kava-labs/kava/x/liquid/types"
)
const (
SecondsPerYear = 31536000
) )
// NewQuerier is the module level router for state queries // NewQuerier is the module level router for state queries
@ -31,6 +39,8 @@ func NewQuerier(k Keeper, legacyQuerierCdc *codec.LegacyAmino) sdk.Querier {
return queryGetRewardFactors(ctx, req, k, legacyQuerierCdc) return queryGetRewardFactors(ctx, req, k, legacyQuerierCdc)
case types.QueryGetEarnRewards: case types.QueryGetEarnRewards:
return queryGetEarnRewards(ctx, req, k, legacyQuerierCdc) return queryGetEarnRewards(ctx, req, k, legacyQuerierCdc)
case types.QueryGetAPYs:
return queryGetAPYs(ctx, req, k, legacyQuerierCdc)
default: default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint", types.ModuleName) return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint", types.ModuleName)
} }
@ -368,3 +378,156 @@ func queryGetRewardFactors(ctx sdk.Context, req abci.RequestQuery, k Keeper, leg
return bz, nil return bz, nil
} }
func queryGetAPYs(ctx sdk.Context, req abci.RequestQuery, k Keeper, legacyQuerierCdc *codec.LegacyAmino) ([]byte, error) {
params := k.GetParams(ctx)
var apys types.APYs
// bkava APY (staking + incentive rewards)
stakingAPR, err := GetStakingAPR(ctx, k, params)
if err != nil {
return nil, err
}
apys = append(apys, types.NewAPY(liquidtypes.DefaultDerivativeDenom, stakingAPR))
// Incentive only APYs
for _, param := range params.EarnRewardPeriods {
// Skip bkava as it's calculated earlier with staking rewards
if param.CollateralType == liquidtypes.DefaultDerivativeDenom {
continue
}
// Value in the vault in the same denom as CollateralType
vaultTotalValue, err := k.earnKeeper.GetVaultTotalValue(ctx, param.CollateralType)
if err != nil {
return nil, err
}
apy, err := GetAPYFromMultiRewardPeriod(ctx, k, param.CollateralType, param, vaultTotalValue.Amount)
if err != nil {
return nil, err
}
apys = append(apys, types.NewAPY(param.CollateralType, apy))
}
// Marshal APYs
res := types.NewQueryGetAPYsResponse(apys)
bz, err := codec.MarshalJSONIndent(legacyQuerierCdc, res)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}
// GetStakingAPR returns the total APR for staking and incentive rewards
func GetStakingAPR(ctx sdk.Context, k Keeper, params types.Params) (sdk.Dec, error) {
// Get staking APR + incentive APR
inflationRate := k.mintKeeper.GetMinter(ctx).Inflation
communityTax := k.distrKeeper.GetCommunityTax(ctx)
bondedTokens := k.stakingKeeper.TotalBondedTokens(ctx)
circulatingSupply := k.bankKeeper.GetSupply(ctx, types.BondDenom)
// Staking APR = (Inflation Rate * (1 - Community Tax)) / (Bonded Tokens / Circulating Supply)
stakingAPR := inflationRate.
Mul(sdk.OneDec().Sub(communityTax)).
Quo(bondedTokens.ToDec().
Quo(circulatingSupply.Amount.ToDec()))
// Get incentive APR
bkavaRewardPeriod, found := params.EarnRewardPeriods.GetMultiRewardPeriod(liquidtypes.DefaultDerivativeDenom)
if !found {
// No incentive rewards for bkava, only staking rewards
return stakingAPR, nil
}
// Total amount of bkava in earn vaults, this may be lower than total bank
// supply of bkava as some bkava may not be deposited in earn vaults
totalEarnBkavaDeposited := sdk.ZeroInt()
var iterErr error
k.earnKeeper.IterateVaultRecords(ctx, func(record earntypes.VaultRecord) (stop bool) {
if !k.liquidKeeper.IsDerivativeDenom(ctx, record.TotalShares.Denom) {
return false
}
vaultValue, err := k.earnKeeper.GetVaultTotalValue(ctx, record.TotalShares.Denom)
if err != nil {
iterErr = err
return false
}
totalEarnBkavaDeposited = totalEarnBkavaDeposited.Add(vaultValue.Amount)
return false
})
if iterErr != nil {
return sdk.ZeroDec(), iterErr
}
// Incentive APR = rewards per second * seconds per year / total supplied to earn vaults
// Override collateral type to use "kava" instead of "bkava" when fetching
incentiveAPY, err := GetAPYFromMultiRewardPeriod(ctx, k, types.BondDenom, bkavaRewardPeriod, totalEarnBkavaDeposited)
if err != nil {
return sdk.ZeroDec(), err
}
totalAPY := stakingAPR.Add(incentiveAPY)
return totalAPY, nil
}
// GetAPYFromMultiRewardPeriod calculates the APY for a given MultiRewardPeriod
func GetAPYFromMultiRewardPeriod(
ctx sdk.Context,
k Keeper,
collateralType string,
rewardPeriod types.MultiRewardPeriod,
totalSupply sdk.Int,
) (sdk.Dec, error) {
if totalSupply.IsZero() {
return sdk.ZeroDec(), nil
}
// Get USD value of collateral type
collateralUSDValue, err := k.pricefeedKeeper.GetCurrentPrice(ctx, getMarketID(collateralType))
if err != nil {
return sdk.ZeroDec(), err
}
// Total USD value of the collateral type total supply
totalSupplyUSDValue := totalSupply.ToDec().Mul(collateralUSDValue.Price)
totalUSDRewardsPerSecond := sdk.ZeroDec()
// In many cases, RewardsPerSecond are assets that are different from the
// CollateralType, so we need to use the USD value of CollateralType and
// RewardsPerSecond to determine the APY.
for _, reward := range rewardPeriod.RewardsPerSecond {
// Get USD value of 1 unit of reward asset type, using TWAP
rewardDenomUSDValue, err := k.pricefeedKeeper.GetCurrentPrice(ctx, getMarketID(reward.Denom))
if err != nil {
return sdk.ZeroDec(), fmt.Errorf("failed to get price for RewardsPerSecond asset %s: %w", reward.Denom, err)
}
rewardPerSecond := reward.Amount.ToDec().Mul(rewardDenomUSDValue.Price)
totalUSDRewardsPerSecond = totalUSDRewardsPerSecond.Add(rewardPerSecond)
}
// APY = USD rewards per second * seconds per year / USD total supplied
apy := totalUSDRewardsPerSecond.
MulInt64(SecondsPerYear).
Quo(totalSupplyUSDValue)
return apy, nil
}
func getMarketID(denom string) string {
if denom == types.BondDenom {
// Rewrite "ukava" to "kava" as pricefeed only has "kava" and not "ukava"
return getMarketID("kava")
}
return fmt.Sprintf("%s:usd:30", denom)
}

View File

@ -0,0 +1,102 @@
package keeper_test
import (
"testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types"
earntypes "github.com/kava-labs/kava/x/earn/types"
"github.com/kava-labs/kava/x/incentive/keeper"
"github.com/kava-labs/kava/x/incentive/types"
"github.com/stretchr/testify/suite"
)
type QuerierTestSuite struct {
unitTester
}
func TestQuerierTestSuite(t *testing.T) {
suite.Run(t, new(QuerierTestSuite))
}
func (suite *QuerierTestSuite) TestGetStakingAPR() {
communityTax := sdk.MustNewDecFromStr("0.90")
inflation := sdk.MustNewDecFromStr("0.75")
bondedTokens := int64(120_000_000_000000)
liquidStakedTokens := int64(60_000_000_000000)
totalSupply := int64(289_138_414_286684)
suite.keeper = suite.NewTestKeeper(&fakeParamSubspace{}).
WithDistrKeeper(
newFakeDistrKeeper().setCommunityTax(communityTax),
).
WithMintKeeper(
newFakeMintKeeper().
setMinter(minttypes.NewMinter(inflation, sdk.OneDec())),
).
WithStakingKeeper(
newFakeStakingKeeper().addBondedTokens(bondedTokens),
).
WithBankKeeper(
newFakeBankKeeper().setSupply(sdk.NewCoin(types.BondDenom, sdk.NewInt(totalSupply))),
).
WithEarnKeeper(
newFakeEarnKeeper().
addVault("bkava-asdf", earntypes.NewVaultShare("bkava-asdf", sdk.NewDec(liquidStakedTokens))),
).
WithLiquidKeeper(
newFakeLiquidKeeper().addDerivative(suite.ctx, "bkava-asdf", sdk.NewInt(liquidStakedTokens)),
).
WithPricefeedKeeper(
newFakePricefeedKeeper().
setPrice(
pricefeedtypes.NewCurrentPrice(
"kava:usd:30",
sdk.MustNewDecFromStr("1.5"),
)),
).
Build()
// ~18% APR
expectedStakingAPY := inflation.
Mul(sdk.OneDec().Sub(communityTax)).
Quo(sdk.NewDec(bondedTokens).Quo(sdk.NewDec(totalSupply)))
// Staking APR = (Inflation Rate * (1 - Community Tax)) / (Bonded Tokens / Circulating Supply)
aprWithoutIncentives, err := keeper.GetStakingAPR(suite.ctx, suite.keeper, types.Params{})
suite.Require().NoError(err)
suite.Require().Equal(
expectedStakingAPY,
aprWithoutIncentives,
)
suite.T().Logf("Staking APR without incentives: %s", aprWithoutIncentives)
params := types.Params{
EarnRewardPeriods: types.MultiRewardPeriods{
{
Active: true,
CollateralType: "bkava",
Start: suite.ctx.BlockTime().Add(-time.Hour),
End: suite.ctx.BlockTime().Add(time.Hour),
RewardsPerSecond: sdk.NewCoins(
sdk.NewCoin("ukava", sdk.NewInt(190258)),
),
},
},
}
aprWithIncentives, err := keeper.GetStakingAPR(suite.ctx, suite.keeper, params)
suite.Require().NoError(err)
// Approx 10% increase in APR from incentives
suite.Require().Equal(sdk.MustNewDecFromStr("0.280711113729177500"), aprWithIncentives)
suite.Require().Truef(
aprWithIncentives.GT(aprWithoutIncentives),
"APR with incentives (%s) should be greater than APR without incentives (%s)",
)
}

View File

@ -8,8 +8,10 @@ import (
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store" "github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
db "github.com/tendermint/tm-db" db "github.com/tendermint/tm-db"
@ -74,7 +76,11 @@ func (suite *unitTester) NewKeeper(
ak types.AccountKeeper, stk types.StakingKeeper, swk types.SwapKeeper, ak types.AccountKeeper, stk types.StakingKeeper, swk types.SwapKeeper,
svk types.SavingsKeeper, lqk types.LiquidKeeper, ek types.EarnKeeper, svk types.SavingsKeeper, lqk types.LiquidKeeper, ek types.EarnKeeper,
) keeper.Keeper { ) keeper.Keeper {
return keeper.NewKeeper(suite.cdc, suite.incentiveStoreKey, paramSubspace, bk, cdpk, hk, ak, stk, swk, svk, lqk, ek) return keeper.NewKeeper(
suite.cdc, suite.incentiveStoreKey, paramSubspace,
bk, cdpk, hk, ak, stk, swk, svk, lqk, ek,
nil, nil, nil,
)
} }
func (suite *unitTester) storeGlobalBorrowIndexes(indexes types.MultiRewardIndexes) { func (suite *unitTester) storeGlobalBorrowIndexes(indexes types.MultiRewardIndexes) {
@ -133,6 +139,96 @@ func (suite *unitTester) storeEarnClaim(claim types.EarnClaim) {
suite.keeper.SetEarnClaim(suite.ctx, claim) suite.keeper.SetEarnClaim(suite.ctx, claim)
} }
type TestKeeperBuilder struct {
cdc codec.Codec
key sdk.StoreKey
paramSubspace types.ParamSubspace
accountKeeper types.AccountKeeper
bankKeeper types.BankKeeper
cdpKeeper types.CdpKeeper
hardKeeper types.HardKeeper
stakingKeeper types.StakingKeeper
swapKeeper types.SwapKeeper
savingsKeeper types.SavingsKeeper
liquidKeeper types.LiquidKeeper
earnKeeper types.EarnKeeper
// Keepers used for APY queries
mintKeeper types.MintKeeper
distrKeeper types.DistrKeeper
pricefeedKeeper types.PricefeedKeeper
}
func (suite *unitTester) NewTestKeeper(
paramSubspace types.ParamSubspace,
) *TestKeeperBuilder {
if !paramSubspace.HasKeyTable() {
paramSubspace = paramSubspace.WithKeyTable(types.ParamKeyTable())
}
return &TestKeeperBuilder{
cdc: suite.cdc,
key: suite.incentiveStoreKey,
paramSubspace: paramSubspace,
accountKeeper: nil,
bankKeeper: nil,
cdpKeeper: nil,
hardKeeper: nil,
stakingKeeper: nil,
swapKeeper: nil,
savingsKeeper: nil,
liquidKeeper: nil,
earnKeeper: nil,
mintKeeper: nil,
distrKeeper: nil,
pricefeedKeeper: nil,
}
}
func (tk *TestKeeperBuilder) WithPricefeedKeeper(k types.PricefeedKeeper) *TestKeeperBuilder {
tk.pricefeedKeeper = k
return tk
}
func (tk *TestKeeperBuilder) WithDistrKeeper(k types.DistrKeeper) *TestKeeperBuilder {
tk.distrKeeper = k
return tk
}
func (tk *TestKeeperBuilder) WithBankKeeper(k types.BankKeeper) *TestKeeperBuilder {
tk.bankKeeper = k
return tk
}
func (tk *TestKeeperBuilder) WithStakingKeeper(k types.StakingKeeper) *TestKeeperBuilder {
tk.stakingKeeper = k
return tk
}
func (tk *TestKeeperBuilder) WithMintKeeper(k types.MintKeeper) *TestKeeperBuilder {
tk.mintKeeper = k
return tk
}
func (tk *TestKeeperBuilder) WithEarnKeeper(k types.EarnKeeper) *TestKeeperBuilder {
tk.earnKeeper = k
return tk
}
func (tk *TestKeeperBuilder) WithLiquidKeeper(k types.LiquidKeeper) *TestKeeperBuilder {
tk.liquidKeeper = k
return tk
}
func (tk *TestKeeperBuilder) Build() keeper.Keeper {
return keeper.NewKeeper(
tk.cdc, tk.key, tk.paramSubspace,
tk.bankKeeper, tk.cdpKeeper, tk.hardKeeper, tk.accountKeeper,
tk.stakingKeeper, tk.swapKeeper, tk.savingsKeeper, tk.liquidKeeper,
tk.earnKeeper, tk.mintKeeper, tk.distrKeeper, tk.pricefeedKeeper,
)
}
// fakeParamSubspace is a stub paramSpace to simplify keeper unit test setup. // fakeParamSubspace is a stub paramSpace to simplify keeper unit test setup.
type fakeParamSubspace struct { type fakeParamSubspace struct {
params types.Params params types.Params
@ -410,6 +506,15 @@ func (k *fakeEarnKeeper) GetVaultTotalShares(
return vaultShares, found return vaultShares, found
} }
func (k *fakeEarnKeeper) GetVaultTotalValue(ctx sdk.Context, denom string) (sdk.Coin, error) {
vaultShares, found := k.vaultShares[denom]
if !found {
return sdk.NewCoin(denom, sdk.ZeroInt()), nil
}
return sdk.NewCoin(denom, vaultShares.Amount.RoundInt()), nil
}
func (k *fakeEarnKeeper) GetVaultAccountShares( func (k *fakeEarnKeeper) GetVaultAccountShares(
ctx sdk.Context, ctx sdk.Context,
acc sdk.AccAddress, acc sdk.AccAddress,
@ -519,6 +624,112 @@ func (k *fakeLiquidKeeper) getRewardAmount(
return amt.QuoRaw(10).MulRaw(duration) return amt.QuoRaw(10).MulRaw(duration)
} }
type fakeDistrKeeper struct {
communityTax sdk.Dec
}
var _ types.DistrKeeper = newFakeDistrKeeper()
func newFakeDistrKeeper() *fakeDistrKeeper {
return &fakeDistrKeeper{}
}
func (k *fakeDistrKeeper) setCommunityTax(percent sdk.Dec) *fakeDistrKeeper {
k.communityTax = percent
return k
}
func (k *fakeDistrKeeper) GetCommunityTax(ctx sdk.Context) (percent sdk.Dec) {
return k.communityTax
}
type fakeMintKeeper struct {
minter minttypes.Minter
}
var _ types.MintKeeper = newFakeMintKeeper()
func newFakeMintKeeper() *fakeMintKeeper {
return &fakeMintKeeper{}
}
func (k *fakeMintKeeper) setMinter(minter minttypes.Minter) *fakeMintKeeper {
k.minter = minter
return k
}
func (k *fakeMintKeeper) GetMinter(ctx sdk.Context) (minter minttypes.Minter) {
return k.minter
}
type fakePricefeedKeeper struct {
prices map[string]pricefeedtypes.CurrentPrice
}
var _ types.PricefeedKeeper = newFakePricefeedKeeper()
func newFakePricefeedKeeper() *fakePricefeedKeeper {
return &fakePricefeedKeeper{
prices: map[string]pricefeedtypes.CurrentPrice{},
}
}
func (k *fakePricefeedKeeper) setPrice(price pricefeedtypes.CurrentPrice) *fakePricefeedKeeper {
k.prices[price.MarketID] = price
return k
}
func (k *fakePricefeedKeeper) GetCurrentPrice(ctx sdk.Context, marketID string) (pricefeedtypes.CurrentPrice, error) {
price, found := k.prices[marketID]
if !found {
return pricefeedtypes.CurrentPrice{}, fmt.Errorf("price not found for market %s", marketID)
}
return price, nil
}
type fakeBankKeeper struct {
supply map[string]sdk.Int
}
var _ types.BankKeeper = newFakeBankKeeper()
func newFakeBankKeeper() *fakeBankKeeper {
return &fakeBankKeeper{
supply: map[string]sdk.Int{},
}
}
func (k *fakeBankKeeper) setSupply(coins ...sdk.Coin) *fakeBankKeeper {
for _, coin := range coins {
k.supply[coin.Denom] = coin.Amount
}
return k
}
func (k *fakeBankKeeper) SendCoinsFromModuleToAccount(
ctx sdk.Context,
senderModule string,
recipientAddr sdk.AccAddress,
amt sdk.Coins,
) error {
panic("not implemented")
}
func (k *fakeBankKeeper) GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins {
panic("not implemented")
}
func (k *fakeBankKeeper) GetSupply(ctx sdk.Context, denom string) sdk.Coin {
supply, found := k.supply[denom]
if !found {
return sdk.NewCoin(denom, sdk.ZeroInt())
}
return sdk.NewCoin(denom, supply)
}
// Assorted Testing Data // Assorted Testing Data
// note: amino panics when encoding times ≥ the start of year 10000. // note: amino panics when encoding times ≥ the start of year 10000.

View File

@ -3,11 +3,13 @@ package types
import ( import (
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"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
cdptypes "github.com/kava-labs/kava/x/cdp/types" cdptypes "github.com/kava-labs/kava/x/cdp/types"
earntypes "github.com/kava-labs/kava/x/earn/types" earntypes "github.com/kava-labs/kava/x/earn/types"
hardtypes "github.com/kava-labs/kava/x/hard/types" hardtypes "github.com/kava-labs/kava/x/hard/types"
pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types"
savingstypes "github.com/kava-labs/kava/x/savings/types" savingstypes "github.com/kava-labs/kava/x/savings/types"
) )
@ -23,6 +25,7 @@ type ParamSubspace interface {
type BankKeeper interface { type BankKeeper interface {
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
GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins
GetSupply(ctx sdk.Context, denom string) sdk.Coin
} }
// StakingKeeper defines the expected staking keeper for module accounts // StakingKeeper defines the expected staking keeper for module accounts
@ -67,6 +70,7 @@ type SavingsKeeper interface {
// EarnKeeper defines the required methods needed by this modules keeper // EarnKeeper defines the required methods needed by this modules keeper
type EarnKeeper interface { type EarnKeeper interface {
GetVaultTotalShares(ctx sdk.Context, denom string) (shares earntypes.VaultShare, found bool) GetVaultTotalShares(ctx sdk.Context, denom string) (shares earntypes.VaultShare, found bool)
GetVaultTotalValue(ctx sdk.Context, denom string) (sdk.Coin, error)
GetVaultAccountShares(ctx sdk.Context, acc sdk.AccAddress) (shares earntypes.VaultShares, found bool) GetVaultAccountShares(ctx sdk.Context, acc sdk.AccAddress) (shares earntypes.VaultShares, found bool)
IterateVaultRecords(ctx sdk.Context, cb func(record earntypes.VaultRecord) (stop bool)) IterateVaultRecords(ctx sdk.Context, cb func(record earntypes.VaultRecord) (stop bool))
} }
@ -90,6 +94,21 @@ type AccountKeeper interface {
GetModuleAccount(ctx sdk.Context, name string) authtypes.ModuleAccountI GetModuleAccount(ctx sdk.Context, name string) authtypes.ModuleAccountI
} }
// MintKeeper defines the required methods needed by this modules keeper
type MintKeeper interface {
GetMinter(ctx sdk.Context) (minter minttypes.Minter)
}
// DistrKeeper defines the required methods needed by this modules keeper
type DistrKeeper interface {
GetCommunityTax(ctx sdk.Context) (percent sdk.Dec)
}
// PricefeedKeeper defines the required methods needed by this modules keeper
type PricefeedKeeper interface {
GetCurrentPrice(ctx sdk.Context, marketID string) (pricefeedtypes.CurrentPrice, error)
}
// CDPHooks event hooks for other keepers to run code in response to CDP modifications // CDPHooks event hooks for other keepers to run code in response to CDP modifications
type CDPHooks interface { type CDPHooks interface {
AfterCDPCreated(ctx sdk.Context, cdp cdptypes.CDP) AfterCDPCreated(ctx sdk.Context, cdp cdptypes.CDP)

View File

@ -14,6 +14,7 @@ const (
QueryGetEarnRewards = "earn-rewards" QueryGetEarnRewards = "earn-rewards"
QueryGetRewardFactors = "reward-factors" QueryGetRewardFactors = "reward-factors"
QueryGetParams = "parameters" QueryGetParams = "parameters"
QueryGetAPYs = "apys"
RestClaimCollateralType = "collateral_type" RestClaimCollateralType = "collateral_type"
RestClaimOwner = "owner" RestClaimOwner = "owner"
@ -64,3 +65,32 @@ func NewQueryGetRewardFactorsResponse(usdxMintingFactors RewardIndexes, supplyFa
EarnRewardFactors: earnFactors, EarnRewardFactors: earnFactors,
} }
} }
// APY contains the APY for a given collateral type
type APY struct {
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
APY sdk.Dec `json:"apy" yaml:"apy"`
}
// NewAPY returns a new instance of APY
func NewAPY(collateralType string, apy sdk.Dec) APY {
return APY{
CollateralType: collateralType,
APY: apy,
}
}
// APYs is a slice of APY
type APYs []APY
// QueryGetAPYsResponse holds the response to a APY query
type QueryGetAPYsResponse struct {
Earn []APY `json:"earn" yaml:"earn"`
}
// NewQueryGetAPYsResponse returns a new instance of QueryGetAPYsResponse
func NewQueryGetAPYsResponse(earn []APY) QueryGetAPYsResponse {
return QueryGetAPYsResponse{
Earn: earn,
}
}