github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/x/gov/keeper/tally.go (about)

     1  package keeper
     2  
     3  import (
     4  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
     5  	"github.com/fibonacci-chain/fbc/x/gov/types"
     6  	"github.com/fibonacci-chain/fbc/x/staking/exported"
     7  )
     8  
     9  // validatorGovInfo used for tallying
    10  type validatorGovInfo struct {
    11  	Address             sdk.ValAddress   // address of the validator operator
    12  	BondedTokens        sdk.Int          // Power of a Validator
    13  	DelegatorShares     sdk.Dec          // Total outstanding delegator shares
    14  	DelegatorDeductions sdk.Dec          // Delegator deductions from validator's delegators voting independently
    15  	Vote                types.VoteOption // Vote of the validator
    16  }
    17  
    18  func newValidatorGovInfo(address sdk.ValAddress, bondedTokens sdk.Int, delegatorShares,
    19  	delegatorDeductions sdk.Dec, vote types.VoteOption) validatorGovInfo {
    20  
    21  	return validatorGovInfo{
    22  		Address:             address,
    23  		BondedTokens:        bondedTokens,
    24  		DelegatorShares:     delegatorShares,
    25  		DelegatorDeductions: delegatorDeductions,
    26  		Vote:                vote,
    27  	}
    28  }
    29  
    30  func tallyDelegatorVotes(
    31  	ctx sdk.Context, keeper Keeper, currValidators map[string]validatorGovInfo, proposalID uint64,
    32  	voteP *types.Vote, voterPower, totalVotedPower *sdk.Dec, results map[types.VoteOption]sdk.Dec,
    33  ) {
    34  	// iterate over all the votes
    35  	votesIterator := keeper.GetVotes(ctx, proposalID)
    36  	if voteP != nil {
    37  		votesIterator = append(votesIterator, *voteP)
    38  	}
    39  	for i := 0; i < len(votesIterator); i++ {
    40  		vote := votesIterator[i]
    41  
    42  		// if validator, just record it in the map
    43  		// if delegator tally voting power
    44  		valAddrStr := sdk.ValAddress(vote.Voter).String()
    45  		if val, ok := currValidators[valAddrStr]; ok {
    46  			val.Vote = vote.Option
    47  			currValidators[valAddrStr] = val
    48  		} else {
    49  			// iterate over all delegations from voter, deduct from any delegated-to validators
    50  			delegation := keeper.sk.Delegator(ctx, vote.Voter)
    51  			if delegation == nil {
    52  				continue
    53  			}
    54  			for _, val := range delegation.GetShareAddedValidatorAddresses() {
    55  				valAddrStr := val.String()
    56  				if valInfo, ok := currValidators[valAddrStr]; ok {
    57  					valInfo.DelegatorDeductions = valInfo.DelegatorDeductions.Add(delegation.GetLastAddedShares())
    58  					currValidators[valAddrStr] = valInfo
    59  
    60  					votedPower := delegation.GetLastAddedShares()
    61  					// calculate vote power of delegator for voterPowerRate
    62  					if voteP != nil && vote.Voter.Equals(voteP.Voter) {
    63  						voterPower.Add(votedPower)
    64  					}
    65  					results[vote.Option] = results[vote.Option].Add(votedPower)
    66  					*totalVotedPower = totalVotedPower.Add(votedPower)
    67  				}
    68  			}
    69  		}
    70  	}
    71  }
    72  
    73  func tallyValidatorVotes(
    74  	currValidators map[string]validatorGovInfo, voteP *types.Vote, voterPower,
    75  	totalPower, totalVotedPower *sdk.Dec, results map[types.VoteOption]sdk.Dec,
    76  ) {
    77  	// iterate over the validators again to tally their voting power
    78  	for key, val := range currValidators {
    79  		// calculate all vote power of current validators including delegated for voterPowerRate
    80  		*totalPower = totalPower.Add(val.DelegatorShares)
    81  		if val.Vote == types.OptionEmpty {
    82  			continue
    83  		}
    84  
    85  		valValidVotedPower := val.DelegatorShares.Sub(val.DelegatorDeductions)
    86  		if voteP != nil && sdk.ValAddress(voteP.Voter).String() == key {
    87  			// calculate vote power of validator after deduction for voterPowerRate
    88  			*voterPower = voterPower.Add(valValidVotedPower)
    89  		}
    90  		results[val.Vote] = results[val.Vote].Add(valValidVotedPower)
    91  		*totalVotedPower = totalVotedPower.Add(valValidVotedPower)
    92  	}
    93  }
    94  
    95  func preTally(
    96  	ctx sdk.Context, keeper Keeper, proposal types.Proposal, voteP *types.Vote,
    97  ) (results map[types.VoteOption]sdk.Dec, totalVotedPower sdk.Dec, voterPowerRate sdk.Dec) {
    98  	results = make(map[types.VoteOption]sdk.Dec)
    99  	results[types.OptionYes] = sdk.ZeroDec()
   100  	results[types.OptionAbstain] = sdk.ZeroDec()
   101  	results[types.OptionNo] = sdk.ZeroDec()
   102  	results[types.OptionNoWithVeto] = sdk.ZeroDec()
   103  
   104  	totalVotedPower = sdk.ZeroDec()
   105  	totalPower := sdk.ZeroDec()
   106  	voterPower := sdk.ZeroDec()
   107  	currValidators := make(map[string]validatorGovInfo)
   108  
   109  	// fetch all the current validators except candidate, insert them into currValidators
   110  	keeper.sk.IterateBondedValidatorsByPower(ctx, func(index int64, validator exported.ValidatorI) (stop bool) {
   111  		currValidators[validator.GetOperator().String()] = newValidatorGovInfo(
   112  			validator.GetOperator(),
   113  			validator.GetBondedTokens(),
   114  			validator.GetDelegatorShares(),
   115  			sdk.ZeroDec(),
   116  			types.OptionEmpty,
   117  		)
   118  
   119  		return false
   120  	})
   121  
   122  	tallyDelegatorVotes(ctx, keeper, currValidators, proposal.ProposalID,
   123  		voteP, &voterPower, &totalVotedPower, results)
   124  
   125  	tallyValidatorVotes(currValidators, voteP, &voterPower, &totalPower, &totalVotedPower, results)
   126  	if totalPower.GT(sdk.ZeroDec()) {
   127  		voterPowerRate = voterPower.Quo(totalPower)
   128  	} else {
   129  		voterPowerRate = sdk.ZeroDec()
   130  	}
   131  
   132  	return results, totalVotedPower, voterPowerRate
   133  }
   134  
   135  // tally and return status before voting period end time
   136  func tallyStatusInVotePeriod(
   137  	ctx sdk.Context, keeper Keeper, tallyResults types.TallyResult,
   138  ) (types.ProposalStatus, bool) {
   139  	tallyParams := keeper.GetTallyParams(ctx)
   140  	totalPower := tallyResults.TotalPower
   141  	// TODO: Upgrade the spec to cover all of these cases & remove pseudocode.
   142  	// If there is no staked coins, the proposal fails
   143  	if totalPower.IsZero() {
   144  		return types.StatusRejected, false
   145  	}
   146  	// If no one votes (everyone abstains), proposal fails
   147  	if totalPower.Sub(tallyResults.Abstain).Equal(sdk.ZeroDec()) {
   148  		return types.StatusRejected, false
   149  	}
   150  	// If more than 1/3 of voters veto, proposal fails
   151  	if tallyResults.NoWithVeto.Quo(totalPower).GT(tallyParams.Veto) {
   152  		return types.StatusRejected, true
   153  	}
   154  	// If more than or equal to 1/2 of non-abstain vote not Yes, proposal fails
   155  	if tallyResults.NoWithVeto.Add(tallyResults.No).Quo(totalPower.Sub(tallyResults.Abstain)).
   156  		GTE(tallyParams.Threshold) {
   157  		return types.StatusRejected, false
   158  	}
   159  	// If more than 2/3 of totalPower vote Yes, proposal passes
   160  	if tallyResults.Yes.Quo(totalPower).GT(tallyParams.YesInVotePeriod) {
   161  		return types.StatusPassed, false
   162  	}
   163  
   164  	return types.StatusVotingPeriod, false
   165  }
   166  
   167  // tally and return status expire voting period end time
   168  func tallyStatusExpireVotePeriod(
   169  	ctx sdk.Context, keeper Keeper, tallyResults types.TallyResult,
   170  ) (types.ProposalStatus, bool) {
   171  	tallyParams := keeper.GetTallyParams(ctx)
   172  	totalVoted := tallyResults.TotalVotedPower
   173  	totalPower := tallyResults.TotalPower
   174  	// TODO: Upgrade the spec to cover all of these cases & remove pseudo code.
   175  	// If there is no staked coins, the proposal fails
   176  	if totalPower.IsZero() {
   177  		return types.StatusRejected, false
   178  	}
   179  	// If there is not enough quorum of votes, the proposal fails
   180  	percentVoting := totalVoted.Quo(totalPower)
   181  	if percentVoting.LT(tallyParams.Quorum) {
   182  		return types.StatusRejected, true
   183  	}
   184  	// If no one votes (everyone abstains), proposal fails
   185  	if totalVoted.Sub(tallyResults.Abstain).Equal(sdk.ZeroDec()) {
   186  		return types.StatusRejected, false
   187  	}
   188  	// If more than 1/3 of voters veto, proposal fails
   189  	if tallyResults.NoWithVeto.Quo(totalVoted).GT(tallyParams.Veto) {
   190  		return types.StatusRejected, true
   191  	}
   192  	// If more than 1/2 of non-abstaining voters vote Yes, proposal passes
   193  	if tallyResults.Yes.Quo(totalVoted.Sub(tallyResults.Abstain)).GT(tallyParams.Threshold) {
   194  		return types.StatusPassed, false
   195  	}
   196  	// If more than 1/2 of non-abstaining voters vote No, proposal fails
   197  
   198  	return types.StatusRejected, false
   199  }
   200  
   201  // Tally counts the votes for proposal
   202  func Tally(ctx sdk.Context, keeper Keeper, proposal types.Proposal, isExpireVoteEndTime bool,
   203  ) (types.ProposalStatus, bool, types.TallyResult) {
   204  	results, totalVotedPower, _ := preTally(ctx, keeper, proposal, nil)
   205  	tallyResults := types.NewTallyResultFromMap(results)
   206  	tallyResults.TotalPower = keeper.totalPower(ctx)
   207  	tallyResults.TotalVotedPower = totalVotedPower
   208  
   209  	if isExpireVoteEndTime {
   210  		status, distribute := tallyStatusExpireVotePeriod(ctx, keeper, tallyResults)
   211  		return status, distribute, tallyResults
   212  	}
   213  	status, distribute := tallyStatusInVotePeriod(ctx, keeper, tallyResults)
   214  	return status, distribute, tallyResults
   215  }