package keeper import ( "context" stderrors "errors" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "cosmossdk.io/collections" "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" v3 "cosmossdk.io/x/gov/migrations/v3" v1 "cosmossdk.io/x/gov/types/v1" "cosmossdk.io/x/gov/types/v1beta1" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" ) var ( _ v1.QueryServer = queryServer{} defaultVoteOptions = &v1.ProposalVoteOptions{ OptionOne: "yes", OptionTwo: "abstain", OptionThree: "no", OptionFour: "no_with_veto", OptionSpam: "spam", } ) type queryServer struct{ k *Keeper } func NewQueryServer(k *Keeper) v1.QueryServer { return queryServer{k: k} } func (q queryServer) Constitution(ctx context.Context, _ *v1.QueryConstitutionRequest) (*v1.QueryConstitutionResponse, error) { constitution, err := q.k.Constitution.Get(ctx) if err != nil { return nil, err } return &v1.QueryConstitutionResponse{Constitution: constitution}, nil } // Proposal returns proposal details based on ProposalID func (q queryServer) Proposal(ctx context.Context, req *v1.QueryProposalRequest) (*v1.QueryProposalResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "invalid request") } if req.ProposalId == 0 { return nil, status.Error(codes.InvalidArgument, "proposal id can not be 0") } proposal, err := q.k.Proposals.Get(ctx, req.ProposalId) if err == nil { return &v1.QueryProposalResponse{Proposal: &proposal}, nil } if errors.IsOf(err, collections.ErrNotFound) { return nil, status.Errorf(codes.NotFound, "proposal %d doesn't exist", req.ProposalId) } return nil, status.Error(codes.Internal, err.Error()) } // ProposalVoteOptions returns the proposal votes options // It returns the stringified vote options if the proposal is a multiple choice proposal // Otherwise it returns the generic vote options func (q queryServer) ProposalVoteOptions(ctx context.Context, req *v1.QueryProposalVoteOptionsRequest) (*v1.QueryProposalVoteOptionsResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "invalid request") } if req.ProposalId == 0 { return nil, status.Error(codes.InvalidArgument, "proposal id can not be 0") } ok, err := q.k.Proposals.Has(ctx, req.ProposalId) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } if !ok { return nil, status.Errorf(codes.NotFound, "proposal %d doesn't exist", req.ProposalId) } voteOptions, err := q.k.ProposalVoteOptions.Get(ctx, req.ProposalId) if err != nil { if stderrors.Is(err, collections.ErrNotFound) { // fallback to generic vote options return &v1.QueryProposalVoteOptionsResponse{ VoteOptions: defaultVoteOptions, }, nil } return nil, status.Error(codes.Internal, err.Error()) } return &v1.QueryProposalVoteOptionsResponse{ VoteOptions: &v1.ProposalVoteOptions{ OptionOne: voteOptions.OptionOne, OptionTwo: voteOptions.OptionTwo, OptionThree: voteOptions.OptionThree, OptionFour: voteOptions.OptionFour, OptionSpam: defaultVoteOptions.OptionSpam, }, }, nil } // Proposals implements the Query/Proposals gRPC method func (q queryServer) Proposals(ctx context.Context, req *v1.QueryProposalsRequest) (*v1.QueryProposalsResponse, error) { filteredProposals, pageRes, err := query.CollectionFilteredPaginate(ctx, q.k.Proposals, req.Pagination, func(key uint64, p v1.Proposal) (include bool, err error) { matchVoter, matchDepositor, matchStatus := true, true, true // match status (if supplied/valid) if v1.ValidProposalStatus(req.ProposalStatus) { matchStatus = p.Status == req.ProposalStatus } // match voter address (if supplied) if len(req.Voter) > 0 { voter, err := q.k.authKeeper.AddressCodec().StringToBytes(req.Voter) if err != nil { return false, err } has, err := q.k.Votes.Has(ctx, collections.Join(p.Id, sdk.AccAddress(voter))) // if no error, vote found, matchVoter = true matchVoter = err == nil && has } // match depositor (if supplied) if len(req.Depositor) > 0 { depositor, err := q.k.authKeeper.AddressCodec().StringToBytes(req.Depositor) if err != nil { return false, err } has, err := q.k.Deposits.Has(ctx, collections.Join(p.Id, sdk.AccAddress(depositor))) // if no error, deposit found, matchDepositor = true matchDepositor = err == nil && has } // if all match, append to results if matchVoter && matchDepositor && matchStatus { return true, nil } // continue to next item, do not include because we're appending results above. return false, nil }, func(_ uint64, value v1.Proposal) (*v1.Proposal, error) { return &value, nil }) if err != nil && !errors.IsOf(err, collections.ErrInvalidIterator) { return nil, status.Error(codes.Internal, err.Error()) } return &v1.QueryProposalsResponse{Proposals: filteredProposals, Pagination: pageRes}, nil } // Vote returns Voted information based on proposalID, voterAddr func (q queryServer) Vote(ctx context.Context, req *v1.QueryVoteRequest) (*v1.QueryVoteResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "invalid request") } if req.ProposalId == 0 { return nil, status.Error(codes.InvalidArgument, "proposal id can not be 0") } if req.Voter == "" { return nil, status.Error(codes.InvalidArgument, "empty voter address") } voter, err := q.k.authKeeper.AddressCodec().StringToBytes(req.Voter) if err != nil { return nil, err } vote, err := q.k.Votes.Get(ctx, collections.Join(req.ProposalId, sdk.AccAddress(voter))) if err == nil { return &v1.QueryVoteResponse{Vote: &vote}, nil } if errors.IsOf(err, collections.ErrNotFound) { return nil, status.Errorf(codes.InvalidArgument, "voter: %v not found for proposal: %v", req.Voter, req.ProposalId) } return nil, status.Error(codes.Internal, err.Error()) } // Votes returns single proposal's votes func (q queryServer) Votes(ctx context.Context, req *v1.QueryVotesRequest) (*v1.QueryVotesResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "invalid request") } if req.ProposalId == 0 { return nil, status.Error(codes.InvalidArgument, "proposal id can not be 0") } votes, pageRes, err := query.CollectionPaginate(ctx, q.k.Votes, req.Pagination, func(_ collections.Pair[uint64, sdk.AccAddress], value v1.Vote) (vote *v1.Vote, err error) { return &value, nil }, query.WithCollectionPaginationPairPrefix[uint64, sdk.AccAddress](req.ProposalId)) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } return &v1.QueryVotesResponse{Votes: votes, Pagination: pageRes}, nil } // Params queries all params func (q queryServer) Params(ctx context.Context, req *v1.QueryParamsRequest) (*v1.QueryParamsResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "invalid request") } params, err := q.k.Params.Get(ctx) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } return &v1.QueryParamsResponse{Params: ¶ms}, nil } // MessageBasedParams queries params for a specific message func (q queryServer) MessageBasedParams(ctx context.Context, req *v1.QueryMessageBasedParamsRequest) (*v1.QueryMessageBasedParamsResponse, error) { if req == nil || req.MsgUrl == "" { return nil, status.Error(codes.InvalidArgument, "invalid request") } params, err := q.k.MessageBasedParams.Get(ctx, req.MsgUrl) if err == nil { return &v1.QueryMessageBasedParamsResponse{Params: ¶ms}, nil } if errors.IsOf(err, collections.ErrNotFound) { resp, err := q.Params(ctx, &v1.QueryParamsRequest{}) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } return &v1.QueryMessageBasedParamsResponse{Params: &v1.MessageBasedParams{ VotingPeriod: resp.Params.VotingPeriod, Quorum: resp.Params.Quorum, Threshold: resp.Params.Threshold, VetoThreshold: resp.Params.VetoThreshold, }}, nil } return nil, status.Error(codes.Internal, err.Error()) } // Deposit queries single deposit information based on proposalID, depositAddr. func (q queryServer) Deposit(ctx context.Context, req *v1.QueryDepositRequest) (*v1.QueryDepositResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "invalid request") } if req.ProposalId == 0 { return nil, status.Error(codes.InvalidArgument, "proposal id can not be 0") } if req.Depositor == "" { return nil, status.Error(codes.InvalidArgument, "empty depositor address") } depositor, err := q.k.authKeeper.AddressCodec().StringToBytes(req.Depositor) if err != nil { return nil, err } deposit, err := q.k.Deposits.Get(ctx, collections.Join(req.ProposalId, sdk.AccAddress(depositor))) if err != nil { return nil, status.Error(codes.NotFound, err.Error()) } return &v1.QueryDepositResponse{Deposit: &deposit}, nil } // Deposits returns single proposal's all deposits func (q queryServer) Deposits(ctx context.Context, req *v1.QueryDepositsRequest) (*v1.QueryDepositsResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "invalid request") } if req.ProposalId == 0 { return nil, status.Error(codes.InvalidArgument, "proposal id can not be 0") } var deposits []*v1.Deposit deposits, pageRes, err := query.CollectionPaginate(ctx, q.k.Deposits, req.Pagination, func(_ collections.Pair[uint64, sdk.AccAddress], deposit v1.Deposit) (*v1.Deposit, error) { return &deposit, nil }, query.WithCollectionPaginationPairPrefix[uint64, sdk.AccAddress](req.ProposalId)) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } return &v1.QueryDepositsResponse{Deposits: deposits, Pagination: pageRes}, nil } // TallyResult queries the tally of a proposal vote func (q queryServer) TallyResult(ctx context.Context, req *v1.QueryTallyResultRequest) (*v1.QueryTallyResultResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "invalid request") } if req.ProposalId == 0 { return nil, status.Error(codes.InvalidArgument, "proposal id can not be 0") } proposal, err := q.k.Proposals.Get(ctx, req.ProposalId) if err != nil { if errors.IsOf(err, collections.ErrNotFound) { return nil, status.Errorf(codes.NotFound, "proposal %d doesn't exist", req.ProposalId) } return nil, status.Error(codes.Internal, err.Error()) } var tallyResult v1.TallyResult switch { case proposal.Status == v1.StatusDepositPeriod: tallyResult = v1.EmptyTallyResult() case proposal.Status == v1.StatusPassed || proposal.Status == v1.StatusRejected || proposal.Status == v1.StatusFailed: tallyResult = *proposal.FinalTallyResult default: // proposal is in voting period var err error _, _, tallyResult, err = q.k.Tally(ctx, proposal) if err != nil { return nil, err } } return &v1.QueryTallyResultResponse{Tally: &tallyResult}, nil } var _ v1beta1.QueryServer = legacyQueryServer{} type legacyQueryServer struct{ qs v1.QueryServer } // NewLegacyQueryServer returns an implementation of the v1beta1 legacy QueryServer interface. func NewLegacyQueryServer(k *Keeper) v1beta1.QueryServer { return &legacyQueryServer{qs: NewQueryServer(k)} } func (q legacyQueryServer) Proposal(ctx context.Context, req *v1beta1.QueryProposalRequest) (*v1beta1.QueryProposalResponse, error) { resp, err := q.qs.Proposal(ctx, &v1.QueryProposalRequest{ ProposalId: req.ProposalId, }) if err != nil { return nil, err } proposal, err := v3.ConvertToLegacyProposal(*resp.Proposal) if err != nil { return nil, err } return &v1beta1.QueryProposalResponse{Proposal: proposal}, nil } func (q legacyQueryServer) Proposals(ctx context.Context, req *v1beta1.QueryProposalsRequest) (*v1beta1.QueryProposalsResponse, error) { resp, err := q.qs.Proposals(ctx, &v1.QueryProposalsRequest{ ProposalStatus: v1.ProposalStatus(req.ProposalStatus), Voter: req.Voter, Depositor: req.Depositor, Pagination: req.Pagination, }) if err != nil { return nil, err } legacyProposals := make([]v1beta1.Proposal, len(resp.Proposals)) for idx, proposal := range resp.Proposals { legacyProposals[idx], err = v3.ConvertToLegacyProposal(*proposal) if err != nil { return nil, err } } return &v1beta1.QueryProposalsResponse{ Proposals: legacyProposals, Pagination: resp.Pagination, }, nil } func (q legacyQueryServer) Vote(ctx context.Context, req *v1beta1.QueryVoteRequest) (*v1beta1.QueryVoteResponse, error) { resp, err := q.qs.Vote(ctx, &v1.QueryVoteRequest{ ProposalId: req.ProposalId, Voter: req.Voter, }) if err != nil { return nil, err } vote, err := v3.ConvertToLegacyVote(*resp.Vote) if err != nil { return nil, err } return &v1beta1.QueryVoteResponse{Vote: vote}, nil } func (q legacyQueryServer) Votes(ctx context.Context, req *v1beta1.QueryVotesRequest) (*v1beta1.QueryVotesResponse, error) { resp, err := q.qs.Votes(ctx, &v1.QueryVotesRequest{ ProposalId: req.ProposalId, Pagination: req.Pagination, }) if err != nil { return nil, err } votes := make([]v1beta1.Vote, len(resp.Votes)) for i, v := range resp.Votes { votes[i], err = v3.ConvertToLegacyVote(*v) if err != nil { return nil, err } } return &v1beta1.QueryVotesResponse{ Votes: votes, Pagination: resp.Pagination, }, nil } func (q legacyQueryServer) Params(ctx context.Context, req *v1beta1.QueryParamsRequest) (*v1beta1.QueryParamsResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "invalid request") } resp, err := q.qs.Params(ctx, &v1.QueryParamsRequest{ ParamsType: req.ParamsType, }) if err != nil { return nil, err } response := &v1beta1.QueryParamsResponse{} switch req.ParamsType { case v1beta1.ParamDeposit: minDeposit := sdk.NewCoins(resp.Params.MinDeposit...) response.DepositParams = v1beta1.NewDepositParams(minDeposit, *resp.Params.MaxDepositPeriod) case v1beta1.ParamVoting: response.VotingParams = v1beta1.NewVotingParams(*resp.Params.VotingPeriod) case v1beta1.ParamTallying: quorum, err := sdkmath.LegacyNewDecFromStr(resp.Params.Quorum) if err != nil { return nil, err } threshold, err := sdkmath.LegacyNewDecFromStr(resp.Params.Threshold) if err != nil { return nil, err } vetoThreshold, err := sdkmath.LegacyNewDecFromStr(resp.Params.VetoThreshold) if err != nil { return nil, err } response.TallyParams = v1beta1.NewTallyParams(quorum, threshold, vetoThreshold) default: return nil, status.Errorf(codes.InvalidArgument, "%s is not a valid parameter type", req.ParamsType) } return response, nil } func (q legacyQueryServer) Deposit(ctx context.Context, req *v1beta1.QueryDepositRequest) (*v1beta1.QueryDepositResponse, error) { resp, err := q.qs.Deposit(ctx, &v1.QueryDepositRequest{ ProposalId: req.ProposalId, Depositor: req.Depositor, }) if err != nil { return nil, err } deposit := v3.ConvertToLegacyDeposit(resp.Deposit) return &v1beta1.QueryDepositResponse{Deposit: deposit}, nil } func (q legacyQueryServer) Deposits(ctx context.Context, req *v1beta1.QueryDepositsRequest) (*v1beta1.QueryDepositsResponse, error) { resp, err := q.qs.Deposits(ctx, &v1.QueryDepositsRequest{ ProposalId: req.ProposalId, Pagination: req.Pagination, }) if err != nil { return nil, err } deposits := make([]v1beta1.Deposit, len(resp.Deposits)) for idx, deposit := range resp.Deposits { deposits[idx] = v3.ConvertToLegacyDeposit(deposit) } return &v1beta1.QueryDepositsResponse{Deposits: deposits, Pagination: resp.Pagination}, nil } func (q legacyQueryServer) TallyResult(ctx context.Context, req *v1beta1.QueryTallyResultRequest) (*v1beta1.QueryTallyResultResponse, error) { resp, err := q.qs.TallyResult(ctx, &v1.QueryTallyResultRequest{ ProposalId: req.ProposalId, }) if err != nil { return nil, err } tally, err := v3.ConvertToLegacyTallyResult(resp.Tally) if err != nil { return nil, err } return &v1beta1.QueryTallyResultResponse{Tally: tally}, nil }