forked from cosmos/cosmos-sdk
-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
44c5d17
commit cac4e52
Showing
10 changed files
with
198 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,127 +1,19 @@ | ||
package keeper | ||
|
||
import ( | ||
"cosmossdk.io/math" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" | ||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" | ||
) | ||
|
||
// TODO: Break into several smaller functions for clarity | ||
|
||
// Tally iterates over the votes and updates the tally of a proposal based on the voting power of the | ||
// voters | ||
func (keeper Keeper) Tally(ctx sdk.Context, proposal v1.Proposal) (passes bool, burnDeposits bool, tallyResults v1.TallyResult) { | ||
results := make(map[v1.VoteOption]sdk.Dec) | ||
results[v1.OptionYes] = math.LegacyZeroDec() | ||
results[v1.OptionAbstain] = math.LegacyZeroDec() | ||
results[v1.OptionNo] = math.LegacyZeroDec() | ||
results[v1.OptionNoWithVeto] = math.LegacyZeroDec() | ||
|
||
totalVotingPower := math.LegacyZeroDec() | ||
currValidators := make(map[string]v1.ValidatorGovInfo) | ||
|
||
// fetch all the bonded validators, insert them into currValidators | ||
keeper.sk.IterateBondedValidatorsByPower(ctx, func(index int64, validator stakingtypes.ValidatorI) (stop bool) { | ||
currValidators[validator.GetOperator().String()] = v1.NewValidatorGovInfo( | ||
validator.GetOperator(), | ||
validator.GetBondedTokens(), | ||
validator.GetDelegatorShares(), | ||
math.LegacyZeroDec(), | ||
v1.WeightedVoteOptions{}, | ||
) | ||
|
||
return false | ||
}) | ||
|
||
keeper.IterateVotes(ctx, proposal.Id, func(vote v1.Vote) bool { | ||
// if validator, just record it in the map | ||
voter := sdk.MustAccAddressFromBech32(vote.Voter) | ||
|
||
valAddrStr := sdk.ValAddress(voter.Bytes()).String() | ||
if val, ok := currValidators[valAddrStr]; ok { | ||
val.Vote = vote.Options | ||
currValidators[valAddrStr] = val | ||
} | ||
|
||
// iterate over all delegations from voter, deduct from any delegated-to validators | ||
keeper.sk.IterateDelegations(ctx, voter, func(index int64, delegation stakingtypes.DelegationI) (stop bool) { | ||
valAddrStr := delegation.GetValidatorAddr().String() | ||
|
||
if val, ok := currValidators[valAddrStr]; ok { | ||
// There is no need to handle the special case that validator address equal to voter address. | ||
// Because voter's voting power will tally again even if there will be deduction of voter's voting power from validator. | ||
val.DelegatorDeductions = val.DelegatorDeductions.Add(delegation.GetShares()) | ||
currValidators[valAddrStr] = val | ||
|
||
// delegation shares * bonded / total shares | ||
votingPower := delegation.GetShares().MulInt(val.BondedTokens).Quo(val.DelegatorShares) | ||
|
||
for _, option := range vote.Options { | ||
weight, _ := sdk.NewDecFromStr(option.Weight) | ||
subPower := votingPower.Mul(weight) | ||
results[option.Option] = results[option.Option].Add(subPower) | ||
} | ||
totalVotingPower = totalVotingPower.Add(votingPower) | ||
} | ||
|
||
return false | ||
}) | ||
|
||
keeper.deleteVote(ctx, vote.ProposalId, voter) | ||
return false | ||
}) | ||
|
||
// iterate over the validators again to tally their voting power | ||
for _, val := range currValidators { | ||
if len(val.Vote) == 0 { | ||
continue | ||
} | ||
|
||
sharesAfterDeductions := val.DelegatorShares.Sub(val.DelegatorDeductions) | ||
votingPower := sharesAfterDeductions.MulInt(val.BondedTokens).Quo(val.DelegatorShares) | ||
|
||
for _, option := range val.Vote { | ||
weight, _ := sdk.NewDecFromStr(option.Weight) | ||
subPower := votingPower.Mul(weight) | ||
results[option.Option] = results[option.Option].Add(subPower) | ||
} | ||
totalVotingPower = totalVotingPower.Add(votingPower) | ||
} | ||
|
||
params := keeper.GetParams(ctx) | ||
tallyResults = v1.NewTallyResultFromMap(results) | ||
|
||
// TODO: Upgrade the spec to cover all of these cases & remove pseudocode. | ||
// If there is no staked coins, the proposal fails | ||
if keeper.sk.TotalBondedTokens(ctx).IsZero() { | ||
return false, false, tallyResults | ||
} | ||
|
||
// If there is not enough quorum of votes, the proposal fails | ||
percentVoting := totalVotingPower.Quo(sdk.NewDecFromInt(keeper.sk.TotalBondedTokens(ctx))) | ||
quorum, _ := sdk.NewDecFromStr(params.Quorum) | ||
if percentVoting.LT(quorum) { | ||
return false, params.BurnVoteQuorum, tallyResults | ||
} | ||
|
||
// If no one votes (everyone abstains), proposal fails | ||
if totalVotingPower.Sub(results[v1.OptionAbstain]).Equal(math.LegacyZeroDec()) { | ||
return false, false, tallyResults | ||
} | ||
|
||
// If more than 1/3 of voters veto, proposal fails | ||
vetoThreshold, _ := sdk.NewDecFromStr(params.VetoThreshold) | ||
if results[v1.OptionNoWithVeto].Quo(totalVotingPower).GT(vetoThreshold) { | ||
return false, params.BurnVoteVeto, tallyResults | ||
} | ||
|
||
// If more than 1/2 of non-abstaining voters vote Yes, proposal passes | ||
threshold, _ := sdk.NewDecFromStr(params.Threshold) | ||
if results[v1.OptionYes].Quo(totalVotingPower.Sub(results[v1.OptionAbstain])).GT(threshold) { | ||
return true, false, tallyResults | ||
tallyHandler := keeper.tallyHandler | ||
if tallyHandler == nil { | ||
tallyHandler = NewDefaultTallyHandler(keeper) | ||
} | ||
|
||
// If more than 1/2 of non-abstaining voters vote No, proposal fails | ||
return false, false, tallyResults | ||
return tallyHandler.Tally(ctx, proposal) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
package keeper | ||
|
||
import ( | ||
"cosmossdk.io/math" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" | ||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" | ||
) | ||
|
||
var _ v1.TallyHandler = DefaultTallyHandler{} | ||
|
||
// DefaultTallyHandler is the default tally handler for x/gov | ||
type DefaultTallyHandler struct { | ||
keeper Keeper | ||
} | ||
|
||
// NewDefaultTallyHandler creates a new tally handler. | ||
func NewDefaultTallyHandler(k Keeper) DefaultTallyHandler { | ||
return DefaultTallyHandler{ | ||
keeper: k, | ||
} | ||
} | ||
|
||
// TODO: Break Tally into several smaller functions for clarity | ||
|
||
// Tally iterates over the votes and updates the tally of a proposal based on the voting power of the | ||
// voters | ||
func (dth DefaultTallyHandler) Tally( | ||
ctx sdk.Context, | ||
proposal v1.Proposal, | ||
) (passes bool, burnDeposits bool, tallyResults v1.TallyResult) { | ||
results := make(map[v1.VoteOption]sdk.Dec) | ||
results[v1.OptionYes] = math.LegacyZeroDec() | ||
results[v1.OptionAbstain] = math.LegacyZeroDec() | ||
results[v1.OptionNo] = math.LegacyZeroDec() | ||
results[v1.OptionNoWithVeto] = math.LegacyZeroDec() | ||
|
||
totalVotingPower := math.LegacyZeroDec() | ||
currValidators := make(map[string]v1.ValidatorGovInfo) | ||
|
||
// fetch all the bonded validators, insert them into currValidators | ||
dth.keeper.sk.IterateBondedValidatorsByPower(ctx, func(index int64, validator stakingtypes.ValidatorI) (stop bool) { | ||
currValidators[validator.GetOperator().String()] = v1.NewValidatorGovInfo( | ||
validator.GetOperator(), | ||
validator.GetBondedTokens(), | ||
validator.GetDelegatorShares(), | ||
math.LegacyZeroDec(), | ||
v1.WeightedVoteOptions{}, | ||
) | ||
|
||
return false | ||
}) | ||
|
||
dth.keeper.IterateVotes(ctx, proposal.Id, func(vote v1.Vote) bool { | ||
// if validator, just record it in the map | ||
voter := sdk.MustAccAddressFromBech32(vote.Voter) | ||
|
||
valAddrStr := sdk.ValAddress(voter.Bytes()).String() | ||
if val, ok := currValidators[valAddrStr]; ok { | ||
val.Vote = vote.Options | ||
currValidators[valAddrStr] = val | ||
} | ||
|
||
// iterate over all delegations from voter, deduct from any delegated-to validators | ||
dth.keeper.sk.IterateDelegations(ctx, voter, func(index int64, delegation stakingtypes.DelegationI) (stop bool) { | ||
valAddrStr := delegation.GetValidatorAddr().String() | ||
|
||
if val, ok := currValidators[valAddrStr]; ok { | ||
// There is no need to handle the special case that validator address equal to voter address. | ||
// Because voter's voting power will tally again even if there will be deduction of voter's voting power from validator. | ||
val.DelegatorDeductions = val.DelegatorDeductions.Add(delegation.GetShares()) | ||
currValidators[valAddrStr] = val | ||
|
||
// delegation shares * bonded / total shares | ||
votingPower := delegation.GetShares().MulInt(val.BondedTokens).Quo(val.DelegatorShares) | ||
|
||
for _, option := range vote.Options { | ||
weight, _ := sdk.NewDecFromStr(option.Weight) | ||
subPower := votingPower.Mul(weight) | ||
results[option.Option] = results[option.Option].Add(subPower) | ||
} | ||
totalVotingPower = totalVotingPower.Add(votingPower) | ||
} | ||
|
||
return false | ||
}) | ||
|
||
dth.keeper.DeleteVote(ctx, vote.ProposalId, voter) | ||
return false | ||
}) | ||
|
||
// iterate over the validators again to tally their voting power | ||
for _, val := range currValidators { | ||
if len(val.Vote) == 0 { | ||
continue | ||
} | ||
|
||
sharesAfterDeductions := val.DelegatorShares.Sub(val.DelegatorDeductions) | ||
votingPower := sharesAfterDeductions.MulInt(val.BondedTokens).Quo(val.DelegatorShares) | ||
|
||
for _, option := range val.Vote { | ||
weight, _ := sdk.NewDecFromStr(option.Weight) | ||
subPower := votingPower.Mul(weight) | ||
results[option.Option] = results[option.Option].Add(subPower) | ||
} | ||
totalVotingPower = totalVotingPower.Add(votingPower) | ||
} | ||
|
||
params := dth.keeper.GetParams(ctx) | ||
tallyResults = v1.NewTallyResultFromMap(results) | ||
|
||
// TODO: Upgrade the spec to cover all of these cases & remove pseudocode. | ||
// If there is no staked coins, the proposal fails | ||
if dth.keeper.sk.TotalBondedTokens(ctx).IsZero() { | ||
return false, false, tallyResults | ||
} | ||
|
||
// If there is not enough quorum of votes, the proposal fails | ||
percentVoting := totalVotingPower.Quo(sdk.NewDecFromInt(dth.keeper.sk.TotalBondedTokens(ctx))) | ||
quorum, _ := sdk.NewDecFromStr(params.Quorum) | ||
if percentVoting.LT(quorum) { | ||
return false, params.BurnVoteQuorum, tallyResults | ||
} | ||
|
||
// If no one votes (everyone abstains), proposal fails | ||
if totalVotingPower.Sub(results[v1.OptionAbstain]).Equal(math.LegacyZeroDec()) { | ||
return false, false, tallyResults | ||
} | ||
|
||
// If more than 1/3 of voters veto, proposal fails | ||
vetoThreshold, _ := sdk.NewDecFromStr(params.VetoThreshold) | ||
if results[v1.OptionNoWithVeto].Quo(totalVotingPower).GT(vetoThreshold) { | ||
return false, params.BurnVoteVeto, tallyResults | ||
} | ||
|
||
// If more than 1/2 of non-abstaining voters vote Yes, proposal passes | ||
threshold, _ := sdk.NewDecFromStr(params.Threshold) | ||
if results[v1.OptionYes].Quo(totalVotingPower.Sub(results[v1.OptionAbstain])).GT(threshold) { | ||
return true, false, tallyResults | ||
} | ||
|
||
// If more than 1/2 of non-abstaining voters vote No, proposal fails | ||
return false, false, tallyResults | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.