-
Notifications
You must be signed in to change notification settings - Fork 368
/
tally_handler.go
253 lines (215 loc) · 8.15 KB
/
tally_handler.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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
package app
import (
sdk "github.com/cosmos/cosmos-sdk/types"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper"
"github.com/cosmos/cosmos-sdk/x/gov/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
earnkeeper "github.com/kava-labs/kava/x/earn/keeper"
liquidkeeper "github.com/kava-labs/kava/x/liquid/keeper"
liquidtypes "github.com/kava-labs/kava/x/liquid/types"
savingskeeper "github.com/kava-labs/kava/x/savings/keeper"
)
var _ govtypes.TallyHandler = TallyHandler{}
// TallyHandler is the tally handler for kava
type TallyHandler struct {
gk govkeeper.Keeper
stk stakingkeeper.Keeper
svk savingskeeper.Keeper
ek earnkeeper.Keeper
lk liquidkeeper.Keeper
bk bankkeeper.Keeper
}
// NewTallyHandler creates a new tally handler.
func NewTallyHandler(
gk govkeeper.Keeper, stk stakingkeeper.Keeper, svk savingskeeper.Keeper,
ek earnkeeper.Keeper, lk liquidkeeper.Keeper, bk bankkeeper.Keeper,
) TallyHandler {
return TallyHandler{
gk: gk,
stk: stk,
svk: svk,
ek: ek,
lk: lk,
bk: bk,
}
}
func (th TallyHandler) Tally(ctx sdk.Context, proposal types.Proposal) (passes bool, burnDeposits bool, tallyResults types.TallyResult) {
results := make(map[types.VoteOption]sdk.Dec)
results[types.OptionYes] = sdk.ZeroDec()
results[types.OptionAbstain] = sdk.ZeroDec()
results[types.OptionNo] = sdk.ZeroDec()
results[types.OptionNoWithVeto] = sdk.ZeroDec()
totalVotingPower := sdk.ZeroDec()
currValidators := make(map[string]types.ValidatorGovInfo)
// fetch all the bonded validators, insert them into currValidators
th.stk.IterateBondedValidatorsByPower(ctx, func(index int64, validator stakingtypes.ValidatorI) (stop bool) {
currValidators[validator.GetOperator().String()] = types.NewValidatorGovInfo(
validator.GetOperator(),
validator.GetBondedTokens(),
validator.GetDelegatorShares(),
sdk.ZeroDec(),
types.WeightedVoteOptions{},
)
return false
})
th.gk.IterateVotes(ctx, proposal.ProposalId, func(vote types.Vote) bool {
// if validator, just record it in the map
voter, err := sdk.AccAddressFromBech32(vote.Voter)
if err != nil {
panic(err)
}
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
th.stk.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 deduct 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 {
subPower := votingPower.Mul(option.Weight)
results[option.Option] = results[option.Option].Add(subPower)
}
totalVotingPower = totalVotingPower.Add(votingPower)
}
return false
})
// get voter bkava and update total voting power and results
addrBkava := th.getAddrBkava(ctx, voter).toCoins()
for _, coin := range addrBkava {
valAddr, err := liquidtypes.ParseLiquidStakingTokenDenom(coin.Denom)
if err != nil {
break
}
// reduce delegator shares by the amount of voter bkava for the validator
valAddrStr := valAddr.String()
if val, ok := currValidators[valAddrStr]; ok {
val.DelegatorDeductions = val.DelegatorDeductions.Add(coin.Amount.ToDec())
currValidators[valAddrStr] = val
}
// votingPower = amount of ukava coin
stakedCoins, err := th.lk.GetStakedTokensForDerivatives(ctx, sdk.NewCoins(coin))
if err != nil {
// error is returned only if the bkava denom is incorrect, which should never happen here.
panic(err)
}
votingPower := stakedCoins.Amount.ToDec()
for _, option := range vote.Options {
subPower := votingPower.Mul(option.Weight)
results[option.Option] = results[option.Option].Add(subPower)
}
totalVotingPower = totalVotingPower.Add(votingPower)
}
th.gk.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 {
subPower := votingPower.Mul(option.Weight)
results[option.Option] = results[option.Option].Add(subPower)
}
totalVotingPower = totalVotingPower.Add(votingPower)
}
tallyParams := th.gk.GetTallyParams(ctx)
tallyResults = types.NewTallyResultFromMap(results)
// TODO: Upgrade the spec to cover all of these cases & remove pseudocode.
// If there is no staked coins, the proposal fails
if th.stk.TotalBondedTokens(ctx).IsZero() {
return false, false, tallyResults
}
// If there is not enough quorum of votes, the proposal fails
percentVoting := totalVotingPower.Quo(th.stk.TotalBondedTokens(ctx).ToDec())
if percentVoting.LT(tallyParams.Quorum) {
return false, true, tallyResults
}
// If no one votes (everyone abstains), proposal fails
if totalVotingPower.Sub(results[types.OptionAbstain]).Equal(sdk.ZeroDec()) {
return false, false, tallyResults
}
// If more than 1/3 of voters veto, proposal fails
if results[types.OptionNoWithVeto].Quo(totalVotingPower).GT(tallyParams.VetoThreshold) {
return false, true, tallyResults
}
// If more than 1/2 of non-abstaining voters vote Yes, proposal passes
if results[types.OptionYes].Quo(totalVotingPower.Sub(results[types.OptionAbstain])).GT(tallyParams.Threshold) {
return true, false, tallyResults
}
// If more than 1/2 of non-abstaining voters vote No, proposal fails
return false, false, tallyResults
}
// bkavaByDenom a map of the bkava denom and the amount of bkava for that denom.
type bkavaByDenom map[string]sdk.Int
func (bkavaMap bkavaByDenom) add(coin sdk.Coin) {
_, found := bkavaMap[coin.Denom]
if !found {
bkavaMap[coin.Denom] = sdk.ZeroInt()
}
bkavaMap[coin.Denom].Add(coin.Amount)
}
func (bkavaMap bkavaByDenom) toCoins() sdk.Coins {
coins := sdk.Coins{}
for denom, amt := range bkavaMap {
coins.Add(sdk.NewCoin(denom, amt))
}
return coins.Sort()
}
// getAddrBkava returns a map of validator address & the amount of bkava
// of the addr for each validator.
func (th TallyHandler) getAddrBkava(ctx sdk.Context, addr sdk.AccAddress) bkavaByDenom {
results := make(bkavaByDenom)
th.addBkavaFromWallet(ctx, addr, results)
th.addBkavaFromSavings(ctx, addr, results)
th.addBkavaFromEarn(ctx, addr, results)
return results
}
// addBkavaFromWallet adds all addr balances of bkava in x/bank.
func (th TallyHandler) addBkavaFromWallet(ctx sdk.Context, addr sdk.AccAddress, bkava bkavaByDenom) {
coins := th.bk.GetAllBalances(ctx, addr)
for _, coin := range coins {
if th.lk.IsDerivativeDenom(ctx, coin.Denom) {
bkava.add(coin)
}
}
}
// addBkavaFromSavings adds all addr deposits of bkava in x/savings.
func (th TallyHandler) addBkavaFromSavings(ctx sdk.Context, addr sdk.AccAddress, bkava bkavaByDenom) {
deposit, found := th.svk.GetDeposit(ctx, addr)
if !found {
return
}
for _, coin := range deposit.Amount {
if th.lk.IsDerivativeDenom(ctx, coin.Denom) {
bkava.add(coin)
}
}
}
// addBkavaFromEarn adds all addr deposits of bkava in x/earn.
func (th TallyHandler) addBkavaFromEarn(ctx sdk.Context, addr sdk.AccAddress, bkava bkavaByDenom) {
shares, found := th.ek.GetVaultAccountShares(ctx, addr)
if !found {
return
}
for _, share := range shares {
if th.lk.IsDerivativeDenom(ctx, share.Denom) {
if coin, err := th.ek.ConvertToAssets(ctx, share); err != nil {
bkava.add(coin)
}
}
}
}