feat(x/gov): add MaxVoteOptionsLen (#20087)

This commit is contained in:
Marcello Ardizzone 2024-04-22 11:46:53 +02:00 committed by GitHub
parent d11304b974
commit 2645e1cda9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 245 additions and 22 deletions

View File

@ -14,11 +14,12 @@ import (
)
var (
md_Module protoreflect.MessageDescriptor
fd_Module_max_metadata_len protoreflect.FieldDescriptor
fd_Module_authority protoreflect.FieldDescriptor
fd_Module_max_title_len protoreflect.FieldDescriptor
fd_Module_max_summary_len protoreflect.FieldDescriptor
md_Module protoreflect.MessageDescriptor
fd_Module_max_metadata_len protoreflect.FieldDescriptor
fd_Module_authority protoreflect.FieldDescriptor
fd_Module_max_title_len protoreflect.FieldDescriptor
fd_Module_max_summary_len protoreflect.FieldDescriptor
fd_Module_max_vote_options_len protoreflect.FieldDescriptor
)
func init() {
@ -28,6 +29,7 @@ func init() {
fd_Module_authority = md_Module.Fields().ByName("authority")
fd_Module_max_title_len = md_Module.Fields().ByName("max_title_len")
fd_Module_max_summary_len = md_Module.Fields().ByName("max_summary_len")
fd_Module_max_vote_options_len = md_Module.Fields().ByName("max_vote_options_len")
}
var _ protoreflect.Message = (*fastReflection_Module)(nil)
@ -119,6 +121,12 @@ func (x *fastReflection_Module) Range(f func(protoreflect.FieldDescriptor, proto
return
}
}
if x.MaxVoteOptionsLen != uint64(0) {
value := protoreflect.ValueOfUint64(x.MaxVoteOptionsLen)
if !f(fd_Module_max_vote_options_len, value) {
return
}
}
}
// Has reports whether a field is populated.
@ -142,6 +150,8 @@ func (x *fastReflection_Module) Has(fd protoreflect.FieldDescriptor) bool {
return x.MaxTitleLen != uint64(0)
case "cosmos.gov.module.v1.Module.max_summary_len":
return x.MaxSummaryLen != uint64(0)
case "cosmos.gov.module.v1.Module.max_vote_options_len":
return x.MaxVoteOptionsLen != uint64(0)
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.gov.module.v1.Module"))
@ -166,6 +176,8 @@ func (x *fastReflection_Module) Clear(fd protoreflect.FieldDescriptor) {
x.MaxTitleLen = uint64(0)
case "cosmos.gov.module.v1.Module.max_summary_len":
x.MaxSummaryLen = uint64(0)
case "cosmos.gov.module.v1.Module.max_vote_options_len":
x.MaxVoteOptionsLen = uint64(0)
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.gov.module.v1.Module"))
@ -194,6 +206,9 @@ func (x *fastReflection_Module) Get(descriptor protoreflect.FieldDescriptor) pro
case "cosmos.gov.module.v1.Module.max_summary_len":
value := x.MaxSummaryLen
return protoreflect.ValueOfUint64(value)
case "cosmos.gov.module.v1.Module.max_vote_options_len":
value := x.MaxVoteOptionsLen
return protoreflect.ValueOfUint64(value)
default:
if descriptor.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.gov.module.v1.Module"))
@ -222,6 +237,8 @@ func (x *fastReflection_Module) Set(fd protoreflect.FieldDescriptor, value proto
x.MaxTitleLen = value.Uint()
case "cosmos.gov.module.v1.Module.max_summary_len":
x.MaxSummaryLen = value.Uint()
case "cosmos.gov.module.v1.Module.max_vote_options_len":
x.MaxVoteOptionsLen = value.Uint()
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.gov.module.v1.Module"))
@ -250,6 +267,8 @@ func (x *fastReflection_Module) Mutable(fd protoreflect.FieldDescriptor) protore
panic(fmt.Errorf("field max_title_len of message cosmos.gov.module.v1.Module is not mutable"))
case "cosmos.gov.module.v1.Module.max_summary_len":
panic(fmt.Errorf("field max_summary_len of message cosmos.gov.module.v1.Module is not mutable"))
case "cosmos.gov.module.v1.Module.max_vote_options_len":
panic(fmt.Errorf("field max_vote_options_len of message cosmos.gov.module.v1.Module is not mutable"))
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.gov.module.v1.Module"))
@ -271,6 +290,8 @@ func (x *fastReflection_Module) NewField(fd protoreflect.FieldDescriptor) protor
return protoreflect.ValueOfUint64(uint64(0))
case "cosmos.gov.module.v1.Module.max_summary_len":
return protoreflect.ValueOfUint64(uint64(0))
case "cosmos.gov.module.v1.Module.max_vote_options_len":
return protoreflect.ValueOfUint64(uint64(0))
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.gov.module.v1.Module"))
@ -353,6 +374,9 @@ func (x *fastReflection_Module) ProtoMethods() *protoiface.Methods {
if x.MaxSummaryLen != 0 {
n += 1 + runtime.Sov(uint64(x.MaxSummaryLen))
}
if x.MaxVoteOptionsLen != 0 {
n += 1 + runtime.Sov(uint64(x.MaxVoteOptionsLen))
}
if x.unknownFields != nil {
n += len(x.unknownFields)
}
@ -382,6 +406,11 @@ func (x *fastReflection_Module) ProtoMethods() *protoiface.Methods {
i -= len(x.unknownFields)
copy(dAtA[i:], x.unknownFields)
}
if x.MaxVoteOptionsLen != 0 {
i = runtime.EncodeVarint(dAtA, i, uint64(x.MaxVoteOptionsLen))
i--
dAtA[i] = 0x28
}
if x.MaxSummaryLen != 0 {
i = runtime.EncodeVarint(dAtA, i, uint64(x.MaxSummaryLen))
i--
@ -542,6 +571,25 @@ func (x *fastReflection_Module) ProtoMethods() *protoiface.Methods {
break
}
}
case 5:
if wireType != 0 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field MaxVoteOptionsLen", wireType)
}
x.MaxVoteOptionsLen = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow
}
if iNdEx >= l {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
x.MaxVoteOptionsLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := runtime.Skip(dAtA[iNdEx:])
@ -607,6 +655,9 @@ type Module struct {
// max_summary_len defines the maximum proposal summary length.
// Defaults to 10200 if not explicitly set.
MaxSummaryLen uint64 `protobuf:"varint,4,opt,name=max_summary_len,json=maxSummaryLen,proto3" json:"max_summary_len,omitempty"`
// max_vote_options_len defines the maximum number of vote options a proposal can have.
// Defaults to 0 if not explicitly set.
MaxVoteOptionsLen uint64 `protobuf:"varint,5,opt,name=max_vote_options_len,json=maxVoteOptionsLen,proto3" json:"max_vote_options_len,omitempty"`
}
func (x *Module) Reset() {
@ -657,6 +708,13 @@ func (x *Module) GetMaxSummaryLen() uint64 {
return 0
}
func (x *Module) GetMaxVoteOptionsLen() uint64 {
if x != nil {
return x.MaxVoteOptionsLen
}
return 0
}
var File_cosmos_gov_module_v1_module_proto protoreflect.FileDescriptor
var file_cosmos_gov_module_v1_module_proto_rawDesc = []byte{
@ -665,7 +723,7 @@ var file_cosmos_gov_module_v1_module_proto_rawDesc = []byte{
0x6f, 0x74, 0x6f, 0x12, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x67, 0x6f, 0x76, 0x2e,
0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x20, 0x63, 0x6f, 0x73, 0x6d, 0x6f,
0x73, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x6d,
0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb8, 0x01, 0x0a, 0x06,
0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe9, 0x01, 0x0a, 0x06,
0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x65,
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6c, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
0x52, 0x0e, 0x6d, 0x61, 0x78, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4c, 0x65, 0x6e,
@ -675,22 +733,25 @@ var file_cosmos_gov_module_v1_module_proto_rawDesc = []byte{
0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x4c,
0x65, 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72,
0x79, 0x5f, 0x6c, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x6d, 0x61, 0x78,
0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x4c, 0x65, 0x6e, 0x3a, 0x1a, 0xba, 0xc0, 0x96, 0xda,
0x01, 0x14, 0x0a, 0x12, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f,
0x2f, 0x78, 0x2f, 0x67, 0x6f, 0x76, 0x42, 0xca, 0x01, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x63,
0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x67, 0x6f, 0x76, 0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
0x2e, 0x76, 0x31, 0x42, 0x0b, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f,
0x50, 0x01, 0x5a, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f,
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x67, 0x6f, 0x76, 0x2f,
0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x47, 0x4d, 0xaa, 0x02, 0x14, 0x43, 0x6f, 0x73, 0x6d, 0x6f,
0x73, 0x2e, 0x47, 0x6f, 0x76, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x56, 0x31, 0xca,
0x02, 0x14, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x47, 0x6f, 0x76, 0x5c, 0x4d, 0x6f, 0x64,
0x75, 0x6c, 0x65, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x20, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c,
0x47, 0x6f, 0x76, 0x5c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50,
0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x17, 0x43, 0x6f, 0x73, 0x6d,
0x6f, 0x73, 0x3a, 0x3a, 0x47, 0x6f, 0x76, 0x3a, 0x3a, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x3a,
0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x4c, 0x65, 0x6e, 0x12, 0x2f, 0x0a, 0x14, 0x6d, 0x61,
0x78, 0x5f, 0x76, 0x6f, 0x74, 0x65, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x6c,
0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x6d, 0x61, 0x78, 0x56, 0x6f, 0x74,
0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4c, 0x65, 0x6e, 0x3a, 0x1a, 0xba, 0xc0, 0x96,
0xda, 0x01, 0x14, 0x0a, 0x12, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69,
0x6f, 0x2f, 0x78, 0x2f, 0x67, 0x6f, 0x76, 0x42, 0xca, 0x01, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e,
0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x67, 0x6f, 0x76, 0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c,
0x65, 0x2e, 0x76, 0x31, 0x42, 0x0b, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74,
0x6f, 0x50, 0x01, 0x5a, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69,
0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x67, 0x6f, 0x76,
0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x6d, 0x6f, 0x64, 0x75, 0x6c,
0x65, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x47, 0x4d, 0xaa, 0x02, 0x14, 0x43, 0x6f, 0x73, 0x6d,
0x6f, 0x73, 0x2e, 0x47, 0x6f, 0x76, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x56, 0x31,
0xca, 0x02, 0x14, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x47, 0x6f, 0x76, 0x5c, 0x4d, 0x6f,
0x64, 0x75, 0x6c, 0x65, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x20, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73,
0x5c, 0x47, 0x6f, 0x76, 0x5c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5c, 0x56, 0x31, 0x5c, 0x47,
0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x17, 0x43, 0x6f, 0x73,
0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x47, 0x6f, 0x76, 0x3a, 0x3a, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

View File

@ -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.

View File

@ -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

View File

@ -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 {

View File

@ -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
@ -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,
}
}

View File

@ -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{
@ -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
}

View File

@ -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:

View File

@ -166,3 +166,63 @@ func TestVotes_MultipleChoiceProposal(t *testing.T) {
require.NoError(t, govKeeper.AddVote(ctx, proposalID, addrs[1], v1.NewNonSplitVoteOption(v1.OptionTwo), ""))
require.NoError(t, govKeeper.AddVote(ctx, proposalID, addrs[0], v1.NewNonSplitVoteOption(v1.OptionThree), ""))
}
func TestVotes_CustomMaxVoteOptionsLen(t *testing.T) {
maxVoteOptionsLen := 3
govKeeper, mocks, _, ctx := setupGovKeeperWithMaxVoteOptionsLen(t, uint64(maxVoteOptionsLen))
authKeeper, bankKeeper, stakingKeeper := mocks.acctKeeper, mocks.bankKeeper, mocks.stakingKeeper
addrs := simtestutil.AddTestAddrsIncremental(bankKeeper, stakingKeeper, ctx, 2, sdkmath.NewInt(10000000))
authKeeper.EXPECT().AddressCodec().Return(address.NewBech32Codec("cosmos")).AnyTimes()
addrs1Str, err := authKeeper.AddressCodec().BytesToString(addrs[1])
require.NoError(t, err)
tp := TestProposal
proposal, err := govKeeper.SubmitProposal(ctx, tp, "", "title", "description", sdk.AccAddress("cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r"), v1.ProposalType_PROPOSAL_TYPE_STANDARD)
require.NoError(t, err)
proposalID := proposal.Id
metadata := "metadata"
require.Error(t, govKeeper.AddVote(ctx, proposalID, addrs[0], v1.NewNonSplitVoteOption(v1.OptionYes), metadata), "proposal not on voting period")
require.Error(t, govKeeper.AddVote(ctx, 10, addrs[0], v1.NewNonSplitVoteOption(v1.OptionYes), ""), "invalid proposal ID")
proposal.Status = v1.StatusVotingPeriod
err = govKeeper.Proposals.Set(ctx, proposal.Id, proposal)
require.NoError(t, err)
// not exceeding MaxVoteOptionsLen, no errors should be thrown
require.NoError(t, govKeeper.AddVote(ctx, proposalID, addrs[1], v1.WeightedVoteOptions{
v1.NewWeightedVoteOption(v1.OptionYes, sdkmath.LegacyNewDecWithPrec(60, 2)),
v1.NewWeightedVoteOption(v1.OptionNo, sdkmath.LegacyNewDecWithPrec(30, 2)),
v1.NewWeightedVoteOption(v1.OptionAbstain, sdkmath.LegacyNewDecWithPrec(10, 2)),
}, ""))
vote, err := govKeeper.Votes.Get(ctx, collections.Join(proposalID, addrs[1]))
require.Nil(t, err)
require.Equal(t, addrs1Str, vote.Voter)
require.Equal(t, proposalID, vote.ProposalId)
require.True(t, len(vote.Options) == 3)
require.Equal(t, v1.OptionYes, vote.Options[0].Option)
require.Equal(t, v1.OptionNo, vote.Options[1].Option)
require.Equal(t, v1.OptionAbstain, vote.Options[2].Option)
require.Equal(t, vote.Options[0].Weight, sdkmath.LegacyNewDecWithPrec(60, 2).String())
require.Equal(t, vote.Options[1].Weight, sdkmath.LegacyNewDecWithPrec(30, 2).String())
require.Equal(t, vote.Options[2].Weight, sdkmath.LegacyNewDecWithPrec(10, 2).String())
// exceeding MaxVoteOptionsLen, an error should be thrown
err = govKeeper.AddVote(ctx, proposalID, addrs[1], v1.WeightedVoteOptions{
v1.NewWeightedVoteOption(v1.OptionYes, sdkmath.LegacyNewDecWithPrec(60, 2)),
v1.NewWeightedVoteOption(v1.OptionNo, sdkmath.LegacyNewDecWithPrec(30, 2)),
v1.NewWeightedVoteOption(v1.OptionAbstain, sdkmath.LegacyNewDecWithPrec(5, 2)),
v1.NewWeightedVoteOption(v1.OptionNoWithVeto, sdkmath.LegacyNewDecWithPrec(5, 2)),
}, "")
require.Error(t, err)
require.Contains(t, err.Error(), "too many weighted vote options")
// only one vote should have gone through
var votes v1.Votes
require.NoError(t, govKeeper.Votes.Walk(ctx, nil, func(_ collections.Pair[uint64, sdk.AccAddress], value v1.Vote) (stop bool, err error) {
votes = append(votes, &value)
return false, nil
}))
require.Len(t, votes, 1)
}

View File

@ -24,4 +24,8 @@ message Module {
// max_summary_len defines the maximum proposal summary length.
// Defaults to 10200 if not explicitly set.
uint64 max_summary_len = 4;
// max_vote_options_len defines the maximum number of vote options a proposal can have.
// Defaults to 0 if not explicitly set.
uint64 max_vote_options_len = 5;
}

View File

@ -27,4 +27,5 @@ var (
ErrInvalidDepositDenom = errors.Register(ModuleName, 23, "invalid deposit denom")
ErrTitleTooLong = errors.Register(ModuleName, 24, "title too long")
ErrTooLateToCancel = errors.Register(ModuleName, 25, "too late to cancel proposal")
ErrTooManyVoteOptions = errors.Register(ModuleName, 26, "too many weighted vote options")
)