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, ¶ms) 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, ¶ms) 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 }