Skip to content

Commit

Permalink
feat(x/gov): add MaxVoteOptionsLen (cosmos#20087)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcello33 authored Apr 22, 2024
1 parent d11304b commit 2645e1c
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 22 deletions.
105 changes: 83 additions & 22 deletions api/cosmos/gov/module/v1/module.pulsar.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions x/gov/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Features

* [#20087](https://github.com/cosmos/cosmos-sdk/pull/20087) add `MaxVoteOptionsLen`
* [#19592](https://github.com/cosmos/cosmos-sdk/pull/19592) Add custom tally function.
* [#19304](https://github.com/cosmos/cosmos-sdk/pull/19304) Add `MsgSudoExec` for allowing executing any message as a sudo.
* [#19101](https://github.com/cosmos/cosmos-sdk/pull/19101) Add message based params configuration.
Expand Down
2 changes: 2 additions & 0 deletions x/gov/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1beta1/g

For a weighted vote to be valid, the `options` field must not contain duplicate vote options, and the sum of weights of all options must be equal to 1.

The maximum number of weighted vote options can be limited by the developer via a config parameter, named `MaxVoteOptionsLen`, which gets passed into the gov keeper.

### Quorum

Quorum is defined as the minimum percentage of voting power that needs to be
Expand Down
69 changes: 69 additions & 0 deletions x/gov/keeper/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,75 @@ func setupGovKeeper(t *testing.T, expectations ...func(sdk.Context, mocks)) (
return govKeeper, m, encCfg, ctx
}

// setupGovKeeperWithMaxVoteOptionsLen creates a govKeeper with a defined maxVoteOptionsLen, as well as all its dependencies.
func setupGovKeeperWithMaxVoteOptionsLen(t *testing.T, maxVoteOptionsLen uint64, expectations ...func(sdk.Context, mocks)) (
*keeper.Keeper,
mocks,
moduletestutil.TestEncodingConfig,
sdk.Context,
) {
t.Helper()
key := storetypes.NewKVStoreKey(types.StoreKey)
storeService := runtime.NewKVStoreService(key)
testCtx := testutil.DefaultContextWithDB(t, key, storetypes.NewTransientStoreKey("transient_test"))
ctx := testCtx.Ctx.WithHeaderInfo(header.Info{Time: time.Now()})
encCfg := moduletestutil.MakeTestEncodingConfig(codectestutil.CodecOptions{})
v1.RegisterInterfaces(encCfg.InterfaceRegistry)
v1beta1.RegisterInterfaces(encCfg.InterfaceRegistry)
banktypes.RegisterInterfaces(encCfg.InterfaceRegistry)

baseApp := baseapp.NewBaseApp(
"authz",
log.NewNopLogger(),
testCtx.DB,
encCfg.TxConfig.TxDecoder(),
)
baseApp.SetCMS(testCtx.CMS)
baseApp.SetInterfaceRegistry(encCfg.InterfaceRegistry)

environment := runtime.NewEnvironment(storeService, log.NewNopLogger(), runtime.EnvWithRouterService(baseApp.GRPCQueryRouter(), baseApp.MsgServiceRouter()))

// gomock initializations
ctrl := gomock.NewController(t)
m := mocks{
acctKeeper: govtestutil.NewMockAccountKeeper(ctrl),
bankKeeper: govtestutil.NewMockBankKeeper(ctrl),
stakingKeeper: govtestutil.NewMockStakingKeeper(ctrl),
poolKeeper: govtestutil.NewMockPoolKeeper(ctrl),
}
if len(expectations) == 0 {
err := mockDefaultExpectations(ctx, m)
require.NoError(t, err)
} else {
for _, exp := range expectations {
exp(ctx, m)
}
}

govAddr, err := m.acctKeeper.AddressCodec().BytesToString(govAcct)
require.NoError(t, err)

config := keeper.DefaultConfig()
config.MaxVoteOptionsLen = maxVoteOptionsLen

// Gov keeper initializations
govKeeper := keeper.NewKeeper(encCfg.Codec, environment, m.acctKeeper, m.bankKeeper, m.stakingKeeper, m.poolKeeper, config, govAddr)
require.NoError(t, govKeeper.ProposalID.Set(ctx, 1))
govRouter := v1beta1.NewRouter() // Also register legacy gov handlers to test them too.
govRouter.AddRoute(types.RouterKey, v1beta1.ProposalHandler)
govKeeper.SetLegacyRouter(govRouter)
err = govKeeper.Params.Set(ctx, v1.DefaultParams())
require.NoError(t, err)
err = govKeeper.Constitution.Set(ctx, "constitution")
require.NoError(t, err)

// Register all handlers for the MegServiceRouter.
v1.RegisterMsgServer(baseApp.MsgServiceRouter(), keeper.NewMsgServerImpl(govKeeper))
banktypes.RegisterMsgServer(baseApp.MsgServiceRouter(), nil) // Nil is fine here as long as we never execute the proposal's Msgs.

return govKeeper, m, encCfg, ctx
}

// trackMockBalances sets up expected calls on the Mock BankKeeper, and also
// locally tracks accounts balances (not modules balances).
func trackMockBalances(bankKeeper *govtestutil.MockBankKeeper) error {
Expand Down
5 changes: 5 additions & 0 deletions x/gov/keeper/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ type Config struct {
MaxMetadataLen uint64
// MaxSummaryLen defines the amount of characters that can be used for proposal summary
MaxSummaryLen uint64
// MaxVoteOptionsLen defines the maximum number of vote options a proposal can have.
// This only applies to WeightedVoteOption messages and not to the VoteOption messages
// 0 means this param is disabled, hence all supported options are allowed
MaxVoteOptionsLen uint64
// CalculateVoteResultsAndVotingPowerFn is a function signature for calculating vote results and voting power
// Keeping it nil will use the default implementation
CalculateVoteResultsAndVotingPowerFn CalculateVoteResultsAndVotingPowerFn
Expand All @@ -37,6 +41,7 @@ func DefaultConfig() Config {
MaxTitleLen: 255,
MaxMetadataLen: 255,
MaxSummaryLen: 10200,
MaxVoteOptionsLen: 0, // 0 means this param is disabled, hence all supported options are allowed
CalculateVoteResultsAndVotingPowerFn: nil,
}
}
15 changes: 15 additions & 0 deletions x/gov/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ func NewKeeper(
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{
Expand Down Expand Up @@ -230,3 +234,14 @@ func (k Keeper) assertSummaryLength(summary string) error {
}
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
}
5 changes: 5 additions & 0 deletions x/gov/keeper/vote.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ func (k Keeper) AddVote(ctx context.Context, proposalID uint64, voterAddr sdk.Ac
return err
}

err = k.assertVoteOptionsLen(options)
if err != nil {
return err
}

for _, option := range options {
switch proposal.ProposalType {
case v1.ProposalType_PROPOSAL_TYPE_OPTIMISTIC:
Expand Down
Loading

0 comments on commit 2645e1c

Please sign in to comment.