github.com/cosmos/cosmos-sdk@v0.50.10/x/gov/keeper/tally.go (about)

     1  package keeper
     2  
     3  import (
     4  	"context"
     5  
     6  	"cosmossdk.io/collections"
     7  	"cosmossdk.io/math"
     8  
     9  	sdk "github.com/cosmos/cosmos-sdk/types"
    10  	v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
    11  	stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
    12  )
    13  
    14  // TODO: Break into several smaller functions for clarity
    15  
    16  // Tally iterates over the votes and updates the tally of a proposal based on the voting power of the
    17  // voters
    18  func (keeper Keeper) Tally(ctx context.Context, proposal v1.Proposal) (passes, burnDeposits bool, tallyResults v1.TallyResult, err error) {
    19  	results := make(map[v1.VoteOption]math.LegacyDec)
    20  	results[v1.OptionYes] = math.LegacyZeroDec()
    21  	results[v1.OptionAbstain] = math.LegacyZeroDec()
    22  	results[v1.OptionNo] = math.LegacyZeroDec()
    23  	results[v1.OptionNoWithVeto] = math.LegacyZeroDec()
    24  
    25  	totalVotingPower := math.LegacyZeroDec()
    26  	currValidators := make(map[string]v1.ValidatorGovInfo)
    27  
    28  	// fetch all the bonded validators, insert them into currValidators
    29  	err = keeper.sk.IterateBondedValidatorsByPower(ctx, func(index int64, validator stakingtypes.ValidatorI) (stop bool) {
    30  		valBz, err := keeper.sk.ValidatorAddressCodec().StringToBytes(validator.GetOperator())
    31  		if err != nil {
    32  			return false
    33  		}
    34  		currValidators[validator.GetOperator()] = v1.NewValidatorGovInfo(
    35  			valBz,
    36  			validator.GetBondedTokens(),
    37  			validator.GetDelegatorShares(),
    38  			math.LegacyZeroDec(),
    39  			v1.WeightedVoteOptions{},
    40  		)
    41  
    42  		return false
    43  	})
    44  	if err != nil {
    45  		return false, false, tallyResults, err
    46  	}
    47  
    48  	rng := collections.NewPrefixedPairRange[uint64, sdk.AccAddress](proposal.Id)
    49  	err = keeper.Votes.Walk(ctx, rng, func(key collections.Pair[uint64, sdk.AccAddress], vote v1.Vote) (bool, error) {
    50  		// if validator, just record it in the map
    51  		voter, err := keeper.authKeeper.AddressCodec().StringToBytes(vote.Voter)
    52  		if err != nil {
    53  			return false, err
    54  		}
    55  
    56  		valAddrStr, err := keeper.sk.ValidatorAddressCodec().BytesToString(voter)
    57  		if err != nil {
    58  			return false, err
    59  		}
    60  		if val, ok := currValidators[valAddrStr]; ok {
    61  			val.Vote = vote.Options
    62  			currValidators[valAddrStr] = val
    63  		}
    64  
    65  		// iterate over all delegations from voter, deduct from any delegated-to validators
    66  		err = keeper.sk.IterateDelegations(ctx, voter, func(index int64, delegation stakingtypes.DelegationI) (stop bool) {
    67  			valAddrStr := delegation.GetValidatorAddr()
    68  
    69  			if val, ok := currValidators[valAddrStr]; ok {
    70  				// There is no need to handle the special case that validator address equal to voter address.
    71  				// Because voter's voting power will tally again even if there will be deduction of voter's voting power from validator.
    72  				val.DelegatorDeductions = val.DelegatorDeductions.Add(delegation.GetShares())
    73  				currValidators[valAddrStr] = val
    74  
    75  				// delegation shares * bonded / total shares
    76  				votingPower := delegation.GetShares().MulInt(val.BondedTokens).Quo(val.DelegatorShares)
    77  
    78  				for _, option := range vote.Options {
    79  					weight, _ := math.LegacyNewDecFromStr(option.Weight)
    80  					subPower := votingPower.Mul(weight)
    81  					results[option.Option] = results[option.Option].Add(subPower)
    82  				}
    83  				totalVotingPower = totalVotingPower.Add(votingPower)
    84  			}
    85  
    86  			return false
    87  		})
    88  		if err != nil {
    89  			return false, err
    90  		}
    91  
    92  		return false, keeper.Votes.Remove(ctx, collections.Join(vote.ProposalId, sdk.AccAddress(voter)))
    93  	})
    94  
    95  	if err != nil {
    96  		return false, false, tallyResults, err
    97  	}
    98  
    99  	// iterate over the validators again to tally their voting power
   100  	for _, val := range currValidators {
   101  		if len(val.Vote) == 0 {
   102  			continue
   103  		}
   104  
   105  		sharesAfterDeductions := val.DelegatorShares.Sub(val.DelegatorDeductions)
   106  		votingPower := sharesAfterDeductions.MulInt(val.BondedTokens).Quo(val.DelegatorShares)
   107  
   108  		for _, option := range val.Vote {
   109  			weight, _ := math.LegacyNewDecFromStr(option.Weight)
   110  			subPower := votingPower.Mul(weight)
   111  			results[option.Option] = results[option.Option].Add(subPower)
   112  		}
   113  		totalVotingPower = totalVotingPower.Add(votingPower)
   114  	}
   115  
   116  	params, err := keeper.Params.Get(ctx)
   117  	if err != nil {
   118  		return false, false, tallyResults, err
   119  	}
   120  	tallyResults = v1.NewTallyResultFromMap(results)
   121  
   122  	// TODO: Upgrade the spec to cover all of these cases & remove pseudocode.
   123  	// If there is no staked coins, the proposal fails
   124  	totalBonded, err := keeper.sk.TotalBondedTokens(ctx)
   125  	if err != nil {
   126  		return false, false, tallyResults, err
   127  	}
   128  
   129  	if totalBonded.IsZero() {
   130  		return false, false, tallyResults, nil
   131  	}
   132  
   133  	// If there is not enough quorum of votes, the proposal fails
   134  	percentVoting := totalVotingPower.Quo(math.LegacyNewDecFromInt(totalBonded))
   135  	quorum, _ := math.LegacyNewDecFromStr(params.Quorum)
   136  	if percentVoting.LT(quorum) {
   137  		return false, params.BurnVoteQuorum, tallyResults, nil
   138  	}
   139  
   140  	// If no one votes (everyone abstains), proposal fails
   141  	if totalVotingPower.Sub(results[v1.OptionAbstain]).Equal(math.LegacyZeroDec()) {
   142  		return false, false, tallyResults, nil
   143  	}
   144  
   145  	// If more than 1/3 of voters veto, proposal fails
   146  	vetoThreshold, _ := math.LegacyNewDecFromStr(params.VetoThreshold)
   147  	if results[v1.OptionNoWithVeto].Quo(totalVotingPower).GT(vetoThreshold) {
   148  		return false, params.BurnVoteVeto, tallyResults, nil
   149  	}
   150  
   151  	// If more than 1/2 of non-abstaining voters vote Yes, proposal passes
   152  	// For expedited 2/3
   153  	var thresholdStr string
   154  	if proposal.Expedited {
   155  		thresholdStr = params.GetExpeditedThreshold()
   156  	} else {
   157  		thresholdStr = params.GetThreshold()
   158  	}
   159  
   160  	threshold, _ := math.LegacyNewDecFromStr(thresholdStr)
   161  
   162  	if results[v1.OptionYes].Quo(totalVotingPower.Sub(results[v1.OptionAbstain])).GT(threshold) {
   163  		return true, false, tallyResults, nil
   164  	}
   165  
   166  	// If more than 1/2 of non-abstaining voters vote No, proposal fails
   167  	return false, false, tallyResults, nil
   168  }