248 lines
9.8 KiB
Go
248 lines
9.8 KiB
Go
package keeper
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"cosmossdk.io/collections"
|
|
"cosmossdk.io/core/appmodule"
|
|
"cosmossdk.io/x/gov/types"
|
|
v1 "cosmossdk.io/x/gov/types/v1"
|
|
"cosmossdk.io/x/gov/types/v1beta1"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
)
|
|
|
|
// Keeper defines the governance module Keeper
|
|
type Keeper struct {
|
|
appmodule.Environment
|
|
|
|
authKeeper types.AccountKeeper
|
|
bankKeeper types.BankKeeper
|
|
poolKeeper types.PoolKeeper
|
|
// The reference to the DelegationSet and ValidatorSet to get information about validators and delegators
|
|
sk types.StakingKeeper
|
|
|
|
// GovHooks
|
|
hooks types.GovHooks
|
|
|
|
// The codec for binary encoding/decoding.
|
|
cdc codec.Codec
|
|
|
|
// Legacy Proposal router
|
|
legacyRouter v1beta1.Router
|
|
|
|
// Config represent extra module configuration
|
|
config Config
|
|
|
|
// the address capable of executing a MsgUpdateParams message. Typically, this
|
|
// should be the x/gov module account.
|
|
authority string
|
|
|
|
Schema collections.Schema
|
|
// Constitution value: constitution
|
|
Constitution collections.Item[string]
|
|
// Params stores the governance parameters
|
|
Params collections.Item[v1.Params]
|
|
// MessageBasedParams store message-based governance parameters
|
|
// key:proposal-msg-url | value MessageBasedParams
|
|
MessageBasedParams collections.Map[string, v1.MessageBasedParams]
|
|
// Deposits key: proposalID+depositorAddr | value: Deposit
|
|
Deposits collections.Map[collections.Pair[uint64, sdk.AccAddress], v1.Deposit]
|
|
// Votes key: proposalID+voterAddr | value: Vote
|
|
Votes collections.Map[collections.Pair[uint64, sdk.AccAddress], v1.Vote]
|
|
// ProposalID is a counter for proposals. It tracks the next proposal ID to be issued.
|
|
ProposalID collections.Sequence
|
|
// Proposals key:proposalID | value: Proposal
|
|
Proposals collections.Map[uint64, v1.Proposal]
|
|
// ProposalVoteOptions key: proposalID | value:
|
|
// This is used to store multiple choice vote options
|
|
ProposalVoteOptions collections.Map[uint64, v1.ProposalVoteOptions]
|
|
// ActiveProposalsQueue key: votingEndTime+proposalID | value: proposalID
|
|
ActiveProposalsQueue collections.Map[collections.Pair[time.Time, uint64], uint64] // TODO(tip): this should be simplified and go into an index.
|
|
// InactiveProposalsQueue key: depositEndTime+proposalID | value: proposalID
|
|
InactiveProposalsQueue collections.Map[collections.Pair[time.Time, uint64], uint64] // TODO(tip): this should be simplified and go into an index.
|
|
}
|
|
|
|
// GetAuthority returns the x/gov module's authority.
|
|
func (k Keeper) GetAuthority() string {
|
|
return k.authority
|
|
}
|
|
|
|
// NewKeeper returns a governance keeper. It handles:
|
|
// - submitting governance proposals
|
|
// - depositing funds into proposals, and activating upon sufficient funds being deposited
|
|
// - users voting on proposals, with weight proportional to stake in the system
|
|
// - and tallying the result of the vote.
|
|
//
|
|
// CONTRACT: the parameter Subspace must have the param key table already initialized
|
|
func NewKeeper(
|
|
cdc codec.Codec, env appmodule.Environment, authKeeper types.AccountKeeper,
|
|
bankKeeper types.BankKeeper, sk types.StakingKeeper, pk types.PoolKeeper,
|
|
config Config, authority string,
|
|
) *Keeper {
|
|
// ensure governance module account is set
|
|
if addr := authKeeper.GetModuleAddress(types.ModuleName); addr == nil {
|
|
panic(fmt.Sprintf("%s module account has not been set", types.ModuleName))
|
|
}
|
|
|
|
if _, err := authKeeper.AddressCodec().StringToBytes(authority); err != nil {
|
|
panic(fmt.Sprintf("invalid authority address: %s", authority))
|
|
}
|
|
|
|
defaultConfig := DefaultConfig()
|
|
// If MaxMetadataLen not set by app developer, set to default value.
|
|
if config.MaxTitleLen == 0 {
|
|
config.MaxTitleLen = defaultConfig.MaxTitleLen
|
|
}
|
|
// If MaxMetadataLen not set by app developer, set to default value.
|
|
if config.MaxMetadataLen == 0 {
|
|
config.MaxMetadataLen = defaultConfig.MaxMetadataLen
|
|
}
|
|
// If MaxMetadataLen not set by app developer, set to default value.
|
|
if config.MaxSummaryLen == 0 {
|
|
config.MaxSummaryLen = defaultConfig.MaxSummaryLen
|
|
}
|
|
// If MaxVoteOptionsLen not set by app developer, set to default value, meaning all supported options are allowed
|
|
if config.MaxVoteOptionsLen == 0 {
|
|
config.MaxVoteOptionsLen = defaultConfig.MaxVoteOptionsLen
|
|
}
|
|
|
|
sb := collections.NewSchemaBuilder(env.KVStoreService)
|
|
k := &Keeper{
|
|
Environment: env,
|
|
authKeeper: authKeeper,
|
|
bankKeeper: bankKeeper,
|
|
sk: sk,
|
|
poolKeeper: pk,
|
|
cdc: cdc,
|
|
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)),
|
|
MessageBasedParams: collections.NewMap(sb, types.MessageBasedParamsKey, "proposal_messaged_based_params", collections.StringKey, codec.CollValue[v1.MessageBasedParams](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)),
|
|
ProposalVoteOptions: collections.NewMap(sb, types.ProposalVoteOptionsKeyPrefix, "proposal_vote_options", collections.Uint64Key, codec.CollValue[v1.ProposalVoteOptions](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
|
|
}
|
|
schema, err := sb.Build()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
k.Schema = schema
|
|
return k
|
|
}
|
|
|
|
// Hooks gets the hooks for governance Keeper
|
|
func (k *Keeper) Hooks() types.GovHooks {
|
|
if k.hooks == nil {
|
|
// return a no-op implementation if no hooks are set
|
|
return types.MultiGovHooks{}
|
|
}
|
|
|
|
return k.hooks
|
|
}
|
|
|
|
// SetHooks sets the hooks for governance
|
|
func (k *Keeper) SetHooks(gh types.GovHooks) *Keeper {
|
|
if k.hooks != nil {
|
|
panic("cannot set governance hooks twice")
|
|
}
|
|
|
|
k.hooks = gh
|
|
|
|
return k
|
|
}
|
|
|
|
// SetLegacyRouter sets the legacy router for governance
|
|
func (k *Keeper) SetLegacyRouter(router v1beta1.Router) {
|
|
// It is vital to seal the governance proposal router here as to not allow
|
|
// further handlers to be registered after the keeper is created since this
|
|
// could create invalid or non-deterministic behavior.
|
|
router.Seal()
|
|
k.legacyRouter = router
|
|
}
|
|
|
|
// LegacyRouter returns the gov keeper's legacy router
|
|
func (k Keeper) LegacyRouter() v1beta1.Router {
|
|
return k.legacyRouter
|
|
}
|
|
|
|
// GetGovernanceAccount returns the governance ModuleAccount
|
|
func (k Keeper) GetGovernanceAccount(ctx context.Context) sdk.ModuleAccountI {
|
|
return k.authKeeper.GetModuleAccount(ctx, types.ModuleName)
|
|
}
|
|
|
|
// ModuleAccountAddress returns gov module account address
|
|
func (k Keeper) ModuleAccountAddress() sdk.AccAddress {
|
|
return k.authKeeper.GetModuleAddress(types.ModuleName)
|
|
}
|
|
|
|
// validateProposalLengths checks message metadata, summary and title
|
|
// to have the expected length otherwise returns an error.
|
|
func (k Keeper) validateProposalLengths(metadata, title, summary string) error {
|
|
if err := k.assertMetadataLength(metadata); err != nil {
|
|
return err
|
|
}
|
|
if err := k.assertSummaryLength(summary); err != nil {
|
|
return err
|
|
}
|
|
if err := k.assertTitleLength(title); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// assertTitleLength returns an error if given title length
|
|
// is greater than a pre-defined MaxTitleLen.
|
|
func (k Keeper) assertTitleLength(title string) error {
|
|
if len(title) == 0 {
|
|
return errors.New("proposal title cannot be empty")
|
|
}
|
|
|
|
if uint64(len(title)) > k.config.MaxTitleLen {
|
|
return types.ErrTitleTooLong.Wrapf("got title with length %d", len(title))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// assertMetadataLength returns an error if given metadata length
|
|
// is greater than a pre-defined MaxMetadataLen.
|
|
func (k Keeper) assertMetadataLength(metadata string) error {
|
|
if uint64(len(metadata)) > k.config.MaxMetadataLen {
|
|
return types.ErrMetadataTooLong.Wrapf("got metadata with length %d", len(metadata))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// assertSummaryLength returns an error if given summary length
|
|
// is greater than a pre-defined MaxSummaryLen.
|
|
func (k Keeper) assertSummaryLength(summary string) error {
|
|
if len(summary) == 0 {
|
|
return errors.New("proposal summary cannot be empty")
|
|
}
|
|
|
|
if uint64(len(summary)) > k.config.MaxSummaryLen {
|
|
return types.ErrSummaryTooLong.Wrapf("got summary with length %d", len(summary))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// assertVoteOptionsLen returns an error if given vote options length
|
|
// is greater than a pre-defined MaxVoteOptionsLen.
|
|
// It's only being checked when config.MaxVoteOptionsLen > 0 (param enabled)
|
|
func (k Keeper) assertVoteOptionsLen(options v1.WeightedVoteOptions) error {
|
|
maxVoteOptionsLen := k.config.MaxVoteOptionsLen
|
|
if maxVoteOptionsLen > 0 && uint64(len(options)) > maxVoteOptionsLen {
|
|
return types.ErrTooManyVoteOptions.Wrapf("got %d weighted vote options, maximum allowed is %d", len(options), k.config.MaxVoteOptionsLen)
|
|
}
|
|
return nil
|
|
}
|