0g-chain/x/cdp/keeper/querier.go
drklee3 3375484f79
Update deprecated Cosmos methods (#1530)
* Use cosmossdk.io/errors for deprecated error methods

* Update error registration with cosmossdk.io/errors

* Use cosmossdk.io/math for deprecated sdk.Int alias

* Fix modified proto file

* Update sdk.Int usage in swap hooks

* Update e2e test deprecated method usage
2023-04-05 16:21:59 -07:00

485 lines
16 KiB
Go

package keeper
import (
"fmt"
"sort"
abci "github.com/tendermint/tendermint/abci/types"
errorsmod "cosmossdk.io/errors"
"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"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/kava-labs/kava/x/cdp/types"
)
// NewQuerier returns a new querier function
func NewQuerier(keeper Keeper, legacyQuerierCdc *codec.LegacyAmino) sdk.Querier {
return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err error) {
switch path[0] {
case types.QueryGetCdp:
return queryGetCdp(ctx, req, keeper, legacyQuerierCdc)
case types.QueryGetCdps:
return queryGetCdps(ctx, req, keeper, legacyQuerierCdc)
case types.QueryGetCdpDeposits:
return queryGetDeposits(ctx, req, keeper, legacyQuerierCdc)
case types.QueryGetCdpsByCollateralType: // legacy, maintained for REST API
return queryGetCdpsByCollateralType(ctx, req, keeper, legacyQuerierCdc)
case types.QueryGetCdpsByCollateralization: // legacy, maintained for REST API
return queryGetCdpsByRatio(ctx, req, keeper, legacyQuerierCdc)
case types.QueryGetParams:
return queryGetParams(ctx, req, keeper, legacyQuerierCdc)
case types.QueryGetAccounts:
return queryGetAccounts(ctx, req, keeper, legacyQuerierCdc)
case types.QueryGetTotalPrincipal:
return queryGetTotalPrincipal(ctx, req, keeper, legacyQuerierCdc)
case types.QueryGetTotalCollateral:
return queryGetTotalCollateral(ctx, req, keeper, legacyQuerierCdc)
default:
return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint %s", types.ModuleName, path[0])
}
}
}
// query a specific cdp
func queryGetCdp(ctx sdk.Context, req abci.RequestQuery, keeper Keeper, legacyQuerierCdc *codec.LegacyAmino) ([]byte, error) {
var requestParams types.QueryCdpParams
err := legacyQuerierCdc.UnmarshalJSON(req.Data, &requestParams)
if err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
_, valid := keeper.GetCollateral(ctx, requestParams.CollateralType)
if !valid {
return nil, errorsmod.Wrap(types.ErrInvalidCollateral, requestParams.CollateralType)
}
cdp, found := keeper.GetCdpByOwnerAndCollateralType(ctx, requestParams.Owner, requestParams.CollateralType)
if !found {
return nil, errorsmod.Wrapf(types.ErrCdpNotFound, "owner %s, denom %s", requestParams.Owner, requestParams.CollateralType)
}
augmentedCDP := keeper.LoadAugmentedCDP(ctx, cdp)
bz, err := codec.MarshalJSONIndent(legacyQuerierCdc, augmentedCDP)
if err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}
// query deposits on a particular cdp
func queryGetDeposits(ctx sdk.Context, req abci.RequestQuery, keeper Keeper, legacyQuerierCdc *codec.LegacyAmino) ([]byte, error) {
var requestParams types.QueryCdpDeposits
err := legacyQuerierCdc.UnmarshalJSON(req.Data, &requestParams)
if err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
_, valid := keeper.GetCollateral(ctx, requestParams.CollateralType)
if !valid {
return nil, errorsmod.Wrap(types.ErrInvalidCollateral, requestParams.CollateralType)
}
cdp, found := keeper.GetCdpByOwnerAndCollateralType(ctx, requestParams.Owner, requestParams.CollateralType)
if !found {
return nil, errorsmod.Wrapf(types.ErrCdpNotFound, "owner %s, denom %s", requestParams.Owner, requestParams.CollateralType)
}
deposits := keeper.GetDeposits(ctx, cdp.ID)
bz, err := codec.MarshalJSONIndent(legacyQuerierCdc, deposits)
if err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}
// query cdps with matching denom and ratio LESS THAN the input ratio
func queryGetCdpsByRatio(ctx sdk.Context, req abci.RequestQuery, keeper Keeper, legacyQuerierCdc *codec.LegacyAmino) ([]byte, error) {
var requestParams types.QueryCdpsByRatioParams
err := legacyQuerierCdc.UnmarshalJSON(req.Data, &requestParams)
if err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
_, valid := keeper.GetCollateral(ctx, requestParams.CollateralType)
if !valid {
return nil, errorsmod.Wrap(types.ErrInvalidCollateral, requestParams.CollateralType)
}
ratio, err := keeper.CalculateCollateralizationRatioFromAbsoluteRatio(ctx, requestParams.CollateralType, requestParams.Ratio, "liquidation")
if err != nil {
return nil, errorsmod.Wrap(err, "couldn't get collateralization ratio from absolute ratio")
}
cdps := keeper.GetAllCdpsByCollateralTypeAndRatio(ctx, requestParams.CollateralType, ratio)
// augment CDPs by adding collateral value and collateralization ratio
var augmentedCDPs types.AugmentedCDPs
for _, cdp := range cdps {
augmentedCDP := keeper.LoadAugmentedCDP(ctx, cdp)
augmentedCDPs = append(augmentedCDPs, augmentedCDP)
}
bz, err := codec.MarshalJSONIndent(legacyQuerierCdc, augmentedCDPs)
if err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}
// query all cdps with matching collateral type
func queryGetCdpsByCollateralType(ctx sdk.Context, req abci.RequestQuery, keeper Keeper, legacyQuerierCdc *codec.LegacyAmino) ([]byte, error) {
var requestParams types.QueryCdpsByCollateralTypeParams
err := legacyQuerierCdc.UnmarshalJSON(req.Data, &requestParams)
if err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
_, valid := keeper.GetCollateral(ctx, requestParams.CollateralType)
if !valid {
return nil, errorsmod.Wrap(types.ErrInvalidCollateral, requestParams.CollateralType)
}
cdps := keeper.GetAllCdpsByCollateralType(ctx, requestParams.CollateralType)
// augment CDPs by adding collateral value and collateralization ratio
var augmentedCDPs types.AugmentedCDPs
for _, cdp := range cdps {
augmentedCDP := keeper.LoadAugmentedCDP(ctx, cdp)
augmentedCDPs = append(augmentedCDPs, augmentedCDP)
}
bz, err := codec.MarshalJSONIndent(legacyQuerierCdc, augmentedCDPs)
if err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}
// query params in the cdp store
func queryGetParams(ctx sdk.Context, req abci.RequestQuery, keeper Keeper, legacyQuerierCdc *codec.LegacyAmino) ([]byte, error) {
// Get params
params := keeper.GetParams(ctx)
// Encode results
bz, err := codec.MarshalJSONIndent(legacyQuerierCdc, params)
if err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}
// query cdp module accounts
func queryGetAccounts(ctx sdk.Context, req abci.RequestQuery, keeper Keeper, legacyQuerierCdc *codec.LegacyAmino) ([]byte, error) {
cdpAccAccount := keeper.accountKeeper.GetModuleAccount(ctx, types.ModuleName)
liquidatorAccAccount := keeper.accountKeeper.GetModuleAccount(ctx, types.LiquidatorMacc)
accounts := []authtypes.ModuleAccount{
*cdpAccAccount.(*authtypes.ModuleAccount),
*liquidatorAccAccount.(*authtypes.ModuleAccount),
}
// Encode results
bz, err := codec.MarshalJSONIndent(legacyQuerierCdc, accounts)
if err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}
// query cdps in store and filter by request params
func queryGetCdps(ctx sdk.Context, req abci.RequestQuery, keeper Keeper, legacyQuerierCdc *codec.LegacyAmino) ([]byte, error) {
var params types.QueryCdpsParams
err := legacyQuerierCdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
// Filter CDPs
filteredCDPs, err := FilterCDPs(ctx, keeper, params)
if err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
bz, err := codec.MarshalJSONIndent(legacyQuerierCdc, filteredCDPs)
if err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}
// query total amount of principal (ie. usdx) that has been minted with a particular collateral type
func queryGetTotalPrincipal(ctx sdk.Context, req abci.RequestQuery, keeper Keeper, legacyQuerierCdc *codec.LegacyAmino) ([]byte, error) {
var params types.QueryGetTotalPrincipalParams
err := legacyQuerierCdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
var queryCollateralTypes []string
if params.CollateralType != "" {
// Single collateralType provided
queryCollateralTypes = append(queryCollateralTypes, params.CollateralType)
} else {
// No collateralType provided, respond with all of them
keeperParams := keeper.GetParams(ctx)
for _, collateral := range keeperParams.CollateralParams {
queryCollateralTypes = append(queryCollateralTypes, collateral.Type)
}
}
var collateralPrincipals types.TotalPrincipals
for _, queryType := range queryCollateralTypes {
// Hardcoded to default USDX
principalAmount := keeper.GetTotalPrincipal(ctx, queryType, types.DefaultStableDenom)
// Wrap it in an sdk.Coin
totalAmountCoin := sdk.NewCoin(types.DefaultStableDenom, principalAmount)
totalPrincipal := types.NewTotalPrincipal(queryType, totalAmountCoin)
collateralPrincipals = append(collateralPrincipals, totalPrincipal)
}
bz, err := codec.MarshalJSONIndent(legacyQuerierCdc, collateralPrincipals)
if err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}
// query total amount of collateral (ie. btcb) that has been deposited with a particular collateral type
func queryGetTotalCollateral(ctx sdk.Context, req abci.RequestQuery, keeper Keeper, legacyQuerierCdc *codec.LegacyAmino) ([]byte, error) {
var request types.QueryGetTotalCollateralParams
err := legacyQuerierCdc.UnmarshalJSON(req.Data, &request)
if err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
params := keeper.GetParams(ctx)
denomCollateralTypes := make(map[string][]string)
// collect collateral types for each denom
for _, collateralParam := range params.CollateralParams {
denomCollateralTypes[collateralParam.Denom] = append(denomCollateralTypes[collateralParam.Denom], collateralParam.Type)
}
// sort collateral types alphabetically
for _, collateralTypes := range denomCollateralTypes {
sort.Slice(collateralTypes, func(i int, j int) bool {
return collateralTypes[i] < collateralTypes[j]
})
}
// get total collateral in all cdps
cdpAccount := keeper.accountKeeper.GetModuleAccount(ctx, types.ModuleName)
totalCdpCollateral := keeper.bankKeeper.GetAllBalances(ctx, cdpAccount.GetAddress())
var response types.TotalCollaterals
for denom, collateralTypes := range denomCollateralTypes {
// skip any denoms that do not match the requested collateral type
if request.CollateralType != "" {
match := false
for _, ctype := range collateralTypes {
if ctype == request.CollateralType {
match = true
}
}
if !match {
continue
}
}
totalCollateral := totalCdpCollateral.AmountOf(denom)
// we need to query individual cdps for denoms with more than one collateral type
for i := len(collateralTypes) - 1; i > 0; i-- {
cdps := keeper.GetAllCdpsByCollateralType(ctx, collateralTypes[i])
collateral := sdk.ZeroInt()
for _, cdp := range cdps {
collateral = collateral.Add(cdp.Collateral.Amount)
}
totalCollateral = totalCollateral.Sub(collateral)
// if we have no collateralType filter, or the filter matches, include it in the response
if request.CollateralType == "" || collateralTypes[i] == request.CollateralType {
response = append(response, types.NewTotalCollateral(collateralTypes[i], sdk.NewCoin(denom, collateral)))
}
// skip the rest of the cdp queries if we have a matching filter
if collateralTypes[i] == request.CollateralType {
break
}
}
if request.CollateralType == "" || collateralTypes[0] == request.CollateralType {
// all leftover total collateral belongs to the first collateral type
response = append(response, types.NewTotalCollateral(collateralTypes[0], sdk.NewCoin(denom, totalCollateral)))
}
}
// sort to ensure deterministic response
sort.Slice(response, func(i int, j int) bool {
return response[i].CollateralType < response[j].CollateralType
})
// encode response
bz, err := codec.MarshalJSONIndent(legacyQuerierCdc, response)
if err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}
// FilterCDPs queries the store for all CDPs that match query params
func FilterCDPs(ctx sdk.Context, k Keeper, params types.QueryCdpsParams) (types.AugmentedCDPs, error) {
var matchCollateralType, matchOwner, matchID, matchRatio types.CDPs
// match cdp owner (if supplied)
if len(params.Owner) > 0 {
denoms := k.GetCollateralTypes(ctx)
for _, denom := range denoms {
cdp, found := k.GetCdpByOwnerAndCollateralType(ctx, params.Owner, denom)
if found {
matchOwner = append(matchOwner, cdp)
}
}
}
// match cdp collateral denom (if supplied)
if len(params.CollateralType) > 0 {
// if owner is specified only iterate over already matched cdps for efficiency
if len(params.Owner) > 0 {
for _, cdp := range matchOwner {
if cdp.Type == params.CollateralType {
matchCollateralType = append(matchCollateralType, cdp)
}
}
} else {
_, found := k.GetCollateral(ctx, params.CollateralType)
if !found {
return nil, fmt.Errorf("invalid collateral type")
}
matchCollateralType = k.GetAllCdpsByCollateralType(ctx, params.CollateralType)
}
}
// match cdp ID (if supplied)
if params.ID != 0 {
denoms := k.GetCollateralTypes(ctx)
for _, denom := range denoms {
cdp, found := k.GetCDP(ctx, denom, params.ID)
if found {
matchID = append(matchID, cdp)
}
}
}
// match cdp ratio (if supplied)
if !params.Ratio.IsNil() && params.Ratio.GT(sdk.ZeroDec()) {
denoms := k.GetCollateralTypes(ctx)
for _, denom := range denoms {
ratio, err := k.CalculateCollateralizationRatioFromAbsoluteRatio(ctx, denom, params.Ratio, "liquidation")
if err != nil {
continue
}
cdpsUnderRatio := k.GetAllCdpsByCollateralTypeAndRatio(ctx, denom, ratio)
matchRatio = append(matchRatio, cdpsUnderRatio...)
}
}
var commonCDPs types.CDPs
// If no params specified, fetch all CDPs
if params.CollateralType == "" && len(params.Owner) == 0 && params.ID == 0 && params.Ratio.Equal(sdk.ZeroDec()) {
commonCDPs = k.GetAllCdps(ctx)
}
// Find the intersection of any matched CDPs
if params.CollateralType != "" {
if len(matchCollateralType) == 0 {
return nil, nil
}
commonCDPs = matchCollateralType
}
if len(params.Owner) > 0 {
if len(matchCollateralType) > 0 {
if len(commonCDPs) > 0 {
commonCDPs = FindIntersection(commonCDPs, matchOwner)
} else {
commonCDPs = matchOwner
}
} else {
commonCDPs = matchOwner
}
}
if params.ID != 0 {
if len(matchID) == 0 {
return nil, nil
}
if len(commonCDPs) > 0 {
commonCDPs = FindIntersection(commonCDPs, matchID)
} else {
commonCDPs = matchID
}
}
if !params.Ratio.IsNil() && params.Ratio.GT(sdk.ZeroDec()) {
if len(matchRatio) == 0 {
return nil, nil
}
if len(commonCDPs) > 0 {
commonCDPs = FindIntersection(commonCDPs, matchRatio)
} else {
commonCDPs = matchRatio
}
}
// Load augmented CDPs
var augmentedCDPs types.AugmentedCDPs
for _, cdp := range commonCDPs {
augmentedCDP := k.LoadAugmentedCDP(ctx, cdp)
augmentedCDPs = append(augmentedCDPs, augmentedCDP)
}
// Apply page and limit params
start, end := client.Paginate(len(augmentedCDPs), params.Page, params.Limit, 100)
if start < 0 || end < 0 {
return nil, nil
}
return augmentedCDPs[start:end], nil
}
// FindIntersection finds the intersection of two CDP arrays in linear time complexity O(n + n)
func FindIntersection(x types.CDPs, y types.CDPs) types.CDPs {
cdpSet := make(types.CDPs, 0)
cdpMap := make(map[uint64]bool)
for i := 0; i < len(x); i++ {
cdp := x[i]
cdpMap[cdp.ID] = true
}
for i := 0; i < len(y); i++ {
cdp := y[i]
if _, found := cdpMap[cdp.ID]; found {
cdpSet = append(cdpSet, cdp)
}
}
return cdpSet
}