From ed70bf336f6d1906967ded4fa12d5b19270a4fb1 Mon Sep 17 00:00:00 2001 From: Alex | Interchain Labs Date: Mon, 7 Apr 2025 08:51:09 -0400 Subject: [PATCH] feat: custom gov tally fn (#24355) Co-authored-by: Tyler <48813565+technicallyty@users.noreply.github.com> --- CHANGELOG.md | 1 + simapp/app.go | 1 + tests/e2e/gov/tx.go | 14 ++-- x/gov/depinject.go | 141 +++++++++++++++++++++++++++++++++++++++ x/gov/keeper/deposit.go | 74 ++++++++++---------- x/gov/keeper/keeper.go | 60 +++++++++++------ x/gov/keeper/proposal.go | 68 +++++++++---------- x/gov/keeper/tally.go | 120 +++++++++++++++++++++------------ x/gov/keeper/vote.go | 14 ++-- x/gov/module.go | 118 -------------------------------- 10 files changed, 346 insertions(+), 265 deletions(-) create mode 100644 x/gov/depinject.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 7df6e7d2d0..260315e044 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (x/protocolpool) [#23933](https://github.com/cosmos/cosmos-sdk/pull/23933) Add x/protocolpool module. * x/distribution can now utilize an externally managed community pool. NOTE: this will make the message handlers for FundCommunityPool and CommunityPoolSpend error, as well as the query handler for CommunityPool. * (client) [#18101](https://github.com/cosmos/cosmos-sdk/pull/18101) Add a `keyring-default-keyname` in `client.toml` for specifying a default key name, and skip the need to use the `--from` flag when signing transactions. +* (x/gov) [#24355](https://github.com/cosmos/cosmos-sdk/pull/24355) Allow users to set a custom CalculateVoteResultsAndVotingPower function to be used in govkeeper.Tally. ### Improvements diff --git a/simapp/app.go b/simapp/app.go index 5929ea44b6..15fa80aaa4 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -461,6 +461,7 @@ func NewSimApp( app.MsgServiceRouter(), govConfig, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + // govkeeper.WithCustomCalculateVoteResultsAndVotingPowerFn(...), // Add if you want to use a custom vote calculation function. ) // Set legacy router for backwards compatibility with gov v1beta1 diff --git a/tests/e2e/gov/tx.go b/tests/e2e/gov/tx.go index d804c35c1a..30bc06c821 100644 --- a/tests/e2e/gov/tx.go +++ b/tests/e2e/gov/tx.go @@ -211,7 +211,7 @@ func (s *E2ETestSuite) TestNewCmdSubmitLegacyProposal() { { "invalid proposal (file)", []string{ - fmt.Sprintf("--%s=%s", cli.FlagProposal, invalidPropFile.Name()), //nolint:staticcheck // we are intentionally using a deprecated flag here. + fmt.Sprintf("--%s=%s", cli.FlagProposal, invalidPropFile.Name()), //nolint:staticcheck // keep deprecated flag for test fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, math.NewInt(10))).String()), @@ -221,8 +221,8 @@ func (s *E2ETestSuite) TestNewCmdSubmitLegacyProposal() { { "invalid proposal", []string{ - fmt.Sprintf("--%s='Where is the title!?'", cli.FlagDescription), //nolint:staticcheck // we are intentionally using a deprecated flag here. - fmt.Sprintf("--%s=%s", cli.FlagProposalType, v1beta1.ProposalTypeText), //nolint:staticcheck // we are intentionally using a deprecated flag here. + fmt.Sprintf("--%s='Where is the title!?'", cli.FlagDescription), //nolint:staticcheck // keep deprecated flag for test + fmt.Sprintf("--%s=%s", cli.FlagProposalType, v1beta1.ProposalTypeText), //nolint:staticcheck // keep deprecated flag for test fmt.Sprintf("--%s=%s", cli.FlagDeposit, sdk.NewCoin(s.cfg.BondDenom, math.NewInt(10000)).String()), fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), @@ -232,9 +232,9 @@ func (s *E2ETestSuite) TestNewCmdSubmitLegacyProposal() { }, { "valid transaction (file)", - //nolint:staticcheck // we are intentionally using a deprecated flag here. + []string{ - fmt.Sprintf("--%s=%s", cli.FlagProposal, validPropFile.Name()), + fmt.Sprintf("--%s=%s", cli.FlagProposal, validPropFile.Name()), //nolint:staticcheck // keep deprecated flag for test fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), @@ -246,8 +246,8 @@ func (s *E2ETestSuite) TestNewCmdSubmitLegacyProposal() { "valid transaction", []string{ fmt.Sprintf("--%s='Text Proposal'", cli.FlagTitle), - fmt.Sprintf("--%s='Where is the title!?'", cli.FlagDescription), //nolint:staticcheck // we are intentionally using a deprecated flag here. - fmt.Sprintf("--%s=%s", cli.FlagProposalType, v1beta1.ProposalTypeText), //nolint:staticcheck // we are intentionally using a deprecated flag here. + fmt.Sprintf("--%s='Where is the title!?'", cli.FlagDescription), //nolint:staticcheck // keep deprecated flag for test + fmt.Sprintf("--%s=%s", cli.FlagProposalType, v1beta1.ProposalTypeText), //nolint:staticcheck // keep deprecated flag for test fmt.Sprintf("--%s=%s", cli.FlagDeposit, sdk.NewCoin(s.cfg.BondDenom, math.NewInt(100000)).String()), fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), diff --git a/x/gov/depinject.go b/x/gov/depinject.go new file mode 100644 index 0000000000..737d6fec44 --- /dev/null +++ b/x/gov/depinject.go @@ -0,0 +1,141 @@ +package gov + +import ( + "fmt" + "maps" + "slices" + "sort" + "strings" + + modulev1 "cosmossdk.io/api/cosmos/gov/module/v1" + "cosmossdk.io/core/appmodule" + "cosmossdk.io/core/store" + "cosmossdk.io/depinject" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/gov/keeper" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" +) + +func init() { + appmodule.Register( + &modulev1.Module{}, + appmodule.Provide(ProvideModule, ProvideKeyTable), + appmodule.Invoke(InvokeAddRoutes, InvokeSetHooks)) +} + +type ModuleInputs struct { + depinject.In + + Config *modulev1.Module + Cdc codec.Codec + StoreService store.KVStoreService + ModuleKey depinject.OwnModuleKey + MsgServiceRouter baseapp.MessageRouter + + AccountKeeper govtypes.AccountKeeper + BankKeeper govtypes.BankKeeper + StakingKeeper govtypes.StakingKeeper + DistributionKeeper govtypes.DistributionKeeper + + // CustomCalculateVoteResultsAndVotingPowerFn is an optional input to set a custom CalculateVoteResultsAndVotingPowerFn. + // If this function is not provided, the default function is used. + CustomCalculateVoteResultsAndVotingPowerFn keeper.CalculateVoteResultsAndVotingPowerFn `optional:"true"` + + // LegacySubspace is used solely for migration of x/params managed parameters + LegacySubspace govtypes.ParamSubspace `optional:"true"` +} + +type ModuleOutputs struct { + depinject.Out + + Module appmodule.AppModule + Keeper *keeper.Keeper + HandlerRoute v1beta1.HandlerRoute +} + +func ProvideModule(in ModuleInputs) ModuleOutputs { + defaultConfig := govtypes.DefaultConfig() + if in.Config.MaxMetadataLen != 0 { + defaultConfig.MaxMetadataLen = in.Config.MaxMetadataLen + } + + // default to governance authority if not provided + authority := authtypes.NewModuleAddress(govtypes.ModuleName) + if in.Config.Authority != "" { + authority = authtypes.NewModuleAddressOrBech32Address(in.Config.Authority) + } + + var opts []keeper.InitOption + if in.CustomCalculateVoteResultsAndVotingPowerFn != nil { + opts = append(opts, keeper.WithCustomCalculateVoteResultsAndVotingPowerFn(in.CustomCalculateVoteResultsAndVotingPowerFn)) + } + + k := keeper.NewKeeper( + in.Cdc, + in.StoreService, + in.AccountKeeper, + in.BankKeeper, + in.StakingKeeper, + in.DistributionKeeper, + in.MsgServiceRouter, + defaultConfig, + authority.String(), + opts..., + ) + m := NewAppModule(in.Cdc, k, in.AccountKeeper, in.BankKeeper, in.LegacySubspace) + hr := v1beta1.HandlerRoute{Handler: v1beta1.ProposalHandler, RouteKey: govtypes.RouterKey} + + return ModuleOutputs{Module: m, Keeper: k, HandlerRoute: hr} +} + +func ProvideKeyTable() paramtypes.KeyTable { + return v1.ParamKeyTable() //nolint:staticcheck // we still need this for upgrades +} + +func InvokeAddRoutes(keeper *keeper.Keeper, routes []v1beta1.HandlerRoute) { + if keeper == nil || routes == nil { + return + } + + // Default route order is a lexical sort by RouteKey. + // Explicit ordering can be added to the module config if required. + slices.SortFunc(routes, func(x, y v1beta1.HandlerRoute) int { + return strings.Compare(x.RouteKey, y.RouteKey) + }) + + router := v1beta1.NewRouter() + for _, r := range routes { + router.AddRoute(r.RouteKey, r.Handler) + } + keeper.SetLegacyRouter(router) +} + +func InvokeSetHooks(keeper *keeper.Keeper, govHooks map[string]govtypes.GovHooksWrapper) error { + if keeper == nil || govHooks == nil { + return nil + } + + // Default ordering is lexical by module name. + // Explicit ordering can be added to the module config if required. + modNames := slices.Sorted(maps.Keys(govHooks)) + order := modNames + sort.Strings(order) + + var multiHooks govtypes.MultiGovHooks + for _, modName := range order { + hook, ok := govHooks[modName] + if !ok { + return fmt.Errorf("can't find staking hooks for module %s", modName) + } + multiHooks = append(multiHooks, hook) + } + + keeper.SetHooks(multiHooks) + return nil +} diff --git a/x/gov/keeper/deposit.go b/x/gov/keeper/deposit.go index 9229c30432..af4979504e 100644 --- a/x/gov/keeper/deposit.go +++ b/x/gov/keeper/deposit.go @@ -17,17 +17,17 @@ import ( ) // SetDeposit sets a Deposit to the gov store -func (keeper Keeper) SetDeposit(ctx context.Context, deposit v1.Deposit) error { - depositor, err := keeper.authKeeper.AddressCodec().StringToBytes(deposit.Depositor) +func (k Keeper) SetDeposit(ctx context.Context, deposit v1.Deposit) error { + depositor, err := k.authKeeper.AddressCodec().StringToBytes(deposit.Depositor) if err != nil { return err } - return keeper.Deposits.Set(ctx, collections.Join(deposit.ProposalId, sdk.AccAddress(depositor)), deposit) + return k.Deposits.Set(ctx, collections.Join(deposit.ProposalId, sdk.AccAddress(depositor)), deposit) } // GetDeposits returns all the deposits of a proposal -func (keeper Keeper) GetDeposits(ctx context.Context, proposalID uint64) (deposits v1.Deposits, err error) { - err = keeper.IterateDeposits(ctx, proposalID, func(_ collections.Pair[uint64, sdk.AccAddress], deposit v1.Deposit) (bool, error) { +func (k Keeper) GetDeposits(ctx context.Context, proposalID uint64) (deposits v1.Deposits, err error) { + err = k.IterateDeposits(ctx, proposalID, func(_ collections.Pair[uint64, sdk.AccAddress], deposit v1.Deposit) (bool, error) { deposits = append(deposits, &deposit) return false, nil }) @@ -35,23 +35,23 @@ func (keeper Keeper) GetDeposits(ctx context.Context, proposalID uint64) (deposi } // DeleteAndBurnDeposits deletes and burns all the deposits on a specific proposal. -func (keeper Keeper) DeleteAndBurnDeposits(ctx context.Context, proposalID uint64) error { +func (k Keeper) DeleteAndBurnDeposits(ctx context.Context, proposalID uint64) error { coinsToBurn := sdk.NewCoins() - err := keeper.IterateDeposits(ctx, proposalID, func(key collections.Pair[uint64, sdk.AccAddress], deposit v1.Deposit) (stop bool, err error) { + err := k.IterateDeposits(ctx, proposalID, func(key collections.Pair[uint64, sdk.AccAddress], deposit v1.Deposit) (stop bool, err error) { coinsToBurn = coinsToBurn.Add(deposit.Amount...) - return false, keeper.Deposits.Remove(ctx, key) + return false, k.Deposits.Remove(ctx, key) }) if err != nil { return err } - return keeper.bankKeeper.BurnCoins(ctx, types.ModuleName, coinsToBurn) + return k.bankKeeper.BurnCoins(ctx, types.ModuleName, coinsToBurn) } // IterateDeposits iterates over all the proposals deposits and performs a callback function -func (keeper Keeper) IterateDeposits(ctx context.Context, proposalID uint64, cb func(key collections.Pair[uint64, sdk.AccAddress], value v1.Deposit) (bool, error)) error { +func (k Keeper) IterateDeposits(ctx context.Context, proposalID uint64, cb func(key collections.Pair[uint64, sdk.AccAddress], value v1.Deposit) (bool, error)) error { rng := collections.NewPrefixedPairRange[uint64, sdk.AccAddress](proposalID) - err := keeper.Deposits.Walk(ctx, rng, cb) + err := k.Deposits.Walk(ctx, rng, cb) if err != nil { return err } @@ -60,9 +60,9 @@ func (keeper Keeper) IterateDeposits(ctx context.Context, proposalID uint64, cb // AddDeposit adds or updates a deposit of a specific depositor on a specific proposal. // Activates voting period when appropriate and returns true in that case, else returns false. -func (keeper Keeper) AddDeposit(ctx context.Context, proposalID uint64, depositorAddr sdk.AccAddress, depositAmount sdk.Coins) (bool, error) { +func (k Keeper) AddDeposit(ctx context.Context, proposalID uint64, depositorAddr sdk.AccAddress, depositAmount sdk.Coins) (bool, error) { // Checks to see if proposal exists - proposal, err := keeper.Proposals.Get(ctx, proposalID) + proposal, err := k.Proposals.Get(ctx, proposalID) if err != nil { return false, err } @@ -73,7 +73,7 @@ func (keeper Keeper) AddDeposit(ctx context.Context, proposalID uint64, deposito } // Check coins to be deposited match the proposal's deposit params - params, err := keeper.Params.Get(ctx) + params, err := k.Params.Get(ctx) if err != nil { return false, err } @@ -85,7 +85,7 @@ func (keeper Keeper) AddDeposit(ctx context.Context, proposalID uint64, deposito } // the deposit must only contain valid denoms (listed in the min deposit param) - if err := keeper.validateDepositDenom(ctx, params, depositAmount); err != nil { + if err := k.validateDepositDenom(ctx, params, depositAmount); err != nil { return false, err } @@ -121,14 +121,14 @@ func (keeper Keeper) AddDeposit(ctx context.Context, proposalID uint64, deposito } // update the governance module's account coins pool - err = keeper.bankKeeper.SendCoinsFromAccountToModule(ctx, depositorAddr, types.ModuleName, depositAmount) + err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, depositorAddr, types.ModuleName, depositAmount) if err != nil { return false, err } // Update proposal proposal.TotalDeposit = sdk.NewCoins(proposal.TotalDeposit...).Add(depositAmount...) - err = keeper.SetProposal(ctx, proposal) + err = k.SetProposal(ctx, proposal) if err != nil { return false, err } @@ -136,7 +136,7 @@ func (keeper Keeper) AddDeposit(ctx context.Context, proposalID uint64, deposito // Check if deposit has provided sufficient total funds to transition the proposal into the voting period activatedVotingPeriod := false if proposal.Status == v1.StatusDepositPeriod && sdk.NewCoins(proposal.TotalDeposit...).IsAllGTE(minDepositAmount) { - err = keeper.ActivateVotingPeriod(ctx, proposal) + err = k.ActivateVotingPeriod(ctx, proposal) if err != nil { return false, err } @@ -145,7 +145,7 @@ func (keeper Keeper) AddDeposit(ctx context.Context, proposalID uint64, deposito } // Add or update deposit object - deposit, err := keeper.Deposits.Get(ctx, collections.Join(proposalID, depositorAddr)) + deposit, err := k.Deposits.Get(ctx, collections.Join(proposalID, depositorAddr)) switch { case err == nil: // deposit exists @@ -159,7 +159,7 @@ func (keeper Keeper) AddDeposit(ctx context.Context, proposalID uint64, deposito } // called when deposit has been added to a proposal, however the proposal may not be active - err = keeper.Hooks().AfterProposalDeposit(ctx, proposalID, depositorAddr) + err = k.Hooks().AfterProposalDeposit(ctx, proposalID, depositorAddr) if err != nil { return false, err } @@ -174,7 +174,7 @@ func (keeper Keeper) AddDeposit(ctx context.Context, proposalID uint64, deposito ), ) - err = keeper.SetDeposit(ctx, deposit) + err = k.SetDeposit(ctx, deposit) if err != nil { return false, err } @@ -185,17 +185,17 @@ func (keeper Keeper) AddDeposit(ctx context.Context, proposalID uint64, deposito // ChargeDeposit will charge proposal cancellation fee (deposits * proposal_cancel_burn_rate) and // send to a destAddress if defined or burn otherwise. // Remaining funds are send back to the depositor. -func (keeper Keeper) ChargeDeposit(ctx context.Context, proposalID uint64, destAddress, proposalCancelRate string) error { +func (k Keeper) ChargeDeposit(ctx context.Context, proposalID uint64, destAddress, proposalCancelRate string) error { rate := sdkmath.LegacyMustNewDecFromStr(proposalCancelRate) var cancellationCharges sdk.Coins - deposits, err := keeper.GetDeposits(ctx, proposalID) + deposits, err := k.GetDeposits(ctx, proposalID) if err != nil { return err } for _, deposit := range deposits { - depositerAddress, err := keeper.authKeeper.AddressCodec().StringToBytes(deposit.Depositor) + depositerAddress, err := k.authKeeper.AddressCodec().StringToBytes(deposit.Depositor) if err != nil { return err } @@ -220,14 +220,14 @@ func (keeper Keeper) ChargeDeposit(ctx context.Context, proposalID uint64, destA } if !remainingAmount.IsZero() { - err := keeper.bankKeeper.SendCoinsFromModuleToAccount( + err := k.bankKeeper.SendCoinsFromModuleToAccount( ctx, types.ModuleName, depositerAddress, remainingAmount, ) if err != nil { return err } } - err = keeper.Deposits.Remove(ctx, collections.Join(deposit.ProposalId, sdk.AccAddress(depositerAddress))) + err = k.Deposits.Remove(ctx, collections.Join(deposit.ProposalId, sdk.AccAddress(depositerAddress))) if err != nil { return err } @@ -236,25 +236,25 @@ func (keeper Keeper) ChargeDeposit(ctx context.Context, proposalID uint64, destA // burn the cancellation fee or sent the cancellation charges to destination address. if !cancellationCharges.IsZero() { // get the distribution module account address - distributionAddress := keeper.authKeeper.GetModuleAddress(disttypes.ModuleName) + distributionAddress := k.authKeeper.GetModuleAddress(disttypes.ModuleName) switch { case destAddress == "": // burn the cancellation charges from deposits - err := keeper.bankKeeper.BurnCoins(ctx, types.ModuleName, cancellationCharges) + err := k.bankKeeper.BurnCoins(ctx, types.ModuleName, cancellationCharges) if err != nil { return err } case distributionAddress.String() == destAddress: - err := keeper.distrKeeper.FundCommunityPool(ctx, cancellationCharges, keeper.ModuleAccountAddress()) + err := k.distrKeeper.FundCommunityPool(ctx, cancellationCharges, k.ModuleAccountAddress()) if err != nil { return err } default: - destAccAddress, err := keeper.authKeeper.AddressCodec().StringToBytes(destAddress) + destAccAddress, err := k.authKeeper.AddressCodec().StringToBytes(destAddress) if err != nil { return err } - err = keeper.bankKeeper.SendCoinsFromModuleToAccount( + err = k.bankKeeper.SendCoinsFromModuleToAccount( ctx, types.ModuleName, destAccAddress, cancellationCharges, ) if err != nil { @@ -267,14 +267,14 @@ func (keeper Keeper) ChargeDeposit(ctx context.Context, proposalID uint64, destA } // RefundAndDeleteDeposits refunds and deletes all the deposits on a specific proposal. -func (keeper Keeper) RefundAndDeleteDeposits(ctx context.Context, proposalID uint64) error { - return keeper.IterateDeposits(ctx, proposalID, func(key collections.Pair[uint64, sdk.AccAddress], deposit v1.Deposit) (bool, error) { +func (k Keeper) RefundAndDeleteDeposits(ctx context.Context, proposalID uint64) error { + return k.IterateDeposits(ctx, proposalID, func(key collections.Pair[uint64, sdk.AccAddress], deposit v1.Deposit) (bool, error) { depositor := key.K2() - err := keeper.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, depositor, deposit.Amount) + err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, depositor, deposit.Amount) if err != nil { return false, err } - err = keeper.Deposits.Remove(ctx, key) + err = k.Deposits.Remove(ctx, key) return false, err }) } @@ -282,7 +282,7 @@ func (keeper Keeper) RefundAndDeleteDeposits(ctx context.Context, proposalID uin // validateInitialDeposit validates if initial deposit is greater than or equal to the minimum // required at the time of proposal submission. This threshold amount is determined by // the deposit parameters. Returns nil on success, error otherwise. -func (keeper Keeper) validateInitialDeposit(ctx context.Context, params v1.Params, initialDeposit sdk.Coins, expedited bool) error { +func (k Keeper) validateInitialDeposit(ctx context.Context, params v1.Params, initialDeposit sdk.Coins, expedited bool) error { if !initialDeposit.IsValid() || initialDeposit.IsAnyNegative() { return errors.Wrap(sdkerrors.ErrInvalidCoins, initialDeposit.String()) } @@ -312,7 +312,7 @@ func (keeper Keeper) validateInitialDeposit(ctx context.Context, params v1.Param } // validateDepositDenom validates if the deposit denom is accepted by the governance module. -func (keeper Keeper) validateDepositDenom(ctx context.Context, params v1.Params, depositAmount sdk.Coins) error { +func (k Keeper) validateDepositDenom(ctx context.Context, params v1.Params, depositAmount sdk.Coins) error { denoms := []string{} acceptedDenoms := make(map[string]bool, len(params.MinDeposit)) for _, coin := range params.MinDeposit { diff --git a/x/gov/keeper/keeper.go b/x/gov/keeper/keeper.go index 63ff7c2527..ffb25d98e7 100644 --- a/x/gov/keeper/keeper.go +++ b/x/gov/keeper/keeper.go @@ -43,6 +43,8 @@ type Keeper struct { config types.Config + calculateVoteResultsAndVotingPowerFn CalculateVoteResultsAndVotingPowerFn + // the address capable of executing a MsgUpdateParams message. Typically, this // should be the x/gov module account. authority string @@ -59,6 +61,20 @@ type Keeper struct { VotingPeriodProposals collections.Map[uint64, []byte] // TODO(tip): this could be a keyset or index. } +type InitOption func(*Keeper) + +// WithCustomCalculateVoteResultsAndVotingPowerFn is an optional input to set a custom CalculateVoteResultsAndVotingPowerFn. +// If this function is not provided, the default function is used. +func WithCustomCalculateVoteResultsAndVotingPowerFn(calculateVoteResultsAndVotingPowerFn CalculateVoteResultsAndVotingPowerFn) InitOption { + return func(k *Keeper) { + if calculateVoteResultsAndVotingPowerFn == nil { + panic("calculateVoteResultsAndVotingPowerFn cannot be nil") + } + + k.calculateVoteResultsAndVotingPowerFn = calculateVoteResultsAndVotingPowerFn + } +} + // GetAuthority returns the x/gov module's authority. func (k Keeper) GetAuthority() string { return k.authority @@ -74,7 +90,7 @@ func (k Keeper) GetAuthority() string { func NewKeeper( cdc codec.Codec, storeService corestoretypes.KVStoreService, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, sk types.StakingKeeper, distrKeeper types.DistributionKeeper, - router baseapp.MessageRouter, config types.Config, authority string, + router baseapp.MessageRouter, config types.Config, authority string, initOptions ...InitOption, ) *Keeper { // ensure governance module account is set if addr := authKeeper.GetModuleAddress(types.ModuleName); addr == nil { @@ -92,25 +108,31 @@ func NewKeeper( sb := collections.NewSchemaBuilder(storeService) k := &Keeper{ - storeService: storeService, - authKeeper: authKeeper, - bankKeeper: bankKeeper, - distrKeeper: distrKeeper, - sk: sk, - cdc: cdc, - router: router, - config: config, - authority: authority, - Constitution: collections.NewItem(sb, types.ConstitutionKey, "constitution", collections.StringValue), - Params: collections.NewItem(sb, types.ParamsKey, "params", codec.CollValue[v1.Params](cdc)), - Deposits: collections.NewMap(sb, types.DepositsKeyPrefix, "deposits", collections.PairKeyCodec(collections.Uint64Key, sdk.LengthPrefixedAddressKey(sdk.AccAddressKey)), codec.CollValue[v1.Deposit](cdc)), // nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility - Votes: collections.NewMap(sb, types.VotesKeyPrefix, "votes", collections.PairKeyCodec(collections.Uint64Key, sdk.LengthPrefixedAddressKey(sdk.AccAddressKey)), codec.CollValue[v1.Vote](cdc)), // nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility - ProposalID: collections.NewSequence(sb, types.ProposalIDKey, "proposal_id"), - Proposals: collections.NewMap(sb, types.ProposalsKeyPrefix, "proposals", collections.Uint64Key, codec.CollValue[v1.Proposal](cdc)), - ActiveProposalsQueue: collections.NewMap(sb, types.ActiveProposalQueuePrefix, "active_proposals_queue", collections.PairKeyCodec(sdk.TimeKey, collections.Uint64Key), collections.Uint64Value), // sdk.TimeKey is needed to retain state compatibility - InactiveProposalsQueue: collections.NewMap(sb, types.InactiveProposalQueuePrefix, "inactive_proposals_queue", collections.PairKeyCodec(sdk.TimeKey, collections.Uint64Key), collections.Uint64Value), // sdk.TimeKey is needed to retain state compatibility - VotingPeriodProposals: collections.NewMap(sb, types.VotingPeriodProposalKeyPrefix, "voting_period_proposals", collections.Uint64Key, collections.BytesValue), + storeService: storeService, + authKeeper: authKeeper, + bankKeeper: bankKeeper, + distrKeeper: distrKeeper, + sk: sk, + cdc: cdc, + router: router, + config: config, + calculateVoteResultsAndVotingPowerFn: defaultCalculateVoteResultsAndVotingPower, + authority: authority, + Constitution: collections.NewItem(sb, types.ConstitutionKey, "constitution", collections.StringValue), + Params: collections.NewItem(sb, types.ParamsKey, "params", codec.CollValue[v1.Params](cdc)), + Deposits: collections.NewMap(sb, types.DepositsKeyPrefix, "deposits", collections.PairKeyCodec(collections.Uint64Key, sdk.LengthPrefixedAddressKey(sdk.AccAddressKey)), codec.CollValue[v1.Deposit](cdc)), // nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility + Votes: collections.NewMap(sb, types.VotesKeyPrefix, "votes", collections.PairKeyCodec(collections.Uint64Key, sdk.LengthPrefixedAddressKey(sdk.AccAddressKey)), codec.CollValue[v1.Vote](cdc)), // nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility + ProposalID: collections.NewSequence(sb, types.ProposalIDKey, "proposal_id"), + Proposals: collections.NewMap(sb, types.ProposalsKeyPrefix, "proposals", collections.Uint64Key, codec.CollValue[v1.Proposal](cdc)), + ActiveProposalsQueue: collections.NewMap(sb, types.ActiveProposalQueuePrefix, "active_proposals_queue", collections.PairKeyCodec(sdk.TimeKey, collections.Uint64Key), collections.Uint64Value), // sdk.TimeKey is needed to retain state compatibility + InactiveProposalsQueue: collections.NewMap(sb, types.InactiveProposalQueuePrefix, "inactive_proposals_queue", collections.PairKeyCodec(sdk.TimeKey, collections.Uint64Key), collections.Uint64Value), // sdk.TimeKey is needed to retain state compatibility + VotingPeriodProposals: collections.NewMap(sb, types.VotingPeriodProposalKeyPrefix, "voting_period_proposals", collections.Uint64Key, collections.BytesValue), } + + for _, opt := range initOptions { + opt(k) + } + schema, err := sb.Build() if err != nil { panic(err) diff --git a/x/gov/keeper/proposal.go b/x/gov/keeper/proposal.go index 6499d36f11..9118e28ce9 100644 --- a/x/gov/keeper/proposal.go +++ b/x/gov/keeper/proposal.go @@ -16,21 +16,21 @@ import ( ) // SubmitProposal creates a new proposal given an array of messages -func (keeper Keeper) SubmitProposal(ctx context.Context, messages []sdk.Msg, metadata, title, summary string, proposer sdk.AccAddress, expedited bool) (v1.Proposal, error) { +func (k Keeper) SubmitProposal(ctx context.Context, messages []sdk.Msg, metadata, title, summary string, proposer sdk.AccAddress, expedited bool) (v1.Proposal, error) { sdkCtx := sdk.UnwrapSDKContext(ctx) - err := keeper.assertMetadataLength(metadata) + err := k.assertMetadataLength(metadata) if err != nil { return v1.Proposal{}, err } // assert summary is no longer than predefined max length of metadata - err = keeper.assertSummaryLength(summary) + err = k.assertSummaryLength(summary) if err != nil { return v1.Proposal{}, err } // assert title is no longer than predefined max length of metadata - err = keeper.assertMetadataLength(title) + err = k.assertMetadataLength(title) if err != nil { return v1.Proposal{}, err } @@ -50,7 +50,7 @@ func (keeper Keeper) SubmitProposal(ctx context.Context, messages []sdk.Msg, met } } - signers, _, err := keeper.cdc.GetMsgV1Signers(msg) + signers, _, err := k.cdc.GetMsgV1Signers(msg) if err != nil { return v1.Proposal{}, err } @@ -59,12 +59,12 @@ func (keeper Keeper) SubmitProposal(ctx context.Context, messages []sdk.Msg, met } // assert that the governance module account is the only signer of the messages - if !bytes.Equal(signers[0], keeper.GetGovernanceAccount(ctx).GetAddress()) { + if !bytes.Equal(signers[0], k.GetGovernanceAccount(ctx).GetAddress()) { return v1.Proposal{}, errorsmod.Wrapf(types.ErrInvalidSigner, sdk.AccAddress(signers[0]).String()) } // use the msg service router to see that there is a valid route for that message. - handler := keeper.router.Handler(msg) + handler := k.router.Handler(msg) if handler == nil { return v1.Proposal{}, errorsmod.Wrap(types.ErrUnroutableProposalMsg, sdk.MsgTypeURL(msg)) } @@ -86,12 +86,12 @@ func (keeper Keeper) SubmitProposal(ctx context.Context, messages []sdk.Msg, met } - proposalID, err := keeper.ProposalID.Next(ctx) + proposalID, err := k.ProposalID.Next(ctx) if err != nil { return v1.Proposal{}, err } - params, err := keeper.Params.Get(ctx) + params, err := k.Params.Get(ctx) if err != nil { return v1.Proposal{}, err } @@ -104,17 +104,17 @@ func (keeper Keeper) SubmitProposal(ctx context.Context, messages []sdk.Msg, met return v1.Proposal{}, err } - err = keeper.SetProposal(ctx, proposal) + err = k.SetProposal(ctx, proposal) if err != nil { return v1.Proposal{}, err } - err = keeper.InactiveProposalsQueue.Set(ctx, collections.Join(*proposal.DepositEndTime, proposalID), proposalID) + err = k.InactiveProposalsQueue.Set(ctx, collections.Join(*proposal.DepositEndTime, proposalID), proposalID) if err != nil { return v1.Proposal{}, err } // called right after a proposal is submitted - err = keeper.Hooks().AfterProposalSubmission(ctx, proposalID) + err = k.Hooks().AfterProposalSubmission(ctx, proposalID) if err != nil { return v1.Proposal{}, err } @@ -132,9 +132,9 @@ func (keeper Keeper) SubmitProposal(ctx context.Context, messages []sdk.Msg, met } // CancelProposal will cancel proposal before the voting period ends -func (keeper Keeper) CancelProposal(ctx context.Context, proposalID uint64, proposer string) error { +func (k Keeper) CancelProposal(ctx context.Context, proposalID uint64, proposer string) error { sdkCtx := sdk.UnwrapSDKContext(ctx) - proposal, err := keeper.Proposals.Get(ctx, proposalID) + proposal, err := k.Proposals.Get(ctx, proposalID) if err != nil { return err } @@ -162,29 +162,29 @@ func (keeper Keeper) CancelProposal(ctx context.Context, proposalID uint64, prop // burn the (deposits * proposal_cancel_rate) amount or sent to cancellation destination address. // and deposits * (1 - proposal_cancel_rate) will be sent to depositors. - params, err := keeper.Params.Get(ctx) + params, err := k.Params.Get(ctx) if err != nil { return err } - err = keeper.ChargeDeposit(ctx, proposal.Id, params.ProposalCancelDest, params.ProposalCancelRatio) + err = k.ChargeDeposit(ctx, proposal.Id, params.ProposalCancelDest, params.ProposalCancelRatio) if err != nil { return err } if proposal.VotingStartTime != nil { - err = keeper.deleteVotes(ctx, proposal.Id) + err = k.deleteVotes(ctx, proposal.Id) if err != nil { return err } } - err = keeper.DeleteProposal(ctx, proposal.Id) + err = k.DeleteProposal(ctx, proposal.Id) if err != nil { return err } - keeper.Logger(ctx).Info( + k.Logger(ctx).Info( "proposal is canceled by proposer", "proposal", proposal.Id, "proposer", proposal.Proposer, @@ -194,57 +194,57 @@ func (keeper Keeper) CancelProposal(ctx context.Context, proposalID uint64, prop } // SetProposal sets a proposal to store. -func (keeper Keeper) SetProposal(ctx context.Context, proposal v1.Proposal) error { +func (k Keeper) SetProposal(ctx context.Context, proposal v1.Proposal) error { if proposal.Status == v1.StatusVotingPeriod { - err := keeper.VotingPeriodProposals.Set(ctx, proposal.Id, []byte{1}) + err := k.VotingPeriodProposals.Set(ctx, proposal.Id, []byte{1}) if err != nil { return err } } else { - err := keeper.VotingPeriodProposals.Remove(ctx, proposal.Id) + err := k.VotingPeriodProposals.Remove(ctx, proposal.Id) if err != nil { return err } } - return keeper.Proposals.Set(ctx, proposal.Id, proposal) + return k.Proposals.Set(ctx, proposal.Id, proposal) } // DeleteProposal deletes a proposal from store. -func (keeper Keeper) DeleteProposal(ctx context.Context, proposalID uint64) error { - proposal, err := keeper.Proposals.Get(ctx, proposalID) +func (k Keeper) DeleteProposal(ctx context.Context, proposalID uint64) error { + proposal, err := k.Proposals.Get(ctx, proposalID) if err != nil { return err } if proposal.DepositEndTime != nil { - err := keeper.InactiveProposalsQueue.Remove(ctx, collections.Join(*proposal.DepositEndTime, proposalID)) + err := k.InactiveProposalsQueue.Remove(ctx, collections.Join(*proposal.DepositEndTime, proposalID)) if err != nil { return err } } if proposal.VotingEndTime != nil { - err := keeper.ActiveProposalsQueue.Remove(ctx, collections.Join(*proposal.VotingEndTime, proposalID)) + err := k.ActiveProposalsQueue.Remove(ctx, collections.Join(*proposal.VotingEndTime, proposalID)) if err != nil { return err } - err = keeper.VotingPeriodProposals.Remove(ctx, proposalID) + err = k.VotingPeriodProposals.Remove(ctx, proposalID) if err != nil { return err } } - return keeper.Proposals.Remove(ctx, proposalID) + return k.Proposals.Remove(ctx, proposalID) } // ActivateVotingPeriod activates the voting period of a proposal -func (keeper Keeper) ActivateVotingPeriod(ctx context.Context, proposal v1.Proposal) error { +func (k Keeper) ActivateVotingPeriod(ctx context.Context, proposal v1.Proposal) error { sdkCtx := sdk.UnwrapSDKContext(ctx) startTime := sdkCtx.BlockHeader().Time proposal.VotingStartTime = &startTime var votingPeriod *time.Duration - params, err := keeper.Params.Get(ctx) + params, err := k.Params.Get(ctx) if err != nil { return err } @@ -257,15 +257,15 @@ func (keeper Keeper) ActivateVotingPeriod(ctx context.Context, proposal v1.Propo endTime := proposal.VotingStartTime.Add(*votingPeriod) proposal.VotingEndTime = &endTime proposal.Status = v1.StatusVotingPeriod - err = keeper.SetProposal(ctx, proposal) + err = k.SetProposal(ctx, proposal) if err != nil { return err } - err = keeper.InactiveProposalsQueue.Remove(ctx, collections.Join(*proposal.DepositEndTime, proposal.Id)) + err = k.InactiveProposalsQueue.Remove(ctx, collections.Join(*proposal.DepositEndTime, proposal.Id)) if err != nil { return err } - return keeper.ActiveProposalsQueue.Set(ctx, collections.Join(*proposal.VotingEndTime, proposal.Id), proposal.Id) + return k.ActiveProposalsQueue.Set(ctx, collections.Join(*proposal.VotingEndTime, proposal.Id), proposal.Id) } diff --git a/x/gov/keeper/tally.go b/x/gov/keeper/tally.go index 972911d5ca..7b606ab4cb 100644 --- a/x/gov/keeper/tally.go +++ b/x/gov/keeper/tally.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "fmt" "cosmossdk.io/collections" "cosmossdk.io/math" @@ -11,66 +12,57 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) -// TODO: Break into several smaller functions for clarity +// CalculateVoteResultsAndVotingPowerFn is a function signature for calculating vote results and voting power +// It can be overridden to customize the voting power calculation for proposals +// It gets the proposal tallied and the validators governance infos (bonded tokens, voting power, etc.) +// It must return the total voting power and the results of the vote +type CalculateVoteResultsAndVotingPowerFn func( + ctx context.Context, + k Keeper, + proposal v1.Proposal, + validators map[string]v1.ValidatorGovInfo, +) (totalVoterPower math.LegacyDec, results map[v1.VoteOption]math.LegacyDec, err error) -// 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 context.Context, proposal v1.Proposal) (passes, burnDeposits bool, tallyResults v1.TallyResult, err error) { - results := make(map[v1.VoteOption]math.LegacyDec) +func defaultCalculateVoteResultsAndVotingPower( + ctx context.Context, + k Keeper, + proposal v1.Proposal, + validators map[string]v1.ValidatorGovInfo, +) (totalVoterPower math.LegacyDec, results map[v1.VoteOption]math.LegacyDec, err error) { + totalVotingPower := math.LegacyZeroDec() + + results = make(map[v1.VoteOption]math.LegacyDec) 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 - err = keeper.sk.IterateBondedValidatorsByPower(ctx, func(index int64, validator stakingtypes.ValidatorI) (stop bool) { - valBz, err := keeper.sk.ValidatorAddressCodec().StringToBytes(validator.GetOperator()) - if err != nil { - return false - } - currValidators[validator.GetOperator()] = v1.NewValidatorGovInfo( - valBz, - validator.GetBondedTokens(), - validator.GetDelegatorShares(), - math.LegacyZeroDec(), - v1.WeightedVoteOptions{}, - ) - - return false - }) - if err != nil { - return false, false, tallyResults, err - } - rng := collections.NewPrefixedPairRange[uint64, sdk.AccAddress](proposal.Id) - err = keeper.Votes.Walk(ctx, rng, func(key collections.Pair[uint64, sdk.AccAddress], vote v1.Vote) (bool, error) { + err = k.Votes.Walk(ctx, rng, func(key collections.Pair[uint64, sdk.AccAddress], vote v1.Vote) (bool, error) { // if validator, just record it in the map - voter, err := keeper.authKeeper.AddressCodec().StringToBytes(vote.Voter) + voter, err := k.authKeeper.AddressCodec().StringToBytes(vote.Voter) if err != nil { return false, err } - valAddrStr, err := keeper.sk.ValidatorAddressCodec().BytesToString(voter) + valAddrStr, err := k.sk.ValidatorAddressCodec().BytesToString(voter) if err != nil { return false, err } - if val, ok := currValidators[valAddrStr]; ok { + if val, ok := validators[valAddrStr]; ok { val.Vote = vote.Options - currValidators[valAddrStr] = val + validators[valAddrStr] = val } // iterate over all delegations from voter, deduct from any delegated-to validators - err = keeper.sk.IterateDelegations(ctx, voter, func(index int64, delegation stakingtypes.DelegationI) (stop bool) { + err = k.sk.IterateDelegations(ctx, voter, func(index int64, delegation stakingtypes.DelegationI) (stop bool) { valAddrStr := delegation.GetValidatorAddr() - if val, ok := currValidators[valAddrStr]; ok { + if val, ok := validators[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 + validators[valAddrStr] = val // delegation shares * bonded / total shares votingPower := delegation.GetShares().MulInt(val.BondedTokens).Quo(val.DelegatorShares) @@ -89,14 +81,14 @@ func (keeper Keeper) Tally(ctx context.Context, proposal v1.Proposal) (passes, b return false, err } - return false, keeper.Votes.Remove(ctx, collections.Join(vote.ProposalId, sdk.AccAddress(voter))) + return false, k.Votes.Remove(ctx, collections.Join(vote.ProposalId, sdk.AccAddress(voter))) }) if err != nil { - return false, false, tallyResults, err + return math.LegacyZeroDec(), nil, fmt.Errorf("error while iterating delegations: %w", err) } // iterate over the validators again to tally their voting power - for _, val := range currValidators { + for _, val := range validators { if len(val.Vote) == 0 { continue } @@ -112,15 +104,52 @@ func (keeper Keeper) Tally(ctx context.Context, proposal v1.Proposal) (passes, b totalVotingPower = totalVotingPower.Add(votingPower) } - params, err := keeper.Params.Get(ctx) - if err != nil { - return false, false, tallyResults, err + return totalVotingPower, results, nil +} + +// getCurrentValidators fetches all the bonded validators, insert them into currValidators +func (k Keeper) getCurrentValidators(ctx context.Context) (map[string]v1.ValidatorGovInfo, error) { + currValidators := make(map[string]v1.ValidatorGovInfo) + if err := k.sk.IterateBondedValidatorsByPower(ctx, func(index int64, validator stakingtypes.ValidatorI) (stop bool) { + valBz, err := k.sk.ValidatorAddressCodec().StringToBytes(validator.GetOperator()) + if err != nil { + return false + } + currValidators[validator.GetOperator()] = v1.NewValidatorGovInfo( + valBz, + validator.GetBondedTokens(), + validator.GetDelegatorShares(), + math.LegacyZeroDec(), + v1.WeightedVoteOptions{}, + ) + + return false + }); err != nil { + return nil, err } + + return currValidators, nil +} + +// Tally iterates over the votes and updates the tally of a proposal based on the voting power of the +// voters +func (k Keeper) Tally(ctx context.Context, proposal v1.Proposal) (passes, burnDeposits bool, tallyResults v1.TallyResult, err error) { + currValidators, err := k.getCurrentValidators(ctx) + if err != nil { + return false, false, tallyResults, fmt.Errorf("error while getting current validators: %w", err) + } + + tallyFn := k.calculateVoteResultsAndVotingPowerFn + totalVotingPower, results, err := tallyFn(ctx, k, proposal, currValidators) + if err != nil { + return false, false, tallyResults, fmt.Errorf("error while calculating tally results: %w", err) + } + 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 - totalBonded, err := keeper.sk.TotalBondedTokens(ctx) + totalBonded, err := k.sk.TotalBondedTokens(ctx) if err != nil { return false, false, tallyResults, err } @@ -129,6 +158,11 @@ func (keeper Keeper) Tally(ctx context.Context, proposal v1.Proposal) (passes, b return false, false, tallyResults, nil } + params, err := k.Params.Get(ctx) + if err != nil { + return false, false, tallyResults, fmt.Errorf("error while getting params: %w", err) + } + // If there is not enough quorum of votes, the proposal fails percentVoting := totalVotingPower.Quo(math.LegacyNewDecFromInt(totalBonded)) quorum, _ := math.LegacyNewDecFromStr(params.Quorum) diff --git a/x/gov/keeper/vote.go b/x/gov/keeper/vote.go index 466fe4c2a2..ec89446a86 100644 --- a/x/gov/keeper/vote.go +++ b/x/gov/keeper/vote.go @@ -13,9 +13,9 @@ import ( ) // AddVote adds a vote on a specific proposal -func (keeper Keeper) AddVote(ctx context.Context, proposalID uint64, voterAddr sdk.AccAddress, options v1.WeightedVoteOptions, metadata string) error { +func (k Keeper) AddVote(ctx context.Context, proposalID uint64, voterAddr sdk.AccAddress, options v1.WeightedVoteOptions, metadata string) error { // Check if proposal is in voting period. - inVotingPeriod, err := keeper.VotingPeriodProposals.Has(ctx, proposalID) + inVotingPeriod, err := k.VotingPeriodProposals.Has(ctx, proposalID) if err != nil { return err } @@ -24,7 +24,7 @@ func (keeper Keeper) AddVote(ctx context.Context, proposalID uint64, voterAddr s return errors.Wrapf(types.ErrInactiveProposal, "%d", proposalID) } - err = keeper.assertMetadataLength(metadata) + err = k.assertMetadataLength(metadata) if err != nil { return err } @@ -36,13 +36,13 @@ func (keeper Keeper) AddVote(ctx context.Context, proposalID uint64, voterAddr s } vote := v1.NewVote(proposalID, voterAddr, options, metadata) - err = keeper.Votes.Set(ctx, collections.Join(proposalID, voterAddr), vote) + err = k.Votes.Set(ctx, collections.Join(proposalID, voterAddr), vote) if err != nil { return err } // called after a vote on a proposal is cast - err = keeper.Hooks().AfterProposalVote(ctx, proposalID, voterAddr) + err = k.Hooks().AfterProposalVote(ctx, proposalID, voterAddr) if err != nil { return err } @@ -61,9 +61,9 @@ func (keeper Keeper) AddVote(ctx context.Context, proposalID uint64, voterAddr s } // deleteVotes deletes all the votes from a given proposalID. -func (keeper Keeper) deleteVotes(ctx context.Context, proposalID uint64) error { +func (k Keeper) deleteVotes(ctx context.Context, proposalID uint64) error { rng := collections.NewPrefixedPairRange[uint64, sdk.AccAddress](proposalID) - err := keeper.Votes.Clear(ctx, rng) + err := k.Votes.Clear(ctx, rng) if err != nil { return err } diff --git a/x/gov/module.go b/x/gov/module.go index d671c0f186..6948a54798 100644 --- a/x/gov/module.go +++ b/x/gov/module.go @@ -4,21 +4,13 @@ import ( "context" "encoding/json" "fmt" - "maps" - "slices" - "sort" - "strings" gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" - modulev1 "cosmossdk.io/api/cosmos/gov/module/v1" "cosmossdk.io/core/address" "cosmossdk.io/core/appmodule" - store "cosmossdk.io/core/store" - "cosmossdk.io/depinject" - "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -26,7 +18,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" govclient "github.com/cosmos/cosmos-sdk/x/gov/client" "github.com/cosmos/cosmos-sdk/x/gov/client/cli" "github.com/cosmos/cosmos-sdk/x/gov/keeper" @@ -34,7 +25,6 @@ import ( govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" ) const ConsensusVersion = 5 @@ -153,114 +143,6 @@ func (am AppModule) IsOnePerModuleType() {} // IsAppModule implements the appmodule.AppModule interface. func (am AppModule) IsAppModule() {} -func init() { - appmodule.Register( - &modulev1.Module{}, - appmodule.Provide(ProvideModule, ProvideKeyTable), - appmodule.Invoke(InvokeAddRoutes, InvokeSetHooks)) -} - -type ModuleInputs struct { - depinject.In - - Config *modulev1.Module - Cdc codec.Codec - StoreService store.KVStoreService - ModuleKey depinject.OwnModuleKey - MsgServiceRouter baseapp.MessageRouter - - AccountKeeper govtypes.AccountKeeper - BankKeeper govtypes.BankKeeper - StakingKeeper govtypes.StakingKeeper - DistributionKeeper govtypes.DistributionKeeper - - // LegacySubspace is used solely for migration of x/params managed parameters - LegacySubspace govtypes.ParamSubspace `optional:"true"` -} - -type ModuleOutputs struct { - depinject.Out - - Module appmodule.AppModule - Keeper *keeper.Keeper - HandlerRoute v1beta1.HandlerRoute -} - -func ProvideModule(in ModuleInputs) ModuleOutputs { - defaultConfig := govtypes.DefaultConfig() - if in.Config.MaxMetadataLen != 0 { - defaultConfig.MaxMetadataLen = in.Config.MaxMetadataLen - } - - // default to governance authority if not provided - authority := authtypes.NewModuleAddress(govtypes.ModuleName) - if in.Config.Authority != "" { - authority = authtypes.NewModuleAddressOrBech32Address(in.Config.Authority) - } - - k := keeper.NewKeeper( - in.Cdc, - in.StoreService, - in.AccountKeeper, - in.BankKeeper, - in.StakingKeeper, - in.DistributionKeeper, - in.MsgServiceRouter, - defaultConfig, - authority.String(), - ) - m := NewAppModule(in.Cdc, k, in.AccountKeeper, in.BankKeeper, in.LegacySubspace) - hr := v1beta1.HandlerRoute{Handler: v1beta1.ProposalHandler, RouteKey: govtypes.RouterKey} - - return ModuleOutputs{Module: m, Keeper: k, HandlerRoute: hr} -} - -func ProvideKeyTable() paramtypes.KeyTable { - return v1.ParamKeyTable() //nolint:staticcheck // we still need this for upgrades -} - -func InvokeAddRoutes(keeper *keeper.Keeper, routes []v1beta1.HandlerRoute) { - if keeper == nil || routes == nil { - return - } - - // Default route order is a lexical sort by RouteKey. - // Explicit ordering can be added to the module config if required. - slices.SortFunc(routes, func(x, y v1beta1.HandlerRoute) int { - return strings.Compare(x.RouteKey, y.RouteKey) - }) - - router := v1beta1.NewRouter() - for _, r := range routes { - router.AddRoute(r.RouteKey, r.Handler) - } - keeper.SetLegacyRouter(router) -} - -func InvokeSetHooks(keeper *keeper.Keeper, govHooks map[string]govtypes.GovHooksWrapper) error { - if keeper == nil || govHooks == nil { - return nil - } - - // Default ordering is lexical by module name. - // Explicit ordering can be added to the module config if required. - modNames := slices.Sorted(maps.Keys(govHooks)) - order := modNames - sort.Strings(order) - - var multiHooks govtypes.MultiGovHooks - for _, modName := range order { - hook, ok := govHooks[modName] - if !ok { - return fmt.Errorf("can't find staking hooks for module %s", modName) - } - multiHooks = append(multiHooks, hook) - } - - keeper.SetHooks(multiHooks) - return nil -} - // RegisterServices registers module services. func (am AppModule) RegisterServices(cfg module.Configurator) { msgServer := keeper.NewMsgServerImpl(am.keeper)