mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 00:05: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,
|
&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,
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
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/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.
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user