From ea1e4effd1f311ad2bf9dce00cf4578b6ab03411 Mon Sep 17 00:00:00 2001 From: dongsam Date: Fri, 11 Mar 2022 15:21:12 +0900 Subject: [PATCH] feat: add additional voting powers hook on gov (cherry picked from commit d76f9225b259c4705dd4c368c4c0a4b6f8f673cd) --- x/gov/keeper/hooks.go | 7 +++++++ x/gov/keeper/hooks_test.go | 6 ++++++ x/gov/keeper/tally.go | 27 +++++++++++++++++++++++---- x/gov/spec/02_state.md | 10 ++++++++-- x/gov/types/expected_keepers.go | 2 ++ x/gov/types/hooks.go | 5 +++++ x/gov/types/vote.go | 6 +++++- 7 files changed, 56 insertions(+), 7 deletions(-) diff --git a/x/gov/keeper/hooks.go b/x/gov/keeper/hooks.go index a63efdcc74f4..c308f37d8587 100644 --- a/x/gov/keeper/hooks.go +++ b/x/gov/keeper/hooks.go @@ -42,3 +42,10 @@ func (keeper Keeper) AfterProposalVotingPeriodEnded(ctx sdk.Context, proposalID keeper.hooks.AfterProposalVotingPeriodEnded(ctx, proposalID) } } + +// SetAdditionalVotingPowers - call hook if registered +func (keeper Keeper) SetAdditionalVotingPowers(ctx sdk.Context, votes types.Votes, votingPowers *types.AdditionalVotingPowers) { + if keeper.hooks != nil { + keeper.hooks.SetAdditionalVotingPowers(ctx, votes, votingPowers) + } +} diff --git a/x/gov/keeper/hooks_test.go b/x/gov/keeper/hooks_test.go index 536fc198304d..2cc3d53895f8 100644 --- a/x/gov/keeper/hooks_test.go +++ b/x/gov/keeper/hooks_test.go @@ -23,6 +23,7 @@ type MockGovHooksReceiver struct { AfterProposalVoteValid bool AfterProposalFailedMinDepositValid bool AfterProposalVotingPeriodEndedValid bool + SetAdditionalVotingPowersValid bool } func (h *MockGovHooksReceiver) AfterProposalSubmission(ctx sdk.Context, proposalID uint64) { @@ -42,6 +43,9 @@ func (h *MockGovHooksReceiver) AfterProposalFailedMinDeposit(ctx sdk.Context, pr func (h *MockGovHooksReceiver) AfterProposalVotingPeriodEnded(ctx sdk.Context, proposalID uint64) { h.AfterProposalVotingPeriodEndedValid = true } +func (h *MockGovHooksReceiver) SetAdditionalVotingPowers(ctx sdk.Context, votes types.Votes, votingPowers *types.AdditionalVotingPowers) { + h.SetAdditionalVotingPowersValid = true +} func TestHooks(t *testing.T) { app := simapp.Setup(false) @@ -61,6 +65,7 @@ func TestHooks(t *testing.T) { require.False(t, govHooksReceiver.AfterProposalVoteValid) require.False(t, govHooksReceiver.AfterProposalFailedMinDepositValid) require.False(t, govHooksReceiver.AfterProposalVotingPeriodEndedValid) + require.False(t, govHooksReceiver.SetAdditionalVotingPowersValid) tp := TestProposal _, err := app.GovKeeper.SubmitProposal(ctx, tp) @@ -91,4 +96,5 @@ func TestHooks(t *testing.T) { ctx = ctx.WithBlockHeader(newHeader) gov.EndBlocker(ctx, app.GovKeeper) require.True(t, govHooksReceiver.AfterProposalVotingPeriodEndedValid) + require.True(t, govHooksReceiver.SetAdditionalVotingPowersValid) } diff --git a/x/gov/keeper/tally.go b/x/gov/keeper/tally.go index f1f1f325455c..1ed0c8f82bf3 100644 --- a/x/gov/keeper/tally.go +++ b/x/gov/keeper/tally.go @@ -33,10 +33,12 @@ func (keeper Keeper) Tally(ctx sdk.Context, proposal types.Proposal) (passes boo return false }) - keeper.IterateVotes(ctx, proposal.ProposalId, func(vote types.Vote) bool { + additionalVotingPower := types.AdditionalVotingPowers{} + votes := keeper.GetVotes(ctx, proposal.ProposalId) + keeper.hooks.SetAdditionalVotingPowers(ctx, votes, &additionalVotingPower) + for _, vote := range votes { // if validator, just record it in the map voter, err := sdk.AccAddressFromBech32(vote.Voter) - if err != nil { panic(err) } @@ -47,6 +49,24 @@ func (keeper Keeper) Tally(ctx sdk.Context, proposal types.Proposal) (passes boo currValidators[valAddrStr] = val } + if aVote, ok := additionalVotingPower[vote.Voter]; ok { + for valAddrStr, votingPower := range aVote { + if val, ok := currValidators[valAddrStr]; ok && val.BondedTokens.IsPositive() { + + // total shares * voting power tokens / bonded + delShares := val.DelegatorShares.MulInt(votingPower.TruncateInt()).QuoInt(val.BondedTokens) + val.DelegatorDeductions = val.DelegatorDeductions.Add(delShares) + currValidators[valAddrStr] = val + + for _, option := range vote.Options { + subPower := votingPower.Mul(option.Weight) + results[option.Option] = results[option.Option].Add(subPower) + } + totalVotingPower = totalVotingPower.Add(votingPower) + } + } + } + // 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() @@ -71,8 +91,7 @@ func (keeper Keeper) Tally(ctx sdk.Context, proposal types.Proposal) (passes boo }) keeper.deleteVote(ctx, vote.ProposalId, voter) - return false - }) + } // iterate over the validators again to tally their voting power for _, val := range currValidators { diff --git a/x/gov/spec/02_state.md b/x/gov/spec/02_state.md index 269ff69272fc..4800002462fe 100644 --- a/x/gov/spec/02_state.md +++ b/x/gov/spec/02_state.md @@ -159,8 +159,14 @@ And the pseudocode for the `ProposalProcessingQueue`: tmpValMap(validator.OperatorAddr).Minus = 0 // Tally - voterIterator = rangeQuery(Governance, ) //return all the addresses that voted on the proposal - for each (voterAddress, vote) in voterIterator + voters = rangeQuery(Governance, ) //return all the addresses that voted on the proposal + // set additional voting powers by hooking other modules + additionalVotingPowersMap = SetAdditionalVotingPowers(voters) + for each (voterAddress, vote) in voters + for each (validator, votingPower) in additionalVotingPowersMap[voterAddress] + tmpValMap(validator).Minus += votingPower.Shares + proposal.updateTally(vote, votingPower.Shares) + delegations = stakingKeeper.getDelegations(voterAddress) // get all delegations for current voter for each delegation in delegations diff --git a/x/gov/types/expected_keepers.go b/x/gov/types/expected_keepers.go index a6e521647f31..dc6fdd333f7d 100644 --- a/x/gov/types/expected_keepers.go +++ b/x/gov/types/expected_keepers.go @@ -60,4 +60,6 @@ type GovHooks interface { AfterProposalVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) // Must be called after a vote on a proposal is cast AfterProposalFailedMinDeposit(ctx sdk.Context, proposalID uint64) // Must be called when proposal fails to reach min deposit AfterProposalVotingPeriodEnded(ctx sdk.Context, proposalID uint64) // Must be called when proposal's finishes it's voting period + // SetAdditionalVotingPowers is a hook for calculating and setting additional voting power for votes in modules other than gov. + SetAdditionalVotingPowers(ctx sdk.Context, votes Votes, votingPowers *AdditionalVotingPowers) // Must be called after get votes on tally } diff --git a/x/gov/types/hooks.go b/x/gov/types/hooks.go index e90b19a08395..9a1a52544895 100644 --- a/x/gov/types/hooks.go +++ b/x/gov/types/hooks.go @@ -40,3 +40,8 @@ func (h MultiGovHooks) AfterProposalVotingPeriodEnded(ctx sdk.Context, proposalI h[i].AfterProposalVotingPeriodEnded(ctx, proposalID) } } +func (h MultiGovHooks) SetAdditionalVotingPowers(ctx sdk.Context, votes Votes, votingPowers *AdditionalVotingPowers) { + for i := range h { + h[i].SetAdditionalVotingPowers(ctx, votes, votingPowers) + } +} diff --git a/x/gov/types/vote.go b/x/gov/types/vote.go index 03d1e5a44f42..86240776ebc0 100644 --- a/x/gov/types/vote.go +++ b/x/gov/types/vote.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - yaml "gopkg.in/yaml.v2" + "gopkg.in/yaml.v2" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -24,6 +24,10 @@ func (v Vote) String() string { // Votes is a collection of Vote objects type Votes []Vote +// AdditionalVotingPowers is additional votingPower map by validators by voters +// voter addr => (validator addr => additional voting power) +type AdditionalVotingPowers map[string]map[string]sdk.Dec + // Equal returns true if two slices (order-dependant) of votes are equal. func (v Votes) Equal(other Votes) bool { if len(v) != len(other) {