diff --git a/x/hard/client/cli/query.go b/x/hard/client/cli/query.go index 4b1ea65a..24c65a19 100644 --- a/x/hard/client/cli/query.go +++ b/x/hard/client/cli/query.go @@ -274,7 +274,7 @@ func queryBorrowsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { page := viper.GetInt(flags.FlagPage) limit := viper.GetInt(flags.FlagLimit) - params := types.NewQueryBorrowParams(page, limit, owner, depositDenom) + params := types.NewQueryBorrowsParams(page, limit, owner, depositDenom) bz, err := cdc.MarshalJSON(params) if err != nil { return err @@ -350,7 +350,7 @@ func queryBorrowCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { owner = borrowOwner } - params := types.NewQueryBorrow(owner) + params := types.NewQueryBorrowParams(owner) bz, err := cdc.MarshalJSON(params) if err != nil { return err diff --git a/x/hard/client/rest/query.go b/x/hard/client/rest/query.go index dc6a33c8..92729f9a 100644 --- a/x/hard/client/rest/query.go +++ b/x/hard/client/rest/query.go @@ -19,6 +19,9 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) { r.HandleFunc(fmt.Sprintf("/%s/deposits", types.ModuleName), queryDepositsHandlerFn(cliCtx)).Methods("GET") r.HandleFunc(fmt.Sprintf("/%s/claims", types.ModuleName), queryClaimsHandlerFn(cliCtx)).Methods("GET") r.HandleFunc(fmt.Sprintf("/%s/accounts", types.ModuleName), queryModAccountsHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/%s/borrows", types.ModuleName), queryBorrowsHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/%s/borrow", types.ModuleName), queryBorrowHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/%s/borrowed", types.ModuleName), queryBorrowedHandlerFn(cliCtx)).Methods("GET") } func queryParamsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { @@ -148,6 +151,135 @@ func queryClaimsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { } } +func queryBorrowsHandlerFn(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 borrowDenom string + var borrowOwner sdk.AccAddress + + if x := r.URL.Query().Get(RestBorrowDenom); len(x) != 0 { + borrowDenom = strings.TrimSpace(x) + } + + if x := r.URL.Query().Get(RestOwner); len(x) != 0 { + borrowOwnerStr := strings.ToLower(strings.TrimSpace(x)) + borrowOwner, err = sdk.AccAddressFromBech32(borrowOwnerStr) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("cannot parse address from borrow owner %s", borrowOwnerStr)) + return + } + } + + params := types.NewQueryBorrowsParams(page, limit, borrowOwner, borrowDenom) + + 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.QueryGetBorrows) + 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) + } +} + +func queryBorrowHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + _, _, _, 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 borrowOwner sdk.AccAddress + + if x := r.URL.Query().Get(RestOwner); len(x) != 0 { + borrowOwnerStr := strings.ToLower(strings.TrimSpace(x)) + borrowOwner, err = sdk.AccAddressFromBech32(borrowOwnerStr) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("cannot parse address from borrow owner %s", borrowOwnerStr)) + return + } + } + + params := types.NewQueryBorrowParams(borrowOwner) + + 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.QueryGetBorrow) + 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) + } +} + +func queryBorrowedHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + _, _, _, 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 borrowDenom string + + if x := r.URL.Query().Get(RestBorrowDenom); len(x) != 0 { + borrowDenom = strings.TrimSpace(x) + } + + params := types.NewQueryBorrowedParams(borrowDenom) + + 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.QueryGetBorrowed) + 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) + } +} + func queryModAccountsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { _, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0) diff --git a/x/hard/client/rest/rest.go b/x/hard/client/rest/rest.go index 95ea3b93..6f0b68e9 100644 --- a/x/hard/client/rest/rest.go +++ b/x/hard/client/rest/rest.go @@ -11,10 +11,11 @@ import ( // REST variable names // nolint const ( - RestOwner = "owner" - RestDenom = "deposit-denom" - RestType = "deposit-type" - RestName = "name" + RestOwner = "owner" + RestDenom = "deposit-denom" + RestType = "deposit-type" + RestBorrowDenom = "borrow-denom" + RestName = "name" ) // RegisterRoutes registers hard-related REST handlers to a router @@ -25,26 +26,45 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) { // PostCreateDepositReq defines the properties of a deposit create request's body type PostCreateDepositReq struct { - BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` - From sdk.AccAddress `json:"from" yaml:"from"` - Amount sdk.Coins `json:"amount" yaml:"amount"` - DepositType string `json:"deposit_type" yaml:"deposit_type"` + BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` + From sdk.AccAddress `json:"from" yaml:"from"` + Amount sdk.Coins `json:"amount" yaml:"amount"` } // PostCreateWithdrawReq defines the properties of a deposit withdraw request's body type PostCreateWithdrawReq struct { - BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` - From sdk.AccAddress `json:"from" yaml:"from"` - Amount sdk.Coins `json:"amount" yaml:"amount"` - DepositType string `json:"deposit_type" yaml:"deposit_type"` + BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` + From sdk.AccAddress `json:"from" yaml:"from"` + Amount sdk.Coins `json:"amount" yaml:"amount"` } // PostClaimReq defines the properties of a claim reward request's body type PostClaimReq struct { - BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` - From sdk.AccAddress `json:"from" yaml:"from"` - Receiver sdk.AccAddress `json:"receiver" yaml:"receiver"` - DepositDenom string `json:"deposit_denom" yaml:"deposit_denom"` - DepositType string `json:"deposit_type" yaml:"deposit_type"` - Multiplier string `json:"multiplier" yaml:"multiplier"` + BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` + From sdk.AccAddress `json:"from" yaml:"from"` + Receiver sdk.AccAddress `json:"receiver" yaml:"receiver"` + DepositDenom string `json:"deposit_denom" yaml:"deposit_denom"` + MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"` + ClaimType string `json:"claim_type" yaml:"claim_type"` +} + +// PostBorrowReq defines the properties of a borrow request's body +type PostBorrowReq struct { + BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` + From sdk.AccAddress `json:"from" yaml:"from"` + Amount sdk.Coins `json:"amount" yaml:"amount"` +} + +// PostRepayReq defines the properties of a repay request's body +type PostRepayReq struct { + BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` + From sdk.AccAddress `json:"from" yaml:"from"` + Amount sdk.Coins `json:"amount" yaml:"amount"` +} + +// PostLiquidateReq defines the properties of a liquidate request's body +type PostLiquidateReq struct { + BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` + From sdk.AccAddress `json:"from" yaml:"from"` + Borrower sdk.AccAddress `json:"borrower" yaml:"borrower"` } diff --git a/x/hard/client/rest/tx.go b/x/hard/client/rest/tx.go index aced599a..96ff1ee3 100644 --- a/x/hard/client/rest/tx.go +++ b/x/hard/client/rest/tx.go @@ -19,6 +19,9 @@ func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) { r.HandleFunc(fmt.Sprintf("/%s/deposit", types.ModuleName), postDepositHandlerFn(cliCtx)).Methods("POST") r.HandleFunc(fmt.Sprintf("/%s/withdraw", types.ModuleName), postWithdrawHandlerFn(cliCtx)).Methods("POST") r.HandleFunc(fmt.Sprintf("/%s/claim", types.ModuleName), postClaimHandlerFn(cliCtx)).Methods("POST") + r.HandleFunc(fmt.Sprintf("/%s/borrow", types.ModuleName), postBorrowHandlerFn(cliCtx)).Methods("POST") + r.HandleFunc(fmt.Sprintf("/%s/repay", types.ModuleName), postRepayHandlerFn(cliCtx)).Methods("POST") + r.HandleFunc(fmt.Sprintf("/%s/liquidate", types.ModuleName), postLiquidateHandlerFn(cliCtx)).Methods("POST") } func postDepositHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { @@ -75,7 +78,70 @@ func postClaimHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } - msg := types.NewMsgClaimReward(req.From, req.Receiver, req.DepositDenom, strings.ToLower(req.DepositType), req.Multiplier) + msg := types.NewMsgClaimReward(req.From, req.Receiver, req.DepositDenom, strings.ToLower(req.ClaimType), req.MultiplierName) + if err := msg.ValidateBasic(); err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) + } +} + +func postBorrowHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Decode POST request body + var req PostBorrowReq + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + return + } + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { + return + } + + msg := types.NewMsgBorrow(req.From, req.Amount) + if err := msg.ValidateBasic(); err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) + } +} + +func postRepayHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Decode POST request body + var req PostRepayReq + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + return + } + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { + return + } + + msg := types.NewMsgRepay(req.From, req.Amount) + if err := msg.ValidateBasic(); err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) + } +} + +func postLiquidateHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Decode POST request body + var req PostLiquidateReq + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + return + } + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { + return + } + + msg := types.NewMsgLiquidate(req.From, req.Borrower) if err := msg.ValidateBasic(); err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return diff --git a/x/hard/handler.go b/x/hard/handler.go index 490c11ae..1d98746c 100644 --- a/x/hard/handler.go +++ b/x/hard/handler.go @@ -25,6 +25,8 @@ func NewHandler(k Keeper) sdk.Handler { return handleMsgBorrow(ctx, k, msg) case types.MsgRepay: return handleMsgRepay(ctx, k, msg) + case types.MsgLiquidate: + return handleMsgLiquidate(ctx, k, msg) default: return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg) } @@ -120,3 +122,21 @@ func handleMsgRepay(ctx sdk.Context, k keeper.Keeper, msg types.MsgRepay) (*sdk. Events: ctx.EventManager().Events(), }, nil } + +func handleMsgLiquidate(ctx sdk.Context, k keeper.Keeper, msg types.MsgLiquidate) (*sdk.Result, error) { + _, err := k.AttemptKeeperLiquidation(ctx, msg.Keeper, msg.Borrower) + if err != nil { + return nil, err + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Keeper.String()), + ), + ) + return &sdk.Result{ + Events: ctx.EventManager().Events(), + }, nil +} diff --git a/x/hard/keeper/querier.go b/x/hard/keeper/querier.go index 9f12abb9..a9c5445a 100644 --- a/x/hard/keeper/querier.go +++ b/x/hard/keeper/querier.go @@ -240,7 +240,7 @@ func queryGetClaims(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, e func queryGetBorrows(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) { - var params types.QueryBorrowParams + var params types.QueryBorrowsParams err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) @@ -297,7 +297,7 @@ func queryGetBorrowed(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, } func queryGetBorrow(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) { - var params types.QueryBorrowParams + var params types.QueryBorrowsParams err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) diff --git a/x/hard/types/msg.go b/x/hard/types/msg.go index 5da01394..e87f9c34 100644 --- a/x/hard/types/msg.go +++ b/x/hard/types/msg.go @@ -53,6 +53,8 @@ var ( _ sdk.Msg = &MsgDeposit{} _ sdk.Msg = &MsgWithdraw{} _ sdk.Msg = &MsgBorrow{} + _ sdk.Msg = &MsgRepay{} + _ sdk.Msg = &MsgLiquidate{} ) // MsgDeposit deposit collateral to the hard module. diff --git a/x/hard/types/querier.go b/x/hard/types/querier.go index eab8bad0..1b7fb4ef 100644 --- a/x/hard/types/querier.go +++ b/x/hard/types/querier.go @@ -69,21 +69,21 @@ func NewQueryAccountParams(page, limit int, name string) QueryAccountParams { } } -// QueryBorrowParams is the params for a filtered borrow query -type QueryBorrowParams struct { +// QueryBorrowsParams is the params for a filtered borrows query +type QueryBorrowsParams struct { Page int `json:"page" yaml:"page"` Limit int `json:"limit" yaml:"limit"` Owner sdk.AccAddress `json:"owner" yaml:"owner"` BorrowDenom string `json:"borrow_denom" yaml:"borrow_denom"` } -// NewQueryBorrowParams creates a new QueryBorrowParams -func NewQueryBorrowParams(page, limit int, owner sdk.AccAddress, depositDenom string) QueryBorrowParams { - return QueryBorrowParams{ +// NewQueryBorrowsParams creates a new QueryBorrowsParams +func NewQueryBorrowsParams(page, limit int, owner sdk.AccAddress, borrowDenom string) QueryBorrowsParams { + return QueryBorrowsParams{ Page: page, Limit: limit, Owner: owner, - BorrowDenom: depositDenom, + BorrowDenom: borrowDenom, } } @@ -99,14 +99,14 @@ func NewQueryBorrowedParams(denom string) QueryBorrowedParams { } } -// QueryBorrow is the params for a current borrow balance query -type QueryBorrow struct { +// QueryBorrowParams is the params for a current borrow balance query +type QueryBorrowParams struct { Owner sdk.AccAddress `json:"owner" yaml:"owner"` } -// NewQueryBorrow creates a new QueryBorrow -func NewQueryBorrow(owner sdk.AccAddress) QueryBorrow { - return QueryBorrow{ +// NewQueryBorrowParams creates a new QueryBorrowParams +func NewQueryBorrowParams(owner sdk.AccAddress) QueryBorrowParams { + return QueryBorrowParams{ Owner: owner, } }