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 }