mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-25 15:55:18 +00:00
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:
parent
4c879bc4fb
commit
be7242d86d
@ -633,6 +633,9 @@ func NewApp(
|
||||
&savingsKeeper,
|
||||
&app.liquidKeeper,
|
||||
&earnKeeper,
|
||||
app.mintKeeper,
|
||||
app.distrKeeper,
|
||||
app.pricefeedKeeper,
|
||||
)
|
||||
app.routerKeeper = routerkeeper.NewKeeper(
|
||||
&app.earnKeeper,
|
||||
|
@ -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/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/apy", types.ModuleName), queryAPYsHandlerFn(cliCtx)).Methods("GET")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,11 @@ type Keeper struct {
|
||||
savingsKeeper types.SavingsKeeper
|
||||
liquidKeeper types.LiquidKeeper
|
||||
earnKeeper types.EarnKeeper
|
||||
|
||||
// Keepers used for APY queries
|
||||
mintKeeper types.MintKeeper
|
||||
distrKeeper types.DistrKeeper
|
||||
pricefeedKeeper types.PricefeedKeeper
|
||||
}
|
||||
|
||||
// NewKeeper creates a new keeper
|
||||
@ -31,24 +36,28 @@ func NewKeeper(
|
||||
cdc codec.Codec, key sdk.StoreKey, paramstore types.ParamSubspace, bk types.BankKeeper,
|
||||
cdpk types.CdpKeeper, hk types.HardKeeper, ak types.AccountKeeper, stk types.StakingKeeper,
|
||||
swpk types.SwapKeeper, svk types.SavingsKeeper, lqk types.LiquidKeeper, ek types.EarnKeeper,
|
||||
mk types.MintKeeper, dk types.DistrKeeper, pfk types.PricefeedKeeper,
|
||||
) Keeper {
|
||||
if !paramstore.HasKeyTable() {
|
||||
paramstore = paramstore.WithKeyTable(types.ParamKeyTable())
|
||||
}
|
||||
|
||||
return Keeper{
|
||||
accountKeeper: ak,
|
||||
cdc: cdc,
|
||||
key: key,
|
||||
paramSubspace: paramstore,
|
||||
bankKeeper: bk,
|
||||
cdpKeeper: cdpk,
|
||||
hardKeeper: hk,
|
||||
stakingKeeper: stk,
|
||||
swapKeeper: swpk,
|
||||
savingsKeeper: svk,
|
||||
liquidKeeper: lqk,
|
||||
earnKeeper: ek,
|
||||
accountKeeper: ak,
|
||||
cdc: cdc,
|
||||
key: key,
|
||||
paramSubspace: paramstore,
|
||||
bankKeeper: bk,
|
||||
cdpKeeper: cdpk,
|
||||
hardKeeper: hk,
|
||||
stakingKeeper: stk,
|
||||
swapKeeper: swpk,
|
||||
savingsKeeper: svk,
|
||||
liquidKeeper: lqk,
|
||||
earnKeeper: ek,
|
||||
mintKeeper: mk,
|
||||
distrKeeper: dk,
|
||||
pricefeedKeeper: pfk,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,21 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
|
||||
earntypes "github.com/kava-labs/kava/x/earn/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
|
||||
@ -31,6 +39,8 @@ func NewQuerier(k Keeper, legacyQuerierCdc *codec.LegacyAmino) sdk.Querier {
|
||||
return queryGetRewardFactors(ctx, req, k, legacyQuerierCdc)
|
||||
case types.QueryGetEarnRewards:
|
||||
return queryGetEarnRewards(ctx, req, k, legacyQuerierCdc)
|
||||
case types.QueryGetAPYs:
|
||||
return queryGetAPYs(ctx, req, k, legacyQuerierCdc)
|
||||
default:
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
102
x/incentive/keeper/querier_test.go
Normal file
102
x/incentive/keeper/querier_test.go
Normal 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)",
|
||||
)
|
||||
}
|
@ -8,8 +8,10 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
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"
|
||||
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/tendermint/tendermint/libs/log"
|
||||
db "github.com/tendermint/tm-db"
|
||||
@ -74,7 +76,11 @@ func (suite *unitTester) NewKeeper(
|
||||
ak types.AccountKeeper, stk types.StakingKeeper, swk types.SwapKeeper,
|
||||
svk types.SavingsKeeper, lqk types.LiquidKeeper, ek types.EarnKeeper,
|
||||
) 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) {
|
||||
@ -133,6 +139,96 @@ func (suite *unitTester) storeEarnClaim(claim types.EarnClaim) {
|
||||
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.
|
||||
type fakeParamSubspace struct {
|
||||
params types.Params
|
||||
@ -410,6 +506,15 @@ func (k *fakeEarnKeeper) GetVaultTotalShares(
|
||||
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(
|
||||
ctx sdk.Context,
|
||||
acc sdk.AccAddress,
|
||||
@ -519,6 +624,112 @@ func (k *fakeLiquidKeeper) getRewardAmount(
|
||||
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
|
||||
|
||||
// note: amino panics when encoding times ≥ the start of year 10000.
|
||||
|
@ -3,11 +3,13 @@ package types
|
||||
import (
|
||||
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"
|
||||
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||
earntypes "github.com/kava-labs/kava/x/earn/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"
|
||||
)
|
||||
|
||||
@ -23,6 +25,7 @@ type ParamSubspace interface {
|
||||
type BankKeeper interface {
|
||||
SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
|
||||
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
|
||||
@ -67,6 +70,7 @@ type SavingsKeeper interface {
|
||||
// EarnKeeper defines the required methods needed by this modules keeper
|
||||
type EarnKeeper interface {
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
type CDPHooks interface {
|
||||
AfterCDPCreated(ctx sdk.Context, cdp cdptypes.CDP)
|
||||
|
@ -14,6 +14,7 @@ const (
|
||||
QueryGetEarnRewards = "earn-rewards"
|
||||
QueryGetRewardFactors = "reward-factors"
|
||||
QueryGetParams = "parameters"
|
||||
QueryGetAPYs = "apys"
|
||||
|
||||
RestClaimCollateralType = "collateral_type"
|
||||
RestClaimOwner = "owner"
|
||||
@ -64,3 +65,32 @@ func NewQueryGetRewardFactorsResponse(usdxMintingFactors RewardIndexes, supplyFa
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user