Querier improvements: CDP and Auction priority 1 queries (#644)

* query auction by lot owner

* add SavingsRateDistributed to store

* v2cdps: filtered cdps query

* update v2cdps cli examples

* add savings rate dist counter to begin blocker

* implement savings rate dist cli query

* implement cdp REST queries

* minor auction CLI/REST updates

* fix auction querier bug

* update REST endpoint to 'cdps'

* update to savings-rate-dist

* update SavingsRateDistributed get/set

* update tests

* fix savings rate dist rounding errors

* 'collateralDenom' -> 'collateralType'

* refactor 'v2cdps' -> 'cdps', add ratio param

* fix augmented CDP type, msg string() method

* fix cdp querier test

* filter query results efficiently

* querier tests

* limit type iteration if owner defined

* improve savings rate dist genesis validation

* default sdk.Dec{} to sdk.ZeroDec in queries

* update condition logic for finding intersection

* fix cdp querier filtering

* Update kava-4 swagger (#653)

* add collateral_type, update cdp params

* savings rate, auctions, get cdps

* drop owner from AuctionResponse

* remove duplicate collateral denom

* update query paths with {collateral-type}
This commit is contained in:
Denali Marsh 2020-09-17 02:45:10 +02:00 committed by GitHub
parent 641d946ae7
commit e2f515ba9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 984 additions and 214 deletions

View File

@ -410,6 +410,8 @@
$ref: '#/definitions/CoinCollateral' $ref: '#/definitions/CoinCollateral'
principal: principal:
$ref: '#/definitions/CoinPrincipal' $ref: '#/definitions/CoinPrincipal'
collateral_type:
$ref: '#/definitions/CollateralType'
responses: responses:
200: 200:
description: Tx was successfully generated description: Tx was successfully generated
@ -419,7 +421,7 @@
description: Invalid request description: Invalid request
500: 500:
description: Server internal error description: Server internal error
/cdp/{owner}/{denom}/deposits: /cdp/{owner}/{collateral-type}/deposits:
post: post:
summary: Create a deposit to cdp transaction summary: Create a deposit to cdp transaction
tags: tags:
@ -436,11 +438,11 @@
type: string type: string
x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c
- in: path - in: path
name: denom name: collateral_type
description: Collateral denom description: Collateral type
required: true required: true
type: string type: string
x-example: xrp x-example: xrp-a
- description: deposit cdp post parameters - description: deposit cdp post parameters
name: post_deposit_req name: post_deposit_req
in: body in: body
@ -456,6 +458,8 @@
$ref: '#/definitions/Address' $ref: '#/definitions/Address'
collateral: collateral:
$ref: '#/definitions/CoinCollateral' $ref: '#/definitions/CoinCollateral'
collateral_type:
$ref: '#/definitions/CollateralType'
responses: responses:
200: 200:
description: Tx was successfully generated description: Tx was successfully generated
@ -465,7 +469,7 @@
description: Invalid request description: Invalid request
500: 500:
description: Server internal error description: Server internal error
/cdp/{owner}/{denom}/withdraw: /cdp/{owner}/{collateral-type}/withdraw:
post: post:
summary: create a withdraw collateral transaction summary: create a withdraw collateral transaction
tags: tags:
@ -482,11 +486,11 @@
type: string type: string
x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c
- in: path - in: path
name: denom name: collateral_type
description: Collateral denom description: Collateral type
required: true required: true
type: string type: string
x-example: xrp x-example: xrp-a
- description: withdraw cdp post parameters - description: withdraw cdp post parameters
name: post_withdraw_req name: post_withdraw_req
in: body in: body
@ -502,6 +506,8 @@
$ref: '#/definitions/Address' $ref: '#/definitions/Address'
collateral: collateral:
$ref: '#/definitions/CoinCollateral' $ref: '#/definitions/CoinCollateral'
collateral_type:
$ref: '#/definitions/CollateralType'
responses: responses:
200: 200:
description: Tx was successfully generated description: Tx was successfully generated
@ -511,7 +517,7 @@
description: Invalid request description: Invalid request
500: 500:
description: Server internal error description: Server internal error
/cdp/{owner}/{denom}/draw: /cdp/{owner}/{collateral-type}/draw:
post: post:
summary: Create a draw debt transaction summary: Create a draw debt transaction
tags: tags:
@ -528,11 +534,11 @@
type: string type: string
x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c
- in: path - in: path
name: denom name: collateral_type
description: Collateral denom description: Collateral type
required: true required: true
type: string type: string
x-example: xrp x-example: xrp-a
- description: draw cdp post parameters - description: draw cdp post parameters
name: post_draw_req name: post_draw_req
in: body in: body
@ -546,6 +552,8 @@
$ref: '#/definitions/Address' $ref: '#/definitions/Address'
principal: principal:
$ref: '#/definitions/CoinPrincipal' $ref: '#/definitions/CoinPrincipal'
collateral_type:
$ref: '#/definitions/CollateralType'
responses: responses:
200: 200:
description: Tx was successfully generated description: Tx was successfully generated
@ -555,7 +563,7 @@
description: Invalid request description: Invalid request
500: 500:
description: Server internal error description: Server internal error
/cdp/{owner}/{denom}/repay: /cdp/{owner}/{collateral-type}/repay:
post: post:
summary: Repay debt from a CDP summary: Repay debt from a CDP
tags: tags:
@ -572,11 +580,11 @@
type: string type: string
x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c
- in: path - in: path
name: denom name: collateral_type
description: Collateral denom description: Collateral type
required: true required: true
type: string type: string
x-example: xrp x-example: xrp-a
- description: repay cdp post parameters - description: repay cdp post parameters
name: post_repay_req name: post_repay_req
in: body in: body
@ -590,6 +598,8 @@
$ref: '#/definitions/Address' $ref: '#/definitions/Address'
payment: payment:
$ref: '#/definitions/CoinPrincipal' $ref: '#/definitions/CoinPrincipal'
collateral_type:
$ref: '#/definitions/CollateralType'
responses: responses:
200: 200:
description: Tx was successfully generated description: Tx was successfully generated
@ -640,7 +650,6 @@
type: number type: number
500: 500:
description: Server internal error description: Server internal error
/cdp/parameters: /cdp/parameters:
get: get:
summary: Get the parameters of the cdp module summary: Get the parameters of the cdp module
@ -669,17 +678,26 @@
surplus_auction_threshold: surplus_auction_threshold:
type: string type: string
example: '1000000000' example: '1000000000'
surplus_auction_lot:
type: string
example: '10000000'
debt_auction_threshold: debt_auction_threshold:
type: string type: string
example: '1000000000' example: '1000000000'
surplus_auction_lot:
type: string
example: '10000000'
savings_distribution_frequency:
type: string
example: '60000000'
circuit_breaker: circuit_breaker:
type: boolean type: boolean
example: false example: false
500: 500:
description: Server internal error description: Server internal error
/cdp/cdps/cdp/{owner}/{denom}: /cdp/cdps/cdp/{owner}/{collateral-type}:
get: get:
summary: Get the cdp associated with the input owner address and collateral denom summary: Get the cdp associated with the input owner address and collateral type
tags: tags:
- CDP - CDP
produces: produces:
@ -692,11 +710,11 @@
type: string type: string
x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c
- in: path - in: path
name: denom name: type
description: Collateral denom description: Collateral type
required: true required: true
type: string type: string
x-example: xrp x-example: xrp-a
responses: responses:
200: 200:
description: CDP associated with owner description: CDP associated with owner
@ -704,23 +722,23 @@
$ref: '#/definitions/CdpResponse' $ref: '#/definitions/CdpResponse'
500: 500:
description: Server internal error description: Server internal error
/cdp/cdps/denom/{denom}: /cdp/cdps/denom/{collateral-type}:
get: get:
summary: Get all cdps with collateral type equal to the input collateral denom summary: Get all cdps with collateral type equal to the input collateral type
tags: tags:
- CDP - CDP
produces: produces:
- application/json - application/json
parameters: parameters:
- in: path - in: path
name: denom name: type
description: Collateral denom description: Collateral Type
required: true required: true
type: string type: string
x-example: xrp x-example: xrp-a
responses: responses:
200: 200:
description: All CDPs with the input collateral denom description: All CDPs with the input collateral type
schema: schema:
type: object type: object
properties: properties:
@ -734,20 +752,20 @@
$ref: '#/definitions/CdpResponse' $ref: '#/definitions/CdpResponse'
500: 500:
description: Server internal error description: Server internal error
/cdp/cdps/ratio/{denom}/{ratio}: /cdp/cdps/ratio/{collateral-type}/{ratio}:
get: get:
summary: Get all cdps with collateral type equal to the input collateral denom and collateralization ratio strictly less than the input ratio summary: Get all cdps with collateral type equal to the input collateral type and collateralization ratio strictly less than the input ratio
tags: tags:
- CDP - CDP
produces: produces:
- application/json - application/json
parameters: parameters:
- in: path - in: path
name: denom name: type
description: Collateral denom description: Collateral type
required: true required: true
type: string type: string
x-example: xrp x-example: xrp-a
- in: path - in: path
name: ratio name: ratio
description: Collateralization ratio description: Collateralization ratio
@ -756,7 +774,7 @@
x-example: "2.0" x-example: "2.0"
responses: responses:
200: 200:
description: All CDPs with the input collateral denom and collateralization ratio less than the input ratio description: All CDPs with the input collateral type and collateralization ratio less than the input ratio
schema: schema:
type: object type: object
properties: properties:
@ -770,9 +788,9 @@
$ref: '#/definitions/CdpResponse' $ref: '#/definitions/CdpResponse'
500: 500:
description: Server internal error description: Server internal error
/cdp/cdps/cdp/deposits/{owner}/{denom}: /cdp/cdps/cdp/deposits/{owner}/{collateral-type}:
get: get:
summary: Get the deposits associated with the cdp owned by the input owner address and with collateral type equal to the input collateral denom summary: Get the deposits associated with the cdp owned by an address for a collateral type
tags: tags:
- CDP - CDP
produces: produces:
@ -785,11 +803,11 @@
type: string type: string
x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c
- in: path - in: path
name: denom name: type
description: Collateral denom description: Collateral type
required: true required: true
type: string type: string
x-example: xrp x-example: xrp-a
responses: responses:
200: 200:
description: Deposits associated with the cdp description: Deposits associated with the cdp
@ -805,6 +823,73 @@
$ref: '#/definitions/CdpDepositResponse' $ref: '#/definitions/CdpDepositResponse'
500: 500:
description: Server internal error description: Server internal error
/cdp/savings-rate-dist:
get:
summary: Get the total savings rate distributed by the cdp module
tags:
- CDP
produces:
- application/json
responses:
200:
description: The distributed savings rate
properties:
height:
type: string
example: "100"
result:
savings_rate_distributed:
type: string
example: "5000000000000"
500:
description: Server internal error
/cdp/cdps:
get:
summary: Query all active cdps
tags:
- CDP
produces:
- application/json
parameters:
- in: query
name: owner
description: Owner address in bech32 format
required: false
type: string
x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c
- in: query
name: collateral-type
description: Collateral type
required: false
type: string
x-example: xrp-a
- in: query
name: id
description: CDP ID
required: false
type: string
x-example: "4"
- in: query
name: ratio
description: Collateralization ratio
required: false
type: string
x-example: "2.75"
responses:
200:
description: Query cdps
schema:
type: object
properties:
height:
type: string
example: "100"
result:
type: array
items:
$ref: '#/definitions/CdpResponse'
500:
description: Internal Server Error
/bep3/swap/create: /bep3/swap/create:
post: post:
summary: Generate a create atomic swap transaction summary: Generate a create atomic swap transaction
@ -1273,7 +1358,7 @@
$ref: '#/definitions/BaseReq' $ref: '#/definitions/BaseReq'
owner: owner:
$ref: '#/definitions/Address' $ref: '#/definitions/Address'
denom: type:
type: string type: string
example: bnb example: bnb
responses: responses:
@ -1285,9 +1370,9 @@
description: Invalid request description: Invalid request
500: 500:
description: Internal server error description: Internal server error
/incentive/claims/{owner}/{denom}: /incentive/claims/{owner}/{collateral-type}:
get: get:
summary: Get outstanding claims for the input owner and denom summary: Get outstanding claims for the input owner and collateral type
tags: tags:
- Incentive - Incentive
produces: produces:
@ -1299,11 +1384,11 @@
type: string type: string
x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c
- in: path - in: path
name: denom name: type
description: Collateral denom description: Collateral type
required: true required: true
type: string type: string
x-example: bnb x-example: bnb-a
responses: responses:
200: 200:
description: USDX Incentive Claims description: USDX Incentive Claims
@ -3457,6 +3542,9 @@
amount: amount:
type: string type: string
example: '555555' example: '555555'
CollateralType:
type: string
example: xrp-a
CoinCollateral: CoinCollateral:
type: object type: object
properties: properties:
@ -3904,6 +3992,9 @@
example: kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9 example: kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9
collateral: collateral:
$ref: '#/definitions/CoinCollateral' $ref: '#/definitions/CoinCollateral'
type:
type: string
example: "xrp-a"
principal: principal:
$ref: '#/definitions/CoinPrincipal' $ref: '#/definitions/CoinPrincipal'
accumulated_fees: accumulated_fees:
@ -3987,6 +4078,15 @@
bid_duration: bid_duration:
type: string type: string
example: 600000000000 example: 600000000000
increment_surplus:
type: string
example: "0.05"
increment_debt:
type: string
example: "0.05"
increment_collateral:
type: string
example: "0.05"
AuctionResponse: AuctionResponse:
type: object type: object
properties: properties:

View File

@ -22,6 +22,7 @@ const (
flagType = "type" flagType = "type"
flagDenom = "denom" flagDenom = "denom"
flagPhase = "phase" flagPhase = "phase"
flagOwner = "owner"
) )
// GetQueryCmd returns the cli query commands for this module // GetQueryCmd returns the cli query commands for this module
@ -77,6 +78,7 @@ func QueryGetAuctionsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
Long: strings.TrimSpace(`Query for all paginated auctions that match optional filters: Long: strings.TrimSpace(`Query for all paginated auctions that match optional filters:
Example: Example:
$ kvcli q auction auctions --type=(collateral|surplus|debt) $ kvcli q auction auctions --type=(collateral|surplus|debt)
$ kvcli q auction auctions --owner=kava1hatdq32u5x4wnxrtv5wzjzmq49sxgjgsj0mffm
$ kvcli q auction auctions --denom=bnb $ kvcli q auction auctions --denom=bnb
$ kvcli q auction auctions --phase=(forward|reverse) $ kvcli q auction auctions --phase=(forward|reverse)
$ kvcli q auction auctions --page=2 --limit=100 $ kvcli q auction auctions --page=2 --limit=100
@ -84,6 +86,7 @@ $ kvcli q auction auctions --page=2 --limit=100
), ),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
strType := viper.GetString(flagType) strType := viper.GetString(flagType)
strOwner := viper.GetString(flagOwner)
strDenom := viper.GetString(flagDenom) strDenom := viper.GetString(flagDenom)
strPhase := viper.GetString(flagPhase) strPhase := viper.GetString(flagPhase)
page := viper.GetInt(flags.FlagPage) page := viper.GetInt(flags.FlagPage)
@ -91,11 +94,12 @@ $ kvcli q auction auctions --page=2 --limit=100
var ( var (
auctionType string auctionType string
auctionOwner sdk.AccAddress
auctionDenom string auctionDenom string
auctionPhase string auctionPhase string
) )
params := types.NewQueryAllAuctionParams(page, limit, auctionType, auctionDenom, auctionPhase) params := types.NewQueryAllAuctionParams(page, limit, auctionType, auctionDenom, auctionPhase, auctionOwner)
if len(strType) != 0 { if len(strType) != 0 {
auctionType = strings.ToLower(strings.TrimSpace(strType)) auctionType = strings.ToLower(strings.TrimSpace(strType))
@ -107,6 +111,18 @@ $ kvcli q auction auctions --page=2 --limit=100
params.Type = auctionType params.Type = auctionType
} }
if len(auctionOwner) != 0 {
if auctionType != types.CollateralAuctionType {
return fmt.Errorf("cannot apply owner flag to non-collateral auction type")
}
auctionOwnerStr := strings.ToLower(strings.TrimSpace(strOwner))
auctionOwner, err := sdk.AccAddressFromBech32(auctionOwnerStr)
if err != nil {
return fmt.Errorf("cannot parse address from auction owner %s", auctionOwnerStr)
}
params.Owner = auctionOwner
}
if len(strDenom) != 0 { if len(strDenom) != 0 {
auctionDenom := strings.TrimSpace(strDenom) auctionDenom := strings.TrimSpace(strDenom)
err := sdk.ValidateDenom(auctionDenom) err := sdk.ValidateDenom(auctionDenom)
@ -160,6 +176,7 @@ $ kvcli q auction auctions --page=2 --limit=100
cmd.Flags().Int(flags.FlagPage, 1, "pagination page of auctions to to query for") cmd.Flags().Int(flags.FlagPage, 1, "pagination page of auctions to to query for")
cmd.Flags().Int(flags.FlagLimit, 100, "pagination limit of auctions to query for") cmd.Flags().Int(flags.FlagLimit, 100, "pagination limit of auctions to query for")
cmd.Flags().String(flagType, "", "(optional) filter by auction type, type: collateral, debt, surplus") cmd.Flags().String(flagType, "", "(optional) filter by auction type, type: collateral, debt, surplus")
cmd.Flags().String(flagOwner, "", "(optional) filter by collateral auction owner")
cmd.Flags().String(flagDenom, "", "(optional) filter by auction denom") cmd.Flags().String(flagDenom, "", "(optional) filter by auction denom")
cmd.Flags().String(flagPhase, "", "(optional) filter by collateral auction phase, phase: forward/reverse") cmd.Flags().String(flagPhase, "", "(optional) filter by collateral auction phase, phase: forward/reverse")

View File

@ -69,6 +69,7 @@ func queryAuctionsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
} }
var auctionType string var auctionType string
var auctionOwner sdk.AccAddress
var auctionDenom string var auctionDenom string
var auctionPhase string var auctionPhase string
@ -82,6 +83,17 @@ func queryAuctionsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
} }
} }
if x := r.URL.Query().Get(RestOwner); len(x) != 0 {
if auctionType != types.CollateralAuctionType {
rest.WriteErrorResponse(w, http.StatusBadRequest, "cannot apply owner flag to non-collateral auction type")
}
auctionOwnerStr := strings.ToLower(strings.TrimSpace(x))
auctionOwner, err = sdk.AccAddressFromBech32(auctionOwnerStr)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("cannot parse address from auction owner %s", auctionOwnerStr))
}
}
if x := r.URL.Query().Get(RestDenom); len(x) != 0 { if x := r.URL.Query().Get(RestDenom); len(x) != 0 {
auctionDenom = strings.TrimSpace(x) auctionDenom = strings.TrimSpace(x)
err := sdk.ValidateDenom(auctionDenom) err := sdk.ValidateDenom(auctionDenom)
@ -103,7 +115,7 @@ func queryAuctionsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
} }
} }
params := types.NewQueryAllAuctionParams(page, limit, auctionType, auctionDenom, auctionPhase) params := types.NewQueryAllAuctionParams(page, limit, auctionType, auctionDenom, auctionPhase, auctionOwner)
bz, err := cliCtx.Codec.MarshalJSON(params) bz, err := cliCtx.Codec.MarshalJSON(params)
if err != nil { if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())

View File

@ -12,6 +12,7 @@ import (
// nolint // nolint
const ( const (
RestType = "type" RestType = "type"
RestOwner = "owner"
RestDenom = "denom" RestDenom = "denom"
RestPhase = "phase" RestPhase = "phase"
) )

View File

@ -1,12 +1,11 @@
package keeper package keeper
import ( import (
abci "github.com/tendermint/tendermint/abci/types"
"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"
"github.com/kava-labs/kava/x/auction/types" "github.com/kava-labs/kava/x/auction/types"
) )
@ -93,13 +92,29 @@ func filterAuctions(ctx sdk.Context, auctions types.Auctions, params types.Query
filteredAuctions := make(types.Auctions, 0, len(auctions)) filteredAuctions := make(types.Auctions, 0, len(auctions))
for _, auc := range auctions { for _, auc := range auctions {
matchType, matchDenom, matchPhase := true, true, true matchType, matchOwner, matchDenom, matchPhase := true, true, true, true
// match auction type (if supplied) // match auction type (if supplied)
if len(params.Type) > 0 { if len(params.Type) > 0 {
matchType = auc.GetType() == params.Type matchType = auc.GetType() == params.Type
} }
// match auction owner (if supplied)
if len(params.Owner) > 0 {
if cAuc, ok := auc.(types.CollateralAuction); ok {
foundOwnerAddr := false
for _, addr := range cAuc.GetLotReturns().Addresses {
if addr.Equals(params.Owner) {
foundOwnerAddr = true
break
}
}
if !foundOwnerAddr {
matchOwner = false
}
}
}
// match auction denom (if supplied) // match auction denom (if supplied)
if len(params.Denom) > 0 { if len(params.Denom) > 0 {
matchDenom = auc.GetBid().Denom == params.Denom || auc.GetLot().Denom == params.Denom matchDenom = auc.GetBid().Denom == params.Denom || auc.GetLot().Denom == params.Denom
@ -110,7 +125,7 @@ func filterAuctions(ctx sdk.Context, auctions types.Auctions, params types.Query
matchPhase = auc.GetPhase() == params.Phase matchPhase = auc.GetPhase() == params.Phase
} }
if matchType && matchDenom && matchPhase { if matchType && matchOwner && matchDenom && matchPhase {
filteredAuctions = append(filteredAuctions, auc) filteredAuctions = append(filteredAuctions, auc)
} }
} }

View File

@ -41,7 +41,7 @@ func (suite *QuerierTestSuite) SetupTest() {
tApp := app.NewTestApp() tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()}) ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
_, addrs := app.GeneratePrivKeyAddressPairs(1) _, addrs := app.GeneratePrivKeyAddressPairs(10)
buyer := addrs[0] buyer := addrs[0]
modName := cdp.LiquidatorMacc modName := cdp.LiquidatorMacc
@ -64,8 +64,16 @@ func (suite *QuerierTestSuite) SetupTest() {
// Populate with auctions // Populate with auctions
randSrc := rand.New(rand.NewSource(int64(1234))) randSrc := rand.New(rand.NewSource(int64(1234)))
for j := 0; j < TestAuctionCount; j++ { for j := 0; j < TestAuctionCount; j++ {
lotAmount := simulation.RandIntBetween(randSrc, 10, 100) var id uint64
id, err := suite.keeper.StartSurplusAuction(suite.ctx, modName, c("token1", int64(lotAmount)), "token2") var err error
lotAmount := int64(simulation.RandIntBetween(randSrc, 10, 100))
ownerAddrIndex := simulation.RandIntBetween(randSrc, 1, 9)
if ownerAddrIndex%2 == 0 {
id, err = suite.keeper.StartSurplusAuction(suite.ctx, modName, c("token1", lotAmount), "token2")
} else {
id, err = suite.keeper.StartCollateralAuction(suite.ctx, modName, c("token1", lotAmount), c("usdx", int64(20)),
[]sdk.AccAddress{addrs[ownerAddrIndex]}, []sdk.Int{sdk.NewInt(lotAmount)}, c("debt", int64(10)))
}
suite.NoError(err) suite.NoError(err)
auc, found := suite.keeper.GetAuction(suite.ctx, id) auc, found := suite.keeper.GetAuction(suite.ctx, id)
@ -108,7 +116,7 @@ func (suite *QuerierTestSuite) TestQueryAuctions() {
query := abci.RequestQuery{ query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetAuctions}, "/"), Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetAuctions}, "/"),
Data: types.ModuleCdc.MustMarshalJSON( Data: types.ModuleCdc.MustMarshalJSON(
types.NewQueryAllAuctionParams(int(1), int(TestAuctionCount), "", "", ""), types.NewQueryAllAuctionParams(int(1), int(TestAuctionCount), "", "", "", nil),
), ),
} }

View File

@ -244,6 +244,11 @@ func (a CollateralAuction) GetPhase() string {
return ForwardAuctionPhase return ForwardAuctionPhase
} }
// GetLotReturns returns a collateral auction's lot owners
func (a CollateralAuction) GetLotReturns() WeightedAddresses {
return a.LotReturns
}
// Validate validates the CollateralAuction fields values. // Validate validates the CollateralAuction fields values.
func (a CollateralAuction) Validate() error { func (a CollateralAuction) Validate() error {
if !a.CorrespondingDebt.IsValid() { if !a.CorrespondingDebt.IsValid() {

View File

@ -1,5 +1,9 @@
package types package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
const ( const (
// QueryGetAuction is the query path for querying one auction // QueryGetAuction is the query path for querying one auction
QueryGetAuction = "auction" QueryGetAuction = "auction"
@ -28,16 +32,18 @@ type QueryAllAuctionParams struct {
Page int `json:"page" yaml:"page"` Page int `json:"page" yaml:"page"`
Limit int `json:"limit" yaml:"limit"` Limit int `json:"limit" yaml:"limit"`
Type string `json:"type" yaml:"type"` Type string `json:"type" yaml:"type"`
Owner sdk.AccAddress `json:"owner" yaml:"owner"`
Denom string `json:"denom" yaml:"denom"` Denom string `json:"denom" yaml:"denom"`
Phase string `json:"phase" yaml:"phase"` Phase string `json:"phase" yaml:"phase"`
} }
// NewQueryAllAuctionParams creates a new QueryAllAuctionParams // NewQueryAllAuctionParams creates a new QueryAllAuctionParams
func NewQueryAllAuctionParams(page, limit int, aucType, aucDenom, aucPhase string) QueryAllAuctionParams { func NewQueryAllAuctionParams(page, limit int, aucType, aucDenom, aucPhase string, aucOwner sdk.AccAddress) QueryAllAuctionParams {
return QueryAllAuctionParams{ return QueryAllAuctionParams{
Page: page, Page: page,
Limit: limit, Limit: limit,
Type: aucType, Type: aucType,
Owner: aucOwner,
Denom: aucDenom, Denom: aucDenom,
Phase: aucPhase, Phase: aucPhase,
} }

View File

@ -137,7 +137,9 @@ var (
DefaultDebtLot = types.DefaultDebtLot DefaultDebtLot = types.DefaultDebtLot
DefaultPreviousDistributionTime = types.DefaultPreviousDistributionTime DefaultPreviousDistributionTime = types.DefaultPreviousDistributionTime
DefaultSavingsDistributionFrequency = types.DefaultSavingsDistributionFrequency DefaultSavingsDistributionFrequency = types.DefaultSavingsDistributionFrequency
DefaultSavingsRateDistributed = types.DefaultSavingsRateDistributed
MaxSortableDec = types.MaxSortableDec MaxSortableDec = types.MaxSortableDec
SavingsRateDistributedKey = types.SavingsRateDistributedKey
) )
type ( type (

View File

@ -2,9 +2,11 @@ package cli
import ( import (
"fmt" "fmt"
"strconv"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/flags"
@ -16,6 +18,14 @@ import (
"github.com/kava-labs/kava/x/cdp/types" "github.com/kava-labs/kava/x/cdp/types"
) )
// Query CDP flags
const (
flagCollateralType = "collateral-type"
flagOwner = "owner"
flagID = "id"
flagRatio = "ratio" // returns CDPs under the given collateralization ratio threshold
)
// GetQueryCmd returns the cli query commands for this module // GetQueryCmd returns the cli query commands for this module
func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
// Group nameservice queries under a subcommand // Group nameservice queries under a subcommand
@ -26,11 +36,11 @@ func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
cdpQueryCmd.AddCommand(flags.GetCommands( cdpQueryCmd.AddCommand(flags.GetCommands(
QueryCdpCmd(queryRoute, cdc), QueryCdpCmd(queryRoute, cdc),
QueryCdpsByCollateralTypeCmd(queryRoute, cdc), QueryGetCdpsCmd(queryRoute, cdc),
QueryCdpsByCollateralTypeAndRatioCmd(queryRoute, cdc),
QueryCdpDepositsCmd(queryRoute, cdc), QueryCdpDepositsCmd(queryRoute, cdc),
QueryParamsCmd(queryRoute, cdc), QueryParamsCmd(queryRoute, cdc),
QueryGetAccounts(queryRoute, cdc), QueryGetAccounts(queryRoute, cdc),
QueryGetSavingsRateDistributed(queryRoute, cdc),
)...) )...)
return cdpQueryCmd return cdpQueryCmd
@ -79,85 +89,103 @@ $ %s query %s cdp kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw atom-a
} }
} }
// QueryCdpsByCollateralTypeCmd returns the command handler for querying cdps for a collateral type // QueryGetCdpsCmd queries the cdps in the store
func QueryCdpsByCollateralTypeCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { func QueryGetCdpsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{ cmd := &cobra.Command{
Use: "cdps [collateral-type]", Use: "cdps",
Short: "query CDPs by collateral", Short: "query cdps with optional filters",
Long: strings.TrimSpace( Long: strings.TrimSpace(`Query for all paginated cdps that match optional filters:
fmt.Sprintf(`List all CDPs collateralized with the specified asset.
Example: Example:
$ %s query %s cdps atom-a $ kvcli q cdp cdps --collateral-type=bnb
`, version.ClientName, types.ModuleName)), $ kvcli q cdp cdps --owner=kava1hatdq32u5x4wnxrtv5wzjzmq49sxgjgsj0mffm
Args: cobra.ExactArgs(1), $ kvcli q cdp cdps --id=21
$ kvcli q cdp cdps --ratio=2.75
$ kvcli q cdp cdps --page=2 --limit=100
`,
),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc) strCollateralType := viper.GetString(flagCollateralType)
strOwner := viper.GetString(flagOwner)
strID := viper.GetString(flagID)
strRatio := viper.GetString(flagRatio)
page := viper.GetInt(flags.FlagPage)
limit := viper.GetInt(flags.FlagLimit)
// Prepare params for querier var (
bz, err := cdc.MarshalJSON(types.QueryCdpsParams{CollateralType: args[0]}) cdpCollateralType string
cdpOwner sdk.AccAddress
cdpID uint64
cdpRatio sdk.Dec
)
params := types.NewQueryCdpsParams(page, limit, cdpCollateralType, cdpOwner, cdpID, cdpRatio)
if len(strCollateralType) != 0 {
cdpCollateralType = strings.ToLower(strings.TrimSpace(strCollateralType))
params.CollateralType = cdpCollateralType
}
if len(strOwner) != 0 {
cdpOwner, err := sdk.AccAddressFromBech32(strings.ToLower(strings.TrimSpace(strOwner)))
if err != nil {
return fmt.Errorf("cannot parse address from cdp owner %s", strOwner)
}
params.Owner = cdpOwner
}
if len(strID) != 0 {
cdpID, err := strconv.ParseUint(strID, 10, 64)
if err != nil {
return fmt.Errorf("cannot parse cdp ID %s", strID)
}
params.ID = cdpID
}
params.Ratio = sdk.ZeroDec()
if len(strRatio) != 0 {
cdpRatio, err := sdk.NewDecFromStr(strRatio)
if err != nil {
return fmt.Errorf("cannot parse cdp ratio %s", strRatio)
}
params.Ratio = cdpRatio
} else {
// Set to sdk.Dec(0) so that if not specified in params it doesn't panic when unmarshaled
params.Ratio = sdk.ZeroDec()
}
bz, err := cdc.MarshalJSON(params)
if err != nil { if err != nil {
return err return err
} }
cliCtx := context.NewCLIContext().WithCodec(cdc)
// Query // Query
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetCdps) res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetCdps), bz)
res, _, err := cliCtx.QueryWithData(route, bz)
if err != nil { if err != nil {
return err return err
} }
// Decode and print results // Decode and print results
var cdps types.AugmentedCDPs var matchingCDPs types.AugmentedCDPs
cdc.MustUnmarshalJSON(res, &cdps) cdc.MustUnmarshalJSON(res, &matchingCDPs)
return cliCtx.PrintOutput(cdps) if len(matchingCDPs) == 0 {
return fmt.Errorf("No matching CDPs found")
}
cliCtx = cliCtx.WithHeight(height)
return cliCtx.PrintOutput(matchingCDPs) // nolint:errcheck
}, },
} }
}
// QueryCdpsByCollateralTypeAndRatioCmd returns the command handler for querying cdps cmd.Flags().Int(flags.FlagPage, 1, "pagination page of CDPs to to query for")
// that are under the specified collateral ratio cmd.Flags().Int(flags.FlagLimit, 100, "pagination limit of CDPs to query for")
func QueryCdpsByCollateralTypeAndRatioCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd.Flags().String(flagCollateralType, "", "(optional) filter by CDP collateral type")
return &cobra.Command{ cmd.Flags().String(flagOwner, "", "(optional) filter by CDP owner")
Use: "cdps-by-ratio [collateral-type] [collateralization-ratio]", cmd.Flags().String(flagID, "", "(optional) filter by CDP ID")
Short: "get cdps under a collateralization ratio", cmd.Flags().String(flagRatio, "", "(optional) filter by CDP collateralization ratio threshold")
Long: strings.TrimSpace(
fmt.Sprintf(`List all CDPs under a specified collateralization ratio.
Collateralization ratio is: collateral * price / debt.
Example: return cmd
$ %s query %s cdps-by-ratio atom-a 1.6
`, version.ClientName, types.ModuleName)),
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
// Prepare params for querier
ratio, err := sdk.NewDecFromStr(args[1])
if err != nil {
return err
}
bz, err := cdc.MarshalJSON(types.QueryCdpsByRatioParams{
CollateralType: args[0],
Ratio: ratio,
})
if err != nil {
return err
}
// Query
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetCdpsByCollateralization)
res, _, err := cliCtx.QueryWithData(route, bz)
if err != nil {
return err
}
// Decode and print results
var cdps types.AugmentedCDPs
cdc.MustUnmarshalJSON(res, &cdps)
return cliCtx.PrintOutput(cdps)
},
}
} }
// QueryCdpDepositsCmd returns the command handler for querying the deposits of a particular cdp // QueryCdpDepositsCmd returns the command handler for querying the deposits of a particular cdp
@ -208,7 +236,7 @@ func QueryParamsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "params", Use: "params",
Short: "get the cdp module parameters", Short: "get the cdp module parameters",
Long: "Get the current global cdp module parameters.", Long: "get the current global cdp module parameters.",
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc) cliCtx := context.NewCLIContext().WithCodec(cdc)
@ -228,11 +256,12 @@ func QueryParamsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
} }
} }
// QueryGetAccounts queries CDP module accounts
func QueryGetAccounts(queryRoute string, cdc *codec.Codec) *cobra.Command { func QueryGetAccounts(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "accounts", Use: "accounts",
Short: "Get module accounts", Short: "get module accounts",
Long: "Get cdp module account addresses", Long: "get cdp module account addresses",
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc) cliCtx := context.NewCLIContext().WithCodec(cdc)
@ -253,3 +282,30 @@ func QueryGetAccounts(queryRoute string, cdc *codec.Codec) *cobra.Command {
}, },
} }
} }
// QueryGetSavingsRateDistributed queries the total amount of savings rate distributed in USDX
func QueryGetSavingsRateDistributed(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "savings-rate-dist",
Short: "get total amount of savings rate distributed in USDX",
Long: "get total amount of savings rate distributed",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
// Query
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetSavingsRateDistributed), nil)
if err != nil {
return err
}
cliCtx = cliCtx.WithHeight(height)
// Decode and print results
var out sdk.Int
if err := cdc.UnmarshalJSON(res, &out); err != nil {
return fmt.Errorf("failed to unmarshal sdk.Int: %w", err)
}
return cliCtx.PrintOutput(out)
},
}
}

View File

@ -3,6 +3,8 @@ package rest
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"strconv"
"strings"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
@ -16,9 +18,11 @@ import (
func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) { func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) {
r.HandleFunc("/cdp/accounts", getAccountsHandlerFn(cliCtx)).Methods("GET") r.HandleFunc("/cdp/accounts", getAccountsHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc("/cdp/parameters", getParamsHandlerFn(cliCtx)).Methods("GET") r.HandleFunc("/cdp/parameters", getParamsHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc("/cdp/savingsRateDist", getSavingsRateDistributedHandler(cliCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/cdp/cdps/cdp/{%s}/{%s}", types.RestOwner, types.RestCollateralType), queryCdpHandlerFn(cliCtx)).Methods("GET") r.HandleFunc(fmt.Sprintf("/cdp/cdps/cdp/{%s}/{%s}", types.RestOwner, types.RestCollateralType), queryCdpHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/cdp/cdps/collateralType/{%s}", types.RestCollateralType), queryCdpsHandlerFn(cliCtx)).Methods("GET") r.HandleFunc(fmt.Sprintf("/cdp/cdps"), queryCdpsHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/cdp/cdps/ratio/{%s}/{%s}", types.RestCollateralType, types.RestRatio), queryCdpsByRatioHandlerFn(cliCtx)).Methods("GET") r.HandleFunc(fmt.Sprintf("/cdp/cdps/collateralType/{%s}", types.RestCollateralType), queryCdpsByCollateralTypeHandlerFn(cliCtx)).Methods("GET") // legacy
r.HandleFunc(fmt.Sprintf("/cdp/cdps/ratio/{%s}/{%s}", types.RestCollateralType, types.RestRatio), queryCdpsByRatioHandlerFn(cliCtx)).Methods("GET") // legacy
r.HandleFunc(fmt.Sprintf("/cdp/cdps/cdp/deposits/{%s}/{%s}", types.RestOwner, types.RestCollateralType), queryCdpDepositsHandlerFn(cliCtx)).Methods("GET") r.HandleFunc(fmt.Sprintf("/cdp/cdps/cdp/deposits/{%s}/{%s}", types.RestOwner, types.RestCollateralType), queryCdpDepositsHandlerFn(cliCtx)).Methods("GET")
} }
@ -58,7 +62,7 @@ func queryCdpHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
} }
} }
func queryCdpsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { func queryCdpsByCollateralTypeHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok { if !ok {
@ -68,7 +72,7 @@ func queryCdpsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
vars := mux.Vars(r) vars := mux.Vars(r)
collateralType := vars[types.RestCollateralType] collateralType := vars[types.RestCollateralType]
params := types.NewQueryCdpsParams(collateralType) params := types.NewQueryCdpsByCollateralTypeParams(collateralType)
bz, err := cliCtx.Codec.MarshalJSON(params) bz, err := cliCtx.Codec.MarshalJSON(params)
if err != nil { if err != nil {
@ -76,7 +80,7 @@ func queryCdpsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return return
} }
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/cdp/%s", types.QueryGetCdps), bz) res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/cdp/%s", types.QueryGetCdpsByCollateralType), bz)
if err != nil { if err != nil {
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
return return
@ -193,3 +197,91 @@ func getAccountsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
rest.PostProcessResponse(w, cliCtx, res) rest.PostProcessResponse(w, cliCtx, res)
} }
} }
func getSavingsRateDistributedHandler(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok {
return
}
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/cdp/%s", types.QueryGetSavingsRateDistributed), nil)
cliCtx = cliCtx.WithHeight(height)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
rest.PostProcessResponse(w, cliCtx, res)
}
}
func queryCdpsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
// Parse the query height
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok {
return
}
var cdpCollateralType string
var cdpOwner sdk.AccAddress
var cdpID uint64
var cdpRatio sdk.Dec
if x := r.URL.Query().Get(RestCollateralType); len(x) != 0 {
cdpCollateralType = strings.TrimSpace(x)
}
if x := r.URL.Query().Get(RestOwner); len(x) != 0 {
cdpOwnerStr := strings.ToLower(strings.TrimSpace(x))
cdpOwner, err = sdk.AccAddressFromBech32(cdpOwnerStr)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("cannot parse address from cdp owner %s", cdpOwnerStr))
return
}
}
if x := r.URL.Query().Get(RestID); len(x) != 0 {
cdpID, err = strconv.ParseUint(strings.TrimSpace(x), 10, 64)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
}
if x := r.URL.Query().Get(RestRatio); len(x) != 0 {
cdpRatio, err = sdk.NewDecFromStr(strings.TrimSpace(x))
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
} else {
// Set to sdk.Dec(0) so that if not specified in params it doesn't panic when unmarshaled
cdpRatio = sdk.ZeroDec()
}
params := types.NewQueryCdpsParams(page, limit, cdpCollateralType, cdpOwner, cdpID, cdpRatio)
bz, err := cliCtx.Codec.MarshalJSON(params)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
route := fmt.Sprintf("custom/%s/%s", types.ModuleName, types.QueryGetCdps)
res, height, err := cliCtx.QueryWithData(route, bz)
cliCtx = cliCtx.WithHeight(height)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
rest.PostProcessResponse(w, cliCtx, res)
}
}

View File

@ -8,6 +8,15 @@ import (
"github.com/cosmos/cosmos-sdk/types/rest" "github.com/cosmos/cosmos-sdk/types/rest"
) )
// REST Variable names
// nolint
const (
RestOwner = "owner"
RestCollateralType = "collateral-type"
RestID = "id"
RestRatio = "ratio"
)
// RegisterRoutes - Central function to define routes that get registered by the main application // RegisterRoutes - Central function to define routes that get registered by the main application
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) { func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
registerQueryRoutes(cliCtx, r) registerQueryRoutes(cliCtx, r)

View File

@ -20,7 +20,6 @@ func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) {
r.HandleFunc("/cdp/{owner}/{collateralType}/withdraw", postWithdrawHandlerFn(cliCtx)).Methods("POST") r.HandleFunc("/cdp/{owner}/{collateralType}/withdraw", postWithdrawHandlerFn(cliCtx)).Methods("POST")
r.HandleFunc("/cdp/{owner}/{collateralType}/draw", postDrawHandlerFn(cliCtx)).Methods("POST") r.HandleFunc("/cdp/{owner}/{collateralType}/draw", postDrawHandlerFn(cliCtx)).Methods("POST")
r.HandleFunc("/cdp/{owner}/{collateralType}/repay", postRepayHandlerFn(cliCtx)).Methods("POST") r.HandleFunc("/cdp/{owner}/{collateralType}/repay", postRepayHandlerFn(cliCtx)).Methods("POST")
} }
func postCdpHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { func postCdpHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {

View File

@ -88,6 +88,8 @@ func InitGenesis(ctx sdk.Context, k Keeper, pk types.PricefeedKeeper, sk types.S
for _, d := range gs.Deposits { for _, d := range gs.Deposits {
k.SetDeposit(ctx, d) k.SetDeposit(ctx, d)
} }
k.SetSavingsRateDistributed(ctx, gs.SavingsRateDistributed)
} }
// ExportGenesis export genesis state for cdp module // ExportGenesis export genesis state for cdp module
@ -108,11 +110,12 @@ func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState {
cdpID := k.GetNextCdpID(ctx) cdpID := k.GetNextCdpID(ctx)
debtDenom := k.GetDebtDenom(ctx) debtDenom := k.GetDebtDenom(ctx)
govDenom := k.GetGovDenom(ctx) govDenom := k.GetGovDenom(ctx)
savingsRateDist := k.GetSavingsRateDistributed(ctx)
previousDistributionTime, found := k.GetPreviousSavingsDistribution(ctx) previousDistributionTime, found := k.GetPreviousSavingsDistribution(ctx)
if !found { if !found {
previousDistributionTime = DefaultPreviousDistributionTime previousDistributionTime = DefaultPreviousDistributionTime
} }
return NewGenesisState(params, cdps, deposits, cdpID, debtDenom, govDenom, previousDistributionTime) return NewGenesisState(params, cdps, deposits, cdpID, debtDenom, govDenom, previousDistributionTime, savingsRateDist)
} }

View File

@ -29,6 +29,7 @@ func (suite *GenesisTestSuite) TestInvalidGenState() {
debtDenom string debtDenom string
govDenom string govDenom string
prevDistTime time.Time prevDistTime time.Time
savingsRateDist sdk.Int
} }
type errArgs struct { type errArgs struct {
expectPass bool expectPass bool
@ -53,6 +54,7 @@ func (suite *GenesisTestSuite) TestInvalidGenState() {
debtDenom: "", debtDenom: "",
govDenom: cdp.DefaultGovDenom, govDenom: cdp.DefaultGovDenom,
prevDistTime: cdp.DefaultPreviousDistributionTime, prevDistTime: cdp.DefaultPreviousDistributionTime,
savingsRateDist: cdp.DefaultSavingsRateDistributed,
}, },
errArgs: errArgs{ errArgs: errArgs{
expectPass: false, expectPass: false,
@ -68,6 +70,7 @@ func (suite *GenesisTestSuite) TestInvalidGenState() {
debtDenom: cdp.DefaultDebtDenom, debtDenom: cdp.DefaultDebtDenom,
govDenom: "", govDenom: "",
prevDistTime: cdp.DefaultPreviousDistributionTime, prevDistTime: cdp.DefaultPreviousDistributionTime,
savingsRateDist: cdp.DefaultSavingsRateDistributed,
}, },
errArgs: errArgs{ errArgs: errArgs{
expectPass: false, expectPass: false,
@ -83,21 +86,40 @@ func (suite *GenesisTestSuite) TestInvalidGenState() {
debtDenom: cdp.DefaultDebtDenom, debtDenom: cdp.DefaultDebtDenom,
govDenom: cdp.DefaultGovDenom, govDenom: cdp.DefaultGovDenom,
prevDistTime: time.Time{}, prevDistTime: time.Time{},
savingsRateDist: cdp.DefaultSavingsRateDistributed,
}, },
errArgs: errArgs{ errArgs: errArgs{
expectPass: false, expectPass: false,
contains: "previous distribution time not set", contains: "previous distribution time not set",
}, },
}, },
{
name: "negative savings rate distributed",
args: args{
params: cdp.DefaultParams(),
cdps: cdp.CDPs{},
deposits: cdp.Deposits{},
debtDenom: cdp.DefaultDebtDenom,
govDenom: cdp.DefaultGovDenom,
prevDistTime: cdp.DefaultPreviousDistributionTime,
savingsRateDist: sdk.NewInt(-100),
},
errArgs: errArgs{
expectPass: false,
contains: "savings rate distributed should not be negative",
},
},
} }
for _, tc := range testCases { for _, tc := range testCases {
suite.Run(tc.name, func() { suite.Run(tc.name, func() {
gs := cdp.NewGenesisState(tc.args.params, tc.args.cdps, tc.args.deposits, tc.args.startingID, tc.args.debtDenom, tc.args.govDenom, tc.args.prevDistTime) gs := cdp.NewGenesisState(tc.args.params, tc.args.cdps, tc.args.deposits, tc.args.startingID,
tc.args.debtDenom, tc.args.govDenom, tc.args.prevDistTime, tc.args.savingsRateDist)
err := gs.Validate() err := gs.Validate()
if tc.errArgs.expectPass { if tc.errArgs.expectPass {
suite.Require().NoError(err) suite.Require().NoError(err)
} else { } else {
suite.Require().Error(err) suite.Require().Error(err)
suite.T().Log(err)
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains)) suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
} }
}) })

View File

@ -110,3 +110,23 @@ func (k Keeper) IterateCdpsByCollateralRatio(ctx sdk.Context, collateralType str
} }
} }
// SetSavingsRateDistributed sets the SavingsRateDistributed in the store
func (k Keeper) SetSavingsRateDistributed(ctx sdk.Context, totalDistributed sdk.Int) {
store := prefix.NewStore(ctx.KVStore(k.key), types.SavingsRateDistributedKey)
bz := k.cdc.MustMarshalBinaryLengthPrefixed(totalDistributed)
store.Set([]byte{}, bz)
}
// GetSavingsRateDistributed gets the SavingsRateDistributed from the store
func (k Keeper) GetSavingsRateDistributed(ctx sdk.Context) sdk.Int {
savingsRateDistributed := sdk.ZeroInt()
store := prefix.NewStore(ctx.KVStore(k.key), types.SavingsRateDistributedKey)
bz := store.Get([]byte{})
if bz == nil {
return savingsRateDistributed
}
k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &savingsRateDistributed)
return savingsRateDistributed
}

View File

@ -0,0 +1,48 @@
package keeper_test
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
tmtime "github.com/tendermint/tendermint/types/time"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/cdp/keeper"
)
type KeeperTestSuite struct {
suite.Suite
keeper keeper.Keeper
app app.TestApp
ctx sdk.Context
}
func (suite *KeeperTestSuite) SetupTest() {
config := sdk.GetConfig()
app.SetBech32AddressPrefixes(config)
suite.ResetChain()
return
}
func (suite *KeeperTestSuite) ResetChain() {
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
keeper := tApp.GetCDPKeeper()
suite.app = tApp
suite.ctx = ctx
suite.keeper = keeper
}
func (suite *KeeperTestSuite) TestGetSetSavingsRateDistributed() {
suite.ResetChain()
// Set savings rate distributed value
savingsRateDist := sdk.NewInt(555000555000)
suite.keeper.SetSavingsRateDistributed(suite.ctx, savingsRateDist)
// Check store's savings rate distributed value
s := suite.keeper.GetSavingsRateDistributed(suite.ctx)
suite.Equal(savingsRateDist, s)
}

View File

@ -31,6 +31,16 @@ func (k Keeper) GetCollateral(ctx sdk.Context, collateralType string) (types.Col
return types.CollateralParam{}, false return types.CollateralParam{}, false
} }
// GetCollateralTypes returns an array of collateral types
func (k Keeper) GetCollateralTypes(ctx sdk.Context) []string {
params := k.GetParams(ctx)
var denoms []string
for _, cp := range params.CollateralParams {
denoms = append(denoms, cp.Type)
}
return denoms
}
// GetDebtParam returns the debt param with matching denom // GetDebtParam returns the debt param with matching denom
func (k Keeper) GetDebtParam(ctx sdk.Context, denom string) (types.DebtParam, bool) { func (k Keeper) GetDebtParam(ctx sdk.Context, denom string) (types.DebtParam, bool) {
dp := k.GetParams(ctx).DebtParam dp := k.GetParams(ctx).DebtParam

View File

@ -3,6 +3,7 @@ package keeper
import ( import (
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
"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"
@ -18,15 +19,19 @@ func NewQuerier(keeper Keeper) sdk.Querier {
case types.QueryGetCdp: case types.QueryGetCdp:
return queryGetCdp(ctx, req, keeper) return queryGetCdp(ctx, req, keeper)
case types.QueryGetCdps: case types.QueryGetCdps:
return queryGetCdps(ctx, req, keeper)
case types.QueryGetCdpDeposits:
return queryGetDeposits(ctx, req, keeper)
case types.QueryGetCdpsByCollateralType: // legacy, maintained for REST API
return queryGetCdpsByCollateralType(ctx, req, keeper) return queryGetCdpsByCollateralType(ctx, req, keeper)
case types.QueryGetCdpsByCollateralization: case types.QueryGetCdpsByCollateralization: // legacy, maintained for REST API
return queryGetCdpsByRatio(ctx, req, keeper) return queryGetCdpsByRatio(ctx, req, keeper)
case types.QueryGetParams: case types.QueryGetParams:
return queryGetParams(ctx, req, keeper) return queryGetParams(ctx, req, keeper)
case types.QueryGetCdpDeposits:
return queryGetDeposits(ctx, req, keeper)
case types.QueryGetAccounts: case types.QueryGetAccounts:
return queryGetAccounts(ctx, req, keeper) return queryGetAccounts(ctx, req, keeper)
case types.QueryGetSavingsRateDistributed:
return queryGetSavingsRateDistributed(ctx, req, keeper)
default: default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint %s", types.ModuleName, path[0]) return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint %s", types.ModuleName, path[0])
} }
@ -43,7 +48,7 @@ func queryGetCdp(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte,
_, valid := keeper.GetCollateralTypePrefix(ctx, requestParams.CollateralType) _, valid := keeper.GetCollateralTypePrefix(ctx, requestParams.CollateralType)
if !valid { if !valid {
return nil, sdkerrors.Wrap(types.ErrCollateralNotSupported, requestParams.CollateralType) return nil, sdkerrors.Wrap(types.ErrInvalidCollateral, requestParams.CollateralType)
} }
cdp, found := keeper.GetCdpByOwnerAndCollateralType(ctx, requestParams.Owner, requestParams.CollateralType) cdp, found := keeper.GetCdpByOwnerAndCollateralType(ctx, requestParams.Owner, requestParams.CollateralType)
@ -71,7 +76,7 @@ func queryGetDeposits(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]
_, valid := keeper.GetCollateralTypePrefix(ctx, requestParams.CollateralType) _, valid := keeper.GetCollateralTypePrefix(ctx, requestParams.CollateralType)
if !valid { if !valid {
return nil, sdkerrors.Wrap(types.ErrCollateralNotSupported, requestParams.CollateralType) return nil, sdkerrors.Wrap(types.ErrInvalidCollateral, requestParams.CollateralType)
} }
cdp, found := keeper.GetCdpByOwnerAndCollateralType(ctx, requestParams.Owner, requestParams.CollateralType) cdp, found := keeper.GetCdpByOwnerAndCollateralType(ctx, requestParams.Owner, requestParams.CollateralType)
@ -98,7 +103,7 @@ func queryGetCdpsByRatio(ctx sdk.Context, req abci.RequestQuery, keeper Keeper)
} }
_, valid := keeper.GetCollateralTypePrefix(ctx, requestParams.CollateralType) _, valid := keeper.GetCollateralTypePrefix(ctx, requestParams.CollateralType)
if !valid { if !valid {
return nil, sdkerrors.Wrap(types.ErrCollateralNotSupported, requestParams.CollateralType) return nil, sdkerrors.Wrap(types.ErrInvalidCollateral, requestParams.CollateralType)
} }
ratio, err := keeper.CalculateCollateralizationRatioFromAbsoluteRatio(ctx, requestParams.CollateralType, requestParams.Ratio, "liquidation") ratio, err := keeper.CalculateCollateralizationRatioFromAbsoluteRatio(ctx, requestParams.CollateralType, requestParams.Ratio, "liquidation")
@ -120,16 +125,16 @@ func queryGetCdpsByRatio(ctx sdk.Context, req abci.RequestQuery, keeper Keeper)
return bz, nil return bz, nil
} }
// query all cdps with matching collateral denom // query all cdps with matching collateral type
func queryGetCdpsByCollateralType(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) { func queryGetCdpsByCollateralType(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) {
var requestParams types.QueryCdpsParams var requestParams types.QueryCdpsByCollateralTypeParams
err := types.ModuleCdc.UnmarshalJSON(req.Data, &requestParams) err := types.ModuleCdc.UnmarshalJSON(req.Data, &requestParams)
if err != nil { if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
} }
_, valid := keeper.GetCollateralTypePrefix(ctx, requestParams.CollateralType) _, valid := keeper.GetCollateralTypePrefix(ctx, requestParams.CollateralType)
if !valid { if !valid {
return nil, sdkerrors.Wrap(types.ErrCollateralNotSupported, requestParams.CollateralType) return nil, sdkerrors.Wrap(types.ErrInvalidCollateral, requestParams.CollateralType)
} }
cdps := keeper.GetAllCdpsByCollateralType(ctx, requestParams.CollateralType) cdps := keeper.GetAllCdpsByCollateralType(ctx, requestParams.CollateralType)
@ -178,3 +183,175 @@ func queryGetAccounts(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]
} }
return bz, nil return bz, nil
} }
// query get savings rate distributed in the cdp store
func queryGetSavingsRateDistributed(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) {
// Get savings rate distributed
savingsRateDist := keeper.GetSavingsRateDistributed(ctx)
// Encode results
bz, err := codec.MarshalJSONIndent(types.ModuleCdc, savingsRateDist)
if err != nil {
return nil, sdkerrors.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) ([]byte, error) {
var params types.QueryCdpsParams
err := types.ModuleCdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
// Filter CDPs
filteredCDPs := FilterCDPs(ctx, keeper, params)
bz, err := codec.MarshalJSONIndent(keeper.cdc, filteredCDPs)
if err != nil {
return nil, sdkerrors.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 {
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 {
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.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 len(params.CollateralType) == 0 && len(params.Owner) == 0 && params.ID == 0 && params.Ratio.Equal(sdk.ZeroDec()) {
commonCDPs = k.GetAllCdps(ctx)
}
// Find the intersection of any matched CDPs
if len(params.CollateralType) > 0 {
if len(matchCollateralType) > 0 {
commonCDPs = matchCollateralType
} else {
return types.AugmentedCDPs{}
}
}
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 {
if len(commonCDPs) > 0 {
commonCDPs = FindIntersection(commonCDPs, matchID)
} else {
commonCDPs = matchID
}
} else {
return types.AugmentedCDPs{}
}
}
if params.Ratio.GT(sdk.ZeroDec()) {
if len(matchRatio) > 0 {
if len(commonCDPs) > 0 {
commonCDPs = FindIntersection(commonCDPs, matchRatio)
} else {
commonCDPs = matchRatio
}
} else {
return types.AugmentedCDPs{}
}
}
// 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
var paginatedCDPs types.AugmentedCDPs
start, end := client.Paginate(len(augmentedCDPs), params.Page, params.Limit, 100)
if start < 0 || end < 0 {
paginatedCDPs = types.AugmentedCDPs{}
} else {
paginatedCDPs = augmentedCDPs[start:end]
}
return paginatedCDPs
}
// 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
}

View File

@ -116,7 +116,7 @@ func (suite *QuerierTestSuite) TestQueryCdp() {
ctx := suite.ctx.WithIsCheckTx(false) ctx := suite.ctx.WithIsCheckTx(false)
query := abci.RequestQuery{ query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetCdp}, "/"), Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetCdp}, "/"),
Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryCdpParams(suite.cdps[0].Owner, suite.cdps[0].Collateral.Denom+"-a")), Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryCdpParams(suite.cdps[0].Owner, suite.cdps[0].Type)),
} }
bz, err := suite.querier(ctx, []string{types.QueryGetCdp}, query) bz, err := suite.querier(ctx, []string{types.QueryGetCdp}, query)
suite.Nil(err) suite.Nil(err)
@ -156,10 +156,10 @@ func (suite *QuerierTestSuite) TestQueryCdp() {
func (suite *QuerierTestSuite) TestQueryCdpsByCollateralType() { func (suite *QuerierTestSuite) TestQueryCdpsByCollateralType() {
ctx := suite.ctx.WithIsCheckTx(false) ctx := suite.ctx.WithIsCheckTx(false)
query := abci.RequestQuery{ query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetCdps}, "/"), Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetCdpsByCollateralType}, "/"),
Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryCdpsParams(suite.cdps[0].Collateral.Denom + "-a")), Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryCdpsByCollateralTypeParams(suite.cdps[0].Type)),
} }
bz, err := suite.querier(ctx, []string{types.QueryGetCdps}, query) bz, err := suite.querier(ctx, []string{types.QueryGetCdpsByCollateralType}, query)
suite.Nil(err) suite.Nil(err)
suite.NotNil(bz) suite.NotNil(bz)
@ -168,10 +168,10 @@ func (suite *QuerierTestSuite) TestQueryCdpsByCollateralType() {
suite.Equal(50, len(c)) suite.Equal(50, len(c))
query = abci.RequestQuery{ query = abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetCdps}, "/"), Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetCdpsByCollateralType}, "/"),
Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryCdpsParams("lol-a")), Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryCdpsByCollateralTypeParams("lol-a")),
} }
_, err = suite.querier(ctx, []string{types.QueryGetCdps}, query) _, err = suite.querier(ctx, []string{types.QueryGetCdpsByCollateralType}, query)
suite.Error(err) suite.Error(err)
} }
@ -265,7 +265,7 @@ func (suite *QuerierTestSuite) TestQueryDeposits() {
ctx := suite.ctx.WithIsCheckTx(false) ctx := suite.ctx.WithIsCheckTx(false)
query := abci.RequestQuery{ query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetCdpDeposits}, "/"), Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetCdpDeposits}, "/"),
Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryCdpDeposits(suite.cdps[0].Owner, suite.cdps[0].Collateral.Denom+"-a")), Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryCdpDeposits(suite.cdps[0].Owner, suite.cdps[0].Type)),
} }
bz, err := suite.querier(ctx, []string{types.QueryGetCdpDeposits}, query) bz, err := suite.querier(ctx, []string{types.QueryGetCdpDeposits}, query)
@ -303,6 +303,89 @@ func (suite *QuerierTestSuite) TestQueryAccounts() {
suite.Require().True(findByName("savings")) suite.Require().True(findByName("savings"))
} }
func (suite *QuerierTestSuite) TestQuerySavingsRateDistributed() {
ctx := suite.ctx.WithIsCheckTx(false)
bz, err := suite.querier(ctx, []string{types.QueryGetSavingsRateDistributed}, abci.RequestQuery{})
suite.Nil(err)
suite.NotNil(bz)
var distAmount sdk.Int
suite.Nil(types.ModuleCdc.UnmarshalJSON(bz, &distAmount))
suite.True(sdk.ZeroInt().Equal(distAmount))
}
func (suite *QuerierTestSuite) TestFindIntersection() {
a := types.CDPs{suite.cdps[0], suite.cdps[1], suite.cdps[2], suite.cdps[3], suite.cdps[4]}
b := types.CDPs{suite.cdps[3], suite.cdps[4], suite.cdps[5], suite.cdps[6], suite.cdps[7]}
expectedIntersection1 := types.CDPs{suite.cdps[3], suite.cdps[4]}
intersection1 := keeper.FindIntersection(a, b)
suite.Require().Equal(intersection1, expectedIntersection1)
c := types.CDPs{suite.cdps[0], suite.cdps[1], suite.cdps[2], suite.cdps[3], suite.cdps[4]}
d := types.CDPs{suite.cdps[5], suite.cdps[6], suite.cdps[7], suite.cdps[8], suite.cdps[9]}
expectedIntersection2 := types.CDPs{}
intersection2 := keeper.FindIntersection(c, d)
suite.Require().Equal(intersection2, expectedIntersection2)
e := types.CDPs{suite.cdps[0]}
f := types.CDPs{}
expectedIntersection3 := types.CDPs{}
intersection3 := keeper.FindIntersection(e, f)
suite.Require().Equal(intersection3, expectedIntersection3)
}
func (suite *QuerierTestSuite) TestFilterCDPs() {
paramsType := types.NewQueryCdpsParams(1, 100, "btc-a", sdk.AccAddress{}, 0, sdk.ZeroDec())
filteredCDPs1 := keeper.FilterCDPs(suite.ctx, suite.keeper, paramsType)
suite.Require().Equal(len(filteredCDPs1), 50)
paramsOwner := types.NewQueryCdpsParams(1, 100, "", suite.cdps[10].Owner, 0, sdk.ZeroDec())
filteredCDPs2 := keeper.FilterCDPs(suite.ctx, suite.keeper, paramsOwner)
suite.Require().Equal(len(filteredCDPs2), 1)
suite.Require().Equal(filteredCDPs2[0].Owner, suite.cdps[10].Owner)
paramsID := types.NewQueryCdpsParams(1, 100, "", sdk.AccAddress{}, 68, sdk.ZeroDec())
filteredCDPs3 := keeper.FilterCDPs(suite.ctx, suite.keeper, paramsID)
suite.Require().Equal(len(filteredCDPs3), 1)
suite.Require().Equal(filteredCDPs3[0].ID, suite.cdps[68-1].ID)
ratioCountBtc := 0
btcRatio := d("2500")
for _, cdp := range suite.cdps {
if cdp.Type == "btc-a" {
absoluteRatio := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, cdp.Collateral, cdp.Type, cdp.Principal)
collateralizationRatio, _ := suite.keeper.CalculateCollateralizationRatioFromAbsoluteRatio(suite.ctx, cdp.Type, absoluteRatio, "liquidation")
if collateralizationRatio.LT(btcRatio) {
ratioCountBtc += 1
}
}
}
paramsTypeAndRatio := types.NewQueryCdpsParams(1, 100, "btc-a", sdk.AccAddress{}, 0, sdk.NewDec(2500))
filteredCDPs4 := keeper.FilterCDPs(suite.ctx, suite.keeper, paramsTypeAndRatio)
suite.Require().Equal(len(filteredCDPs4), ratioCountBtc)
}
func (suite *QuerierTestSuite) TestQueryCdps() {
ctx := suite.ctx.WithIsCheckTx(false)
params := types.NewQueryCdpsParams(1, 100, "btc-a", sdk.AccAddress{}, 0, sdk.ZeroDec())
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetCdps}, "/"),
Data: types.ModuleCdc.MustMarshalJSON(params),
}
bz, err := suite.querier(ctx, []string{types.QueryGetCdps}, query)
suite.Nil(err)
suite.NotNil(bz)
output := types.AugmentedCDPs{}
suite.Nil(types.ModuleCdc.UnmarshalJSON(bz, &output))
suite.Equal(50, len(output))
}
func TestQuerierTestSuite(t *testing.T) { func TestQuerierTestSuite(t *testing.T) {
suite.Run(t, new(QuerierTestSuite)) suite.Run(t, new(QuerierTestSuite))
} }

View File

@ -58,6 +58,11 @@ func (k Keeper) DistributeSavingsRate(ctx sdk.Context, debtDenom string) error {
return false return false
} }
// update total savings rate distributed by surplus to distribute
previousSavingsDistributed := k.GetSavingsRateDistributed(ctx)
newTotalDistributed := previousSavingsDistributed.Add(interest)
k.SetSavingsRateDistributed(ctx, newTotalDistributed)
interestCoins := sdk.NewCoins(sdk.NewCoin(debtDenom, interest)) interestCoins := sdk.NewCoins(sdk.NewCoin(debtDenom, interest))
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.SavingsRateMacc, acc.GetAddress(), interestCoins) err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.SavingsRateMacc, acc.GetAddress(), interestCoins)
if err != nil { if err != nil {

View File

@ -22,11 +22,13 @@ type SavingsTestSuite struct {
app app.TestApp app app.TestApp
ctx sdk.Context ctx sdk.Context
addrs []sdk.AccAddress addrs []sdk.AccAddress
amountToDistribute int64
} }
func (suite *SavingsTestSuite) SetupTest() { func (suite *SavingsTestSuite) SetupTest() {
tApp := app.NewTestApp() tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()}) ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
_, addrs := app.GeneratePrivKeyAddressPairs(3) _, addrs := app.GeneratePrivKeyAddressPairs(3)
authGS := app.NewAuthGenState( authGS := app.NewAuthGenState(
addrs, addrs,
@ -34,25 +36,33 @@ func (suite *SavingsTestSuite) SetupTest() {
cs(c("usdx", 100000)), cs(c("usdx", 50000)), cs(c("usdx", 50000)), cs(c("usdx", 100000)), cs(c("usdx", 50000)), cs(c("usdx", 50000)),
}, },
) )
tApp.InitializeFromGenesisStates( tApp.InitializeFromGenesisStates(
authGS, authGS,
NewPricefeedGenStateMulti(), NewPricefeedGenStateMulti(),
NewCDPGenStateMulti(), NewCDPGenStateMulti(),
) )
sk := tApp.GetSupplyKeeper() sk := tApp.GetSupplyKeeper()
macc := sk.GetModuleAccount(ctx, types.SavingsRateMacc) macc := sk.GetModuleAccount(ctx, types.SavingsRateMacc)
err := sk.MintCoins(ctx, macc.GetName(), cs(c("usdx", 10000))) distAmount := int64(10000)
err := sk.MintCoins(ctx, macc.GetName(), cs(c("usdx", distAmount)))
suite.NoError(err) suite.NoError(err)
keeper := tApp.GetCDPKeeper() keeper := tApp.GetCDPKeeper()
suite.app = tApp suite.app = tApp
suite.keeper = keeper suite.keeper = keeper
suite.ctx = ctx suite.ctx = ctx
suite.addrs = addrs suite.addrs = addrs
suite.amountToDistribute = distAmount
} }
func (suite *SavingsTestSuite) TestApplySavingsRate() { func (suite *SavingsTestSuite) TestApplySavingsRate() {
preSavingsRateDistAmount := suite.keeper.GetSavingsRateDistributed(suite.ctx)
err := suite.keeper.DistributeSavingsRate(suite.ctx, "usdx") err := suite.keeper.DistributeSavingsRate(suite.ctx, "usdx")
suite.NoError(err) suite.NoError(err)
ak := suite.app.GetAccountKeeper() ak := suite.app.GetAccountKeeper()
acc0 := ak.GetAccount(suite.ctx, suite.addrs[0]) acc0 := ak.GetAccount(suite.ctx, suite.addrs[0])
suite.Equal(cs(c("usdx", 105000)), acc0.GetCoins()) suite.Equal(cs(c("usdx", 105000)), acc0.GetCoins())
@ -60,9 +70,14 @@ func (suite *SavingsTestSuite) TestApplySavingsRate() {
suite.Equal(cs(c("usdx", 52500)), acc1.GetCoins()) suite.Equal(cs(c("usdx", 52500)), acc1.GetCoins())
acc2 := ak.GetAccount(suite.ctx, suite.addrs[2]) acc2 := ak.GetAccount(suite.ctx, suite.addrs[2])
suite.Equal(cs(c("usdx", 52500)), acc2.GetCoins()) suite.Equal(cs(c("usdx", 52500)), acc2.GetCoins())
sk := suite.app.GetSupplyKeeper() sk := suite.app.GetSupplyKeeper()
macc := sk.GetModuleAccount(suite.ctx, types.SavingsRateMacc) macc := sk.GetModuleAccount(suite.ctx, types.SavingsRateMacc)
suite.True(macc.GetCoins().AmountOf("usdx").IsZero()) suite.True(macc.GetCoins().AmountOf("usdx").IsZero())
expectedPostSavingsRateDistAmount := preSavingsRateDistAmount.Add(sdk.NewInt(suite.amountToDistribute))
postSavingsRateDistAmount := suite.keeper.GetSavingsRateDistributed(suite.ctx)
suite.True(expectedPostSavingsRateDistAmount.Equal(postSavingsRateDistAmount))
} }
func (suite *SavingsTestSuite) TestGetSetPreviousDistributionTime() { func (suite *SavingsTestSuite) TestGetSetPreviousDistributionTime() {
@ -76,7 +91,21 @@ func (suite *SavingsTestSuite) TestGetSetPreviousDistributionTime() {
pdt, f := suite.keeper.GetPreviousSavingsDistribution(suite.ctx) pdt, f := suite.keeper.GetPreviousSavingsDistribution(suite.ctx)
suite.True(f) suite.True(f)
suite.Equal(now, pdt) suite.Equal(now, pdt)
}
func (suite *SavingsTestSuite) TestGetSetSavingsRateDistributed() {
// Savings rate dist set to 0 when the default genesis is used
preSavingsRateDistAmount := suite.keeper.GetSavingsRateDistributed(suite.ctx)
suite.True(preSavingsRateDistAmount.Equal(types.DefaultSavingsRateDistributed))
// Adding new dist amount to existing dist so default genesis value can be updated in the future
amountToDistribute := sdk.NewInt(9876543210)
newTotalDistributed := preSavingsRateDistAmount.Add(amountToDistribute)
suite.NotPanics(func() { suite.keeper.SetSavingsRateDistributed(suite.ctx, newTotalDistributed) })
postSavingsRateDistAmount := suite.keeper.GetSavingsRateDistributed(suite.ctx)
suite.Equal(newTotalDistributed, postSavingsRateDistAmount)
} }
func TestSavingsTestSuite(t *testing.T) { func TestSavingsTestSuite(t *testing.T) {

View File

@ -121,6 +121,7 @@ func NewAugmentedCDP(cdp CDP, collateralValue sdk.Coin, collateralizationRatio s
CDP: CDP{ CDP: CDP{
ID: cdp.ID, ID: cdp.ID,
Owner: cdp.Owner, Owner: cdp.Owner,
Type: cdp.Type,
Collateral: cdp.Collateral, Collateral: cdp.Collateral,
Principal: cdp.Principal, Principal: cdp.Principal,
AccumulatedFees: cdp.AccumulatedFees, AccumulatedFees: cdp.AccumulatedFees,
@ -146,7 +147,7 @@ func (augCDP AugmentedCDP) String() string {
Collateralization ratio: %s`, Collateralization ratio: %s`,
augCDP.Owner, augCDP.Owner,
augCDP.ID, augCDP.ID,
augCDP.Collateral.Denom, augCDP.Type,
augCDP.Collateral, augCDP.Collateral,
augCDP.CollateralValue, augCDP.CollateralValue,
augCDP.Principal, augCDP.Principal,

View File

@ -17,10 +17,12 @@ type GenesisState struct {
DebtDenom string `json:"debt_denom" yaml:"debt_denom"` DebtDenom string `json:"debt_denom" yaml:"debt_denom"`
GovDenom string `json:"gov_denom" yaml:"gov_denom"` GovDenom string `json:"gov_denom" yaml:"gov_denom"`
PreviousDistributionTime time.Time `json:"previous_distribution_time" yaml:"previous_distribution_time"` PreviousDistributionTime time.Time `json:"previous_distribution_time" yaml:"previous_distribution_time"`
SavingsRateDistributed sdk.Int `json:"savings_rate_distributed" yaml:"savings_rate_distributed"`
} }
// NewGenesisState returns a new genesis state // NewGenesisState returns a new genesis state
func NewGenesisState(params Params, cdps CDPs, deposits Deposits, startingCdpID uint64, debtDenom, govDenom string, previousDistTime time.Time) GenesisState { func NewGenesisState(params Params, cdps CDPs, deposits Deposits, startingCdpID uint64,
debtDenom, govDenom string, previousDistTime time.Time, savingsRateDist sdk.Int) GenesisState {
return GenesisState{ return GenesisState{
Params: params, Params: params,
CDPs: cdps, CDPs: cdps,
@ -29,6 +31,7 @@ func NewGenesisState(params Params, cdps CDPs, deposits Deposits, startingCdpID
DebtDenom: debtDenom, DebtDenom: debtDenom,
GovDenom: govDenom, GovDenom: govDenom,
PreviousDistributionTime: previousDistTime, PreviousDistributionTime: previousDistTime,
SavingsRateDistributed: savingsRateDist,
} }
} }
@ -42,6 +45,7 @@ func DefaultGenesisState() GenesisState {
DefaultDebtDenom, DefaultDebtDenom,
DefaultGovDenom, DefaultGovDenom,
DefaultPreviousDistributionTime, DefaultPreviousDistributionTime,
DefaultSavingsRateDistributed,
) )
} }
@ -65,6 +69,10 @@ func (gs GenesisState) Validate() error {
return fmt.Errorf("previous distribution time not set") return fmt.Errorf("previous distribution time not set")
} }
if err := validateSavingsRateDistributed(gs.SavingsRateDistributed); err != nil {
return err
}
if err := sdk.ValidateDenom(gs.DebtDenom); err != nil { if err := sdk.ValidateDenom(gs.DebtDenom); err != nil {
return fmt.Errorf(fmt.Sprintf("debt denom invalid: %v", err)) return fmt.Errorf(fmt.Sprintf("debt denom invalid: %v", err))
} }
@ -76,6 +84,19 @@ func (gs GenesisState) Validate() error {
return nil return nil
} }
func validateSavingsRateDistributed(i interface{}) error {
savingsRateDist, ok := i.(sdk.Int)
if !ok {
return fmt.Errorf("invalid parameter type: %T", i)
}
if savingsRateDist.IsNegative() {
return fmt.Errorf("savings rate distributed should not be negative: %s", savingsRateDist)
}
return nil
}
// Equal checks whether two gov GenesisState structs are equivalent // Equal checks whether two gov GenesisState structs are equivalent
func (gs GenesisState) Equal(gs2 GenesisState) bool { func (gs GenesisState) Equal(gs2 GenesisState) bool {
b1 := ModuleCdc.MustMarshalBinaryBare(gs) b1 := ModuleCdc.MustMarshalBinaryBare(gs)

View File

@ -47,6 +47,7 @@ var sep = []byte(":")
// - 0x07<denom>:feeRate // - 0x07<denom>:feeRate
// - 0x08:previousDistributionTime // - 0x08:previousDistributionTime
// - 0x09<marketID>:downTime // - 0x09<marketID>:downTime
// - 0x10:totalDistributed
// KVStore key prefixes // KVStore key prefixes
var ( var (
@ -60,6 +61,7 @@ var (
PrincipalKeyPrefix = []byte{0x07} PrincipalKeyPrefix = []byte{0x07}
PreviousDistributionTimeKey = []byte{0x08} PreviousDistributionTimeKey = []byte{0x08}
PricefeedStatusKeyPrefix = []byte{0x09} PricefeedStatusKeyPrefix = []byte{0x09}
SavingsRateDistributedKey = []byte{0x10}
) )
// GetCdpIDBytes returns the byte representation of the cdpID // GetCdpIDBytes returns the byte representation of the cdpID

View File

@ -76,7 +76,8 @@ func (msg MsgCreateCDP) String() string {
Sender: %s Sender: %s
Collateral: %s Collateral: %s
Principal: %s Principal: %s
`, msg.Sender, msg.Collateral, msg.Principal) Collateral Type: %s
`, msg.Sender, msg.Collateral, msg.Principal, msg.CollateralType)
} }
// MsgDeposit deposit collateral to an existing cdp. // MsgDeposit deposit collateral to an existing cdp.

View File

@ -23,6 +23,7 @@ var (
KeyDebtLot = []byte("DebtLot") KeyDebtLot = []byte("DebtLot")
KeySurplusThreshold = []byte("SurplusThreshold") KeySurplusThreshold = []byte("SurplusThreshold")
KeySurplusLot = []byte("SurplusLot") KeySurplusLot = []byte("SurplusLot")
KeySavingsRateDistributed = []byte("SavingsRateDistributed")
DefaultGlobalDebt = sdk.NewCoin(DefaultStableDenom, sdk.ZeroInt()) DefaultGlobalDebt = sdk.NewCoin(DefaultStableDenom, sdk.ZeroInt())
DefaultCircuitBreaker = false DefaultCircuitBreaker = false
DefaultCollateralParams = CollateralParams{} DefaultCollateralParams = CollateralParams{}
@ -43,6 +44,7 @@ var (
DefaultDebtLot = sdk.NewInt(10000000000) DefaultDebtLot = sdk.NewInt(10000000000)
DefaultPreviousDistributionTime = tmtime.Canonical(time.Unix(0, 0)) DefaultPreviousDistributionTime = tmtime.Canonical(time.Unix(0, 0))
DefaultSavingsDistributionFrequency = time.Hour * 12 DefaultSavingsDistributionFrequency = time.Hour * 12
DefaultSavingsRateDistributed = sdk.NewInt(0)
minCollateralPrefix = 0 minCollateralPrefix = 0
maxCollateralPrefix = 255 maxCollateralPrefix = 255
stabilityFeeMax = sdk.MustNewDecFromStr("1.000000051034942716") // 500% APR stabilityFeeMax = sdk.MustNewDecFromStr("1.000000051034942716") // 500% APR

View File

@ -7,28 +7,18 @@ import (
// Querier routes for the cdp module // Querier routes for the cdp module
const ( const (
QueryGetCdp = "cdp" QueryGetCdp = "cdp"
QueryGetCdpDeposits = "deposits"
QueryGetCdps = "cdps" QueryGetCdps = "cdps"
QueryGetCdpsByCollateralization = "ratio" QueryGetCdpDeposits = "deposits"
QueryGetCdpsByCollateralization = "ratio" // legacy query, maintained for REST API
QueryGetCdpsByCollateralType = "collateralType" // legacy query, maintained for REST API
QueryGetParams = "params" QueryGetParams = "params"
QueryGetAccounts = "accounts" QueryGetAccounts = "accounts"
QueryGetSavingsRateDistributed = "savings-rate-dist"
RestOwner = "owner" RestOwner = "owner"
RestCollateralType = "collateral-type" RestCollateralType = "collateral-type"
RestRatio = "ratio" RestRatio = "ratio"
) )
// QueryCdpsParams params for query /cdp/cdps
type QueryCdpsParams struct {
CollateralType string // get CDPs with this collateral type
}
// NewQueryCdpsParams returns QueryCdpsParams
func NewQueryCdpsParams(collateralType string) QueryCdpsParams {
return QueryCdpsParams{
CollateralType: collateralType,
}
}
// QueryCdpParams params for query /cdp/cdp // QueryCdpParams params for query /cdp/cdp
type QueryCdpParams struct { type QueryCdpParams struct {
CollateralType string // get CDPs with this collateral type CollateralType string // get CDPs with this collateral type
@ -43,6 +33,28 @@ func NewQueryCdpParams(owner sdk.AccAddress, collateralType string) QueryCdpPara
} }
} }
// QueryCdpsParams is the params for a filtered CDP query
type QueryCdpsParams struct {
Page int `json:"page" yaml:"page"`
Limit int `json:"limit" yaml:"limit"`
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
Owner sdk.AccAddress `json:"owner" yaml:"owner"`
ID uint64 `json:"id" yaml:"id"`
Ratio sdk.Dec `json:"ratio" yaml:"ratio"`
}
// NewQueryCdpsParams creates a new QueryCdpsParams
func NewQueryCdpsParams(page, limit int, collateralType string, owner sdk.AccAddress, id uint64, ratio sdk.Dec) QueryCdpsParams {
return QueryCdpsParams{
Page: page,
Limit: limit,
CollateralType: collateralType,
Owner: owner,
ID: id,
Ratio: ratio,
}
}
// QueryCdpDeposits params for query /cdp/deposits // QueryCdpDeposits params for query /cdp/deposits
type QueryCdpDeposits struct { type QueryCdpDeposits struct {
CollateralType string // get CDPs with this collateral type CollateralType string // get CDPs with this collateral type
@ -57,6 +69,18 @@ func NewQueryCdpDeposits(owner sdk.AccAddress, collateralType string) QueryCdpDe
} }
} }
// QueryCdpsByCollateralTypeParams params for query /cdp/cdps/{denom}
type QueryCdpsByCollateralTypeParams struct {
CollateralType string // get CDPs with this collateral type
}
// NewQueryCdpsByCollateralTypeParams returns QueryCdpsByCollateralTypeParams
func NewQueryCdpsByCollateralTypeParams(collateralType string) QueryCdpsByCollateralTypeParams {
return QueryCdpsByCollateralTypeParams{
CollateralType: collateralType,
}
}
// QueryCdpsByRatioParams params for query /cdp/cdps/ratio // QueryCdpsByRatioParams params for query /cdp/cdps/ratio
type QueryCdpsByRatioParams struct { type QueryCdpsByRatioParams struct {
CollateralType string CollateralType string