github.com/cosmos/cosmos-sdk@v0.50.10/x/group/types.go (about)

     1  package group
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	proto "github.com/cosmos/gogoproto/proto"
     8  
     9  	errorsmod "cosmossdk.io/errors"
    10  
    11  	codectypes "github.com/cosmos/cosmos-sdk/codec/types"
    12  	sdk "github.com/cosmos/cosmos-sdk/types"
    13  	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
    14  	"github.com/cosmos/cosmos-sdk/x/group/errors"
    15  	"github.com/cosmos/cosmos-sdk/x/group/internal/math"
    16  	"github.com/cosmos/cosmos-sdk/x/group/internal/orm"
    17  )
    18  
    19  // DecisionPolicyResult is the result of whether a proposal passes or not a
    20  // decision policy.
    21  type DecisionPolicyResult struct {
    22  	// Allow determines if the proposal is allowed to pass.
    23  	Allow bool
    24  	// Final determines if the tally result is final or not. If final, then
    25  	// votes are pruned, and the tally result is saved in the proposal's
    26  	// `FinalTallyResult` field.
    27  	Final bool
    28  }
    29  
    30  // DecisionPolicy is the persistent set of rules to determine the result of election on a proposal.
    31  type DecisionPolicy interface {
    32  	proto.Message
    33  
    34  	// GetVotingPeriod returns the duration after proposal submission where
    35  	// votes are accepted.
    36  	GetVotingPeriod() time.Duration
    37  	// GetMinExecutionPeriod returns the minimum duration after submission
    38  	// where we can execution a proposal. It can be set to 0 or to a value
    39  	// lesser than VotingPeriod to allow TRY_EXEC.
    40  	GetMinExecutionPeriod() time.Duration
    41  	// Allow defines policy-specific logic to allow a proposal to pass or not,
    42  	// based on its tally result, the group's total power and the time since
    43  	// the proposal was submitted.
    44  	Allow(tallyResult TallyResult, totalPower string) (DecisionPolicyResult, error)
    45  
    46  	ValidateBasic() error
    47  	Validate(g GroupInfo, config Config) error
    48  }
    49  
    50  // Implements DecisionPolicy Interface
    51  var _ DecisionPolicy = &ThresholdDecisionPolicy{}
    52  
    53  // NewThresholdDecisionPolicy creates a threshold DecisionPolicy
    54  func NewThresholdDecisionPolicy(threshold string, votingPeriod, minExecutionPeriod time.Duration) DecisionPolicy {
    55  	return &ThresholdDecisionPolicy{threshold, &DecisionPolicyWindows{votingPeriod, minExecutionPeriod}}
    56  }
    57  
    58  // GetVotingPeriod returns the voitng period of ThresholdDecisionPolicy
    59  func (p ThresholdDecisionPolicy) GetVotingPeriod() time.Duration {
    60  	return p.Windows.VotingPeriod
    61  }
    62  
    63  // GetMinExecutionPeriod returns the minimum execution period of ThresholdDecisionPolicy
    64  func (p ThresholdDecisionPolicy) GetMinExecutionPeriod() time.Duration {
    65  	return p.Windows.MinExecutionPeriod
    66  }
    67  
    68  // ValidateBasic does basic validation on ThresholdDecisionPolicy
    69  func (p ThresholdDecisionPolicy) ValidateBasic() error {
    70  	if _, err := math.NewPositiveDecFromString(p.Threshold); err != nil {
    71  		return errorsmod.Wrap(err, "threshold")
    72  	}
    73  
    74  	if p.Windows == nil || p.Windows.VotingPeriod == 0 {
    75  		return errorsmod.Wrap(errors.ErrInvalid, "voting period cannot be zero")
    76  	}
    77  
    78  	return nil
    79  }
    80  
    81  // Allow allows a proposal to pass when the tally of yes votes equals or exceeds the threshold before the timeout.
    82  func (p ThresholdDecisionPolicy) Allow(tallyResult TallyResult, totalPower string) (DecisionPolicyResult, error) {
    83  	threshold, err := math.NewPositiveDecFromString(p.Threshold)
    84  	if err != nil {
    85  		return DecisionPolicyResult{}, errorsmod.Wrap(err, "threshold")
    86  	}
    87  	yesCount, err := math.NewNonNegativeDecFromString(tallyResult.YesCount)
    88  	if err != nil {
    89  		return DecisionPolicyResult{}, errorsmod.Wrap(err, "yes count")
    90  	}
    91  
    92  	totalPowerDec, err := math.NewNonNegativeDecFromString(totalPower)
    93  	if err != nil {
    94  		return DecisionPolicyResult{}, errorsmod.Wrap(err, "total power")
    95  	}
    96  
    97  	// the real threshold of the policy is `min(threshold,total_weight)`. If
    98  	// the group member weights changes (member leaving, member weight update)
    99  	// and the threshold doesn't, we can end up with threshold > total_weight.
   100  	// In this case, as long as everyone votes yes (in which case
   101  	// `yesCount`==`realThreshold`), then the proposal still passes.
   102  	realThreshold := min(threshold, totalPowerDec)
   103  
   104  	if yesCount.Cmp(realThreshold) >= 0 {
   105  		return DecisionPolicyResult{Allow: true, Final: true}, nil
   106  	}
   107  
   108  	totalCounts, err := tallyResult.TotalCounts()
   109  	if err != nil {
   110  		return DecisionPolicyResult{}, err
   111  	}
   112  	undecided, err := math.SubNonNegative(totalPowerDec, totalCounts)
   113  	if err != nil {
   114  		return DecisionPolicyResult{}, err
   115  	}
   116  	// maxYesCount is the max potential number of yes count, i.e the current yes count
   117  	// plus all undecided count (supposing they all vote yes).
   118  	maxYesCount, err := yesCount.Add(undecided)
   119  	if err != nil {
   120  		return DecisionPolicyResult{}, err
   121  	}
   122  
   123  	if maxYesCount.Cmp(realThreshold) < 0 {
   124  		return DecisionPolicyResult{Allow: false, Final: true}, nil
   125  	}
   126  	return DecisionPolicyResult{Allow: false, Final: false}, nil
   127  }
   128  
   129  func min(a, b math.Dec) math.Dec {
   130  	if a.Cmp(b) < 0 {
   131  		return a
   132  	}
   133  	return b
   134  }
   135  
   136  // Validate validates the policy against the group. Note that the threshold
   137  // can actually be greater than the group's total weight: in the Allow method
   138  // we check the tally weight against `min(threshold,total_weight)`.
   139  func (p *ThresholdDecisionPolicy) Validate(g GroupInfo, config Config) error {
   140  	_, err := math.NewPositiveDecFromString(p.Threshold)
   141  	if err != nil {
   142  		return errorsmod.Wrap(err, "threshold")
   143  	}
   144  	_, err = math.NewNonNegativeDecFromString(g.TotalWeight)
   145  	if err != nil {
   146  		return errorsmod.Wrap(err, "group total weight")
   147  	}
   148  
   149  	if p.Windows.MinExecutionPeriod > p.Windows.VotingPeriod+config.MaxExecutionPeriod {
   150  		return errorsmod.Wrap(errors.ErrInvalid, "min_execution_period should be smaller than voting_period + max_execution_period")
   151  	}
   152  	return nil
   153  }
   154  
   155  // Implements DecisionPolicy Interface
   156  var _ DecisionPolicy = &PercentageDecisionPolicy{}
   157  
   158  // NewPercentageDecisionPolicy creates a new percentage DecisionPolicy
   159  func NewPercentageDecisionPolicy(percentage string, votingPeriod, executionPeriod time.Duration) DecisionPolicy {
   160  	return &PercentageDecisionPolicy{percentage, &DecisionPolicyWindows{votingPeriod, executionPeriod}}
   161  }
   162  
   163  // GetVotingPeriod returns the voitng period of PercentageDecisionPolicy
   164  func (p PercentageDecisionPolicy) GetVotingPeriod() time.Duration {
   165  	return p.Windows.VotingPeriod
   166  }
   167  
   168  // GetMinExecutionPeriod returns the minimum execution period of PercentageDecisionPolicy
   169  func (p PercentageDecisionPolicy) GetMinExecutionPeriod() time.Duration {
   170  	return p.Windows.MinExecutionPeriod
   171  }
   172  
   173  // ValidateBasic does basic validation on PercentageDecisionPolicy
   174  func (p PercentageDecisionPolicy) ValidateBasic() error {
   175  	percentage, err := math.NewPositiveDecFromString(p.Percentage)
   176  	if err != nil {
   177  		return errorsmod.Wrap(err, "percentage threshold")
   178  	}
   179  	if percentage.Cmp(math.NewDecFromInt64(1)) == 1 {
   180  		return errorsmod.Wrap(errors.ErrInvalid, "percentage must be > 0 and <= 1")
   181  	}
   182  
   183  	if p.Windows == nil || p.Windows.VotingPeriod == 0 {
   184  		return errorsmod.Wrap(errors.ErrInvalid, "voting period cannot be 0")
   185  	}
   186  
   187  	return nil
   188  }
   189  
   190  // Validate validates the policy against the group.
   191  func (p *PercentageDecisionPolicy) Validate(g GroupInfo, config Config) error {
   192  	if p.Windows.MinExecutionPeriod > p.Windows.VotingPeriod+config.MaxExecutionPeriod {
   193  		return errorsmod.Wrap(errors.ErrInvalid, "min_execution_period should be smaller than voting_period + max_execution_period")
   194  	}
   195  	return nil
   196  }
   197  
   198  // Allow allows a proposal to pass when the tally of yes votes equals or exceeds the percentage threshold before the timeout.
   199  func (p PercentageDecisionPolicy) Allow(tally TallyResult, totalPower string) (DecisionPolicyResult, error) {
   200  	percentage, err := math.NewPositiveDecFromString(p.Percentage)
   201  	if err != nil {
   202  		return DecisionPolicyResult{}, errorsmod.Wrap(err, "percentage")
   203  	}
   204  	yesCount, err := math.NewNonNegativeDecFromString(tally.YesCount)
   205  	if err != nil {
   206  		return DecisionPolicyResult{}, errorsmod.Wrap(err, "yes count")
   207  	}
   208  	totalPowerDec, err := math.NewNonNegativeDecFromString(totalPower)
   209  	if err != nil {
   210  		return DecisionPolicyResult{}, errorsmod.Wrap(err, "total power")
   211  	}
   212  
   213  	yesPercentage, err := yesCount.Quo(totalPowerDec)
   214  	if err != nil {
   215  		return DecisionPolicyResult{}, err
   216  	}
   217  
   218  	if yesPercentage.Cmp(percentage) >= 0 {
   219  		return DecisionPolicyResult{Allow: true, Final: true}, nil
   220  	}
   221  
   222  	totalCounts, err := tally.TotalCounts()
   223  	if err != nil {
   224  		return DecisionPolicyResult{}, err
   225  	}
   226  	undecided, err := math.SubNonNegative(totalPowerDec, totalCounts)
   227  	if err != nil {
   228  		return DecisionPolicyResult{}, err
   229  	}
   230  	sum, err := yesCount.Add(undecided)
   231  	if err != nil {
   232  		return DecisionPolicyResult{}, err
   233  	}
   234  	sumPercentage, err := sum.Quo(totalPowerDec)
   235  	if err != nil {
   236  		return DecisionPolicyResult{}, err
   237  	}
   238  	if sumPercentage.Cmp(percentage) < 0 {
   239  		return DecisionPolicyResult{Allow: false, Final: true}, nil
   240  	}
   241  	return DecisionPolicyResult{Allow: false, Final: false}, nil
   242  }
   243  
   244  var _ orm.Validateable = GroupPolicyInfo{}
   245  
   246  // NewGroupPolicyInfo creates a new GroupPolicyInfo instance
   247  func NewGroupPolicyInfo(address sdk.AccAddress, group uint64, admin sdk.AccAddress, metadata string,
   248  	version uint64, decisionPolicy DecisionPolicy, createdAt time.Time,
   249  ) (GroupPolicyInfo, error) {
   250  	p := GroupPolicyInfo{
   251  		Address:   address.String(),
   252  		GroupId:   group,
   253  		Admin:     admin.String(),
   254  		Metadata:  metadata,
   255  		Version:   version,
   256  		CreatedAt: createdAt,
   257  	}
   258  
   259  	err := p.SetDecisionPolicy(decisionPolicy)
   260  	if err != nil {
   261  		return GroupPolicyInfo{}, err
   262  	}
   263  
   264  	return p, nil
   265  }
   266  
   267  // SetDecisionPolicy sets the decision policy for GroupPolicyInfo.
   268  func (g *GroupPolicyInfo) SetDecisionPolicy(decisionPolicy DecisionPolicy) error {
   269  	any, err := codectypes.NewAnyWithValue(decisionPolicy)
   270  	if err != nil {
   271  		return err
   272  	}
   273  	g.DecisionPolicy = any
   274  	return nil
   275  }
   276  
   277  // GetDecisionPolicy gets the decision policy of GroupPolicyInfo
   278  func (g GroupPolicyInfo) GetDecisionPolicy() (DecisionPolicy, error) {
   279  	decisionPolicy, ok := g.DecisionPolicy.GetCachedValue().(DecisionPolicy)
   280  	if !ok {
   281  		return nil, sdkerrors.ErrInvalidType.Wrapf("expected %T, got %T", (DecisionPolicy)(nil), g.DecisionPolicy.GetCachedValue())
   282  	}
   283  
   284  	return decisionPolicy, nil
   285  }
   286  
   287  // UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces
   288  func (g GroupPolicyInfo) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
   289  	var decisionPolicy DecisionPolicy
   290  	return unpacker.UnpackAny(g.DecisionPolicy, &decisionPolicy)
   291  }
   292  
   293  func (g GroupInfo) PrimaryKeyFields() []interface{} {
   294  	return []interface{}{g.Id}
   295  }
   296  
   297  // ValidateBasic does basic validation on group info.
   298  func (g GroupInfo) ValidateBasic() error {
   299  	if g.Id == 0 {
   300  		return errorsmod.Wrap(errors.ErrEmpty, "group's GroupId")
   301  	}
   302  
   303  	_, err := sdk.AccAddressFromBech32(g.Admin)
   304  	if err != nil {
   305  		return errorsmod.Wrap(err, "admin")
   306  	}
   307  
   308  	if _, err := math.NewNonNegativeDecFromString(g.TotalWeight); err != nil {
   309  		return errorsmod.Wrap(err, "total weight")
   310  	}
   311  	if g.Version == 0 {
   312  		return errorsmod.Wrap(errors.ErrEmpty, "version")
   313  	}
   314  	return nil
   315  }
   316  
   317  func (g GroupPolicyInfo) PrimaryKeyFields() []interface{} {
   318  	addr := sdk.MustAccAddressFromBech32(g.Address)
   319  
   320  	return []interface{}{addr.Bytes()}
   321  }
   322  
   323  func (g Proposal) PrimaryKeyFields() []interface{} {
   324  	return []interface{}{g.Id}
   325  }
   326  
   327  // ValidateBasic does basic validation on group policy info.
   328  func (g GroupPolicyInfo) ValidateBasic() error {
   329  	_, err := sdk.AccAddressFromBech32(g.Admin)
   330  	if err != nil {
   331  		return errorsmod.Wrap(err, "group policy admin")
   332  	}
   333  	_, err = sdk.AccAddressFromBech32(g.Address)
   334  	if err != nil {
   335  		return errorsmod.Wrap(err, "group policy account address")
   336  	}
   337  
   338  	if g.GroupId == 0 {
   339  		return errorsmod.Wrap(errors.ErrEmpty, "group policy's group id")
   340  	}
   341  	if g.Version == 0 {
   342  		return errorsmod.Wrap(errors.ErrEmpty, "group policy version")
   343  	}
   344  	policy, err := g.GetDecisionPolicy()
   345  	if err != nil {
   346  		return errorsmod.Wrap(err, "group policy decision policy")
   347  	}
   348  
   349  	if err := policy.ValidateBasic(); err != nil {
   350  		return errorsmod.Wrap(err, "group policy's decision policy")
   351  	}
   352  	return nil
   353  }
   354  
   355  func (g GroupMember) PrimaryKeyFields() []interface{} {
   356  	addr := sdk.MustAccAddressFromBech32(g.Member.Address)
   357  
   358  	return []interface{}{g.GroupId, addr.Bytes()}
   359  }
   360  
   361  // ValidateBasic does basic validation on group member.
   362  func (g GroupMember) ValidateBasic() error {
   363  	if g.GroupId == 0 {
   364  		return errorsmod.Wrap(errors.ErrEmpty, "group member's group id")
   365  	}
   366  
   367  	if _, err := sdk.AccAddressFromBech32(g.Member.Address); err != nil {
   368  		return errorsmod.Wrap(err, "group member's address")
   369  	}
   370  
   371  	if _, err := math.NewNonNegativeDecFromString(g.Member.Weight); err != nil {
   372  		return errorsmod.Wrap(err, "weight must be non negative")
   373  	}
   374  
   375  	return nil
   376  }
   377  
   378  // MemberToMemberRequest converts a `Member` (used for storage)
   379  // to a `MemberRequest` (used in requests). The only difference
   380  // between the two is that `MemberRequest` doesn't have any `AddedAt` field
   381  // since it cannot be set as part of requests.
   382  func MemberToMemberRequest(m *Member) MemberRequest {
   383  	return MemberRequest{
   384  		Address:  m.Address,
   385  		Weight:   m.Weight,
   386  		Metadata: m.Metadata,
   387  	}
   388  }
   389  
   390  // ValidateBasic does basic validation on proposal.
   391  func (g Proposal) ValidateBasic() error {
   392  	if g.Id == 0 {
   393  		return errorsmod.Wrap(errors.ErrEmpty, "proposal id")
   394  	}
   395  	_, err := sdk.AccAddressFromBech32(g.GroupPolicyAddress)
   396  	if err != nil {
   397  		return errorsmod.Wrap(err, "proposal group policy address")
   398  	}
   399  	if g.GroupVersion == 0 {
   400  		return errorsmod.Wrap(errors.ErrEmpty, "proposal group version")
   401  	}
   402  	if g.GroupPolicyVersion == 0 {
   403  		return errorsmod.Wrap(errors.ErrEmpty, "proposal group policy version")
   404  	}
   405  	_, err = g.FinalTallyResult.GetYesCount()
   406  	if err != nil {
   407  		return errorsmod.Wrap(err, "proposal FinalTallyResult yes count")
   408  	}
   409  	_, err = g.FinalTallyResult.GetNoCount()
   410  	if err != nil {
   411  		return errorsmod.Wrap(err, "proposal FinalTallyResult no count")
   412  	}
   413  	_, err = g.FinalTallyResult.GetAbstainCount()
   414  	if err != nil {
   415  		return errorsmod.Wrap(err, "proposal FinalTallyResult abstain count")
   416  	}
   417  	_, err = g.FinalTallyResult.GetNoWithVetoCount()
   418  	if err != nil {
   419  		return errorsmod.Wrap(err, "proposal FinalTallyResult veto count")
   420  	}
   421  	return nil
   422  }
   423  
   424  func (v Vote) PrimaryKeyFields() []interface{} {
   425  	addr := sdk.MustAccAddressFromBech32(v.Voter)
   426  
   427  	return []interface{}{v.ProposalId, addr.Bytes()}
   428  }
   429  
   430  var _ orm.Validateable = Vote{}
   431  
   432  // ValidateBasic does basic validation on vote.
   433  func (v Vote) ValidateBasic() error {
   434  	_, err := sdk.AccAddressFromBech32(v.Voter)
   435  	if err != nil {
   436  		return errorsmod.Wrap(err, "voter")
   437  	}
   438  	if v.ProposalId == 0 {
   439  		return errorsmod.Wrap(errors.ErrEmpty, "voter ProposalId")
   440  	}
   441  	if v.Option == VOTE_OPTION_UNSPECIFIED {
   442  		return errorsmod.Wrap(errors.ErrEmpty, "voter vote option")
   443  	}
   444  	if _, ok := VoteOption_name[int32(v.Option)]; !ok {
   445  		return errorsmod.Wrap(errors.ErrInvalid, "vote option")
   446  	}
   447  	return nil
   448  }
   449  
   450  // UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces
   451  func (q QueryGroupPoliciesByGroupResponse) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
   452  	return unpackGroupPolicies(unpacker, q.GroupPolicies)
   453  }
   454  
   455  // UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces
   456  func (q QueryGroupPoliciesByAdminResponse) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
   457  	return unpackGroupPolicies(unpacker, q.GroupPolicies)
   458  }
   459  
   460  func unpackGroupPolicies(unpacker codectypes.AnyUnpacker, accs []*GroupPolicyInfo) error {
   461  	for _, g := range accs {
   462  		err := g.UnpackInterfaces(unpacker)
   463  		if err != nil {
   464  			return err
   465  		}
   466  	}
   467  
   468  	return nil
   469  }
   470  
   471  type operation func(x, y math.Dec) (math.Dec, error)
   472  
   473  func (t *TallyResult) operation(vote Vote, weight string, op operation) error {
   474  	weightDec, err := math.NewPositiveDecFromString(weight)
   475  	if err != nil {
   476  		return err
   477  	}
   478  
   479  	yesCount, err := t.GetYesCount()
   480  	if err != nil {
   481  		return errorsmod.Wrap(err, "yes count")
   482  	}
   483  	noCount, err := t.GetNoCount()
   484  	if err != nil {
   485  		return errorsmod.Wrap(err, "no count")
   486  	}
   487  	abstainCount, err := t.GetAbstainCount()
   488  	if err != nil {
   489  		return errorsmod.Wrap(err, "abstain count")
   490  	}
   491  	vetoCount, err := t.GetNoWithVetoCount()
   492  	if err != nil {
   493  		return errorsmod.Wrap(err, "veto count")
   494  	}
   495  
   496  	switch vote.Option {
   497  	case VOTE_OPTION_YES:
   498  		yesCount, err := op(yesCount, weightDec)
   499  		if err != nil {
   500  			return errorsmod.Wrap(err, "yes count")
   501  		}
   502  		t.YesCount = yesCount.String()
   503  	case VOTE_OPTION_NO:
   504  		noCount, err := op(noCount, weightDec)
   505  		if err != nil {
   506  			return errorsmod.Wrap(err, "no count")
   507  		}
   508  		t.NoCount = noCount.String()
   509  	case VOTE_OPTION_ABSTAIN:
   510  		abstainCount, err := op(abstainCount, weightDec)
   511  		if err != nil {
   512  			return errorsmod.Wrap(err, "abstain count")
   513  		}
   514  		t.AbstainCount = abstainCount.String()
   515  	case VOTE_OPTION_NO_WITH_VETO:
   516  		vetoCount, err := op(vetoCount, weightDec)
   517  		if err != nil {
   518  			return errorsmod.Wrap(err, "veto count")
   519  		}
   520  		t.NoWithVetoCount = vetoCount.String()
   521  	default:
   522  		return errorsmod.Wrapf(errors.ErrInvalid, "unknown vote option %s", vote.Option.String())
   523  	}
   524  	return nil
   525  }
   526  
   527  // GetYesCount returns the number of yes counts from tally result.
   528  func (t TallyResult) GetYesCount() (math.Dec, error) {
   529  	yesCount, err := math.NewNonNegativeDecFromString(t.YesCount)
   530  	if err != nil {
   531  		return math.Dec{}, err
   532  	}
   533  	return yesCount, nil
   534  }
   535  
   536  // GetNoCount returns the number of no counts from tally result.
   537  func (t TallyResult) GetNoCount() (math.Dec, error) {
   538  	noCount, err := math.NewNonNegativeDecFromString(t.NoCount)
   539  	if err != nil {
   540  		return math.Dec{}, err
   541  	}
   542  	return noCount, nil
   543  }
   544  
   545  // GetAbstainCount returns the number of abstain counts from tally result.
   546  func (t TallyResult) GetAbstainCount() (math.Dec, error) {
   547  	abstainCount, err := math.NewNonNegativeDecFromString(t.AbstainCount)
   548  	if err != nil {
   549  		return math.Dec{}, err
   550  	}
   551  	return abstainCount, nil
   552  }
   553  
   554  // GetNoWithVetoCount returns the number of no with veto counts from tally result.
   555  func (t TallyResult) GetNoWithVetoCount() (math.Dec, error) {
   556  	vetoCount, err := math.NewNonNegativeDecFromString(t.NoWithVetoCount)
   557  	if err != nil {
   558  		return math.Dec{}, err
   559  	}
   560  	return vetoCount, nil
   561  }
   562  
   563  func (t *TallyResult) Add(vote Vote, weight string) error {
   564  	if err := t.operation(vote, weight, math.Add); err != nil {
   565  		return err
   566  	}
   567  	return nil
   568  }
   569  
   570  // TotalCounts is the sum of all weights.
   571  func (t TallyResult) TotalCounts() (math.Dec, error) {
   572  	yesCount, err := t.GetYesCount()
   573  	if err != nil {
   574  		return math.Dec{}, errorsmod.Wrap(err, "yes count")
   575  	}
   576  	noCount, err := t.GetNoCount()
   577  	if err != nil {
   578  		return math.Dec{}, errorsmod.Wrap(err, "no count")
   579  	}
   580  	abstainCount, err := t.GetAbstainCount()
   581  	if err != nil {
   582  		return math.Dec{}, errorsmod.Wrap(err, "abstain count")
   583  	}
   584  	vetoCount, err := t.GetNoWithVetoCount()
   585  	if err != nil {
   586  		return math.Dec{}, errorsmod.Wrap(err, "veto count")
   587  	}
   588  
   589  	totalCounts := math.NewDecFromInt64(0)
   590  	totalCounts, err = totalCounts.Add(yesCount)
   591  	if err != nil {
   592  		return math.Dec{}, err
   593  	}
   594  	totalCounts, err = totalCounts.Add(noCount)
   595  	if err != nil {
   596  		return math.Dec{}, err
   597  	}
   598  	totalCounts, err = totalCounts.Add(abstainCount)
   599  	if err != nil {
   600  		return math.Dec{}, err
   601  	}
   602  	totalCounts, err = totalCounts.Add(vetoCount)
   603  	if err != nil {
   604  		return math.Dec{}, err
   605  	}
   606  	return totalCounts, nil
   607  }
   608  
   609  // DefaultTallyResult returns a TallyResult with all counts set to 0.
   610  func DefaultTallyResult() TallyResult {
   611  	return TallyResult{
   612  		YesCount:        "0",
   613  		NoCount:         "0",
   614  		NoWithVetoCount: "0",
   615  		AbstainCount:    "0",
   616  	}
   617  }
   618  
   619  // VoteOptionFromString returns a VoteOption from a string. It returns an error
   620  // if the string is invalid.
   621  func VoteOptionFromString(str string) (VoteOption, error) {
   622  	vo, ok := VoteOption_value[str]
   623  	if !ok {
   624  		return VOTE_OPTION_UNSPECIFIED, fmt.Errorf("'%s' is not a valid vote option", str)
   625  	}
   626  	return VoteOption(vo), nil
   627  }