Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add tally result querier #266

Merged
merged 4 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/docs/statik/statik.go

Large diffs are not rendered by default.

591 changes: 511 additions & 80 deletions client/docs/swagger-ui/swagger.yaml

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions proto/ibc/applications/perm/v1/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ option go_package = "github.com/initia-labs/initia/x/ibc/perm/types";

// GenesisState defines the ibc perm genesis state
message GenesisState {
repeated ChannelState channel_states = 1 [
(gogoproto.moretags) = "yaml:\"channel_states\"",
(gogoproto.nullable) = false
];
repeated ChannelState channel_states = 1
[(gogoproto.moretags) = "yaml:\"channel_states\"", (gogoproto.nullable) = false];
}
2 changes: 1 addition & 1 deletion proto/ibc/applications/perm/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ message QueryChannelStatesResponse {
// QueryChannelStateRequest is the request type for the Query/ChannelState RPC method.
message QueryChannelStateRequest {
string channel_id = 1;
string port_id = 2;
string port_id = 2;
}

// QueryChannelStateResponse is the response type for the Query/ChannelState RPC method.
Expand Down
33 changes: 12 additions & 21 deletions proto/ibc/applications/perm/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,16 @@ service Msg {
// the specific ibc channel.
message MsgSetPermissionedRelayers {
option (cosmos.msg.v1.signer) = "authority";
option (amino.name) = "perm/MsgSetPermissionedRelayers";
option (amino.name) = "perm/MsgSetPermissionedRelayers";

option (gogoproto.equal) = false;
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

// authority is the address that controls the module
// (defaults to x/gov unless overwritten).
string authority = 1 [
(gogoproto.moretags) = "yaml:\"authority\"",
(cosmos_proto.scalar) = "cosmos.AddressString"
];
string authority = 1 [(gogoproto.moretags) = "yaml:\"authority\"", (cosmos_proto.scalar) = "cosmos.AddressString"];
string channel_id = 2 [(gogoproto.moretags) = "yaml:\"channel_id\""];
string port_id = 3 [(gogoproto.moretags) = "yaml:\"port_id\""];
string port_id = 3 [(gogoproto.moretags) = "yaml:\"port_id\""];
repeated string relayers = 4 [(gogoproto.moretags) = "yaml:\"relayers\""];
}

Expand All @@ -48,19 +45,16 @@ message MsgSetPermissionedRelayersResponse {}
// MsgHaltChannel defines msg to halt the specific ibc channel.
message MsgHaltChannel {
option (cosmos.msg.v1.signer) = "authority";
option (amino.name) = "perm/MsgHaltChannel";
option (amino.name) = "perm/MsgHaltChannel";

option (gogoproto.equal) = false;
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

// authority is the address that controls the module
// (defaults to x/gov unless overwritten).
string authority = 1 [
(gogoproto.moretags) = "yaml:\"authority\"",
(cosmos_proto.scalar) = "cosmos.AddressString"
];
string authority = 1 [(gogoproto.moretags) = "yaml:\"authority\"", (cosmos_proto.scalar) = "cosmos.AddressString"];
string channel_id = 2 [(gogoproto.moretags) = "yaml:\"channel_id\""];
string port_id = 3 [(gogoproto.moretags) = "yaml:\"port_id\""];
string port_id = 3 [(gogoproto.moretags) = "yaml:\"port_id\""];
}

// MsgHaltChannelResponse defines the Msg/HaltChannel response type.
Expand All @@ -69,19 +63,16 @@ message MsgHaltChannelResponse {}
// MsgResumeChannel defines msg to resume the specific ibc channel.
message MsgResumeChannel {
option (cosmos.msg.v1.signer) = "authority";
option (amino.name) = "perm/MsgResumeChannel";
option (amino.name) = "perm/MsgResumeChannel";

option (gogoproto.equal) = false;
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

// authority is the address that controls the module
// (defaults to x/gov unless overwritten).
string authority = 1 [
(gogoproto.moretags) = "yaml:\"authority\"",
(cosmos_proto.scalar) = "cosmos.AddressString"
];
string authority = 1 [(gogoproto.moretags) = "yaml:\"authority\"", (cosmos_proto.scalar) = "cosmos.AddressString"];
string channel_id = 2 [(gogoproto.moretags) = "yaml:\"channel_id\""];
string port_id = 3 [(gogoproto.moretags) = "yaml:\"port_id\""];
string port_id = 3 [(gogoproto.moretags) = "yaml:\"port_id\""];
}

// MsgResumeChannelResponse defines the Msg/ResumeChannel response type.
Expand Down
10 changes: 5 additions & 5 deletions proto/ibc/applications/perm/v1/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ option go_package = "github.com/initia-labs/initia/x/ibc/perm/types";

// ChannelState defines the channel state for the specific port-id:channel-id pair.
message ChannelState {
string port_id = 1;
string channel_id = 2;
HaltState halt_state = 3 [(gogoproto.nullable) = false];
repeated string relayers = 4;
string port_id = 1;
string channel_id = 2;
HaltState halt_state = 3 [(gogoproto.nullable) = false];
repeated string relayers = 4;
}

// HaltState defines the halt state for the specific port-id:channel-id pair.
message HaltState {
bool halted = 1;
bool halted = 1;
string halted_by = 2;
}
22 changes: 16 additions & 6 deletions proto/initia/gov/v1/gov.proto
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
syntax = "proto3";
package initia.gov.v1;

import "amino/amino.proto";
beer-1 marked this conversation as resolved.
Show resolved Hide resolved
import "cosmos/base/v1beta1/coin.proto";
import "cosmos/gov/v1/gov.proto";
import "cosmos_proto/cosmos.proto";
import "gogoproto/gogo.proto";
import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "cosmos_proto/cosmos.proto";
import "cosmos/gov/v1/gov.proto";
import "amino/amino.proto";
import "google/protobuf/timestamp.proto";

option go_package = "github.com/initia-labs/initia/x/gov/types";
// option (gogoproto.equal_all) = true;
Expand Down Expand Up @@ -120,6 +120,16 @@ message Vesting {
string creator_addr = 3;
}

message TallyResult {
uint64 tally_height = 1;
string total_staking_power = 2 [(cosmos_proto.scalar) = "cosmos.Int"];
string total_vesting_power = 3 [(cosmos_proto.scalar) = "cosmos.Int"];

// v1_tally_result is the original TallyResult from cosmos-sdk,
// which contains both staking and vesting power.
cosmos.gov.v1.TallyResult v1_tally_result = 4;
}

// Proposal defines the core field members of a governance proposal.
message Proposal {
// id defines the unique id of the proposal.
Expand All @@ -134,7 +144,7 @@ message Proposal {
// final_tally_result is the final tally result of the proposal. When
// querying a proposal via gRPC, this field is not populated until the
// proposal's voting period has ended.
cosmos.gov.v1.TallyResult final_tally_result = 4;
TallyResult final_tally_result = 4 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];

// submit_time is the time of proposal submission.
google.protobuf.Timestamp submit_time = 5 [(gogoproto.stdtime) = true];
Expand Down Expand Up @@ -185,4 +195,4 @@ message Proposal {
//
// Since: cosmos-sdk 0.50
string failed_reason = 18;
}
}
26 changes: 22 additions & 4 deletions proto/initia/gov/v1/query.proto
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
syntax = "proto3";
package initia.gov.v1;

import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "cosmos_proto/cosmos.proto";
import "amino/amino.proto";
import "cosmos/base/query/v1beta1/pagination.proto";
import "cosmos/gov/v1/gov.proto";
import "cosmos_proto/cosmos.proto";
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
beer-1 marked this conversation as resolved.
Show resolved Hide resolved
import "initia/gov/v1/gov.proto";

option go_package = "github.com/initia-labs/initia/x/gov/types";
Expand All @@ -31,6 +32,11 @@ service Query {
rpc Proposals(QueryProposalsRequest) returns (QueryProposalsResponse) {
option (google.api.http).get = "/initia/gov/v1/proposals";
}

// TallyResult queries the tally of a proposal vote.
rpc TallyResult(QueryTallyResultRequest) returns (QueryTallyResultResponse) {
option (google.api.http).get = "/initia/gov/v1/proposals/{proposal_id}/tally";
}
}

// QueryParamsRequest is the request type for the Query/Params RPC method.
Expand Down Expand Up @@ -96,4 +102,16 @@ message QueryProposalsResponse {

// pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}
}

// QueryTallyResultRequest is the request type for the Query/Tally RPC method.
message QueryTallyResultRequest {
// proposal_id defines the unique id of the proposal.
uint64 proposal_id = 1;
}

// QueryTallyResultResponse is the response type for the Query/Tally RPC method.
message QueryTallyResultResponse {
// tally defines the requested tally.
TallyResult tally_result = 1 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];
}
4 changes: 2 additions & 2 deletions x/gov/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ func handleTallyResult(
k *keeper.Keeper,
proposal customtypes.Proposal,
passed, burnDeposits bool,
tallyResults v1.TallyResult,
tallyResults customtypes.TallyResult,
) (err error) {
// If an expedited proposal fails, we do not want to update
// the deposit at this point since the proposal is converted to regular.
Expand Down Expand Up @@ -333,7 +333,7 @@ func handleTallyResult(
logMsg = "rejected"
}

proposal.FinalTallyResult = &tallyResults
proposal.FinalTallyResult = tallyResults

err = k.SetProposal(ctx, proposal)
if err != nil {
Expand Down
43 changes: 43 additions & 0 deletions x/gov/keeper/custom_grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,46 @@

return &customtypes.QueryProposalsResponse{Proposals: filteredProposals, Pagination: pageRes}, nil
}

// TallyResult queries the tally of a proposal vote
func (q CustomQueryServer) TallyResult(ctx context.Context, req *customtypes.QueryTallyResultRequest) (*customtypes.QueryTallyResultResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}

Check warning on line 125 in x/gov/keeper/custom_grpc_query.go

View check run for this annotation

Codecov / codecov/patch

x/gov/keeper/custom_grpc_query.go#L124-L125

Added lines #L124 - L125 were not covered by tests

if req.ProposalId == 0 {
return nil, status.Error(codes.InvalidArgument, "proposal id can not be 0")
}

Check warning on line 129 in x/gov/keeper/custom_grpc_query.go

View check run for this annotation

Codecov / codecov/patch

x/gov/keeper/custom_grpc_query.go#L128-L129

Added lines #L128 - L129 were not covered by tests

proposal, err := q.Keeper.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())

Check warning on line 136 in x/gov/keeper/custom_grpc_query.go

View check run for this annotation

Codecov / codecov/patch

x/gov/keeper/custom_grpc_query.go#L133-L136

Added lines #L133 - L136 were not covered by tests
}

var tallyResult customtypes.TallyResult

switch {
case proposal.Status == v1.StatusDepositPeriod:
tallyResult = customtypes.EmptyTallyResult()

Check warning on line 143 in x/gov/keeper/custom_grpc_query.go

View check run for this annotation

Codecov / codecov/patch

x/gov/keeper/custom_grpc_query.go#L142-L143

Added lines #L142 - L143 were not covered by tests

case proposal.Status == v1.StatusPassed || proposal.Status == v1.StatusRejected:
tallyResult = proposal.FinalTallyResult

Check warning on line 146 in x/gov/keeper/custom_grpc_query.go

View check run for this annotation

Codecov / codecov/patch

x/gov/keeper/custom_grpc_query.go#L145-L146

Added lines #L145 - L146 were not covered by tests

default:
// proposal is in voting period
params, err := q.Keeper.Params.Get(ctx)
if err != nil {
return nil, err
}

Check warning on line 153 in x/gov/keeper/custom_grpc_query.go

View check run for this annotation

Codecov / codecov/patch

x/gov/keeper/custom_grpc_query.go#L152-L153

Added lines #L152 - L153 were not covered by tests

_, _, _, tallyResult, err = q.Keeper.Tally(ctx, params, proposal)
if err != nil {
return nil, err
}

Check warning on line 158 in x/gov/keeper/custom_grpc_query.go

View check run for this annotation

Codecov / codecov/patch

x/gov/keeper/custom_grpc_query.go#L157-L158

Added lines #L157 - L158 were not covered by tests
}
Comment on lines +141 to +159
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explicitly handle all proposal statuses to improve code clarity

In the switch statement starting at line 141~, consider explicitly handling all possible proposal statuses. Currently, the code uses a default case to handle proposals in the voting period and any other statuses. Explicitly listing all statuses can enhance readability and prevent unexpected behaviors if new statuses are introduced in the future.

Apply this change to explicitly handle the StatusVotingPeriod and return an error for any unanticipated statuses:

 switch {
 case proposal.Status == v1.StatusDepositPeriod:
   tallyResult = customtypes.EmptyTallyResult()

+case proposal.Status == v1.StatusVotingPeriod:
   // proposal is in voting period
   params, err := q.Keeper.Params.Get(ctx)
   if err != nil {
     return nil, err
   }

   _, _, _, tallyResult, err = q.Keeper.Tally(ctx, params, proposal)
   if err != nil {
     return nil, err
   }
+default:
+  return nil, status.Errorf(codes.InvalidArgument, "invalid proposal status: %s", proposal.Status.String())
 }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
switch {
case proposal.Status == v1.StatusDepositPeriod:
tallyResult = customtypes.EmptyTallyResult()
case proposal.Status == v1.StatusPassed || proposal.Status == v1.StatusRejected:
tallyResult = proposal.FinalTallyResult
default:
// proposal is in voting period
params, err := q.Keeper.Params.Get(ctx)
if err != nil {
return nil, err
}
_, _, _, tallyResult, err = q.Keeper.Tally(ctx, params, proposal)
if err != nil {
return nil, err
}
}
switch {
case proposal.Status == v1.StatusDepositPeriod:
tallyResult = customtypes.EmptyTallyResult()
case proposal.Status == v1.StatusPassed || proposal.Status == v1.StatusRejected:
tallyResult = proposal.FinalTallyResult
case proposal.Status == v1.StatusVotingPeriod:
// proposal is in voting period
params, err := q.Keeper.Params.Get(ctx)
if err != nil {
return nil, err
}
_, _, _, tallyResult, err = q.Keeper.Tally(ctx, params, proposal)
if err != nil {
return nil, err
}
default:
return nil, status.Errorf(codes.InvalidArgument, "invalid proposal status: %s", proposal.Status.String())
}


return &customtypes.QueryTallyResultResponse{TallyResult: tallyResult}, nil
}
89 changes: 89 additions & 0 deletions x/gov/keeper/custom_grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ package keeper_test

import (
"testing"
"time"

"cosmossdk.io/math"
"github.com/stretchr/testify/require"

sdk "github.com/cosmos/cosmos-sdk/types"
v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"

"github.com/initia-labs/initia/x/gov/keeper"
"github.com/initia-labs/initia/x/gov/types"
)
Expand Down Expand Up @@ -115,3 +120,87 @@ func Test_CustomGrpcQuerier_Proposal(t *testing.T) {
require.Equal(t, res.Proposal.Id, uint64(5))
require.False(t, res.Proposal.Emergency)
}

func Test_CustomGrpcQuerier_TallyResult(t *testing.T) {
ctx, input := createDefaultTestInput(t)

setupVesting(t, ctx, input)

proposal, err := input.GovKeeper.SubmitProposal(ctx, nil, "", "test", "description", addrs[0], false)
require.NoError(t, err)

proposalID := proposal.Id
proposal.Status = v1.StatusVotingPeriod
err = input.GovKeeper.SetProposal(ctx, proposal)
require.NoError(t, err)

proposal, err = input.GovKeeper.Proposals.Get(ctx, proposalID)
require.NoError(t, err)

params, err := input.GovKeeper.Params.Get(ctx)
require.NoError(t, err)

quorumReached, passed, _, _, err := input.GovKeeper.Tally(ctx, params, proposal)
require.NoError(t, err)
require.False(t, quorumReached)
require.False(t, passed)

valAddr1 := createValidatorWithCoin(ctx, input,
sdk.NewCoins(sdk.NewInt64Coin(bondDenom, 100_000_000)),
sdk.NewCoins(sdk.NewInt64Coin(bondDenom, 100_000_000)),
1,
)
valAddr2 := createValidatorWithCoin(ctx, input,
sdk.NewCoins(sdk.NewInt64Coin(bondDenom, 100_000_000)),
sdk.NewCoins(sdk.NewInt64Coin(bondDenom, 100_000_000)),
2,
)
beer-1 marked this conversation as resolved.
Show resolved Hide resolved

voterAddr1 := sdk.AccAddress(valAddr1)
voterAddr2 := sdk.AccAddress(valAddr2)

// vote yes
err = input.GovKeeper.AddVote(ctx, proposalID, voterAddr1, v1.WeightedVoteOptions{
{
Option: v1.OptionYes,
Weight: "1",
},
}, "")
require.NoError(t, err)

// vote no
err = input.GovKeeper.AddVote(ctx, proposalID, voterAddr2, v1.WeightedVoteOptions{
{
Option: v1.OptionNo,
Weight: "1",
},
}, "")
require.NoError(t, err)

// add vesting vote
vestingVoter := addrs[1]
err = input.GovKeeper.AddVote(ctx, proposalID, vestingVoter, v1.WeightedVoteOptions{
{
Option: v1.OptionYes,
Weight: "1",
},
}, "")
require.NoError(t, err)

// 15 minutes passed
ctx = ctx.WithBlockTime(time.Now().UTC().Add(time.Minute * 15))
cacheCtx, _ := ctx.CacheContext()

quorumReached, passed, burnDeposits, tallyResults, err := input.GovKeeper.Tally(cacheCtx, params, proposal)
require.NoError(t, err)
require.True(t, quorumReached)
require.True(t, passed)
require.False(t, burnDeposits)
require.Equal(t, tallyResults.V1TallyResult.YesCount, math.LegacyNewDec(1_500_000+100_000_000).TruncateInt().String())
require.Equal(t, tallyResults.V1TallyResult.NoCount, math.LegacyNewDec(100_000_000).TruncateInt().String())

qs := keeper.NewCustomQueryServer(&input.GovKeeper)
res, err := qs.TallyResult(ctx, &types.QueryTallyResultRequest{ProposalId: proposalID})
require.NoError(t, err)
require.Equal(t, tallyResults, res.TallyResult)
}
Loading
Loading