/
tally.go
185 lines (147 loc) · 6.54 KB
/
tally.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
)
// DefaultContractAddr is the wasm contract address generated by code ID 1 and
// instance ID 1.
//
// In other words, the first ever contract to be deployed on this chain will
// necessarily have this address.
//
// Why don't we make this a configurable parameter in the module params?
// Because doing so involves messing with the vanilla gov module's params type,
// which breaks a bunch of code and imo, isn't worth it.
var DefaultContractAddr = wasmkeeper.BuildContractAddressClassic(1, 1)
// Tally iterates over the votes and updates the tally of a proposal based on
// the voting power of the voters.
//
// NOTE: here the voting power of a user is defined as: amount of MARS tokens
// staked + amount locked in vesting.
func (k Keeper) Tally(ctx sdk.Context, proposal govv1.Proposal) (passes bool, burnDeposits bool, tallyResults govv1.TallyResult) {
results := make(map[govv1.VoteOption]sdk.Dec)
results[govv1.OptionYes] = sdk.ZeroDec()
results[govv1.OptionAbstain] = sdk.ZeroDec()
results[govv1.OptionNo] = sdk.ZeroDec()
results[govv1.OptionNoWithVeto] = sdk.ZeroDec()
// fetch all currently bonded validators
currValidators := make(map[string]govv1.ValidatorGovInfo)
k.stakingKeeper.IterateBondedValidatorsByPower(ctx, func(index int64, validator stakingtypes.ValidatorI) (stop bool) {
currValidators[validator.GetOperator().String()] = govv1.NewValidatorGovInfo(
validator.GetOperator(),
validator.GetBondedTokens(),
validator.GetDelegatorShares(),
sdk.ZeroDec(),
govv1.WeightedVoteOptions{},
)
return false
})
// fetch all tokens locked in the vesting contract
tokensInVesting, totalTokensInVesting := MustGetTokensInVesting(ctx, k.wasmKeeper, DefaultContractAddr)
// total amount of tokens bonded with validators
totalTokensBonded := k.stakingKeeper.TotalBondedTokens(ctx)
// total amount of tokens that are eligible to vote in this poll; used to
// determine quorum
totalTokens := sdk.NewDecFromInt(totalTokensBonded.Add(totalTokensInVesting))
// total amount of tokens that have voted in this poll; used to determine
// whether the poll reaches quorum and the pass threshold
totalTokensVoted := sdk.ZeroDec()
// iterate through votes
k.IterateVotes(ctx, proposal.Id, func(vote govv1.Vote) bool {
voterAddr := sdk.MustAccAddressFromBech32(vote.Voter)
// if validator, record its vote options in the map
valAddrStr := sdk.ValAddress(voterAddr).String()
if val, ok := currValidators[valAddrStr]; ok {
val.Vote = vote.Options
currValidators[valAddrStr] = val
}
votingPower := sdk.ZeroDec()
// iterate over all delegations from voter, deduct from any delegated-to
// validators
//
// 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 deduct voter's voting power from validator
k.stakingKeeper.IterateDelegations(ctx, voterAddr, func(index int64, delegation stakingtypes.DelegationI) (stop bool) {
valAddrStr := delegation.GetValidatorAddr().String()
if val, ok := currValidators[valAddrStr]; ok {
val.DelegatorDeductions = val.DelegatorDeductions.Add(delegation.GetShares())
currValidators[valAddrStr] = val
votingPower = votingPower.Add(delegation.GetShares().MulInt(val.BondedTokens).Quo(val.DelegatorShares))
}
return false
})
// if the voter has tokens locked in vesting contract, add that to the
// voting power
if votingPowerInVesting, ok := tokensInVesting[vote.Voter]; ok {
votingPower = votingPower.Add(sdk.NewDecFromInt(votingPowerInVesting))
}
incrementTallyResult(votingPower, vote.Options, results, &totalTokensVoted)
k.deleteVote(ctx, vote.ProposalId, voterAddr)
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)
incrementTallyResult(votingPower, val.Vote, results, &totalTokensVoted)
}
tallyParams := k.GetTallyParams(ctx)
tallyResults = govv1.NewTallyResultFromMap(results)
quorum, err := sdk.NewDecFromStr(tallyParams.Quorum)
if err != nil {
panic(fmt.Sprintf("Invalid governance parameter quorum `%s`: %s", tallyParams.Quorum, err))
}
vetoThreshold, err := sdk.NewDecFromStr(tallyParams.VetoThreshold)
if err != nil {
panic(fmt.Sprintf("Invalid governance parameter vetoThreshold `%s`: %s", tallyParams.VetoThreshold, err))
}
passThreshold, err := sdk.NewDecFromStr(tallyParams.Threshold)
if err != nil {
panic(fmt.Sprintf("Invalid governance parameter passThreshold `%s`: %s", tallyParams.Threshold, err))
}
// if there is no staked coins, the proposal fails
if k.stakingKeeper.TotalBondedTokens(ctx).IsZero() {
return false, false, tallyResults
}
// if there is not enough quorum of votes, the proposal fails
if totalTokensVoted.Quo(totalTokens).LT(quorum) {
return false, false, tallyResults
}
// if everyone abstains, proposal fails
if totalTokensVoted.Sub(results[govv1.OptionAbstain]).IsZero() {
return false, false, tallyResults
}
// if more than 1/3 of voters veto, proposal fails, and deposit burned
//
// NOTE: here 1/3 is defined as 1/3 *of all votes*, including abstaining
// votes. could it make more sense to instead define it as 1/3 *of all
// non-abstaining votes*?
if results[govv1.OptionNoWithVeto].Quo(totalTokensVoted).GT(vetoThreshold) {
return false, true, tallyResults
}
// if no less than 1/2 of non-abstaining voters vote No, proposal fails
if results[govv1.OptionNo].Quo(totalTokensVoted.Sub(results[govv1.OptionAbstain])).GTE(passThreshold) {
return false, false, tallyResults
}
// otherwise, meaning more than 1/2 of non-abstaining voters vote Yes,
// proposal passes
return true, false, tallyResults
}
func incrementTallyResult(votingPower sdk.Dec, options []*govv1.WeightedVoteOption, results map[govv1.VoteOption]sdk.Dec, totalTokensVoted *sdk.Dec) {
for _, option := range options {
weight, err := sdk.NewDecFromStr(option.Weight)
if err != nil {
panic(fmt.Sprintf("Invalid vote weight `%s`: %s", option.Weight, err))
}
subPower := votingPower.Mul(weight)
results[option.Option] = results[option.Option].Add(subPower)
}
*totalTokensVoted = totalTokensVoted.Add(votingPower)
}