diff --git a/simapp/app.go b/simapp/app.go index 0c06a7946a7e..503d06d9aad2 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -315,7 +315,7 @@ func NewSimApp( */ govKeeper := govkeeper.NewKeeper( appCodec, keys[govtypes.StoreKey], app.GetSubspace(govtypes.ModuleName), app.AccountKeeper, app.BankKeeper, - &stakingKeeper, govRouter, app.MsgServiceRouter(), govConfig, + &stakingKeeper, nil, govRouter, app.MsgServiceRouter(), govConfig, ) app.GovKeeper = *govKeeper.SetHooks( diff --git a/x/gov/keeper/keeper.go b/x/gov/keeper/keeper.go index 4558075f48ac..945c2436bbb6 100644 --- a/x/gov/keeper/keeper.go +++ b/x/gov/keeper/keeper.go @@ -27,6 +27,8 @@ type Keeper struct { // The reference to the DelegationSet and ValidatorSet to get information about validators and delegators sk types.StakingKeeper + protocolStakingKeeper types.ProtocolStakingKeeper + // GovHooks hooks types.GovHooks @@ -54,7 +56,8 @@ type Keeper struct { // CONTRACT: the parameter Subspace must have the param key table already initialized func NewKeeper( cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace types.ParamSubspace, - authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, sk types.StakingKeeper, + authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, + sk types.StakingKeeper, protocolStakingKeeper types.ProtocolStakingKeeper, legacyRouter v1beta1.Router, router *baseapp.MsgServiceRouter, config types.Config, ) Keeper { @@ -74,15 +77,16 @@ func NewKeeper( } return Keeper{ - storeKey: key, - paramSpace: paramSpace, - authKeeper: authKeeper, - bankKeeper: bankKeeper, - sk: sk, - cdc: cdc, - legacyRouter: legacyRouter, - router: router, - config: config, + storeKey: key, + paramSpace: paramSpace, + authKeeper: authKeeper, + bankKeeper: bankKeeper, + sk: sk, + protocolStakingKeeper: protocolStakingKeeper, + cdc: cdc, + legacyRouter: legacyRouter, + router: router, + config: config, } } diff --git a/x/gov/keeper/tally.go b/x/gov/keeper/tally.go index 5959989b2490..8d5c9d1d4e93 100644 --- a/x/gov/keeper/tally.go +++ b/x/gov/keeper/tally.go @@ -8,6 +8,8 @@ import ( // TODO: Break into several smaller functions for clarity +// NOTE: We have to check if the KYVE Protocol staking keeper is defined to ensure minimal changes. + // 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) { @@ -33,6 +35,18 @@ func (keeper Keeper) Tally(ctx sdk.Context, proposal v1.Proposal) (passes bool, return false }) + // Fetch and insert all KYVE Protocol validators into list of current validators. + // NOTE: The key used is a normal "kyve1blah" address. + if keeper.protocolStakingKeeper != nil { + for _, rawVal := range keeper.protocolStakingKeeper.GetActiveValidators(ctx) { + // NOTE: We have to typecast to avoid creating import cycles when defining the function interfaces. + if val, ok := rawVal.(v1.ValidatorGovInfo); ok { + address := sdk.AccAddress(val.Address).String() + currValidators[address] = val + } + } + } + keeper.IterateVotes(ctx, proposal.Id, func(vote v1.Vote) bool { // if validator, just record it in the map voter := sdk.MustAccAddressFromBech32(vote.Voter) @@ -42,6 +56,11 @@ func (keeper Keeper) Tally(ctx sdk.Context, proposal v1.Proposal) (passes bool, val.Vote = vote.Options currValidators[valAddrStr] = val } + // Check if the voter is a KYVE Protocol validator. + if val, ok := currValidators[voter.String()]; 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) { @@ -67,6 +86,25 @@ func (keeper Keeper) Tally(ctx sdk.Context, proposal v1.Proposal) (passes bool, return false }) + if keeper.protocolStakingKeeper != nil { + validators, amounts := keeper.protocolStakingKeeper.GetDelegations(ctx, voter.String()) + for idx, address := range validators { + if val, ok := currValidators[address]; ok { + val.DelegatorDeductions = val.DelegatorDeductions.Add(amounts[idx]) + currValidators[address] = val + + votingPower := amounts[idx].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) + } + } + } + keeper.deleteVote(ctx, vote.ProposalId, voter) return false }) @@ -91,14 +129,21 @@ func (keeper Keeper) Tally(ctx sdk.Context, proposal v1.Proposal) (passes bool, tallyParams := keeper.GetTallyParams(ctx) tallyResults = v1.NewTallyResultFromMap(results) + totalBondedTokens := keeper.sk.TotalBondedTokens(ctx) + if keeper.protocolStakingKeeper != nil { + totalBondedTokens = totalBondedTokens.Add( + keeper.protocolStakingKeeper.TotalBondedTokens(ctx), + ) + } + // 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() { + if totalBondedTokens.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))) + percentVoting := totalVotingPower.Quo(sdk.NewDecFromInt(totalBondedTokens)) quorum, _ := sdk.NewDecFromStr(tallyParams.Quorum) if percentVoting.LT(quorum) { return false, false, tallyResults diff --git a/x/gov/types/expected_keepers.go b/x/gov/types/expected_keepers.go index 556387c62ac4..e2b7031585df 100644 --- a/x/gov/types/expected_keepers.go +++ b/x/gov/types/expected_keepers.go @@ -27,6 +27,14 @@ type StakingKeeper interface { ) } +// ProtocolStakingKeeper expected KYVE protocol staking keeper (Protocol Validator and Delegator sets) (noalias) +type ProtocolStakingKeeper interface { + GetActiveValidators(sdk.Context) []interface{} + + TotalBondedTokens(sdk.Context) math.Int + GetDelegations(sdk.Context, string) ([]string, []sdk.Dec) +} + // AccountKeeper defines the expected account keeper (noalias) type AccountKeeper interface { GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI