0g-chain/x/incentive/keeper/grpc_query.go
drklee3 35c848dce6
Add incentive grpc query service (#1503)
* Add incentive grpc query service

* Register query server

* Add grpc query test

* Add grpc query test

* Fix proto lints

* Fix missing savings field, update tests

* Update proto docs

* Use consts for query reward types

* Add owner to queryRewards err

* Add empty reward type to be valid
2023-03-21 15:47:21 -07:00

343 lines
9.7 KiB
Go

package keeper
import (
"context"
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/kava-labs/kava/x/incentive/types"
liquidtypes "github.com/kava-labs/kava/x/liquid/types"
)
const (
RewardTypeHard = "hard"
RewardTypeUSDXMinting = "usdx_minting"
RewardTypeDelegator = "delegator"
RewardTypeSwap = "swap"
RewardTypeSavings = "savings"
RewardTypeEarn = "earn"
)
type queryServer struct {
keeper Keeper
}
var _ types.QueryServer = queryServer{}
// NewQueryServerImpl creates a new server for handling gRPC queries.
func NewQueryServerImpl(keeper Keeper) types.QueryServer {
return &queryServer{
keeper: keeper,
}
}
func (s queryServer) Params(
ctx context.Context,
req *types.QueryParamsRequest,
) (*types.QueryParamsResponse, error) {
if req == nil {
return nil, status.Errorf(codes.InvalidArgument, "empty request")
}
sdkCtx := sdk.UnwrapSDKContext(ctx)
return &types.QueryParamsResponse{
Params: s.keeper.GetParams(sdkCtx),
}, nil
}
func (s queryServer) Rewards(
ctx context.Context,
req *types.QueryRewardsRequest,
) (*types.QueryRewardsResponse, error) {
if req == nil {
return nil, status.Errorf(codes.InvalidArgument, "empty request")
}
sdkCtx := sdk.UnwrapSDKContext(ctx)
res := types.QueryRewardsResponse{}
hasOwner := req.Owner != ""
var owner sdk.AccAddress
if hasOwner {
addr, err := sdk.AccAddressFromBech32(req.Owner)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid address: %s", err)
}
owner = addr
}
if err := s.queryRewards(sdkCtx, &res, owner, hasOwner, req.RewardType); err != nil {
return nil, err
}
if !req.Unsynchronized {
if err := s.synchronizeRewards(sdkCtx, &res); err != nil {
return nil, err
}
}
return &res, nil
}
func (s queryServer) RewardFactors(
ctx context.Context,
req *types.QueryRewardFactorsRequest,
) (*types.QueryRewardFactorsResponse, error) {
if req == nil {
return nil, status.Errorf(codes.InvalidArgument, "empty request")
}
sdkCtx := sdk.UnwrapSDKContext(ctx)
var usdxFactors types.RewardIndexes
s.keeper.IterateUSDXMintingRewardFactors(sdkCtx, func(collateralType string, factor sdk.Dec) (stop bool) {
usdxFactors = usdxFactors.With(collateralType, factor)
return false
})
var supplyFactors types.MultiRewardIndexes
s.keeper.IterateHardSupplyRewardIndexes(sdkCtx, func(denom string, indexes types.RewardIndexes) (stop bool) {
supplyFactors = supplyFactors.With(denom, indexes)
return false
})
var borrowFactors types.MultiRewardIndexes
s.keeper.IterateHardBorrowRewardIndexes(sdkCtx, func(denom string, indexes types.RewardIndexes) (stop bool) {
borrowFactors = borrowFactors.With(denom, indexes)
return false
})
var delegatorFactors types.MultiRewardIndexes
s.keeper.IterateDelegatorRewardIndexes(sdkCtx, func(denom string, indexes types.RewardIndexes) (stop bool) {
delegatorFactors = delegatorFactors.With(denom, indexes)
return false
})
var swapFactors types.MultiRewardIndexes
s.keeper.IterateSwapRewardIndexes(sdkCtx, func(poolID string, indexes types.RewardIndexes) (stop bool) {
swapFactors = swapFactors.With(poolID, indexes)
return false
})
var savingsFactors types.MultiRewardIndexes
s.keeper.IterateSavingsRewardIndexes(sdkCtx, func(denom string, indexes types.RewardIndexes) (stop bool) {
savingsFactors = savingsFactors.With(denom, indexes)
return false
})
var earnFactors types.MultiRewardIndexes
s.keeper.IterateEarnRewardIndexes(sdkCtx, func(denom string, indexes types.RewardIndexes) (stop bool) {
earnFactors = earnFactors.With(denom, indexes)
return false
})
return &types.QueryRewardFactorsResponse{
UsdxMintingRewardFactors: usdxFactors,
HardSupplyRewardFactors: supplyFactors,
HardBorrowRewardFactors: borrowFactors,
DelegatorRewardFactors: delegatorFactors,
SwapRewardFactors: swapFactors,
SavingsRewardFactors: savingsFactors,
EarnRewardFactors: earnFactors,
}, nil
}
func (s queryServer) Apy(
ctx context.Context,
req *types.QueryApyRequest,
) (*types.QueryApyResponse, error) {
if req == nil {
return nil, status.Errorf(codes.InvalidArgument, "empty request")
}
sdkCtx := sdk.UnwrapSDKContext(ctx)
params := s.keeper.GetParams(sdkCtx)
var apys types.APYs
// bkava APY (staking + incentive rewards)
stakingAPR, err := GetStakingAPR(sdkCtx, s.keeper, 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 := s.keeper.earnKeeper.GetVaultTotalValue(sdkCtx, param.CollateralType)
if err != nil {
return nil, err
}
apy, err := GetAPYFromMultiRewardPeriod(sdkCtx, s.keeper, param.CollateralType, param, vaultTotalValue.Amount)
if err != nil {
return nil, err
}
apys = append(apys, types.NewAPY(param.CollateralType, apy))
}
return &types.QueryApyResponse{
Earn: apys,
}, nil
}
// queryRewards queries the rewards for a given owner and reward type, updating
// the response with the results in place.
func (s queryServer) queryRewards(
ctx sdk.Context,
res *types.QueryRewardsResponse,
owner sdk.AccAddress,
hasOwner bool,
rewardType string,
) error {
rewardType = strings.ToLower(rewardType)
isAllRewards := rewardType == ""
if !rewardTypeIsValid(rewardType) {
return status.Errorf(codes.InvalidArgument, "invalid reward type for owner %s: %s", owner, rewardType)
}
if isAllRewards || rewardType == RewardTypeUSDXMinting {
if hasOwner {
usdxMintingClaim, foundUsdxMintingClaim := s.keeper.GetUSDXMintingClaim(ctx, owner)
if foundUsdxMintingClaim {
res.USDXMintingClaims = append(res.USDXMintingClaims, usdxMintingClaim)
}
} else {
usdxMintingClaims := s.keeper.GetAllUSDXMintingClaims(ctx)
res.USDXMintingClaims = append(res.USDXMintingClaims, usdxMintingClaims...)
}
}
if isAllRewards || rewardType == RewardTypeHard {
if hasOwner {
hardClaim, foundHardClaim := s.keeper.GetHardLiquidityProviderClaim(ctx, owner)
if foundHardClaim {
res.HardLiquidityProviderClaims = append(res.HardLiquidityProviderClaims, hardClaim)
}
} else {
hardClaims := s.keeper.GetAllHardLiquidityProviderClaims(ctx)
res.HardLiquidityProviderClaims = append(res.HardLiquidityProviderClaims, hardClaims...)
}
}
if isAllRewards || rewardType == RewardTypeDelegator {
if hasOwner {
delegatorClaim, foundDelegatorClaim := s.keeper.GetDelegatorClaim(ctx, owner)
if foundDelegatorClaim {
res.DelegatorClaims = append(res.DelegatorClaims, delegatorClaim)
}
} else {
delegatorClaims := s.keeper.GetAllDelegatorClaims(ctx)
res.DelegatorClaims = append(res.DelegatorClaims, delegatorClaims...)
}
}
if isAllRewards || rewardType == RewardTypeSwap {
if hasOwner {
swapClaim, foundSwapClaim := s.keeper.GetSwapClaim(ctx, owner)
if foundSwapClaim {
res.SwapClaims = append(res.SwapClaims, swapClaim)
}
} else {
swapClaims := s.keeper.GetAllSwapClaims(ctx)
res.SwapClaims = append(res.SwapClaims, swapClaims...)
}
}
if isAllRewards || rewardType == RewardTypeSavings {
if hasOwner {
savingsClaim, foundSavingsClaim := s.keeper.GetSavingsClaim(ctx, owner)
if foundSavingsClaim {
res.SavingsClaims = append(res.SavingsClaims, savingsClaim)
}
} else {
savingsClaims := s.keeper.GetAllSavingsClaims(ctx)
res.SavingsClaims = append(res.SavingsClaims, savingsClaims...)
}
}
if isAllRewards || rewardType == RewardTypeEarn {
if hasOwner {
earnClaim, foundEarnClaim := s.keeper.GetEarnClaim(ctx, owner)
if foundEarnClaim {
res.EarnClaims = append(res.EarnClaims, earnClaim)
}
} else {
earnClaims := s.keeper.GetAllEarnClaims(ctx)
res.EarnClaims = append(res.EarnClaims, earnClaims...)
}
}
return nil
}
// synchronizeRewards synchronizes all non-empty rewards in place.
func (s queryServer) synchronizeRewards(
ctx sdk.Context,
res *types.QueryRewardsResponse,
) error {
// Synchronize all non-empty rewards
for i, claim := range res.USDXMintingClaims {
res.USDXMintingClaims[i] = s.keeper.SimulateUSDXMintingSynchronization(ctx, claim)
}
for i, claim := range res.HardLiquidityProviderClaims {
res.HardLiquidityProviderClaims[i] = s.keeper.SimulateHardSynchronization(ctx, claim)
}
for i, claim := range res.DelegatorClaims {
res.DelegatorClaims[i] = s.keeper.SimulateDelegatorSynchronization(ctx, claim)
}
for i, claim := range res.SwapClaims {
syncedClaim, found := s.keeper.GetSynchronizedSwapClaim(ctx, claim.Owner)
if !found {
return status.Errorf(codes.Internal, "previously found swap claim for owner %s should still be found", claim.Owner)
}
res.SwapClaims[i] = syncedClaim
}
for i, claim := range res.SavingsClaims {
syncedClaim, found := s.keeper.GetSynchronizedSavingsClaim(ctx, claim.Owner)
if !found {
return status.Errorf(codes.Internal, "previously found savings claim for owner %s should still be found", claim.Owner)
}
res.SavingsClaims[i] = syncedClaim
}
for i, claim := range res.EarnClaims {
syncedClaim, found := s.keeper.GetSynchronizedEarnClaim(ctx, claim.Owner)
if !found {
return status.Errorf(codes.Internal, "previously found earn claim for owner %s should still be found", claim.Owner)
}
res.EarnClaims[i] = syncedClaim
}
return nil
}
func rewardTypeIsValid(rewardType string) bool {
return rewardType == "" ||
rewardType == RewardTypeHard ||
rewardType == RewardTypeUSDXMinting ||
rewardType == RewardTypeDelegator ||
rewardType == RewardTypeSwap ||
rewardType == RewardTypeSavings ||
rewardType == RewardTypeEarn
}