github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/x/gov/client/rest/rest.go (about)

     1  package rest
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net/http"
     7  
     8  	"github.com/gorilla/mux"
     9  
    10  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/client/context"
    11  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    12  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/rest"
    13  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth/client/utils"
    14  	gcutils "github.com/fibonacci-chain/fbc/x/gov/client/utils"
    15  	"github.com/fibonacci-chain/fbc/x/gov/types"
    16  )
    17  
    18  // REST Variable names
    19  // nolint
    20  const (
    21  	RestParamsType     = "type"
    22  	RestProposalID     = "proposal-id"
    23  	RestDepositor      = "depositor"
    24  	RestVoter          = "voter"
    25  	RestProposalStatus = "status"
    26  	RestNumLimit       = "limit"
    27  )
    28  
    29  // ProposalRESTHandler defines a REST handler implemented in another module. The
    30  // sub-route is mounted on the governance REST handler.
    31  type ProposalRESTHandler struct {
    32  	SubRoute string
    33  	Handler  func(http.ResponseWriter, *http.Request)
    34  }
    35  
    36  // RegisterRoutes - Central function to define routes that get registered by the main application
    37  func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, phs []ProposalRESTHandler) {
    38  	propSubRtr := r.PathPrefix("/gov/proposals").Subrouter()
    39  	for _, ph := range phs {
    40  		propSubRtr.HandleFunc(fmt.Sprintf("/%s", ph.SubRoute), ph.Handler).Methods("POST")
    41  	}
    42  
    43  	r.HandleFunc("/gov/proposals", postProposalHandlerFn(cliCtx)).Methods("POST")
    44  	r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), depositHandlerFn(cliCtx)).Methods("POST")
    45  	r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), voteHandlerFn(cliCtx)).Methods("POST")
    46  
    47  	r.HandleFunc(
    48  		fmt.Sprintf("/gov/parameters/{%s}", RestParamsType),
    49  		queryParamsHandlerFn(cliCtx),
    50  	).Methods("GET")
    51  
    52  	r.HandleFunc("/gov/proposals", queryProposalsWithParameterFn(cliCtx)).Methods("GET")
    53  	r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}", RestProposalID), queryProposalHandlerFn(cliCtx)).Methods("GET")
    54  	r.HandleFunc(
    55  		fmt.Sprintf("/gov/proposals/{%s}/proposer", RestProposalID),
    56  		queryProposerHandlerFn(cliCtx),
    57  	).Methods("GET")
    58  	r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), queryDepositsHandlerFn(cliCtx)).Methods("GET")
    59  	r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits/{%s}", RestProposalID, RestDepositor), queryDepositHandlerFn(cliCtx)).Methods("GET")
    60  	r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/tally", RestProposalID), queryTallyOnProposalHandlerFn(cliCtx)).Methods("GET")
    61  	r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), queryVotesOnProposalHandlerFn(cliCtx)).Methods("GET")
    62  	r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes/{%s}", RestProposalID, RestVoter), queryVoteHandlerFn(cliCtx)).Methods("GET")
    63  }
    64  
    65  // PostProposalReq defines the properties of a proposal request's body.
    66  type PostProposalReq struct {
    67  	BaseReq        rest.BaseReq   `json:"base_req" yaml:"base_req"`
    68  	Title          string         `json:"title" yaml:"title"`                     // Title of the proposal
    69  	Description    string         `json:"description" yaml:"description"`         // Description of the proposal
    70  	ProposalType   string         `json:"proposal_type" yaml:"proposal_type"`     // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
    71  	Proposer       sdk.AccAddress `json:"proposer" yaml:"proposer"`               // Address of the proposer
    72  	InitialDeposit sdk.SysCoins   `json:"initial_deposit" yaml:"initial_deposit"` // Coins to add to the proposal's deposit
    73  }
    74  
    75  // DepositReq defines the properties of a deposit request's body.
    76  type DepositReq struct {
    77  	BaseReq   rest.BaseReq   `json:"base_req" yaml:"base_req"`
    78  	Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"` // Address of the depositor
    79  	Amount    sdk.SysCoins   `json:"amount" yaml:"amount"`       // Coins to add to the proposal's deposit
    80  }
    81  
    82  // VoteReq defines the properties of a vote request's body.
    83  type VoteReq struct {
    84  	BaseReq rest.BaseReq   `json:"base_req" yaml:"base_req"`
    85  	Voter   sdk.AccAddress `json:"voter" yaml:"voter"`   // address of the voter
    86  	Option  string         `json:"option" yaml:"option"` // option from OptionSet chosen by the voter
    87  }
    88  
    89  func postProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
    90  	return func(w http.ResponseWriter, r *http.Request) {
    91  		var req PostProposalReq
    92  		if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
    93  			return
    94  		}
    95  
    96  		req.BaseReq = req.BaseReq.Sanitize()
    97  		if !req.BaseReq.ValidateBasic(w) {
    98  			return
    99  		}
   100  
   101  		proposalType := gcutils.NormalizeProposalType(req.ProposalType)
   102  		content := types.ContentFromProposalType(req.Title, req.Description, proposalType)
   103  
   104  		msg := types.NewMsgSubmitProposal(content, req.InitialDeposit, req.Proposer)
   105  		if err := msg.ValidateBasic(); err != nil {
   106  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   107  			return
   108  		}
   109  
   110  		utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg})
   111  	}
   112  }
   113  
   114  func depositHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
   115  	return func(w http.ResponseWriter, r *http.Request) {
   116  		vars := mux.Vars(r)
   117  		strProposalID := vars[RestProposalID]
   118  
   119  		if len(strProposalID) == 0 {
   120  			err := errors.New("proposalId required but not specified")
   121  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   122  			return
   123  		}
   124  
   125  		proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
   126  		if !ok {
   127  			return
   128  		}
   129  
   130  		var req DepositReq
   131  		if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
   132  			return
   133  		}
   134  
   135  		req.BaseReq = req.BaseReq.Sanitize()
   136  		if !req.BaseReq.ValidateBasic(w) {
   137  			return
   138  		}
   139  
   140  		// create the message
   141  		msg := types.NewMsgDeposit(req.Depositor, proposalID, req.Amount)
   142  		if err := msg.ValidateBasic(); err != nil {
   143  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   144  			return
   145  		}
   146  
   147  		utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg})
   148  	}
   149  }
   150  
   151  func voteHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
   152  	return func(w http.ResponseWriter, r *http.Request) {
   153  		vars := mux.Vars(r)
   154  		strProposalID := vars[RestProposalID]
   155  
   156  		if len(strProposalID) == 0 {
   157  			err := errors.New("proposalId required but not specified")
   158  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   159  			return
   160  		}
   161  
   162  		proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
   163  		if !ok {
   164  			return
   165  		}
   166  
   167  		var req VoteReq
   168  		if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
   169  			return
   170  		}
   171  
   172  		req.BaseReq = req.BaseReq.Sanitize()
   173  		if !req.BaseReq.ValidateBasic(w) {
   174  			return
   175  		}
   176  
   177  		voteOption, err := types.VoteOptionFromString(gcutils.NormalizeVoteOption(req.Option))
   178  		if err != nil {
   179  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   180  			return
   181  		}
   182  
   183  		// create the message
   184  		msg := types.NewMsgVote(req.Voter, proposalID, voteOption)
   185  		if err := msg.ValidateBasic(); err != nil {
   186  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   187  			return
   188  		}
   189  
   190  		utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg})
   191  	}
   192  }
   193  
   194  func queryParamsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
   195  	return func(w http.ResponseWriter, r *http.Request) {
   196  		vars := mux.Vars(r)
   197  		paramType := vars[RestParamsType]
   198  
   199  		cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
   200  		if !ok {
   201  			return
   202  		}
   203  
   204  		res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/gov/%s/%s", types.QueryParams, paramType), nil)
   205  		if err != nil {
   206  			rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
   207  			return
   208  		}
   209  
   210  		cliCtx = cliCtx.WithHeight(height)
   211  		rest.PostProcessResponse(w, cliCtx, res)
   212  	}
   213  }
   214  
   215  func queryProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
   216  	return func(w http.ResponseWriter, r *http.Request) {
   217  		vars := mux.Vars(r)
   218  		strProposalID := vars[RestProposalID]
   219  
   220  		if len(strProposalID) == 0 {
   221  			err := errors.New("proposalId required but not specified")
   222  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   223  			return
   224  		}
   225  
   226  		proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
   227  		if !ok {
   228  			return
   229  		}
   230  
   231  		cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
   232  		if !ok {
   233  			return
   234  		}
   235  
   236  		params := types.NewQueryProposalParams(proposalID)
   237  
   238  		bz, err := cliCtx.Codec.MarshalJSON(params)
   239  		if err != nil {
   240  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   241  			return
   242  		}
   243  
   244  		res, height, err := cliCtx.QueryWithData("custom/gov/proposal", bz)
   245  		if err != nil {
   246  			rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
   247  			return
   248  		}
   249  
   250  		cliCtx = cliCtx.WithHeight(height)
   251  		rest.PostProcessResponse(w, cliCtx, res)
   252  	}
   253  }
   254  
   255  func queryDepositsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
   256  	return func(w http.ResponseWriter, r *http.Request) {
   257  		vars := mux.Vars(r)
   258  		strProposalID := vars[RestProposalID]
   259  
   260  		proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
   261  		if !ok {
   262  			return
   263  		}
   264  
   265  		cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
   266  		if !ok {
   267  			return
   268  		}
   269  
   270  		params := types.NewQueryProposalParams(proposalID)
   271  
   272  		bz, err := cliCtx.Codec.MarshalJSON(params)
   273  		if err != nil {
   274  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   275  			return
   276  		}
   277  
   278  		res, _, err := cliCtx.QueryWithData("custom/gov/proposal", bz)
   279  		if err != nil {
   280  			rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
   281  			return
   282  		}
   283  
   284  		var proposal types.Proposal
   285  		if err := cliCtx.Codec.UnmarshalJSON(res, &proposal); err != nil {
   286  			rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
   287  			return
   288  		}
   289  
   290  		// For inactive proposals we must query the txs directly to get the deposits
   291  		// as they're no longer in state.
   292  		propStatus := proposal.Status
   293  		if !(propStatus == types.StatusVotingPeriod || propStatus == types.StatusDepositPeriod) {
   294  			res, err = gcutils.QueryDepositsByTxQuery(cliCtx, params)
   295  		} else {
   296  			res, _, err = cliCtx.QueryWithData("custom/gov/deposits", bz)
   297  		}
   298  
   299  		if err != nil {
   300  			rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
   301  			return
   302  		}
   303  
   304  		rest.PostProcessResponse(w, cliCtx, res)
   305  	}
   306  }
   307  
   308  func queryProposerHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
   309  	return func(w http.ResponseWriter, r *http.Request) {
   310  		vars := mux.Vars(r)
   311  		strProposalID := vars[RestProposalID]
   312  
   313  		proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
   314  		if !ok {
   315  			return
   316  		}
   317  
   318  		cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
   319  		if !ok {
   320  			return
   321  		}
   322  
   323  		res, err := gcutils.QueryProposerByTxQuery(cliCtx, proposalID)
   324  		if err != nil {
   325  			rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
   326  			return
   327  		}
   328  
   329  		rest.PostProcessResponse(w, cliCtx, res)
   330  	}
   331  }
   332  
   333  func queryDepositHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
   334  	return func(w http.ResponseWriter, r *http.Request) {
   335  		vars := mux.Vars(r)
   336  		strProposalID := vars[RestProposalID]
   337  		bechDepositorAddr := vars[RestDepositor]
   338  
   339  		if len(strProposalID) == 0 {
   340  			err := errors.New("proposalId required but not specified")
   341  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   342  			return
   343  		}
   344  
   345  		proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
   346  		if !ok {
   347  			return
   348  		}
   349  
   350  		if len(bechDepositorAddr) == 0 {
   351  			err := errors.New("depositor address required but not specified")
   352  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   353  			return
   354  		}
   355  
   356  		depositorAddr, err := sdk.AccAddressFromBech32(bechDepositorAddr)
   357  		if err != nil {
   358  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   359  			return
   360  		}
   361  
   362  		cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
   363  		if !ok {
   364  			return
   365  		}
   366  
   367  		params := types.NewQueryDepositParams(proposalID, depositorAddr)
   368  
   369  		bz, err := cliCtx.Codec.MarshalJSON(params)
   370  		if err != nil {
   371  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   372  			return
   373  		}
   374  
   375  		res, _, err := cliCtx.QueryWithData("custom/gov/deposit", bz)
   376  		if err != nil {
   377  			rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
   378  			return
   379  		}
   380  
   381  		var deposit types.Deposit
   382  		if err := cliCtx.Codec.UnmarshalJSON(res, &deposit); err != nil {
   383  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   384  			return
   385  		}
   386  
   387  		// For an empty deposit, either the proposal does not exist or is inactive in
   388  		// which case the deposit would be removed from state and should be queried
   389  		// for directly via a txs query.
   390  		if deposit.Empty() {
   391  			bz, err := cliCtx.Codec.MarshalJSON(types.NewQueryProposalParams(proposalID))
   392  			if err != nil {
   393  				rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   394  				return
   395  			}
   396  
   397  			res, _, err = cliCtx.QueryWithData("custom/gov/proposal", bz)
   398  			if err != nil || len(res) == 0 {
   399  				err := fmt.Errorf("proposalID %d does not exist", proposalID)
   400  				rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
   401  				return
   402  			}
   403  
   404  			res, err = gcutils.QueryDepositByTxQuery(cliCtx, params)
   405  			if err != nil {
   406  				rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
   407  				return
   408  			}
   409  		}
   410  
   411  		rest.PostProcessResponse(w, cliCtx, res)
   412  	}
   413  }
   414  
   415  func queryVoteHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
   416  	return func(w http.ResponseWriter, r *http.Request) {
   417  		vars := mux.Vars(r)
   418  		strProposalID := vars[RestProposalID]
   419  		bechVoterAddr := vars[RestVoter]
   420  
   421  		if len(strProposalID) == 0 {
   422  			err := errors.New("proposalId required but not specified")
   423  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   424  			return
   425  		}
   426  
   427  		proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
   428  		if !ok {
   429  			return
   430  		}
   431  
   432  		if len(bechVoterAddr) == 0 {
   433  			err := errors.New("voter address required but not specified")
   434  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   435  			return
   436  		}
   437  
   438  		voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr)
   439  		if err != nil {
   440  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   441  			return
   442  		}
   443  
   444  		cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
   445  		if !ok {
   446  			return
   447  		}
   448  
   449  		params := types.NewQueryVoteParams(proposalID, voterAddr)
   450  
   451  		bz, err := cliCtx.Codec.MarshalJSON(params)
   452  		if err != nil {
   453  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   454  			return
   455  		}
   456  
   457  		res, _, err := cliCtx.QueryWithData("custom/gov/vote", bz)
   458  		if err != nil {
   459  			rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
   460  			return
   461  		}
   462  
   463  		var vote types.Vote
   464  		if err := cliCtx.Codec.UnmarshalJSON(res, &vote); err != nil {
   465  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   466  			return
   467  		}
   468  
   469  		// For an empty vote, either the proposal does not exist or is inactive in
   470  		// which case the vote would be removed from state and should be queried for
   471  		// directly via a txs query.
   472  		if vote.Empty() {
   473  			bz, err := cliCtx.Codec.MarshalJSON(types.NewQueryProposalParams(proposalID))
   474  			if err != nil {
   475  				rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   476  				return
   477  			}
   478  
   479  			res, _, err = cliCtx.QueryWithData("custom/gov/proposal", bz)
   480  			if err != nil || len(res) == 0 {
   481  				err := fmt.Errorf("proposalID %d does not exist", proposalID)
   482  				rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
   483  				return
   484  			}
   485  
   486  			res, err = gcutils.QueryVoteByTxQuery(cliCtx, params)
   487  			if err != nil {
   488  				rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
   489  				return
   490  			}
   491  		}
   492  
   493  		rest.PostProcessResponse(w, cliCtx, res)
   494  	}
   495  }
   496  
   497  // todo: Split this functionality into helper functions to remove the above
   498  func queryVotesOnProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
   499  	return func(w http.ResponseWriter, r *http.Request) {
   500  		vars := mux.Vars(r)
   501  		strProposalID := vars[RestProposalID]
   502  
   503  		if len(strProposalID) == 0 {
   504  			err := errors.New("proposalId required but not specified")
   505  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   506  			return
   507  		}
   508  
   509  		proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
   510  		if !ok {
   511  			return
   512  		}
   513  
   514  		cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
   515  		if !ok {
   516  			return
   517  		}
   518  
   519  		params := types.NewQueryProposalParams(proposalID)
   520  
   521  		bz, err := cliCtx.Codec.MarshalJSON(params)
   522  		if err != nil {
   523  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   524  			return
   525  		}
   526  
   527  		res, _, err := cliCtx.QueryWithData("custom/gov/proposal", bz)
   528  		if err != nil {
   529  			rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
   530  			return
   531  		}
   532  
   533  		var proposal types.Proposal
   534  		if err := cliCtx.Codec.UnmarshalJSON(res, &proposal); err != nil {
   535  			rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
   536  			return
   537  		}
   538  
   539  		// For inactive proposals we must query the txs directly to get the votes
   540  		// as they're no longer in state.
   541  		propStatus := proposal.Status
   542  		if !(propStatus == types.StatusVotingPeriod || propStatus == types.StatusDepositPeriod) {
   543  			res, err = gcutils.QueryVotesByTxQuery(cliCtx, params)
   544  		} else {
   545  			res, _, err = cliCtx.QueryWithData("custom/gov/votes", bz)
   546  		}
   547  
   548  		if err != nil {
   549  			rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
   550  			return
   551  		}
   552  
   553  		rest.PostProcessResponse(w, cliCtx, res)
   554  	}
   555  }
   556  
   557  // todo: Split this functionality into helper functions to remove the above
   558  func queryProposalsWithParameterFn(cliCtx context.CLIContext) http.HandlerFunc {
   559  	return func(w http.ResponseWriter, r *http.Request) {
   560  		bechVoterAddr := r.URL.Query().Get(RestVoter)
   561  		bechDepositorAddr := r.URL.Query().Get(RestDepositor)
   562  		strProposalStatus := r.URL.Query().Get(RestProposalStatus)
   563  		strNumLimit := r.URL.Query().Get(RestNumLimit)
   564  
   565  		params := types.QueryProposalsParams{}
   566  
   567  		if len(bechVoterAddr) != 0 {
   568  			voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr)
   569  			if err != nil {
   570  				rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   571  				return
   572  			}
   573  			params.Voter = voterAddr
   574  		}
   575  
   576  		if len(bechDepositorAddr) != 0 {
   577  			depositorAddr, err := sdk.AccAddressFromBech32(bechDepositorAddr)
   578  			if err != nil {
   579  				rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   580  				return
   581  			}
   582  			params.Depositor = depositorAddr
   583  		}
   584  
   585  		if len(strProposalStatus) != 0 {
   586  			proposalStatus, err := types.ProposalStatusFromString(gcutils.NormalizeProposalStatus(strProposalStatus))
   587  			if err != nil {
   588  				rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   589  				return
   590  			}
   591  			params.ProposalStatus = proposalStatus
   592  		}
   593  		if len(strNumLimit) != 0 {
   594  			numLimit, ok := rest.ParseUint64OrReturnBadRequest(w, strNumLimit)
   595  			if !ok {
   596  				return
   597  			}
   598  			params.Limit = numLimit
   599  		}
   600  
   601  		cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
   602  		if !ok {
   603  			return
   604  		}
   605  
   606  		bz, err := cliCtx.Codec.MarshalJSON(params)
   607  		if err != nil {
   608  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   609  			return
   610  		}
   611  
   612  		res, height, err := cliCtx.QueryWithData("custom/gov/proposals", bz)
   613  		if err != nil {
   614  			rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
   615  			return
   616  		}
   617  
   618  		cliCtx = cliCtx.WithHeight(height)
   619  		rest.PostProcessResponse(w, cliCtx, res)
   620  	}
   621  }
   622  
   623  // todo: Split this functionality into helper functions to remove the above
   624  func queryTallyOnProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
   625  	return func(w http.ResponseWriter, r *http.Request) {
   626  		vars := mux.Vars(r)
   627  		strProposalID := vars[RestProposalID]
   628  
   629  		if len(strProposalID) == 0 {
   630  			err := errors.New("proposalId required but not specified")
   631  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   632  			return
   633  		}
   634  
   635  		proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
   636  		if !ok {
   637  			return
   638  		}
   639  
   640  		cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
   641  		if !ok {
   642  			return
   643  		}
   644  
   645  		params := types.NewQueryProposalParams(proposalID)
   646  
   647  		bz, err := cliCtx.Codec.MarshalJSON(params)
   648  		if err != nil {
   649  			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
   650  			return
   651  		}
   652  
   653  		res, height, err := cliCtx.QueryWithData("custom/gov/tally", bz)
   654  		if err != nil {
   655  			rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
   656  			return
   657  		}
   658  
   659  		cliCtx = cliCtx.WithHeight(height)
   660  		rest.PostProcessResponse(w, cliCtx, res)
   661  	}
   662  }