refactor(group): Distinguish Voting period and Execution period for group policies (#11198)

## Description

Closes: #11092

## TODOs

I'm thinking to do the 2 todos in a separate PR, or else this PR is too big. WDYT?

- [ ] #11246 This involves adding a new index ProposalsByVotingPeriodEnd, so might be better to do in another PR
- [ ] #11245  Also should be done in a separate PR (as it needs the above index)

### Main change 1: Group policy proto defs have `voting_period` and `min_execution_period`

For group policies:

```diff
- // Within this times votes and exec messages can be submitted.
- // timeout is the duration from submission of a proposal to the end of voting period
- google.protobuf.Duration timeout = 2 [(gogoproto.stdduration) = true, (gogoproto.nullable) = false];

+  // voting_period is the duration from submission of a proposal to the end of voting period
+  // Within this times votes can be submitted with MsgVote.
+  google.protobuf.Duration voting_period = 2 [(gogoproto.stdduration) = true, (gogoproto.nullable) = false];

+  // min_execution_period is the minimum duration after the proposal submission
+  // where members can start sending MsgExec. This means that the window for
+  // sending a MsgExec transaction is:
+  // `[ submission + min_execution_period ; submission + voting_period + max_execution_period]`
+  // where max_execution_period is a app-specific config, defined in the keeper.
+  // If not set, min_execution_period will default to 0.
+  google.protobuf.Duration min_execution_period = 3 [(gogoproto.stdduration) = true, (gogoproto.nullable) = false];
```

### Main Change 2: We don't update proposal's FinalTallyResult result on MsgVote/MsgSubmitProposal

Unless the msg has TryExec set to true, in which case the FinalTallyResult is updated ONLY if the tally is final.

### Main Change 3: Add a keeper-level `MaxExecutionPeriod`

MsgExecs will be rejected if they are sent after `voting_period_end + MaxExecutionPeriod`



---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] added `!` to the type prefix if API or client breaking change
- [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting))
- [ ] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules)
- [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing)
- [ ] added a changelog entry to `CHANGELOG.md`
- [ ] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [ ] updated the relevant documentation or specification
- [ ] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed 
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
This commit is contained in:
Amaury 2022-03-02 13:00:59 +01:00 committed by GitHub
parent 26c9a2d06d
commit da36c46f3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1937 additions and 743 deletions

View File

@ -3,15 +3,14 @@ package appv1alpha1
import ( import (
fmt "fmt" fmt "fmt"
io "io"
reflect "reflect"
sync "sync"
runtime "github.com/cosmos/cosmos-proto/runtime" runtime "github.com/cosmos/cosmos-proto/runtime"
protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoiface "google.golang.org/protobuf/runtime/protoiface" protoiface "google.golang.org/protobuf/runtime/protoiface"
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
descriptorpb "google.golang.org/protobuf/types/descriptorpb" descriptorpb "google.golang.org/protobuf/types/descriptorpb"
io "io"
reflect "reflect"
sync "sync"
) )
var _ protoreflect.List = (*_ModuleDescriptor_2_list)(nil) var _ protoreflect.List = (*_ModuleDescriptor_2_list)(nil)

View File

@ -4076,10 +4076,12 @@ type SnapshotIAVLItem struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
Version int64 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"` // version is block height
Height int32 `protobuf:"varint,4,opt,name=height,proto3" json:"height,omitempty"` Version int64 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"`
// height is depth of the tree.
Height int32 `protobuf:"varint,4,opt,name=height,proto3" json:"height,omitempty"`
} }
func (x *SnapshotIAVLItem) Reset() { func (x *SnapshotIAVLItem) Reset() {

File diff suppressed because it is too large Load Diff

View File

@ -3,16 +3,14 @@ package modulev1alpha1
import ( import (
fmt "fmt" fmt "fmt"
io "io"
reflect "reflect"
sync "sync"
runtime "github.com/cosmos/cosmos-proto/runtime" runtime "github.com/cosmos/cosmos-proto/runtime"
_ "github.com/cosmos/cosmos-sdk/api/cosmos/app/v1alpha1"
protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoiface "google.golang.org/protobuf/runtime/protoiface" protoiface "google.golang.org/protobuf/runtime/protoiface"
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
io "io"
_ "github.com/cosmos/cosmos-sdk/api/cosmos/app/v1alpha1" reflect "reflect"
sync "sync"
) )
var ( var (

View File

@ -3,15 +3,14 @@ package ormv1alpha1
import ( import (
fmt "fmt" fmt "fmt"
io "io"
reflect "reflect"
sync "sync"
runtime "github.com/cosmos/cosmos-proto/runtime" runtime "github.com/cosmos/cosmos-proto/runtime"
protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoiface "google.golang.org/protobuf/runtime/protoiface" protoiface "google.golang.org/protobuf/runtime/protoiface"
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
descriptorpb "google.golang.org/protobuf/types/descriptorpb" descriptorpb "google.golang.org/protobuf/types/descriptorpb"
io "io"
reflect "reflect"
sync "sync"
) )
var _ protoreflect.List = (*_TableDescriptor_2_list)(nil) var _ protoreflect.List = (*_TableDescriptor_2_list)(nil)

View File

@ -3,15 +3,14 @@ package ormv1alpha1
import ( import (
fmt "fmt" fmt "fmt"
io "io"
reflect "reflect"
sync "sync"
runtime "github.com/cosmos/cosmos-proto/runtime" runtime "github.com/cosmos/cosmos-proto/runtime"
protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoiface "google.golang.org/protobuf/runtime/protoiface" protoiface "google.golang.org/protobuf/runtime/protoiface"
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
descriptorpb "google.golang.org/protobuf/types/descriptorpb" descriptorpb "google.golang.org/protobuf/types/descriptorpb"
io "io"
reflect "reflect"
sync "sync"
) )
var _ protoreflect.List = (*_ModuleSchemaDescriptor_1_list)(nil) var _ protoreflect.List = (*_ModuleSchemaDescriptor_1_list)(nil)

View File

@ -41,9 +41,8 @@ message ThresholdDecisionPolicy {
// threshold is the minimum weighted sum of yes votes that must be met or exceeded for a proposal to succeed. // threshold is the minimum weighted sum of yes votes that must be met or exceeded for a proposal to succeed.
string threshold = 1; string threshold = 1;
// timeout is the duration from submission of a proposal to the end of voting period // windows defines the different windows for voting and execution.
// Within this times votes and exec messages can be submitted. DecisionPolicyWindows windows = 2;
google.protobuf.Duration timeout = 2 [(gogoproto.stdduration) = true, (gogoproto.nullable) = false];
} }
// PercentageDecisionPolicy implements the DecisionPolicy interface // PercentageDecisionPolicy implements the DecisionPolicy interface
@ -53,9 +52,28 @@ message PercentageDecisionPolicy {
// percentage is the minimum percentage the weighted sum of yes votes must meet for a proposal to succeed. // percentage is the minimum percentage the weighted sum of yes votes must meet for a proposal to succeed.
string percentage = 1; string percentage = 1;
// timeout is the duration from submission of a proposal to the end of voting period // windows defines the different windows for voting and execution.
// Within these times votes and exec messages can be submitted. DecisionPolicyWindows windows = 2;
google.protobuf.Duration timeout = 2 [(gogoproto.stdduration) = true, (gogoproto.nullable) = false]; }
// DecisionPolicyWindows defines the different windows for voting and execution.
message DecisionPolicyWindows {
// voting_period is the duration from submission of a proposal to the end of voting period
// Within this times votes can be submitted with MsgVote.
google.protobuf.Duration voting_period = 1 [(gogoproto.stdduration) = true, (gogoproto.nullable) = false];
// min_execution_period is the minimum duration after the proposal submission
// where members can start sending MsgExec. This means that the window for
// sending a MsgExec transaction is:
// `[ submission + min_execution_period ; submission + voting_period + max_execution_period]`
// where max_execution_period is a app-specific config, defined in the keeper.
// If not set, min_execution_period will default to 0.
//
// Please make sure to set a `min_execution_period` that is smaller than
// `voting_period + max_execution_period`, or else the above execution window
// is empty, meaning that all proposals created with this decision policy
// won't be able to be executed.
google.protobuf.Duration min_execution_period = 2 [(gogoproto.stdduration) = true, (gogoproto.nullable) = false];
} }
// VoteOption enumerates the valid vote options for a given proposal. // VoteOption enumerates the valid vote options for a given proposal.
@ -184,11 +202,12 @@ message Proposal {
// has ended. // has ended.
TallyResult final_tally_result = 10 [(gogoproto.nullable) = false]; TallyResult final_tally_result = 10 [(gogoproto.nullable) = false];
// timeout is the timestamp before which both voting and execution must be // voting_period_end is the timestamp before which voting must be done.
// done. If this timestamp is passed, then the proposal cannot be executed // Unless a successfull MsgExec is called before (to execute a proposal whose
// anymore and should be considered pending delete. This timestamp is checked // tally is successful before the voting period ends), tallying will be done
// against the block header's timestamp. // at this point, and the `final_tally_result`, as well
google.protobuf.Timestamp timeout = 11 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; // as `status` and `result` fields will be accordingly updated.
google.protobuf.Timestamp voting_period_end = 11 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
// executor_result is the final result based on the votes and election rule. Initial value is NotRun. // executor_result is the final result based on the votes and election rule. Initial value is NotRun.
ProposalExecutorResult executor_result = 12; ProposalExecutorResult executor_result = 12;

View File

@ -295,7 +295,7 @@ func NewSimApp(
app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.msgSvcRouter, app.AccountKeeper) app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.msgSvcRouter, app.AccountKeeper)
groupConfig := groupkeeper.DefaultConfig() groupConfig := group.DefaultConfig()
/* /*
Example of setting group params: Example of setting group params:
groupConfig.MaxMetadataLen = 1000 groupConfig.MaxMetadataLen = 1000

View File

@ -306,10 +306,12 @@ func (m *SnapshotStoreItem) GetName() string {
// SnapshotIAVLItem is an exported IAVL node. // SnapshotIAVLItem is an exported IAVL node.
type SnapshotIAVLItem struct { type SnapshotIAVLItem struct {
Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
Version int64 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"` // version is block height
Height int32 `protobuf:"varint,4,opt,name=height,proto3" json:"height,omitempty"` Version int64 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"`
// height is depth of the tree.
Height int32 `protobuf:"varint,4,opt,name=height,proto3" json:"height,omitempty"`
} }
func (m *SnapshotIAVLItem) Reset() { *m = SnapshotIAVLItem{} } func (m *SnapshotIAVLItem) Reset() { *m = SnapshotIAVLItem{} }

View File

@ -553,6 +553,8 @@ func (m *GetTxResponse) GetTxResponse() *types.TxResponse {
// GetBlockWithTxsRequest is the request type for the Service.GetBlockWithTxs // GetBlockWithTxsRequest is the request type for the Service.GetBlockWithTxs
// RPC method. // RPC method.
//
// Since: cosmos-sdk 0.45.2
type GetBlockWithTxsRequest struct { type GetBlockWithTxsRequest struct {
// height is the height of the block to query. // height is the height of the block to query.
Height int64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` Height int64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"`
@ -608,6 +610,8 @@ func (m *GetBlockWithTxsRequest) GetPagination() *query.PageRequest {
} }
// GetBlockWithTxsResponse is the response type for the Service.GetBlockWithTxs method. // GetBlockWithTxsResponse is the response type for the Service.GetBlockWithTxs method.
//
// Since: cosmos-sdk 0.45.2
type GetBlockWithTxsResponse struct { type GetBlockWithTxsResponse struct {
// txs are the transactions in the block. // txs are the transactions in the block.
Txs []*Tx `protobuf:"bytes,1,rep,name=txs,proto3" json:"txs,omitempty"` Txs []*Tx `protobuf:"bytes,1,rep,name=txs,proto3" json:"txs,omitempty"`

View File

@ -120,7 +120,7 @@ func (s *IntegrationTestSuite) SetupSuite() {
val.Address.String(), val.Address.String(),
"1", "1",
validMetadata, validMetadata,
fmt.Sprintf("{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"%d\", \"timeout\":\"30000s\"}", threshold), fmt.Sprintf("{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"%d\", \"windows\":{\"voting_period\":\"30000s\"}}", threshold),
}, },
commonFlags..., commonFlags...,
), ),
@ -140,7 +140,7 @@ func (s *IntegrationTestSuite) SetupSuite() {
val.Address.String(), val.Address.String(),
"1", "1",
validMetadata, validMetadata,
fmt.Sprintf("{\"@type\":\"/cosmos.group.v1beta1.PercentageDecisionPolicy\", \"percentage\":\"%f\", \"timeout\":\"30000s\"}", percentage), fmt.Sprintf("{\"@type\":\"/cosmos.group.v1beta1.PercentageDecisionPolicy\", \"percentage\":\"%f\", \"windows\":{\"voting_period\":\"30000s\"}}", percentage),
}, },
commonFlags..., commonFlags...,
), ),
@ -746,7 +746,7 @@ func (s *IntegrationTestSuite) TestTxCreateGroupWithPolicy() {
validMetadata, validMetadata,
validMetadata, validMetadata,
validMembersFile.Name(), validMembersFile.Name(),
"{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"windows\":{\"voting_period\":\"1s\"}}",
fmt.Sprintf("--%s=%v", client.FlagGroupPolicyAsAdmin, false), fmt.Sprintf("--%s=%v", client.FlagGroupPolicyAsAdmin, false),
}, },
commonFlags..., commonFlags...,
@ -764,7 +764,7 @@ func (s *IntegrationTestSuite) TestTxCreateGroupWithPolicy() {
validMetadata, validMetadata,
validMetadata, validMetadata,
validMembersFile.Name(), validMembersFile.Name(),
"{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"windows\":{\"voting_period\":\"1s\"}}",
fmt.Sprintf("--%s=%v", client.FlagGroupPolicyAsAdmin, true), fmt.Sprintf("--%s=%v", client.FlagGroupPolicyAsAdmin, true),
}, },
commonFlags..., commonFlags...,
@ -782,7 +782,7 @@ func (s *IntegrationTestSuite) TestTxCreateGroupWithPolicy() {
validMetadata, validMetadata,
validMetadata, validMetadata,
validMembersFile.Name(), validMembersFile.Name(),
"{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"windows\":{\"voting_period\":\"1s\"}}",
fmt.Sprintf("--%s=%v", client.FlagGroupPolicyAsAdmin, false), fmt.Sprintf("--%s=%v", client.FlagGroupPolicyAsAdmin, false),
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON),
}, },
@ -801,7 +801,7 @@ func (s *IntegrationTestSuite) TestTxCreateGroupWithPolicy() {
strings.Repeat("a", 256), strings.Repeat("a", 256),
validMetadata, validMetadata,
validMembersFile.Name(), validMembersFile.Name(),
"{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"windows\":{\"voting_period\":\"1s\"}}",
fmt.Sprintf("--%s=%v", client.FlagGroupPolicyAsAdmin, false), fmt.Sprintf("--%s=%v", client.FlagGroupPolicyAsAdmin, false),
}, },
commonFlags..., commonFlags...,
@ -819,7 +819,7 @@ func (s *IntegrationTestSuite) TestTxCreateGroupWithPolicy() {
validMetadata, validMetadata,
strings.Repeat("a", 256), strings.Repeat("a", 256),
validMembersFile.Name(), validMembersFile.Name(),
"{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"windows\":{\"voting_period\":\"1s\"}}",
fmt.Sprintf("--%s=%v", client.FlagGroupPolicyAsAdmin, false), fmt.Sprintf("--%s=%v", client.FlagGroupPolicyAsAdmin, false),
}, },
commonFlags..., commonFlags...,
@ -837,7 +837,7 @@ func (s *IntegrationTestSuite) TestTxCreateGroupWithPolicy() {
validMetadata, validMetadata,
validMetadata, validMetadata,
invalidMembersAddressFile.Name(), invalidMembersAddressFile.Name(),
"{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"windows\":{\"voting_period\":\"1s\"}}",
fmt.Sprintf("--%s=%v", client.FlagGroupPolicyAsAdmin, false), fmt.Sprintf("--%s=%v", client.FlagGroupPolicyAsAdmin, false),
}, },
commonFlags..., commonFlags...,
@ -855,7 +855,7 @@ func (s *IntegrationTestSuite) TestTxCreateGroupWithPolicy() {
validMetadata, validMetadata,
validMetadata, validMetadata,
invalidMembersWeightFile.Name(), invalidMembersWeightFile.Name(),
"{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"windows\":{\"voting_period\":\"1s\"}}",
fmt.Sprintf("--%s=%v", client.FlagGroupPolicyAsAdmin, false), fmt.Sprintf("--%s=%v", client.FlagGroupPolicyAsAdmin, false),
}, },
commonFlags..., commonFlags...,
@ -873,7 +873,7 @@ func (s *IntegrationTestSuite) TestTxCreateGroupWithPolicy() {
validMetadata, validMetadata,
validMetadata, validMetadata,
invalidMembersMetadataFile.Name(), invalidMembersMetadataFile.Name(),
"{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"windows\":{\"voting_period\":\"1s\"}}",
fmt.Sprintf("--%s=%v", client.FlagGroupPolicyAsAdmin, false), fmt.Sprintf("--%s=%v", client.FlagGroupPolicyAsAdmin, false),
}, },
commonFlags..., commonFlags...,
@ -932,7 +932,7 @@ func (s *IntegrationTestSuite) TestTxCreateGroupPolicy() {
val.Address.String(), val.Address.String(),
fmt.Sprintf("%v", groupID), fmt.Sprintf("%v", groupID),
validMetadata, validMetadata,
"{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"windows\":{\"voting_period\":\"1s\"}}",
}, },
commonFlags..., commonFlags...,
), ),
@ -948,7 +948,7 @@ func (s *IntegrationTestSuite) TestTxCreateGroupPolicy() {
val.Address.String(), val.Address.String(),
fmt.Sprintf("%v", groupID), fmt.Sprintf("%v", groupID),
validMetadata, validMetadata,
"{\"@type\":\"/cosmos.group.v1beta1.PercentageDecisionPolicy\", \"percentage\":\"0.5\", \"timeout\":\"1s\"}", "{\"@type\":\"/cosmos.group.v1beta1.PercentageDecisionPolicy\", \"percentage\":\"0.5\", \"windows\":{\"voting_period\":\"1s\"}}",
}, },
commonFlags..., commonFlags...,
), ),
@ -964,7 +964,7 @@ func (s *IntegrationTestSuite) TestTxCreateGroupPolicy() {
val.Address.String(), val.Address.String(),
fmt.Sprintf("%v", groupID), fmt.Sprintf("%v", groupID),
validMetadata, validMetadata,
"{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"windows\":{\"voting_period\":\"1s\"}}",
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON),
}, },
commonFlags..., commonFlags...,
@ -981,7 +981,7 @@ func (s *IntegrationTestSuite) TestTxCreateGroupPolicy() {
wrongAdmin.String(), wrongAdmin.String(),
fmt.Sprintf("%v", groupID), fmt.Sprintf("%v", groupID),
validMetadata, validMetadata,
"{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"windows\":{\"voting_period\":\"1s\"}}",
}, },
commonFlags..., commonFlags...,
), ),
@ -997,7 +997,7 @@ func (s *IntegrationTestSuite) TestTxCreateGroupPolicy() {
val.Address.String(), val.Address.String(),
fmt.Sprintf("%v", groupID), fmt.Sprintf("%v", groupID),
strings.Repeat("a", 500), strings.Repeat("a", 500),
"{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"windows\":{\"voting_period\":\"1s\"}}",
}, },
commonFlags..., commonFlags...,
), ),
@ -1013,7 +1013,7 @@ func (s *IntegrationTestSuite) TestTxCreateGroupPolicy() {
val.Address.String(), val.Address.String(),
"10", "10",
validMetadata, validMetadata,
"{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"windows\":{\"voting_period\":\"1s\"}}",
}, },
commonFlags..., commonFlags...,
), ),
@ -1029,7 +1029,7 @@ func (s *IntegrationTestSuite) TestTxCreateGroupPolicy() {
val.Address.String(), val.Address.String(),
fmt.Sprintf("%v", groupID), fmt.Sprintf("%v", groupID),
validMetadata, validMetadata,
"{\"@type\":\"/cosmos.group.v1beta1.PercentageDecisionPolicy\", \"percentage\":\"-0.5\", \"timeout\":\"1s\"}", "{\"@type\":\"/cosmos.group.v1beta1.PercentageDecisionPolicy\", \"percentage\":\"-0.5\", \"windows\":{\"voting_period\":\"1s\"}}",
}, },
commonFlags..., commonFlags...,
), ),
@ -1045,7 +1045,7 @@ func (s *IntegrationTestSuite) TestTxCreateGroupPolicy() {
val.Address.String(), val.Address.String(),
fmt.Sprintf("%v", groupID), fmt.Sprintf("%v", groupID),
validMetadata, validMetadata,
"{\"@type\":\"/cosmos.group.v1beta1.PercentageDecisionPolicy\", \"percentage\":\"2\", \"timeout\":\"1s\"}", "{\"@type\":\"/cosmos.group.v1beta1.PercentageDecisionPolicy\", \"percentage\":\"2\", \"windows\":{\"voting_period\":\"1s\"}}",
}, },
commonFlags..., commonFlags...,
), ),
@ -1205,7 +1205,7 @@ func (s *IntegrationTestSuite) TestTxUpdateGroupPolicyDecisionPolicy() {
[]string{ []string{
groupPolicy.Admin, groupPolicy.Admin,
groupPolicy.Address, groupPolicy.Address,
"{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"40000s\"}", "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"windows\":{\"voting_period\":\"40000s\"}}",
}, },
commonFlags..., commonFlags...,
), ),
@ -1220,7 +1220,7 @@ func (s *IntegrationTestSuite) TestTxUpdateGroupPolicyDecisionPolicy() {
[]string{ []string{
groupPolicy.Admin, groupPolicy.Admin,
groupPolicy.Address, groupPolicy.Address,
"{\"@type\":\"/cosmos.group.v1beta1.PercentageDecisionPolicy\", \"percentage\":\"0.5\", \"timeout\":\"40000s\"}", "{\"@type\":\"/cosmos.group.v1beta1.PercentageDecisionPolicy\", \"percentage\":\"0.5\", \"windows\":{\"voting_period\":\"40000s\"}}",
}, },
commonFlags..., commonFlags...,
), ),
@ -1235,7 +1235,7 @@ func (s *IntegrationTestSuite) TestTxUpdateGroupPolicyDecisionPolicy() {
[]string{ []string{
groupPolicy.Admin, groupPolicy.Admin,
groupPolicy.Address, groupPolicy.Address,
"{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"50000s\"}", "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"windows\":{\"voting_period\":\"50000s\"}}",
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON),
}, },
commonFlags..., commonFlags...,
@ -1251,7 +1251,7 @@ func (s *IntegrationTestSuite) TestTxUpdateGroupPolicyDecisionPolicy() {
[]string{ []string{
newAdmin.String(), newAdmin.String(),
groupPolicy.Address, groupPolicy.Address,
"{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"windows\":{\"voting_period\":\"1s\"}}",
}, },
commonFlags..., commonFlags...,
), ),
@ -1266,7 +1266,7 @@ func (s *IntegrationTestSuite) TestTxUpdateGroupPolicyDecisionPolicy() {
[]string{ []string{
groupPolicy.Admin, groupPolicy.Admin,
newAdmin.String(), newAdmin.String(),
"{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"windows\":{\"voting_period\":\"1s\"}}",
}, },
commonFlags..., commonFlags...,
), ),
@ -1281,7 +1281,7 @@ func (s *IntegrationTestSuite) TestTxUpdateGroupPolicyDecisionPolicy() {
[]string{ []string{
groupPolicy.Admin, groupPolicy.Admin,
groupPolicy.Address, groupPolicy.Address,
"{\"@type\":\"/cosmos.group.v1beta1.PercentageDecisionPolicy\", \"percentage\":\"-0.5\", \"timeout\":\"1s\"}", "{\"@type\":\"/cosmos.group.v1beta1.PercentageDecisionPolicy\", \"percentage\":\"-0.5\", \"windows\":{\"voting_period\":\"1s\"}}",
}, },
commonFlags..., commonFlags...,
), ),
@ -1296,7 +1296,7 @@ func (s *IntegrationTestSuite) TestTxUpdateGroupPolicyDecisionPolicy() {
[]string{ []string{
groupPolicy.Admin, groupPolicy.Admin,
groupPolicy.Address, groupPolicy.Address,
"{\"@type\":\"/cosmos.group.v1beta1.PercentageDecisionPolicy\", \"percentage\":\"2\", \"timeout\":\"40000s\"}", "{\"@type\":\"/cosmos.group.v1beta1.PercentageDecisionPolicy\", \"percentage\":\"2\", \"windows\":{\"voting_period\":\"40000s\"}}",
}, },
commonFlags..., commonFlags...,
), ),

View File

@ -1,7 +1,12 @@
package keeper package group
import "time"
// Config is a config struct used for intialising the group module to avoid using globals. // Config is a config struct used for intialising the group module to avoid using globals.
type Config struct { type Config struct {
// MaxExecutionPeriod defines the max duration after a proposal's voting
// period ends that members can send a MsgExec to execute the proposal.
MaxExecutionPeriod time.Duration
// MaxMetadataLen defines the max length of the metadata bytes field for various entities within the group module. Defaults to 255 if not explicitly set. // MaxMetadataLen defines the max length of the metadata bytes field for various entities within the group module. Defaults to 255 if not explicitly set.
MaxMetadataLen uint64 MaxMetadataLen uint64
} }
@ -9,6 +14,7 @@ type Config struct {
// DefaultConfig returns the default config for group. // DefaultConfig returns the default config for group.
func DefaultConfig() Config { func DefaultConfig() Config {
return Config{ return Config{
MaxMetadataLen: 255, MaxExecutionPeriod: 2 * time.Hour * 24 * 7, // Two weeks.
MaxMetadataLen: 255,
} }
} }

View File

@ -31,7 +31,9 @@ func TestGenesisStateValidate(t *testing.T) {
} }
err := groupPolicy.SetDecisionPolicy(&ThresholdDecisionPolicy{ err := groupPolicy.SetDecisionPolicy(&ThresholdDecisionPolicy{
Threshold: "1", Threshold: "1",
Timeout: time.Second, Windows: &DecisionPolicyWindows{
VotingPeriod: time.Second,
},
}) })
require.NoError(t, err) require.NoError(t, err)
@ -45,7 +47,9 @@ func TestGenesisStateValidate(t *testing.T) {
} }
err = groupPolicy2.SetDecisionPolicy(&ThresholdDecisionPolicy{ err = groupPolicy2.SetDecisionPolicy(&ThresholdDecisionPolicy{
Threshold: "1", Threshold: "1",
Timeout: 0, Windows: &DecisionPolicyWindows{
VotingPeriod: 0,
},
}) })
require.NoError(t, err) require.NoError(t, err)
@ -67,8 +71,8 @@ func TestGenesisStateValidate(t *testing.T) {
AbstainCount: "0", AbstainCount: "0",
NoWithVetoCount: "0", NoWithVetoCount: "0",
}, },
Timeout: timeout, VotingPeriodEnd: timeout,
ExecutorResult: PROPOSAL_EXECUTOR_RESULT_SUCCESS, ExecutorResult: PROPOSAL_EXECUTOR_RESULT_SUCCESS,
} }
err = proposal.SetMsgs([]sdk.Msg{&banktypes.MsgSend{ err = proposal.SetMsgs([]sdk.Msg{&banktypes.MsgSend{
FromAddress: accAddr.String(), FromAddress: accAddr.String(),

View File

@ -73,7 +73,9 @@ func (s *GenesisTestSuite) TestInitExportGenesis() {
} }
err := groupPolicy.SetDecisionPolicy(&group.ThresholdDecisionPolicy{ err := groupPolicy.SetDecisionPolicy(&group.ThresholdDecisionPolicy{
Threshold: "1", Threshold: "1",
Timeout: time.Second, Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second,
},
}) })
s.Require().NoError(err) s.Require().NoError(err)
@ -95,8 +97,8 @@ func (s *GenesisTestSuite) TestInitExportGenesis() {
AbstainCount: "0", AbstainCount: "0",
NoWithVetoCount: "0", NoWithVetoCount: "0",
}, },
Timeout: timeout, VotingPeriodEnd: timeout,
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
} }
err = proposal.SetMsgs([]sdk.Msg{&banktypes.MsgSend{ err = proposal.SetMsgs([]sdk.Msg{&banktypes.MsgSend{
FromAddress: accAddr.String(), FromAddress: accAddr.String(),
@ -216,7 +218,7 @@ func (s *GenesisTestSuite) assertProposalsEqual(g *group.Proposal, other *group.
require.Equal(g.Status, other.Status) require.Equal(g.Status, other.Status)
require.Equal(g.Result, other.Result) require.Equal(g.Result, other.Result)
require.Equal(g.FinalTallyResult, other.FinalTallyResult) require.Equal(g.FinalTallyResult, other.FinalTallyResult)
require.Equal(g.Timeout, other.Timeout) require.Equal(g.VotingPeriodEnd, other.VotingPeriodEnd)
require.Equal(g.ExecutorResult, other.ExecutorResult) require.Equal(g.ExecutorResult, other.ExecutorResult)
require.Equal(g.GetMsgs(), other.GetMsgs()) require.Equal(g.GetMsgs(), other.GetMsgs())
} }

View File

@ -10,6 +10,7 @@ import (
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/query" "github.com/cosmos/cosmos-sdk/types/query"
"github.com/cosmos/cosmos-sdk/x/group" "github.com/cosmos/cosmos-sdk/x/group"
"github.com/cosmos/cosmos-sdk/x/group/errors"
"github.com/cosmos/cosmos-sdk/x/group/internal/orm" "github.com/cosmos/cosmos-sdk/x/group/internal/orm"
) )
@ -303,3 +304,55 @@ func (q Keeper) getVotesByProposal(ctx sdk.Context, proposalID uint64, pageReque
func (q Keeper) getVotesByVoter(ctx sdk.Context, voter sdk.AccAddress, pageRequest *query.PageRequest) (orm.Iterator, error) { func (q Keeper) getVotesByVoter(ctx sdk.Context, voter sdk.AccAddress, pageRequest *query.PageRequest) (orm.Iterator, error) {
return q.voteByVoterIndex.GetPaginated(ctx.KVStore(q.key), voter.Bytes(), pageRequest) return q.voteByVoterIndex.GetPaginated(ctx.KVStore(q.key), voter.Bytes(), pageRequest)
} }
// Tally is a function that tallies a proposal by iterating through its votes,
// and returns the tally result without modifying the proposal or any state.
// TODO Merge with https://github.com/cosmos/cosmos-sdk/issues/11151
func (q Keeper) Tally(ctx sdk.Context, p group.Proposal, groupId uint64) (group.TallyResult, error) {
// If proposal has already been tallied and updated, then its status is
// closed, in which case we just return the previously stored result.
if p.Status == group.PROPOSAL_STATUS_CLOSED {
return p.FinalTallyResult, nil
}
it, err := q.voteByProposalIndex.Get(ctx.KVStore(q.key), p.Id)
if err != nil {
return group.TallyResult{}, err
}
defer it.Close()
tallyResult := group.DefaultTallyResult()
var vote group.Vote
for {
_, err = it.LoadNext(&vote)
if errors.ErrORMIteratorDone.Is(err) {
break
}
if err != nil {
return group.TallyResult{}, err
}
var member group.GroupMember
err := q.groupMemberTable.GetOne(ctx.KVStore(q.key), orm.PrimaryKey(&group.GroupMember{
GroupId: groupId,
Member: &group.Member{Address: vote.Voter},
}), &member)
switch {
case sdkerrors.ErrNotFound.Is(err):
// If the member left the group after voting, then we simply skip the
// vote.
continue
case err != nil:
// For any other errors, we stop and return the error.
return group.TallyResult{}, err
}
if err := tallyResult.Add(vote, member.Member.Weight); err != nil {
return group.TallyResult{}, sdkerrors.Wrap(err, "add new vote")
}
}
return tallyResult, nil
}

View File

@ -25,10 +25,9 @@ import (
type invariantTestSuite struct { type invariantTestSuite struct {
suite.Suite suite.Suite
ctx sdk.Context ctx sdk.Context
cdc *codec.ProtoCodec cdc *codec.ProtoCodec
key *storetypes.KVStoreKey key *storetypes.KVStoreKey
blockTime time.Time
} }
func TestInvariantTestSuite(t *testing.T) { func TestInvariantTestSuite(t *testing.T) {
@ -81,7 +80,7 @@ func (s *invariantTestSuite) TestTallyVotesInvariant() {
Status: group.PROPOSAL_STATUS_SUBMITTED, Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED, Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "1", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"}, FinalTallyResult: group.TallyResult{YesCount: "1", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
Timeout: prevCtx.BlockTime().Add(time.Second * 600), VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
}, },
@ -95,7 +94,7 @@ func (s *invariantTestSuite) TestTallyVotesInvariant() {
Status: group.PROPOSAL_STATUS_SUBMITTED, Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED, Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "2", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"}, FinalTallyResult: group.TallyResult{YesCount: "2", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
Timeout: curCtx.BlockTime().Add(time.Second * 600), VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
}, },
}, },
@ -110,7 +109,7 @@ func (s *invariantTestSuite) TestTallyVotesInvariant() {
Status: group.PROPOSAL_STATUS_SUBMITTED, Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED, Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "2", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"}, FinalTallyResult: group.TallyResult{YesCount: "2", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
Timeout: prevCtx.BlockTime().Add(time.Second * 600), VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
}, },
curProposal: &group.Proposal{ curProposal: &group.Proposal{
@ -123,7 +122,7 @@ func (s *invariantTestSuite) TestTallyVotesInvariant() {
Status: group.PROPOSAL_STATUS_SUBMITTED, Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED, Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "1", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"}, FinalTallyResult: group.TallyResult{YesCount: "1", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
Timeout: curCtx.BlockTime().Add(time.Second * 600), VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
}, },
expBroken: true, expBroken: true,
@ -139,7 +138,7 @@ func (s *invariantTestSuite) TestTallyVotesInvariant() {
Status: group.PROPOSAL_STATUS_SUBMITTED, Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED, Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "2", AbstainCount: "0", NoWithVetoCount: "0"}, FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "2", AbstainCount: "0", NoWithVetoCount: "0"},
Timeout: prevCtx.BlockTime().Add(time.Second * 600), VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
}, },
curProposal: &group.Proposal{ curProposal: &group.Proposal{
@ -152,7 +151,7 @@ func (s *invariantTestSuite) TestTallyVotesInvariant() {
Status: group.PROPOSAL_STATUS_SUBMITTED, Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED, Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "1", AbstainCount: "0", NoWithVetoCount: "0"}, FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "1", AbstainCount: "0", NoWithVetoCount: "0"},
Timeout: curCtx.BlockTime().Add(time.Second * 600), VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
}, },
expBroken: true, expBroken: true,
@ -168,7 +167,7 @@ func (s *invariantTestSuite) TestTallyVotesInvariant() {
Status: group.PROPOSAL_STATUS_SUBMITTED, Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED, Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "2", NoWithVetoCount: "0"}, FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "2", NoWithVetoCount: "0"},
Timeout: prevCtx.BlockTime().Add(time.Second * 600), VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
}, },
curProposal: &group.Proposal{ curProposal: &group.Proposal{
@ -181,7 +180,7 @@ func (s *invariantTestSuite) TestTallyVotesInvariant() {
Status: group.PROPOSAL_STATUS_SUBMITTED, Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED, Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "1", NoWithVetoCount: "0"}, FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "1", NoWithVetoCount: "0"},
Timeout: curCtx.BlockTime().Add(time.Second * 600), VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
}, },
expBroken: true, expBroken: true,
@ -197,7 +196,7 @@ func (s *invariantTestSuite) TestTallyVotesInvariant() {
Status: group.PROPOSAL_STATUS_SUBMITTED, Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED, Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "2"}, FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "2"},
Timeout: prevCtx.BlockTime().Add(time.Second * 600), VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
}, },
curProposal: &group.Proposal{ curProposal: &group.Proposal{
@ -210,7 +209,7 @@ func (s *invariantTestSuite) TestTallyVotesInvariant() {
Status: group.PROPOSAL_STATUS_SUBMITTED, Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED, Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "1"}, FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "1"},
Timeout: curCtx.BlockTime().Add(time.Second * 600), VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
}, },
expBroken: true, expBroken: true,
@ -413,7 +412,7 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
Status: group.PROPOSAL_STATUS_SUBMITTED, Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED, Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "4", NoCount: "3", AbstainCount: "0", NoWithVetoCount: "0"}, FinalTallyResult: group.TallyResult{YesCount: "4", NoCount: "3", AbstainCount: "0", NoWithVetoCount: "0"},
Timeout: curCtx.BlockTime().Add(time.Second * 600), VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
}, },
votes: []*group.Vote{ votes: []*group.Vote{
@ -471,7 +470,7 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
Status: group.PROPOSAL_STATUS_SUBMITTED, Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED, Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "6", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"}, FinalTallyResult: group.TallyResult{YesCount: "6", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
Timeout: curCtx.BlockTime().Add(time.Second * 600), VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
}, },
votes: []*group.Vote{ votes: []*group.Vote{
@ -529,7 +528,7 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
Status: group.PROPOSAL_STATUS_SUBMITTED, Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED, Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "4", NoCount: "3", AbstainCount: "0", NoWithVetoCount: "0"}, FinalTallyResult: group.TallyResult{YesCount: "4", NoCount: "3", AbstainCount: "0", NoWithVetoCount: "0"},
Timeout: curCtx.BlockTime().Add(time.Second * 600), VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
}, },
votes: []*group.Vote{ votes: []*group.Vote{
@ -561,7 +560,7 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
_, err := groupTable.Create(cacheCurCtx.KVStore(key), groupsInfo) _, err := groupTable.Create(cacheCurCtx.KVStore(key), groupsInfo)
s.Require().NoError(err) s.Require().NoError(err)
err = groupPolicy.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Second)) err = groupPolicy.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Second, 0))
s.Require().NoError(err) s.Require().NoError(err)
err = groupPolicyTable.Create(cacheCurCtx.KVStore(key), groupPolicy) err = groupPolicyTable.Create(cacheCurCtx.KVStore(key), groupPolicy)
s.Require().NoError(err) s.Require().NoError(err)

View File

@ -74,10 +74,10 @@ type Keeper struct {
router *authmiddleware.MsgServiceRouter router *authmiddleware.MsgServiceRouter
config Config config group.Config
} }
func NewKeeper(storeKey storetypes.StoreKey, cdc codec.Codec, router *authmiddleware.MsgServiceRouter, accKeeper group.AccountKeeper, config Config) Keeper { func NewKeeper(storeKey storetypes.StoreKey, cdc codec.Codec, router *authmiddleware.MsgServiceRouter, accKeeper group.AccountKeeper, config group.Config) Keeper {
k := Keeper{ k := Keeper{
key: storeKey, key: storeKey,
router: router, router: router,
@ -207,7 +207,10 @@ func NewKeeper(storeKey storetypes.StoreKey, cdc codec.Codec, router *authmiddle
k.voteTable = *voteTable k.voteTable = *voteTable
if config.MaxMetadataLen == 0 { if config.MaxMetadataLen == 0 {
config.MaxMetadataLen = DefaultConfig().MaxMetadataLen config.MaxMetadataLen = group.DefaultConfig().MaxMetadataLen
}
if config.MaxExecutionPeriod == 0 {
config.MaxExecutionPeriod = group.DefaultConfig().MaxExecutionPeriod
} }
k.config = config k.config = config

View File

@ -61,6 +61,7 @@ func (s *TestSuite) SetupTest() {
policy := group.NewThresholdDecisionPolicy( policy := group.NewThresholdDecisionPolicy(
"2", "2",
time.Second, time.Second,
0,
) )
policyReq := &group.MsgCreateGroupPolicy{ policyReq := &group.MsgCreateGroupPolicy{
Admin: s.addrs[0].String(), Admin: s.addrs[0].String(),
@ -693,6 +694,7 @@ func (s *TestSuite) TestCreateGroupWithPolicy() {
policy: group.NewThresholdDecisionPolicy( policy: group.NewThresholdDecisionPolicy(
"1", "1",
time.Second, time.Second,
0,
), ),
}, },
"group policy as admin is true": { "group policy as admin is true": {
@ -704,6 +706,7 @@ func (s *TestSuite) TestCreateGroupWithPolicy() {
policy: group.NewThresholdDecisionPolicy( policy: group.NewThresholdDecisionPolicy(
"1", "1",
time.Second, time.Second,
0,
), ),
}, },
"group metadata too long": { "group metadata too long": {
@ -716,6 +719,7 @@ func (s *TestSuite) TestCreateGroupWithPolicy() {
policy: group.NewThresholdDecisionPolicy( policy: group.NewThresholdDecisionPolicy(
"1", "1",
time.Second, time.Second,
0,
), ),
expErr: true, expErr: true,
expErrMsg: "limit exceeded", expErrMsg: "limit exceeded",
@ -730,6 +734,7 @@ func (s *TestSuite) TestCreateGroupWithPolicy() {
policy: group.NewThresholdDecisionPolicy( policy: group.NewThresholdDecisionPolicy(
"1", "1",
time.Second, time.Second,
0,
), ),
expErr: true, expErr: true,
expErrMsg: "limit exceeded", expErrMsg: "limit exceeded",
@ -747,6 +752,7 @@ func (s *TestSuite) TestCreateGroupWithPolicy() {
policy: group.NewThresholdDecisionPolicy( policy: group.NewThresholdDecisionPolicy(
"1", "1",
time.Second, time.Second,
0,
), ),
expErr: true, expErr: true,
expErrMsg: "limit exceeded", expErrMsg: "limit exceeded",
@ -763,6 +769,7 @@ func (s *TestSuite) TestCreateGroupWithPolicy() {
policy: group.NewThresholdDecisionPolicy( policy: group.NewThresholdDecisionPolicy(
"1", "1",
time.Second, time.Second,
0,
), ),
expErr: true, expErr: true,
expErrMsg: "expected a positive decimal", expErrMsg: "expected a positive decimal",
@ -776,6 +783,7 @@ func (s *TestSuite) TestCreateGroupWithPolicy() {
policy: group.NewThresholdDecisionPolicy( policy: group.NewThresholdDecisionPolicy(
"10", "10",
time.Second, time.Second,
0,
), ),
expErr: false, expErr: false,
}, },
@ -876,6 +884,7 @@ func (s *TestSuite) TestCreateGroupPolicy() {
policy: group.NewThresholdDecisionPolicy( policy: group.NewThresholdDecisionPolicy(
"1", "1",
time.Second, time.Second,
0,
), ),
}, },
"all good with percentage decision policy": { "all good with percentage decision policy": {
@ -886,6 +895,7 @@ func (s *TestSuite) TestCreateGroupPolicy() {
policy: group.NewPercentageDecisionPolicy( policy: group.NewPercentageDecisionPolicy(
"0.5", "0.5",
time.Second, time.Second,
0,
), ),
}, },
"decision policy threshold > total group weight": { "decision policy threshold > total group weight": {
@ -896,6 +906,7 @@ func (s *TestSuite) TestCreateGroupPolicy() {
policy: group.NewThresholdDecisionPolicy( policy: group.NewThresholdDecisionPolicy(
"10", "10",
time.Second, time.Second,
0,
), ),
}, },
"group id does not exists": { "group id does not exists": {
@ -906,6 +917,7 @@ func (s *TestSuite) TestCreateGroupPolicy() {
policy: group.NewThresholdDecisionPolicy( policy: group.NewThresholdDecisionPolicy(
"1", "1",
time.Second, time.Second,
0,
), ),
expErr: true, expErr: true,
expErrMsg: "not found", expErrMsg: "not found",
@ -918,6 +930,7 @@ func (s *TestSuite) TestCreateGroupPolicy() {
policy: group.NewThresholdDecisionPolicy( policy: group.NewThresholdDecisionPolicy(
"1", "1",
time.Second, time.Second,
0,
), ),
expErr: true, expErr: true,
expErrMsg: "not group admin", expErrMsg: "not group admin",
@ -931,6 +944,7 @@ func (s *TestSuite) TestCreateGroupPolicy() {
policy: group.NewThresholdDecisionPolicy( policy: group.NewThresholdDecisionPolicy(
"1", "1",
time.Second, time.Second,
0,
), ),
expErr: true, expErr: true,
expErrMsg: "limit exceeded", expErrMsg: "limit exceeded",
@ -943,6 +957,7 @@ func (s *TestSuite) TestCreateGroupPolicy() {
policy: group.NewPercentageDecisionPolicy( policy: group.NewPercentageDecisionPolicy(
"-0.5", "-0.5",
time.Second, time.Second,
0,
), ),
expErr: true, expErr: true,
expErrMsg: "expected a positive decimal", expErrMsg: "expected a positive decimal",
@ -955,6 +970,7 @@ func (s *TestSuite) TestCreateGroupPolicy() {
policy: group.NewPercentageDecisionPolicy( policy: group.NewPercentageDecisionPolicy(
"2", "2",
time.Second, time.Second,
0,
), ),
expErr: true, expErr: true,
expErrMsg: "percentage must be > 0 and <= 1", expErrMsg: "percentage must be > 0 and <= 1",
@ -1194,6 +1210,7 @@ func (s *TestSuite) TestUpdateGroupPolicyDecisionPolicy() {
policy: group.NewThresholdDecisionPolicy( policy: group.NewThresholdDecisionPolicy(
"2", "2",
time.Duration(2)*time.Second, time.Duration(2)*time.Second,
0,
), ),
expGroupPolicy: &group.GroupPolicyInfo{ expGroupPolicy: &group.GroupPolicyInfo{
Admin: admin.String(), Admin: admin.String(),
@ -1216,6 +1233,7 @@ func (s *TestSuite) TestUpdateGroupPolicyDecisionPolicy() {
policy: group.NewPercentageDecisionPolicy( policy: group.NewPercentageDecisionPolicy(
"0.5", "0.5",
time.Duration(2)*time.Second, time.Duration(2)*time.Second,
0,
), ),
expGroupPolicy: &group.GroupPolicyInfo{ expGroupPolicy: &group.GroupPolicyInfo{
Admin: admin.String(), Admin: admin.String(),
@ -1278,14 +1296,17 @@ func (s *TestSuite) TestGroupPoliciesByAdminOrGroup() {
group.NewThresholdDecisionPolicy( group.NewThresholdDecisionPolicy(
"1", "1",
time.Second, time.Second,
0,
), ),
group.NewThresholdDecisionPolicy( group.NewThresholdDecisionPolicy(
"10", "10",
time.Second, time.Second,
0,
), ),
group.NewPercentageDecisionPolicy( group.NewPercentageDecisionPolicy(
"0.5", "0.5",
time.Second, time.Second,
0,
), ),
} }
@ -1376,6 +1397,7 @@ func (s *TestSuite) TestSubmitProposal() {
policy := group.NewThresholdDecisionPolicy( policy := group.NewThresholdDecisionPolicy(
"100", "100",
time.Second, time.Second,
0,
) )
err := policyReq.SetDecisionPolicy(policy) err := policyReq.SetDecisionPolicy(policy)
s.Require().NoError(err) s.Require().NoError(err)
@ -1523,7 +1545,7 @@ func (s *TestSuite) TestSubmitProposal() {
Status: group.PROPOSAL_STATUS_SUBMITTED, Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED, Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{ FinalTallyResult: group.TallyResult{
YesCount: "1", YesCount: "0", // Since tally doesn't pass Allow(), we consider the proposal not final
NoCount: "0", NoCount: "0",
AbstainCount: "0", AbstainCount: "0",
NoWithVetoCount: "0", NoWithVetoCount: "0",
@ -1562,7 +1584,7 @@ func (s *TestSuite) TestSubmitProposal() {
s.Assert().Equal(spec.expProposal.Result, proposal.Result) s.Assert().Equal(spec.expProposal.Result, proposal.Result)
s.Assert().Equal(spec.expProposal.FinalTallyResult, proposal.FinalTallyResult) s.Assert().Equal(spec.expProposal.FinalTallyResult, proposal.FinalTallyResult)
s.Assert().Equal(spec.expProposal.ExecutorResult, proposal.ExecutorResult) s.Assert().Equal(spec.expProposal.ExecutorResult, proposal.ExecutorResult)
s.Assert().Equal(s.blockTime.Add(time.Second), proposal.Timeout) s.Assert().Equal(s.blockTime.Add(time.Second), proposal.VotingPeriodEnd)
if spec.msgs == nil { // then empty list is ok if spec.msgs == nil { // then empty list is ok
s.Assert().Len(proposal.GetMsgs(), 0) s.Assert().Len(proposal.GetMsgs(), 0)
@ -1684,6 +1706,7 @@ func (s *TestSuite) TestVote() {
policy := group.NewThresholdDecisionPolicy( policy := group.NewThresholdDecisionPolicy(
"2", "2",
time.Duration(2), time.Duration(2),
0,
) )
policyReq := &group.MsgCreateGroupPolicy{ policyReq := &group.MsgCreateGroupPolicy{
Admin: addr1.String(), Admin: addr1.String(),
@ -1732,23 +1755,19 @@ func (s *TestSuite) TestVote() {
s.Assert().Equal(uint64(1), proposals[0].GroupPolicyVersion) s.Assert().Equal(uint64(1), proposals[0].GroupPolicyVersion)
s.Assert().Equal(group.PROPOSAL_STATUS_SUBMITTED, proposals[0].Status) s.Assert().Equal(group.PROPOSAL_STATUS_SUBMITTED, proposals[0].Status)
s.Assert().Equal(group.PROPOSAL_RESULT_UNFINALIZED, proposals[0].Result) s.Assert().Equal(group.PROPOSAL_RESULT_UNFINALIZED, proposals[0].Result)
s.Assert().Equal(group.TallyResult{ s.Assert().Equal(group.DefaultTallyResult(), proposals[0].FinalTallyResult)
YesCount: "0",
NoCount: "0",
AbstainCount: "0",
NoWithVetoCount: "0",
}, proposals[0].FinalTallyResult)
specs := map[string]struct { specs := map[string]struct {
srcCtx sdk.Context srcCtx sdk.Context
expFinalTallyResult group.TallyResult expTallyResult group.TallyResult // expected after tallying
req *group.MsgVote isFinal bool // is the tally result final?
doBefore func(ctx context.Context) req *group.MsgVote
postRun func(sdkCtx sdk.Context) doBefore func(ctx context.Context)
expProposalStatus group.ProposalStatus postRun func(sdkCtx sdk.Context)
expResult group.ProposalResult expProposalStatus group.ProposalStatus // expected after tallying
expExecutorResult group.ProposalExecutorResult expResult group.ProposalResult // expected after tallying
expErr bool expExecutorResult group.ProposalExecutorResult // expected after tallying
expErr bool
}{ }{
"vote yes": { "vote yes": {
req: &group.MsgVote{ req: &group.MsgVote{
@ -1756,7 +1775,7 @@ func (s *TestSuite) TestVote() {
Voter: addr4.String(), Voter: addr4.String(),
Option: group.VOTE_OPTION_YES, Option: group.VOTE_OPTION_YES,
}, },
expFinalTallyResult: group.TallyResult{ expTallyResult: group.TallyResult{
YesCount: "1", YesCount: "1",
NoCount: "0", NoCount: "0",
AbstainCount: "0", AbstainCount: "0",
@ -1774,12 +1793,13 @@ func (s *TestSuite) TestVote() {
Option: group.VOTE_OPTION_YES, Option: group.VOTE_OPTION_YES,
Exec: group.Exec_EXEC_TRY, Exec: group.Exec_EXEC_TRY,
}, },
expFinalTallyResult: group.TallyResult{ expTallyResult: group.TallyResult{
YesCount: "2", YesCount: "2",
NoCount: "0", NoCount: "0",
AbstainCount: "0", AbstainCount: "0",
NoWithVetoCount: "0", NoWithVetoCount: "0",
}, },
isFinal: true,
expProposalStatus: group.PROPOSAL_STATUS_CLOSED, expProposalStatus: group.PROPOSAL_STATUS_CLOSED,
expResult: group.PROPOSAL_RESULT_ACCEPTED, expResult: group.PROPOSAL_RESULT_ACCEPTED,
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS, expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
@ -1797,7 +1817,7 @@ func (s *TestSuite) TestVote() {
Option: group.VOTE_OPTION_YES, Option: group.VOTE_OPTION_YES,
Exec: group.Exec_EXEC_TRY, Exec: group.Exec_EXEC_TRY,
}, },
expFinalTallyResult: group.TallyResult{ expTallyResult: group.TallyResult{
YesCount: "1", YesCount: "1",
NoCount: "0", NoCount: "0",
AbstainCount: "0", AbstainCount: "0",
@ -1814,7 +1834,7 @@ func (s *TestSuite) TestVote() {
Voter: addr4.String(), Voter: addr4.String(),
Option: group.VOTE_OPTION_NO, Option: group.VOTE_OPTION_NO,
}, },
expFinalTallyResult: group.TallyResult{ expTallyResult: group.TallyResult{
YesCount: "0", YesCount: "0",
NoCount: "1", NoCount: "1",
AbstainCount: "0", AbstainCount: "0",
@ -1831,7 +1851,7 @@ func (s *TestSuite) TestVote() {
Voter: addr4.String(), Voter: addr4.String(),
Option: group.VOTE_OPTION_ABSTAIN, Option: group.VOTE_OPTION_ABSTAIN,
}, },
expFinalTallyResult: group.TallyResult{ expTallyResult: group.TallyResult{
YesCount: "0", YesCount: "0",
NoCount: "0", NoCount: "0",
AbstainCount: "1", AbstainCount: "1",
@ -1848,7 +1868,7 @@ func (s *TestSuite) TestVote() {
Voter: addr4.String(), Voter: addr4.String(),
Option: group.VOTE_OPTION_NO_WITH_VETO, Option: group.VOTE_OPTION_NO_WITH_VETO,
}, },
expFinalTallyResult: group.TallyResult{ expTallyResult: group.TallyResult{
YesCount: "0", YesCount: "0",
NoCount: "0", NoCount: "0",
AbstainCount: "0", AbstainCount: "0",
@ -1865,7 +1885,7 @@ func (s *TestSuite) TestVote() {
Voter: addr3.String(), Voter: addr3.String(),
Option: group.VOTE_OPTION_YES, Option: group.VOTE_OPTION_YES,
}, },
expFinalTallyResult: group.TallyResult{ expTallyResult: group.TallyResult{
YesCount: "2", YesCount: "2",
NoCount: "0", NoCount: "0",
AbstainCount: "0", AbstainCount: "0",
@ -1887,6 +1907,7 @@ func (s *TestSuite) TestVote() {
ProposalId: myProposalID, ProposalId: myProposalID,
Voter: addr3.String(), Voter: addr3.String(),
Option: group.VOTE_OPTION_NO_WITH_VETO, Option: group.VOTE_OPTION_NO_WITH_VETO,
Exec: 1, // Execute the proposal so that its status is final
}) })
s.Require().NoError(err) s.Require().NoError(err)
}, },
@ -1947,7 +1968,7 @@ func (s *TestSuite) TestVote() {
expErr: true, expErr: true,
postRun: func(sdkCtx sdk.Context) {}, postRun: func(sdkCtx sdk.Context) {},
}, },
"on timeout": { "on voting period end": {
req: &group.MsgVote{ req: &group.MsgVote{
ProposalId: myProposalID, ProposalId: myProposalID,
Voter: addr4.String(), Voter: addr4.String(),
@ -1968,6 +1989,7 @@ func (s *TestSuite) TestVote() {
ProposalId: myProposalID, ProposalId: myProposalID,
Voter: addr3.String(), Voter: addr3.String(),
Option: group.VOTE_OPTION_YES, Option: group.VOTE_OPTION_YES,
Exec: 1, // Execute to close the proposal.
}) })
s.Require().NoError(err) s.Require().NoError(err)
}, },
@ -2019,7 +2041,9 @@ func (s *TestSuite) TestVote() {
groupPolicy, groupPolicy,
&group.ThresholdDecisionPolicy{ &group.ThresholdDecisionPolicy{
Threshold: "1", Threshold: "1",
Timeout: time.Second, Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second,
},
}, },
) )
s.Require().NoError(err) s.Require().NoError(err)
@ -2093,16 +2117,26 @@ func (s *TestSuite) TestVote() {
s.Assert().Equal(spec.req.Metadata, votesByVoter[0].Metadata) s.Assert().Equal(spec.req.Metadata, votesByVoter[0].Metadata)
s.Assert().Equal(s.blockTime, votesByVoter[0].SubmitTime) s.Assert().Equal(s.blockTime, votesByVoter[0].SubmitTime)
// and proposal is updated
proposalRes, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ proposalRes, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{
ProposalId: spec.req.ProposalId, ProposalId: spec.req.ProposalId,
}) })
s.Require().NoError(err) s.Require().NoError(err)
proposal := proposalRes.Proposal proposal := proposalRes.Proposal
s.Assert().Equal(spec.expFinalTallyResult, proposal.FinalTallyResult) if spec.isFinal {
s.Assert().Equal(spec.expResult, proposal.Result) s.Assert().Equal(spec.expTallyResult, proposal.FinalTallyResult)
s.Assert().Equal(spec.expProposalStatus, proposal.Status) s.Assert().Equal(spec.expResult, proposal.Result)
s.Assert().Equal(spec.expExecutorResult, proposal.ExecutorResult) s.Assert().Equal(spec.expProposalStatus, proposal.Status)
s.Assert().Equal(spec.expExecutorResult, proposal.ExecutorResult)
} else {
s.Assert().Equal(group.DefaultTallyResult(), proposal.FinalTallyResult) // Make sure proposal isn't mutated.
// do a round of tallying
tallyResult, err := s.keeper.Tally(sdkCtx, *proposal, myGroupID)
s.Require().NoError(err)
s.Assert().Equal(spec.expTallyResult, tallyResult)
}
spec.postRun(sdkCtx) spec.postRun(sdkCtx)
}) })
@ -2370,6 +2404,7 @@ func createGroupAndGroupPolicy(
policy := group.NewThresholdDecisionPolicy( policy := group.NewThresholdDecisionPolicy(
"1", "1",
time.Second, time.Second,
0,
) )
err = groupPolicy.SetDecisionPolicy(policy) err = groupPolicy.SetDecisionPolicy(policy)
s.Require().NoError(err) s.Require().NoError(err)

View File

@ -467,16 +467,11 @@ func (k Keeper) SubmitProposal(goCtx context.Context, req *group.MsgSubmitPropos
} }
// Prevent proposal that can not succeed. // Prevent proposal that can not succeed.
err = policy.Validate(g) err = policy.Validate(g, k.config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Define proposal timout.
// The voting window begins as soon as the proposal is submitted.
timeout := policy.GetTimeout()
window := timeout
m := &group.Proposal{ m := &group.Proposal{
Id: k.proposalTable.Sequence().PeekNextVal(ctx.KVStore(k.key)), Id: k.proposalTable.Sequence().PeekNextVal(ctx.KVStore(k.key)),
Address: req.Address, Address: req.Address,
@ -488,14 +483,10 @@ func (k Keeper) SubmitProposal(goCtx context.Context, req *group.MsgSubmitPropos
Result: group.PROPOSAL_RESULT_UNFINALIZED, Result: group.PROPOSAL_RESULT_UNFINALIZED,
Status: group.PROPOSAL_STATUS_SUBMITTED, Status: group.PROPOSAL_STATUS_SUBMITTED,
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
Timeout: ctx.BlockTime().Add(window), VotingPeriodEnd: ctx.BlockTime().Add(policy.GetVotingPeriod()), // The voting window begins as soon as the proposal is submitted.
FinalTallyResult: group.TallyResult{ FinalTallyResult: group.DefaultTallyResult(),
YesCount: "0",
NoCount: "0",
AbstainCount: "0",
NoWithVetoCount: "0",
},
} }
if err := m.SetMsgs(msgs); err != nil { if err := m.SetMsgs(msgs); err != nil {
return nil, sdkerrors.Wrap(err, "create proposal") return nil, sdkerrors.Wrap(err, "create proposal")
} }
@ -524,6 +515,7 @@ func (k Keeper) SubmitProposal(goCtx context.Context, req *group.MsgSubmitPropos
return &group.MsgSubmitProposalResponse{ProposalId: id}, sdkerrors.Wrap(err, "The proposal was created but failed on vote") return &group.MsgSubmitProposalResponse{ProposalId: id}, sdkerrors.Wrap(err, "The proposal was created but failed on vote")
} }
} }
// Then try to execute the proposal // Then try to execute the proposal
_, err = k.Exec(sdk.WrapSDKContext(ctx), &group.MsgExec{ _, err = k.Exec(sdk.WrapSDKContext(ctx), &group.MsgExec{
ProposalId: id, ProposalId: id,
@ -619,15 +611,7 @@ func (k Keeper) Vote(goCtx context.Context, req *group.MsgVote) (*group.MsgVoteR
if proposal.Status != group.PROPOSAL_STATUS_SUBMITTED { if proposal.Status != group.PROPOSAL_STATUS_SUBMITTED {
return nil, sdkerrors.Wrap(errors.ErrInvalid, "proposal not open for voting") return nil, sdkerrors.Wrap(errors.ErrInvalid, "proposal not open for voting")
} }
proposalTimeout, err := gogotypes.TimestampProto(proposal.Timeout) if ctx.BlockTime().After(proposal.VotingPeriodEnd) {
if err != nil {
return nil, err
}
votingPeriodEnd, err := gogotypes.TimestampFromProto(proposalTimeout)
if err != nil {
return nil, err
}
if votingPeriodEnd.Before(ctx.BlockTime()) || votingPeriodEnd.Equal(ctx.BlockTime()) {
return nil, sdkerrors.Wrap(errors.ErrExpired, "voting period has ended already") return nil, sdkerrors.Wrap(errors.ErrExpired, "voting period has ended already")
} }
@ -663,9 +647,6 @@ func (k Keeper) Vote(goCtx context.Context, req *group.MsgVote) (*group.MsgVoteR
Metadata: metadata, Metadata: metadata,
SubmitTime: ctx.BlockTime(), SubmitTime: ctx.BlockTime(),
} }
if err := proposal.FinalTallyResult.Add(newVote, voter.Member.Weight); err != nil {
return nil, sdkerrors.Wrap(err, "add new vote")
}
// The ORM will return an error if the vote already exists, // The ORM will return an error if the vote already exists,
// making sure than a voter hasn't already voted. // making sure than a voter hasn't already voted.
@ -673,15 +654,6 @@ func (k Keeper) Vote(goCtx context.Context, req *group.MsgVote) (*group.MsgVoteR
return nil, sdkerrors.Wrap(err, "store vote") return nil, sdkerrors.Wrap(err, "store vote")
} }
// Run tally with new votes to close early.
if err := doTally(ctx, &proposal, electorate, policyInfo); err != nil {
return nil, err
}
if err = k.proposalTable.Update(ctx.KVStore(k.key), id, &proposal); err != nil {
return nil, err
}
err = ctx.EventManager().EmitTypedEvent(&group.EventVote{ProposalId: id}) err = ctx.EventManager().EmitTypedEvent(&group.EventVote{ProposalId: id})
if err != nil { if err != nil {
return nil, err return nil, err
@ -701,8 +673,9 @@ func (k Keeper) Vote(goCtx context.Context, req *group.MsgVote) (*group.MsgVoteR
return &group.MsgVoteResponse{}, nil return &group.MsgVoteResponse{}, nil
} }
// doTally updates the proposal status and tally if necessary based on the group policy's decision policy. // doTallyAndUpdate performs a tally, and updates the proposal's
func doTally(ctx sdk.Context, p *group.Proposal, electorate group.GroupInfo, policyInfo group.GroupPolicyInfo) error { // `FinalTallyResult` field only if the tally is final.
func (k Keeper) doTallyAndUpdate(ctx sdk.Context, p *group.Proposal, electorate group.GroupInfo, policyInfo group.GroupPolicyInfo) error {
policy := policyInfo.GetDecisionPolicy() policy := policyInfo.GetDecisionPolicy()
pSubmittedAt, err := gogotypes.TimestampProto(p.SubmitTime) pSubmittedAt, err := gogotypes.TimestampProto(p.SubmitTime)
if err != nil { if err != nil {
@ -712,16 +685,25 @@ func doTally(ctx sdk.Context, p *group.Proposal, electorate group.GroupInfo, pol
if err != nil { if err != nil {
return err return err
} }
switch result, err := policy.Allow(p.FinalTallyResult, electorate.TotalWeight, ctx.BlockTime().Sub(submittedAt)); {
tallyResult, err := k.Tally(ctx, *p, policyInfo.GroupId)
if err != nil {
return err
}
switch result, err := policy.Allow(tallyResult, electorate.TotalWeight, ctx.BlockTime().Sub(submittedAt)); {
case err != nil: case err != nil:
return sdkerrors.Wrap(err, "policy execution") return sdkerrors.Wrap(err, "policy execution")
case result.Allow && result.Final: case result.Allow && result.Final:
p.FinalTallyResult = tallyResult
p.Result = group.PROPOSAL_RESULT_ACCEPTED p.Result = group.PROPOSAL_RESULT_ACCEPTED
p.Status = group.PROPOSAL_STATUS_CLOSED p.Status = group.PROPOSAL_STATUS_CLOSED
case !result.Allow && result.Final: case !result.Allow && result.Final:
p.FinalTallyResult = tallyResult
p.Result = group.PROPOSAL_RESULT_REJECTED p.Result = group.PROPOSAL_RESULT_REJECTED
p.Status = group.PROPOSAL_STATUS_CLOSED p.Status = group.PROPOSAL_STATUS_CLOSED
} }
return nil return nil
} }
@ -770,7 +752,8 @@ func (k Keeper) Exec(goCtx context.Context, req *group.MsgExec) (*group.MsgExecR
proposal.Status = group.PROPOSAL_STATUS_ABORTED proposal.Status = group.PROPOSAL_STATUS_ABORTED
return storeUpdates() return storeUpdates()
} }
if err := doTally(ctx, &proposal, electorate, policyInfo); err != nil {
if err := k.doTallyAndUpdate(ctx, &proposal, electorate, policyInfo); err != nil {
return nil, err return nil, err
} }
} }

View File

@ -11,6 +11,16 @@ import (
// doExecuteMsgs routes the messages to the registered handlers. Messages are limited to those that require no authZ or // doExecuteMsgs routes the messages to the registered handlers. Messages are limited to those that require no authZ or
// by the account of group policy only. Otherwise this gives access to other peoples accounts as the sdk ant handler is bypassed // by the account of group policy only. Otherwise this gives access to other peoples accounts as the sdk ant handler is bypassed
func (s Keeper) doExecuteMsgs(ctx sdk.Context, router *authmiddleware.MsgServiceRouter, proposal group.Proposal, groupPolicyAcc sdk.AccAddress) ([]sdk.Result, error) { func (s Keeper) doExecuteMsgs(ctx sdk.Context, router *authmiddleware.MsgServiceRouter, proposal group.Proposal, groupPolicyAcc sdk.AccAddress) ([]sdk.Result, error) {
// Ensure it's not too late to execute the messages.
// After https://github.com/cosmos/cosmos-sdk/issues/11245, proposals should
// be pruned automatically, so this function should not even be called, as
// the proposal doesn't exist in state. For sanity check, we can still keep
// this simple and cheap check.
expiryDate := proposal.VotingPeriodEnd.Add(s.config.MaxExecutionPeriod)
if expiryDate.Before(ctx.BlockTime()) {
return nil, grouperrors.ErrExpired.Wrapf("proposal expired on %s", expiryDate)
}
msgs := proposal.GetMsgs() msgs := proposal.GetMsgs()
results := make([]sdk.Result, len(msgs)) results := make([]sdk.Result, len(msgs))

View File

@ -367,7 +367,7 @@ func TestMsgCreateGroupWithPolicy(t *testing.T) {
"invalid admin address", "invalid admin address",
func() *group.MsgCreateGroupWithPolicy { func() *group.MsgCreateGroupWithPolicy {
admin := "admin" admin := "admin"
policy := group.NewThresholdDecisionPolicy("1", time.Second) policy := group.NewThresholdDecisionPolicy("1", time.Second, 0)
members := []group.Member{ members := []group.Member{
{ {
Address: member1.String(), Address: member1.String(),
@ -385,7 +385,7 @@ func TestMsgCreateGroupWithPolicy(t *testing.T) {
{ {
"invalid member address", "invalid member address",
func() *group.MsgCreateGroupWithPolicy { func() *group.MsgCreateGroupWithPolicy {
policy := group.NewThresholdDecisionPolicy("1", time.Second) policy := group.NewThresholdDecisionPolicy("1", time.Second, 0)
members := []group.Member{ members := []group.Member{
{ {
Address: "invalid_address", Address: "invalid_address",
@ -403,7 +403,7 @@ func TestMsgCreateGroupWithPolicy(t *testing.T) {
{ {
"negative member's weight not allowed", "negative member's weight not allowed",
func() *group.MsgCreateGroupWithPolicy { func() *group.MsgCreateGroupWithPolicy {
policy := group.NewThresholdDecisionPolicy("1", time.Second) policy := group.NewThresholdDecisionPolicy("1", time.Second, 0)
members := []group.Member{ members := []group.Member{
{ {
Address: member1.String(), Address: member1.String(),
@ -421,7 +421,7 @@ func TestMsgCreateGroupWithPolicy(t *testing.T) {
{ {
"zero member's weight not allowed", "zero member's weight not allowed",
func() *group.MsgCreateGroupWithPolicy { func() *group.MsgCreateGroupWithPolicy {
policy := group.NewThresholdDecisionPolicy("1", time.Second) policy := group.NewThresholdDecisionPolicy("1", time.Second, 0)
members := []group.Member{ members := []group.Member{
{ {
Address: member1.String(), Address: member1.String(),
@ -439,7 +439,7 @@ func TestMsgCreateGroupWithPolicy(t *testing.T) {
{ {
"duplicate member not allowed", "duplicate member not allowed",
func() *group.MsgCreateGroupWithPolicy { func() *group.MsgCreateGroupWithPolicy {
policy := group.NewThresholdDecisionPolicy("1", time.Second) policy := group.NewThresholdDecisionPolicy("1", time.Second, 0)
members := []group.Member{ members := []group.Member{
{ {
Address: member1.String(), Address: member1.String(),
@ -462,7 +462,7 @@ func TestMsgCreateGroupWithPolicy(t *testing.T) {
{ {
"invalid threshold policy", "invalid threshold policy",
func() *group.MsgCreateGroupWithPolicy { func() *group.MsgCreateGroupWithPolicy {
policy := group.NewThresholdDecisionPolicy("-1", time.Second) policy := group.NewThresholdDecisionPolicy("-1", time.Second, 0)
members := []group.Member{ members := []group.Member{
{ {
Address: member1.String(), Address: member1.String(),
@ -480,7 +480,7 @@ func TestMsgCreateGroupWithPolicy(t *testing.T) {
{ {
"valid test case with single member", "valid test case with single member",
func() *group.MsgCreateGroupWithPolicy { func() *group.MsgCreateGroupWithPolicy {
policy := group.NewThresholdDecisionPolicy("1", time.Second) policy := group.NewThresholdDecisionPolicy("1", time.Second, 0)
members := []group.Member{ members := []group.Member{
{ {
Address: member1.String(), Address: member1.String(),
@ -498,7 +498,7 @@ func TestMsgCreateGroupWithPolicy(t *testing.T) {
{ {
"valid test case with multiple members", "valid test case with multiple members",
func() *group.MsgCreateGroupWithPolicy { func() *group.MsgCreateGroupWithPolicy {
policy := group.NewThresholdDecisionPolicy("1", time.Second) policy := group.NewThresholdDecisionPolicy("1", time.Second, 0)
members := []group.Member{ members := []group.Member{
{ {
Address: member1.String(), Address: member1.String(),
@ -566,7 +566,7 @@ func TestMsgCreateGroupPolicy(t *testing.T) {
{ {
"invalid threshold policy", "invalid threshold policy",
func() *group.MsgCreateGroupPolicy { func() *group.MsgCreateGroupPolicy {
policy := group.NewThresholdDecisionPolicy("-1", time.Second) policy := group.NewThresholdDecisionPolicy("-1", time.Second, 0)
req, err := group.NewMsgCreateGroupPolicy(admin, 1, "metadata", policy) req, err := group.NewMsgCreateGroupPolicy(admin, 1, "metadata", policy)
require.NoError(t, err) require.NoError(t, err)
return req return req
@ -575,9 +575,53 @@ func TestMsgCreateGroupPolicy(t *testing.T) {
"expected a positive decimal", "expected a positive decimal",
}, },
{ {
"valid test case", "invalid voting period",
func() *group.MsgCreateGroupPolicy { func() *group.MsgCreateGroupPolicy {
policy := group.NewThresholdDecisionPolicy("1", time.Second) policy := group.NewThresholdDecisionPolicy("-1", time.Duration(0), 0)
req, err := group.NewMsgCreateGroupPolicy(admin, 1, "metadata", policy)
require.NoError(t, err)
return req
},
true,
"expected a positive decimal",
},
{
"invalid execution period",
func() *group.MsgCreateGroupPolicy {
policy := group.NewThresholdDecisionPolicy("-1", time.Minute, 0)
req, err := group.NewMsgCreateGroupPolicy(admin, 1, "metadata", policy)
require.NoError(t, err)
return req
},
true,
"expected a positive decimal",
},
{
"valid test case, only voting period",
func() *group.MsgCreateGroupPolicy {
policy := group.NewThresholdDecisionPolicy("1", time.Second, 0)
req, err := group.NewMsgCreateGroupPolicy(admin, 1, "metadata", policy)
require.NoError(t, err)
return req
},
false,
"",
},
{
"valid test case, voting and execution, empty min exec period",
func() *group.MsgCreateGroupPolicy {
policy := group.NewThresholdDecisionPolicy("1", time.Second, 0)
req, err := group.NewMsgCreateGroupPolicy(admin, 1, "metadata", policy)
require.NoError(t, err)
return req
},
false,
"",
},
{
"valid test case, voting and execution, non-empty min exec period",
func() *group.MsgCreateGroupPolicy {
policy := group.NewThresholdDecisionPolicy("1", time.Second, time.Minute)
req, err := group.NewMsgCreateGroupPolicy(admin, 1, "metadata", policy) req, err := group.NewMsgCreateGroupPolicy(admin, 1, "metadata", policy)
require.NoError(t, err) require.NoError(t, err)
return req return req
@ -588,7 +632,7 @@ func TestMsgCreateGroupPolicy(t *testing.T) {
{ {
"invalid percentage decision policy with zero value", "invalid percentage decision policy with zero value",
func() *group.MsgCreateGroupPolicy { func() *group.MsgCreateGroupPolicy {
percentagePolicy := group.NewPercentageDecisionPolicy("0", time.Second) percentagePolicy := group.NewPercentageDecisionPolicy("0", time.Second, 0)
req, err := group.NewMsgCreateGroupPolicy(admin, 1, "metadata", percentagePolicy) req, err := group.NewMsgCreateGroupPolicy(admin, 1, "metadata", percentagePolicy)
require.NoError(t, err) require.NoError(t, err)
return req return req
@ -599,7 +643,7 @@ func TestMsgCreateGroupPolicy(t *testing.T) {
{ {
"invalid percentage decision policy with negative value", "invalid percentage decision policy with negative value",
func() *group.MsgCreateGroupPolicy { func() *group.MsgCreateGroupPolicy {
percentagePolicy := group.NewPercentageDecisionPolicy("-0.2", time.Second) percentagePolicy := group.NewPercentageDecisionPolicy("-0.2", time.Second, 0)
req, err := group.NewMsgCreateGroupPolicy(admin, 1, "metadata", percentagePolicy) req, err := group.NewMsgCreateGroupPolicy(admin, 1, "metadata", percentagePolicy)
require.NoError(t, err) require.NoError(t, err)
return req return req
@ -610,7 +654,7 @@ func TestMsgCreateGroupPolicy(t *testing.T) {
{ {
"invalid percentage decision policy with value greater than 1", "invalid percentage decision policy with value greater than 1",
func() *group.MsgCreateGroupPolicy { func() *group.MsgCreateGroupPolicy {
percentagePolicy := group.NewPercentageDecisionPolicy("2", time.Second) percentagePolicy := group.NewPercentageDecisionPolicy("2", time.Second, 0)
req, err := group.NewMsgCreateGroupPolicy(admin, 1, "metadata", percentagePolicy) req, err := group.NewMsgCreateGroupPolicy(admin, 1, "metadata", percentagePolicy)
require.NoError(t, err) require.NoError(t, err)
return req return req
@ -621,7 +665,7 @@ func TestMsgCreateGroupPolicy(t *testing.T) {
{ {
"valid test case with percentage decision policy", "valid test case with percentage decision policy",
func() *group.MsgCreateGroupPolicy { func() *group.MsgCreateGroupPolicy {
percentagePolicy := group.NewPercentageDecisionPolicy("0.5", time.Second) percentagePolicy := group.NewPercentageDecisionPolicy("0.5", time.Second, 0)
req, err := group.NewMsgCreateGroupPolicy(admin, 1, "metadata", percentagePolicy) req, err := group.NewMsgCreateGroupPolicy(admin, 1, "metadata", percentagePolicy)
require.NoError(t, err) require.NoError(t, err)
return req return req
@ -647,23 +691,23 @@ func TestMsgCreateGroupPolicy(t *testing.T) {
} }
func TestMsgUpdateGroupPolicyDecisionPolicy(t *testing.T) { func TestMsgUpdateGroupPolicyDecisionPolicy(t *testing.T) {
validPolicy := group.NewThresholdDecisionPolicy("1", time.Second) validPolicy := group.NewThresholdDecisionPolicy("1", time.Second, 0)
msg1, err := group.NewMsgUpdateGroupPolicyDecisionPolicyRequest(admin, member1, validPolicy) msg1, err := group.NewMsgUpdateGroupPolicyDecisionPolicyRequest(admin, member1, validPolicy)
require.NoError(t, err) require.NoError(t, err)
invalidPolicy := group.NewThresholdDecisionPolicy("-1", time.Second) invalidPolicy := group.NewThresholdDecisionPolicy("-1", time.Second, 0)
msg2, err := group.NewMsgUpdateGroupPolicyDecisionPolicyRequest(admin, member2, invalidPolicy) msg2, err := group.NewMsgUpdateGroupPolicyDecisionPolicyRequest(admin, member2, invalidPolicy)
require.NoError(t, err) require.NoError(t, err)
validPercentagePolicy := group.NewPercentageDecisionPolicy("0.7", time.Second) validPercentagePolicy := group.NewPercentageDecisionPolicy("0.7", time.Second, 0)
msg3, err := group.NewMsgUpdateGroupPolicyDecisionPolicyRequest(admin, member3, validPercentagePolicy) msg3, err := group.NewMsgUpdateGroupPolicyDecisionPolicyRequest(admin, member3, validPercentagePolicy)
require.NoError(t, err) require.NoError(t, err)
invalidPercentagePolicy := group.NewPercentageDecisionPolicy("-0.1", time.Second) invalidPercentagePolicy := group.NewPercentageDecisionPolicy("-0.1", time.Second, 0)
msg4, err := group.NewMsgUpdateGroupPolicyDecisionPolicyRequest(admin, member4, invalidPercentagePolicy) msg4, err := group.NewMsgUpdateGroupPolicyDecisionPolicyRequest(admin, member4, invalidPercentagePolicy)
require.NoError(t, err) require.NoError(t, err)
invalidPercentagePolicy2 := group.NewPercentageDecisionPolicy("2", time.Second) invalidPercentagePolicy2 := group.NewPercentageDecisionPolicy("2", time.Second, 0)
msg5, err := group.NewMsgUpdateGroupPolicyDecisionPolicyRequest(admin, member5, invalidPercentagePolicy2) msg5, err := group.NewMsgUpdateGroupPolicyDecisionPolicyRequest(admin, member5, invalidPercentagePolicy2)
require.NoError(t, err) require.NoError(t, err)

View File

@ -56,7 +56,7 @@ func getGroupPolicies(r *rand.Rand, simState *module.SimulationState) []*group.G
groupPolicies := make([]*group.GroupPolicyInfo, 3) groupPolicies := make([]*group.GroupPolicyInfo, 3)
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
acc, _ := simtypes.RandomAcc(r, simState.Accounts) acc, _ := simtypes.RandomAcc(r, simState.Accounts)
any, err := codectypes.NewAnyWithValue(group.NewThresholdDecisionPolicy("10", time.Second*time.Duration(1))) any, err := codectypes.NewAnyWithValue(group.NewThresholdDecisionPolicy("10", time.Second, 0))
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -97,10 +97,10 @@ func getProposals(r *rand.Rand, simState *module.SimulationState) []*group.Propo
AbstainCount: "1", AbstainCount: "1",
NoWithVetoCount: "0", NoWithVetoCount: "0",
}, },
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
Metadata: simtypes.RandStringOfLength(r, 50), Metadata: simtypes.RandStringOfLength(r, 50),
SubmitTime: submittedAt, SubmitTime: submittedAt,
Timeout: timeout, VotingPeriodEnd: timeout,
} }
err := proposal.SetMsgs([]sdk.Msg{&banktypes.MsgSend{ err := proposal.SetMsgs([]sdk.Msg{&banktypes.MsgSend{
FromAddress: fromAddr, FromAddress: fromAddr,

View File

@ -285,7 +285,9 @@ func SimulateMsgCreateGroupWithPolicy(ak group.AccountKeeper, bk group.BankKeepe
members := genGroupMembers(r, accounts) members := genGroupMembers(r, accounts)
decisionPolicy := &group.ThresholdDecisionPolicy{ decisionPolicy := &group.ThresholdDecisionPolicy{
Threshold: fmt.Sprintf("%d", simtypes.RandIntBetween(r, 1, 10)), Threshold: fmt.Sprintf("%d", simtypes.RandIntBetween(r, 1, 10)),
Timeout: time.Second * time.Duration(30*24*60*60), Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second * time.Duration(30*24*60*60),
},
} }
msg := &group.MsgCreateGroupWithPolicy{ msg := &group.MsgCreateGroupWithPolicy{
@ -349,7 +351,9 @@ func SimulateMsgCreateGroupPolicy(ak group.AccountKeeper, bk group.BankKeeper, k
simtypes.RandStringOfLength(r, 10), simtypes.RandStringOfLength(r, 10),
&group.ThresholdDecisionPolicy{ &group.ThresholdDecisionPolicy{
Threshold: fmt.Sprintf("%d", simtypes.RandIntBetween(r, 1, 10)), Threshold: fmt.Sprintf("%d", simtypes.RandIntBetween(r, 1, 10)),
Timeout: time.Second * time.Duration(30*24*60*60), Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second * time.Duration(30*24*60*60),
},
}, },
) )
if err != nil { if err != nil {
@ -400,7 +404,7 @@ func SimulateMsgSubmitProposal(ak group.AccountKeeper, bk group.BankKeeper, k ke
// Return a no-op if we know the proposal cannot be created // Return a no-op if we know the proposal cannot be created
policy := groupPolicy.GetDecisionPolicy() policy := groupPolicy.GetDecisionPolicy()
err = policy.Validate(*g) err = policy.Validate(*g, group.DefaultConfig())
if err != nil { if err != nil {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgSubmitProposal, ""), nil, nil return simtypes.NoOpMsg(group.ModuleName, TypeMsgSubmitProposal, ""), nil, nil
} }
@ -721,7 +725,9 @@ func SimulateMsgUpdateGroupPolicyDecisionPolicy(ak group.AccountKeeper,
msg, err := group.NewMsgUpdateGroupPolicyDecisionPolicyRequest(acc.Address, groupPolicyBech32, &group.ThresholdDecisionPolicy{ msg, err := group.NewMsgUpdateGroupPolicyDecisionPolicyRequest(acc.Address, groupPolicyBech32, &group.ThresholdDecisionPolicy{
Threshold: fmt.Sprintf("%d", simtypes.RandIntBetween(r, 1, 10)), Threshold: fmt.Sprintf("%d", simtypes.RandIntBetween(r, 1, 10)),
Timeout: time.Second * time.Duration(simtypes.RandIntBetween(r, 100, 1000)), Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second * time.Duration(simtypes.RandIntBetween(r, 100, 1000)),
},
}) })
if err != nil { if err != nil {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyDecisionPolicy, err.Error()), nil, err return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyDecisionPolicy, err.Error()), nil, err
@ -820,7 +826,7 @@ func SimulateMsgWithdrawProposal(ak group.AccountKeeper,
ctx := sdk.WrapSDKContext(sdkCtx) ctx := sdk.WrapSDKContext(sdkCtx)
policy := groupPolicy.GetDecisionPolicy() policy := groupPolicy.GetDecisionPolicy()
err = policy.Validate(*g) err = policy.Validate(*g, group.DefaultConfig())
if err != nil { if err != nil {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, err.Error()), nil, nil return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, err.Error()), nil, nil
} }
@ -840,7 +846,7 @@ func SimulateMsgWithdrawProposal(ak group.AccountKeeper,
for _, p := range proposals { for _, p := range proposals {
if p.Status == group.PROPOSAL_STATUS_SUBMITTED { if p.Status == group.PROPOSAL_STATUS_SUBMITTED {
timeout := p.Timeout timeout := p.VotingPeriodEnd
proposal = p proposal = p
proposalID = int(p.Id) proposalID = int(p.Id)
if timeout.Before(sdkCtx.BlockTime()) || timeout.Equal(sdkCtx.BlockTime()) { if timeout.Before(sdkCtx.BlockTime()) || timeout.Equal(sdkCtx.BlockTime()) {
@ -959,7 +965,7 @@ func SimulateMsgVote(ak group.AccountKeeper,
for _, p := range proposals { for _, p := range proposals {
if p.Status == group.PROPOSAL_STATUS_SUBMITTED { if p.Status == group.PROPOSAL_STATUS_SUBMITTED {
timeout := p.Timeout timeout := p.VotingPeriodEnd
proposal = p proposal = p
proposalID = int(p.Id) proposalID = int(p.Id)
if timeout.Before(sdkCtx.BlockTime()) || timeout.Equal(sdkCtx.BlockTime()) { if timeout.Before(sdkCtx.BlockTime()) || timeout.Equal(sdkCtx.BlockTime()) {
@ -1032,27 +1038,27 @@ func SimulateMsgExec(ak group.AccountKeeper,
r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
_, groupPolicy, acc, account, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts) _, groupPolicy, acc, account, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts)
if err != nil { if err != nil {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgExec, ""), nil, err return simtypes.NoOpMsg(TypeMsgExec, TypeMsgExec, ""), nil, err
} }
if groupPolicy == nil { if groupPolicy == nil {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgExec, "no group policy found"), nil, nil return simtypes.NoOpMsg(TypeMsgExec, TypeMsgExec, "no group policy found"), nil, nil
} }
groupPolicyAddr := groupPolicy.Address groupPolicyAddr := groupPolicy.Address
spendableCoins := bk.SpendableCoins(sdkCtx, account.GetAddress()) spendableCoins := bk.SpendableCoins(sdkCtx, account.GetAddress())
fees, err := simtypes.RandomFees(r, sdkCtx, spendableCoins) fees, err := simtypes.RandomFees(r, sdkCtx, spendableCoins)
if err != nil { if err != nil {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgExec, "fee error"), nil, err return simtypes.NoOpMsg(TypeMsgExec, TypeMsgExec, "fee error"), nil, err
} }
ctx := sdk.WrapSDKContext(sdkCtx) ctx := sdk.WrapSDKContext(sdkCtx)
proposalsResult, err := k.ProposalsByGroupPolicy(ctx, &group.QueryProposalsByGroupPolicyRequest{Address: groupPolicyAddr}) proposalsResult, err := k.ProposalsByGroupPolicy(ctx, &group.QueryProposalsByGroupPolicyRequest{Address: groupPolicyAddr})
if err != nil { if err != nil {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgExec, "fail to query group info"), nil, err return simtypes.NoOpMsg(TypeMsgExec, TypeMsgExec, "fail to query group info"), nil, err
} }
proposals := proposalsResult.GetProposals() proposals := proposalsResult.GetProposals()
if len(proposals) == 0 { if len(proposals) == 0 {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgExec, "no proposals found"), nil, nil return simtypes.NoOpMsg(TypeMsgExec, TypeMsgExec, "no proposals found"), nil, nil
} }
proposalID := -1 proposalID := -1
@ -1066,7 +1072,7 @@ func SimulateMsgExec(ak group.AccountKeeper,
// return no-op if no proposal found // return no-op if no proposal found
if proposalID == -1 { if proposalID == -1 {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgExec, "no proposals found"), nil, nil return simtypes.NoOpMsg(TypeMsgExec, TypeMsgExec, "no proposals found"), nil, nil
} }
msg := group.MsgExec{ msg := group.MsgExec{

View File

@ -221,7 +221,7 @@ func (suite *SimTestSuite) TestSimulateSubmitProposal() {
Admin: acc.Address.String(), Admin: acc.Address.String(),
GroupId: groupRes.GroupId, GroupId: groupRes.GroupId,
} }
err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour)) err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour, 0))
suite.Require().NoError(err) suite.Require().NoError(err)
groupPolicyRes, err := suite.app.GroupKeeper.CreateGroupPolicy(ctx, accountReq) groupPolicyRes, err := suite.app.GroupKeeper.CreateGroupPolicy(ctx, accountReq)
suite.Require().NoError(err) suite.Require().NoError(err)
@ -275,7 +275,7 @@ func (suite *SimTestSuite) TestWithdrawProposal() {
Admin: addr, Admin: addr,
GroupId: groupRes.GroupId, GroupId: groupRes.GroupId,
} }
err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour)) err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour, 0))
suite.Require().NoError(err) suite.Require().NoError(err)
groupPolicyRes, err := suite.app.GroupKeeper.CreateGroupPolicy(ctx, accountReq) groupPolicyRes, err := suite.app.GroupKeeper.CreateGroupPolicy(ctx, accountReq)
suite.Require().NoError(err) suite.Require().NoError(err)
@ -342,7 +342,7 @@ func (suite *SimTestSuite) TestSimulateVote() {
GroupId: groupRes.GroupId, GroupId: groupRes.GroupId,
Metadata: "", Metadata: "",
} }
err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour)) err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour, 0))
suite.Require().NoError(err) suite.Require().NoError(err)
groupPolicyRes, err := suite.app.GroupKeeper.CreateGroupPolicy(ctx, accountReq) groupPolicyRes, err := suite.app.GroupKeeper.CreateGroupPolicy(ctx, accountReq)
suite.Require().NoError(err) suite.Require().NoError(err)
@ -408,7 +408,7 @@ func (suite *SimTestSuite) TestSimulateExec() {
Admin: addr, Admin: addr,
GroupId: groupRes.GroupId, GroupId: groupRes.GroupId,
} }
err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour)) err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour, 0))
suite.Require().NoError(err) suite.Require().NoError(err)
groupPolicyRes, err := suite.app.GroupKeeper.CreateGroupPolicy(ctx, accountReq) groupPolicyRes, err := suite.app.GroupKeeper.CreateGroupPolicy(ctx, accountReq)
suite.Require().NoError(err) suite.Require().NoError(err)
@ -430,6 +430,7 @@ func (suite *SimTestSuite) TestSimulateExec() {
ProposalId: proposalRes.ProposalId, ProposalId: proposalRes.ProposalId,
Voter: addr, Voter: addr,
Option: group.VOTE_OPTION_YES, Option: group.VOTE_OPTION_YES,
Exec: 1,
}) })
suite.Require().NoError(err) suite.Require().NoError(err)
@ -607,7 +608,7 @@ func (suite *SimTestSuite) TestSimulateUpdateGroupPolicyAdmin() {
Admin: acc.Address.String(), Admin: acc.Address.String(),
GroupId: groupRes.GroupId, GroupId: groupRes.GroupId,
} }
err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour)) err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour, 0))
suite.Require().NoError(err) suite.Require().NoError(err)
groupPolicyRes, err := suite.app.GroupKeeper.CreateGroupPolicy(ctx, accountReq) groupPolicyRes, err := suite.app.GroupKeeper.CreateGroupPolicy(ctx, accountReq)
suite.Require().NoError(err) suite.Require().NoError(err)
@ -660,7 +661,7 @@ func (suite *SimTestSuite) TestSimulateUpdateGroupPolicyDecisionPolicy() {
Admin: acc.Address.String(), Admin: acc.Address.String(),
GroupId: groupRes.GroupId, GroupId: groupRes.GroupId,
} }
err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour)) err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour, 0))
suite.Require().NoError(err) suite.Require().NoError(err)
groupPolicyRes, err := suite.app.GroupKeeper.CreateGroupPolicy(ctx, accountReq) groupPolicyRes, err := suite.app.GroupKeeper.CreateGroupPolicy(ctx, accountReq)
suite.Require().NoError(err) suite.Require().NoError(err)
@ -713,7 +714,7 @@ func (suite *SimTestSuite) TestSimulateUpdateGroupPolicyMetadata() {
Admin: acc.Address.String(), Admin: acc.Address.String(),
GroupId: groupRes.GroupId, GroupId: groupRes.GroupId,
} }
err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour)) err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour, 0))
suite.Require().NoError(err) suite.Require().NoError(err)
groupPolicyRes, err := suite.app.GroupKeeper.CreateGroupPolicy(ctx, accountReq) groupPolicyRes, err := suite.app.GroupKeeper.CreateGroupPolicy(ctx, accountReq)
suite.Require().NoError(err) suite.Require().NoError(err)

View File

@ -24,18 +24,28 @@ type DecisionPolicyResult struct {
type DecisionPolicy interface { type DecisionPolicy interface {
codec.ProtoMarshaler codec.ProtoMarshaler
// GetVotingPeriod returns the duration after proposal submission where
// votes are accepted.
GetVotingPeriod() time.Duration
// Allow defines policy-specific logic to allow a proposal to pass or not,
// based on its tally result, the group's total power and the time since
// the proposal was submitted.
Allow(tallyResult TallyResult, totalPower string, sinceSubmission time.Duration) (DecisionPolicyResult, error)
ValidateBasic() error ValidateBasic() error
GetTimeout() time.Duration Validate(g GroupInfo, config Config) error
Allow(tallyResult TallyResult, totalPower string, votingDuration time.Duration) (DecisionPolicyResult, error)
Validate(g GroupInfo) error
} }
// Implements DecisionPolicy Interface // Implements DecisionPolicy Interface
var _ DecisionPolicy = &ThresholdDecisionPolicy{} var _ DecisionPolicy = &ThresholdDecisionPolicy{}
// NewThresholdDecisionPolicy creates a threshold DecisionPolicy // NewThresholdDecisionPolicy creates a threshold DecisionPolicy
func NewThresholdDecisionPolicy(threshold string, timeout time.Duration) DecisionPolicy { func NewThresholdDecisionPolicy(threshold string, votingPeriod time.Duration, minExecutionPeriod time.Duration) DecisionPolicy {
return &ThresholdDecisionPolicy{threshold, timeout} return &ThresholdDecisionPolicy{threshold, &DecisionPolicyWindows{votingPeriod, minExecutionPeriod}}
}
func (p ThresholdDecisionPolicy) GetVotingPeriod() time.Duration {
return p.Windows.VotingPeriod
} }
func (p ThresholdDecisionPolicy) ValidateBasic() error { func (p ThresholdDecisionPolicy) ValidateBasic() error {
@ -43,19 +53,17 @@ func (p ThresholdDecisionPolicy) ValidateBasic() error {
return sdkerrors.Wrap(err, "threshold") return sdkerrors.Wrap(err, "threshold")
} }
timeout := p.Timeout if p.Windows == nil || p.Windows.VotingPeriod == 0 {
return sdkerrors.Wrap(errors.ErrInvalid, "voting period cannot be zero")
if timeout <= time.Nanosecond {
return sdkerrors.Wrap(errors.ErrInvalid, "timeout")
} }
return nil return nil
} }
// Allow allows a proposal to pass when the tally of yes votes equals or exceeds the threshold before the timeout. // Allow allows a proposal to pass when the tally of yes votes equals or exceeds the threshold before the timeout.
func (p ThresholdDecisionPolicy) Allow(tallyResult TallyResult, totalPower string, votingDuration time.Duration) (DecisionPolicyResult, error) { func (p ThresholdDecisionPolicy) Allow(tallyResult TallyResult, totalPower string, sinceSubmission time.Duration) (DecisionPolicyResult, error) {
timeout := p.Timeout if sinceSubmission < p.Windows.MinExecutionPeriod {
if timeout <= votingDuration { return DecisionPolicyResult{}, errors.ErrUnauthorized.Wrapf("must wait %s after submission before execution, currently at %s", p.Windows.MinExecutionPeriod, sinceSubmission)
return DecisionPolicyResult{Allow: false, Final: true}, nil
} }
threshold, err := math.NewPositiveDecFromString(p.Threshold) threshold, err := math.NewPositiveDecFromString(p.Threshold)
@ -93,7 +101,7 @@ func (p ThresholdDecisionPolicy) Allow(tallyResult TallyResult, totalPower strin
} }
// Validate returns an error if policy threshold is greater than the total group weight // Validate returns an error if policy threshold is greater than the total group weight
func (p *ThresholdDecisionPolicy) Validate(g GroupInfo) error { func (p *ThresholdDecisionPolicy) Validate(g GroupInfo, config Config) error {
threshold, err := math.NewPositiveDecFromString(p.Threshold) threshold, err := math.NewPositiveDecFromString(p.Threshold)
if err != nil { if err != nil {
return sdkerrors.Wrap(err, "threshold") return sdkerrors.Wrap(err, "threshold")
@ -105,6 +113,9 @@ func (p *ThresholdDecisionPolicy) Validate(g GroupInfo) error {
if threshold.Cmp(totalWeight) > 0 { if threshold.Cmp(totalWeight) > 0 {
return sdkerrors.Wrapf(errors.ErrInvalid, "policy threshold %s should not be greater than the total group weight %s", p.Threshold, g.TotalWeight) return sdkerrors.Wrapf(errors.ErrInvalid, "policy threshold %s should not be greater than the total group weight %s", p.Threshold, g.TotalWeight)
} }
if p.Windows.MinExecutionPeriod > p.Windows.VotingPeriod+config.MaxExecutionPeriod {
return sdkerrors.Wrap(errors.ErrInvalid, "min_execution_period should be smaller than voting_period + max_execution_period")
}
return nil return nil
} }
@ -112,8 +123,12 @@ func (p *ThresholdDecisionPolicy) Validate(g GroupInfo) error {
var _ DecisionPolicy = &PercentageDecisionPolicy{} var _ DecisionPolicy = &PercentageDecisionPolicy{}
// NewPercentageDecisionPolicy creates a new percentage DecisionPolicy // NewPercentageDecisionPolicy creates a new percentage DecisionPolicy
func NewPercentageDecisionPolicy(percentage string, timeout time.Duration) DecisionPolicy { func NewPercentageDecisionPolicy(percentage string, votingPeriod time.Duration, executionPeriod time.Duration) DecisionPolicy {
return &PercentageDecisionPolicy{percentage, timeout} return &PercentageDecisionPolicy{percentage, &DecisionPolicyWindows{votingPeriod, executionPeriod}}
}
func (p PercentageDecisionPolicy) GetVotingPeriod() time.Duration {
return p.Windows.VotingPeriod
} }
func (p PercentageDecisionPolicy) ValidateBasic() error { func (p PercentageDecisionPolicy) ValidateBasic() error {
@ -125,22 +140,24 @@ func (p PercentageDecisionPolicy) ValidateBasic() error {
return sdkerrors.Wrap(errors.ErrInvalid, "percentage must be > 0 and <= 1") return sdkerrors.Wrap(errors.ErrInvalid, "percentage must be > 0 and <= 1")
} }
timeout := p.Timeout if p.Windows == nil || p.Windows.VotingPeriod == 0 {
if timeout <= time.Nanosecond { return sdkerrors.Wrap(errors.ErrInvalid, "voting period cannot be 0")
return sdkerrors.Wrap(errors.ErrInvalid, "timeout") }
return nil
}
func (p *PercentageDecisionPolicy) Validate(g GroupInfo, config Config) error {
if p.Windows.MinExecutionPeriod > p.Windows.VotingPeriod+config.MaxExecutionPeriod {
return sdkerrors.Wrap(errors.ErrInvalid, "min_execution_period should be smaller than voting_period + max_execution_period")
} }
return nil return nil
} }
func (p *PercentageDecisionPolicy) Validate(g GroupInfo) error {
return nil
}
// Allow allows a proposal to pass when the tally of yes votes equals or exceeds the percentage threshold before the timeout. // Allow allows a proposal to pass when the tally of yes votes equals or exceeds the percentage threshold before the timeout.
func (p PercentageDecisionPolicy) Allow(tally TallyResult, totalPower string, votingDuration time.Duration) (DecisionPolicyResult, error) { func (p PercentageDecisionPolicy) Allow(tally TallyResult, totalPower string, sinceSubmission time.Duration) (DecisionPolicyResult, error) {
timeout := p.Timeout if sinceSubmission < p.Windows.MinExecutionPeriod {
if timeout <= votingDuration { return DecisionPolicyResult{}, errors.ErrUnauthorized.Wrapf("must wait %s after submission before execution, currently at %s", p.Windows.MinExecutionPeriod, sinceSubmission)
return DecisionPolicyResult{Allow: false, Final: true}, nil
} }
percentage, err := math.NewPositiveDecFromString(p.Percentage) percentage, err := math.NewPositiveDecFromString(p.Percentage)
@ -535,6 +552,16 @@ func (t TallyResult) TotalCounts() (math.Dec, error) {
return totalCounts, nil return totalCounts, nil
} }
// DefaultTallyResult returns a TallyResult with all counts set to 0.
func DefaultTallyResult() TallyResult {
return TallyResult{
YesCount: "0",
NoCount: "0",
NoWithVetoCount: "0",
AbstainCount: "0",
}
}
// VoteOptionFromString returns a VoteOption from a string. It returns an error // VoteOptionFromString returns a VoteOption from a string. It returns an error
// if the string is invalid. // if the string is invalid.
func VoteOptionFromString(str string) (VoteOption, error) { func VoteOptionFromString(str string) (VoteOption, error) {

View File

@ -307,9 +307,8 @@ func (m *Members) GetMembers() []Member {
type ThresholdDecisionPolicy struct { type ThresholdDecisionPolicy struct {
// threshold is the minimum weighted sum of yes votes that must be met or exceeded for a proposal to succeed. // threshold is the minimum weighted sum of yes votes that must be met or exceeded for a proposal to succeed.
Threshold string `protobuf:"bytes,1,opt,name=threshold,proto3" json:"threshold,omitempty"` Threshold string `protobuf:"bytes,1,opt,name=threshold,proto3" json:"threshold,omitempty"`
// timeout is the duration from submission of a proposal to the end of voting period // windows defines the different windows for voting and execution.
// Within this times votes and exec messages can be submitted. Windows *DecisionPolicyWindows `protobuf:"bytes,2,opt,name=windows,proto3" json:"windows,omitempty"`
Timeout time.Duration `protobuf:"bytes,2,opt,name=timeout,proto3,stdduration" json:"timeout"`
} }
func (m *ThresholdDecisionPolicy) Reset() { *m = ThresholdDecisionPolicy{} } func (m *ThresholdDecisionPolicy) Reset() { *m = ThresholdDecisionPolicy{} }
@ -352,20 +351,19 @@ func (m *ThresholdDecisionPolicy) GetThreshold() string {
return "" return ""
} }
func (m *ThresholdDecisionPolicy) GetTimeout() time.Duration { func (m *ThresholdDecisionPolicy) GetWindows() *DecisionPolicyWindows {
if m != nil { if m != nil {
return m.Timeout return m.Windows
} }
return 0 return nil
} }
// PercentageDecisionPolicy implements the DecisionPolicy interface // PercentageDecisionPolicy implements the DecisionPolicy interface
type PercentageDecisionPolicy struct { type PercentageDecisionPolicy struct {
// percentage is the minimum percentage the weighted sum of yes votes must meet for a proposal to succeed. // percentage is the minimum percentage the weighted sum of yes votes must meet for a proposal to succeed.
Percentage string `protobuf:"bytes,1,opt,name=percentage,proto3" json:"percentage,omitempty"` Percentage string `protobuf:"bytes,1,opt,name=percentage,proto3" json:"percentage,omitempty"`
// timeout is the duration from submission of a proposal to the end of voting period // windows defines the different windows for voting and execution.
// Within these times votes and exec messages can be submitted. Windows *DecisionPolicyWindows `protobuf:"bytes,2,opt,name=windows,proto3" json:"windows,omitempty"`
Timeout time.Duration `protobuf:"bytes,2,opt,name=timeout,proto3,stdduration" json:"timeout"`
} }
func (m *PercentageDecisionPolicy) Reset() { *m = PercentageDecisionPolicy{} } func (m *PercentageDecisionPolicy) Reset() { *m = PercentageDecisionPolicy{} }
@ -408,9 +406,75 @@ func (m *PercentageDecisionPolicy) GetPercentage() string {
return "" return ""
} }
func (m *PercentageDecisionPolicy) GetTimeout() time.Duration { func (m *PercentageDecisionPolicy) GetWindows() *DecisionPolicyWindows {
if m != nil { if m != nil {
return m.Timeout return m.Windows
}
return nil
}
// DecisionPolicyWindows defines the different windows for voting and execution.
type DecisionPolicyWindows struct {
// voting_period is the duration from submission of a proposal to the end of voting period
// Within this times votes can be submitted with MsgVote.
VotingPeriod time.Duration `protobuf:"bytes,1,opt,name=voting_period,json=votingPeriod,proto3,stdduration" json:"voting_period"`
// min_execution_period is the minimum duration after the proposal submission
// where members can start sending MsgExec. This means that the window for
// sending a MsgExec transaction is:
// `[ submission + min_execution_period ; submission + voting_period + max_execution_period]`
// where max_execution_period is a app-specific config, defined in the keeper.
// If not set, min_execution_period will default to 0.
//
// Please make sure to set a `min_execution_period` that is smaller than
// `voting_period + max_execution_period`, or else the above execution window
// is empty, meaning that all proposals created with this decision policy
// won't be able to be executed.
MinExecutionPeriod time.Duration `protobuf:"bytes,2,opt,name=min_execution_period,json=minExecutionPeriod,proto3,stdduration" json:"min_execution_period"`
}
func (m *DecisionPolicyWindows) Reset() { *m = DecisionPolicyWindows{} }
func (m *DecisionPolicyWindows) String() string { return proto.CompactTextString(m) }
func (*DecisionPolicyWindows) ProtoMessage() {}
func (*DecisionPolicyWindows) Descriptor() ([]byte, []int) {
return fileDescriptor_e091dfce5c49c8b6, []int{4}
}
func (m *DecisionPolicyWindows) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *DecisionPolicyWindows) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_DecisionPolicyWindows.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *DecisionPolicyWindows) XXX_Merge(src proto.Message) {
xxx_messageInfo_DecisionPolicyWindows.Merge(m, src)
}
func (m *DecisionPolicyWindows) XXX_Size() int {
return m.Size()
}
func (m *DecisionPolicyWindows) XXX_DiscardUnknown() {
xxx_messageInfo_DecisionPolicyWindows.DiscardUnknown(m)
}
var xxx_messageInfo_DecisionPolicyWindows proto.InternalMessageInfo
func (m *DecisionPolicyWindows) GetVotingPeriod() time.Duration {
if m != nil {
return m.VotingPeriod
}
return 0
}
func (m *DecisionPolicyWindows) GetMinExecutionPeriod() time.Duration {
if m != nil {
return m.MinExecutionPeriod
} }
return 0 return 0
} }
@ -438,7 +502,7 @@ func (m *GroupInfo) Reset() { *m = GroupInfo{} }
func (m *GroupInfo) String() string { return proto.CompactTextString(m) } func (m *GroupInfo) String() string { return proto.CompactTextString(m) }
func (*GroupInfo) ProtoMessage() {} func (*GroupInfo) ProtoMessage() {}
func (*GroupInfo) Descriptor() ([]byte, []int) { func (*GroupInfo) Descriptor() ([]byte, []int) {
return fileDescriptor_e091dfce5c49c8b6, []int{4} return fileDescriptor_e091dfce5c49c8b6, []int{5}
} }
func (m *GroupInfo) XXX_Unmarshal(b []byte) error { func (m *GroupInfo) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b) return m.Unmarshal(b)
@ -521,7 +585,7 @@ func (m *GroupMember) Reset() { *m = GroupMember{} }
func (m *GroupMember) String() string { return proto.CompactTextString(m) } func (m *GroupMember) String() string { return proto.CompactTextString(m) }
func (*GroupMember) ProtoMessage() {} func (*GroupMember) ProtoMessage() {}
func (*GroupMember) Descriptor() ([]byte, []int) { func (*GroupMember) Descriptor() ([]byte, []int) {
return fileDescriptor_e091dfce5c49c8b6, []int{5} return fileDescriptor_e091dfce5c49c8b6, []int{6}
} }
func (m *GroupMember) XXX_Unmarshal(b []byte) error { func (m *GroupMember) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b) return m.Unmarshal(b)
@ -587,7 +651,7 @@ func (m *GroupPolicyInfo) Reset() { *m = GroupPolicyInfo{} }
func (m *GroupPolicyInfo) String() string { return proto.CompactTextString(m) } func (m *GroupPolicyInfo) String() string { return proto.CompactTextString(m) }
func (*GroupPolicyInfo) ProtoMessage() {} func (*GroupPolicyInfo) ProtoMessage() {}
func (*GroupPolicyInfo) Descriptor() ([]byte, []int) { func (*GroupPolicyInfo) Descriptor() ([]byte, []int) {
return fileDescriptor_e091dfce5c49c8b6, []int{6} return fileDescriptor_e091dfce5c49c8b6, []int{7}
} }
func (m *GroupPolicyInfo) XXX_Unmarshal(b []byte) error { func (m *GroupPolicyInfo) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b) return m.Unmarshal(b)
@ -647,11 +711,12 @@ type Proposal struct {
// via gRPC, this field is not populated until the proposal's voting period // via gRPC, this field is not populated until the proposal's voting period
// has ended. // has ended.
FinalTallyResult TallyResult `protobuf:"bytes,10,opt,name=final_tally_result,json=finalTallyResult,proto3" json:"final_tally_result"` FinalTallyResult TallyResult `protobuf:"bytes,10,opt,name=final_tally_result,json=finalTallyResult,proto3" json:"final_tally_result"`
// timeout is the timestamp before which both voting and execution must be // voting_period_end is the timestamp before which voting must be done.
// done. If this timestamp is passed, then the proposal cannot be executed // Unless a successfull MsgExec is called before (to execute a proposal whose
// anymore and should be considered pending delete. This timestamp is checked // tally is successful before the voting period ends), tallying will be done
// against the block header's timestamp. // at this point, and the `final_tally_result`, as well
Timeout time.Time `protobuf:"bytes,11,opt,name=timeout,proto3,stdtime" json:"timeout"` // as `status` and `result` fields will be accordingly updated.
VotingPeriodEnd time.Time `protobuf:"bytes,11,opt,name=voting_period_end,json=votingPeriodEnd,proto3,stdtime" json:"voting_period_end"`
// executor_result is the final result based on the votes and election rule. Initial value is NotRun. // executor_result is the final result based on the votes and election rule. Initial value is NotRun.
ExecutorResult ProposalExecutorResult `protobuf:"varint,12,opt,name=executor_result,json=executorResult,proto3,enum=cosmos.group.v1beta1.ProposalExecutorResult" json:"executor_result,omitempty"` ExecutorResult ProposalExecutorResult `protobuf:"varint,12,opt,name=executor_result,json=executorResult,proto3,enum=cosmos.group.v1beta1.ProposalExecutorResult" json:"executor_result,omitempty"`
// messages is a list of Msgs that will be executed if the proposal passes. // messages is a list of Msgs that will be executed if the proposal passes.
@ -662,7 +727,7 @@ func (m *Proposal) Reset() { *m = Proposal{} }
func (m *Proposal) String() string { return proto.CompactTextString(m) } func (m *Proposal) String() string { return proto.CompactTextString(m) }
func (*Proposal) ProtoMessage() {} func (*Proposal) ProtoMessage() {}
func (*Proposal) Descriptor() ([]byte, []int) { func (*Proposal) Descriptor() ([]byte, []int) {
return fileDescriptor_e091dfce5c49c8b6, []int{7} return fileDescriptor_e091dfce5c49c8b6, []int{8}
} }
func (m *Proposal) XXX_Unmarshal(b []byte) error { func (m *Proposal) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b) return m.Unmarshal(b)
@ -707,7 +772,7 @@ func (m *TallyResult) Reset() { *m = TallyResult{} }
func (m *TallyResult) String() string { return proto.CompactTextString(m) } func (m *TallyResult) String() string { return proto.CompactTextString(m) }
func (*TallyResult) ProtoMessage() {} func (*TallyResult) ProtoMessage() {}
func (*TallyResult) Descriptor() ([]byte, []int) { func (*TallyResult) Descriptor() ([]byte, []int) {
return fileDescriptor_e091dfce5c49c8b6, []int{8} return fileDescriptor_e091dfce5c49c8b6, []int{9}
} }
func (m *TallyResult) XXX_Unmarshal(b []byte) error { func (m *TallyResult) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b) return m.Unmarshal(b)
@ -754,7 +819,7 @@ func (m *Vote) Reset() { *m = Vote{} }
func (m *Vote) String() string { return proto.CompactTextString(m) } func (m *Vote) String() string { return proto.CompactTextString(m) }
func (*Vote) ProtoMessage() {} func (*Vote) ProtoMessage() {}
func (*Vote) Descriptor() ([]byte, []int) { func (*Vote) Descriptor() ([]byte, []int) {
return fileDescriptor_e091dfce5c49c8b6, []int{9} return fileDescriptor_e091dfce5c49c8b6, []int{10}
} }
func (m *Vote) XXX_Unmarshal(b []byte) error { func (m *Vote) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b) return m.Unmarshal(b)
@ -827,6 +892,7 @@ func init() {
proto.RegisterType((*Members)(nil), "cosmos.group.v1beta1.Members") proto.RegisterType((*Members)(nil), "cosmos.group.v1beta1.Members")
proto.RegisterType((*ThresholdDecisionPolicy)(nil), "cosmos.group.v1beta1.ThresholdDecisionPolicy") proto.RegisterType((*ThresholdDecisionPolicy)(nil), "cosmos.group.v1beta1.ThresholdDecisionPolicy")
proto.RegisterType((*PercentageDecisionPolicy)(nil), "cosmos.group.v1beta1.PercentageDecisionPolicy") proto.RegisterType((*PercentageDecisionPolicy)(nil), "cosmos.group.v1beta1.PercentageDecisionPolicy")
proto.RegisterType((*DecisionPolicyWindows)(nil), "cosmos.group.v1beta1.DecisionPolicyWindows")
proto.RegisterType((*GroupInfo)(nil), "cosmos.group.v1beta1.GroupInfo") proto.RegisterType((*GroupInfo)(nil), "cosmos.group.v1beta1.GroupInfo")
proto.RegisterType((*GroupMember)(nil), "cosmos.group.v1beta1.GroupMember") proto.RegisterType((*GroupMember)(nil), "cosmos.group.v1beta1.GroupMember")
proto.RegisterType((*GroupPolicyInfo)(nil), "cosmos.group.v1beta1.GroupPolicyInfo") proto.RegisterType((*GroupPolicyInfo)(nil), "cosmos.group.v1beta1.GroupPolicyInfo")
@ -838,87 +904,91 @@ func init() {
func init() { proto.RegisterFile("cosmos/group/v1beta1/types.proto", fileDescriptor_e091dfce5c49c8b6) } func init() { proto.RegisterFile("cosmos/group/v1beta1/types.proto", fileDescriptor_e091dfce5c49c8b6) }
var fileDescriptor_e091dfce5c49c8b6 = []byte{ var fileDescriptor_e091dfce5c49c8b6 = []byte{
// 1274 bytes of a gzipped FileDescriptorProto // 1344 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x57, 0x4f, 0x6f, 0x1b, 0x45, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x57, 0x4d, 0x6f, 0x1b, 0x45,
0x14, 0xf7, 0x3a, 0x8e, 0xff, 0x3c, 0xa7, 0x8e, 0x35, 0x8d, 0xda, 0x4d, 0x1a, 0x6c, 0xd7, 0xe4, 0x18, 0xf6, 0x3a, 0x8e, 0x3f, 0x5e, 0xa7, 0x8e, 0x99, 0x86, 0x76, 0x93, 0x06, 0xdb, 0x35, 0x39,
0x10, 0x15, 0x6a, 0xb7, 0x01, 0x21, 0x54, 0x95, 0xa2, 0xb5, 0xb3, 0x2d, 0x46, 0xa9, 0x6d, 0x76, 0x44, 0x2d, 0xb5, 0x5b, 0x83, 0x10, 0xaa, 0x2a, 0x90, 0xed, 0x6c, 0x5b, 0xa3, 0xd4, 0x36, 0xbb,
0xd7, 0x09, 0xf4, 0xc0, 0x6a, 0xed, 0x9d, 0x3a, 0x2b, 0xec, 0x1d, 0x6b, 0x77, 0x9c, 0xd6, 0xdf, 0xeb, 0x04, 0x7a, 0x60, 0xb5, 0xf6, 0x4e, 0x9d, 0x15, 0xf6, 0x8e, 0xb5, 0x3b, 0x4e, 0xea, 0x7f,
0xa0, 0x42, 0x42, 0xf4, 0xc0, 0x81, 0x0b, 0x52, 0x25, 0xee, 0x48, 0x48, 0x3d, 0xf1, 0x09, 0x2a, 0xd0, 0x1b, 0x15, 0x02, 0x89, 0x0b, 0x52, 0x25, 0xee, 0x48, 0x48, 0x3d, 0x20, 0x7e, 0x41, 0xc5,
0x4e, 0x15, 0x27, 0x4e, 0x80, 0xd2, 0x4b, 0xb9, 0xf3, 0x01, 0xd0, 0xce, 0xcc, 0xfa, 0x5f, 0x5d, 0xa9, 0xe2, 0xc4, 0x09, 0x50, 0x7b, 0x29, 0xf7, 0xfe, 0x00, 0xb4, 0x33, 0xb3, 0x8e, 0xed, 0xba,
0xab, 0xad, 0xe0, 0x64, 0xcf, 0xbc, 0xdf, 0x9b, 0xf7, 0x7b, 0xef, 0xfd, 0xe6, 0x8d, 0x16, 0x0a, 0x56, 0x52, 0xc1, 0xa9, 0x99, 0x79, 0x9f, 0xe7, 0x9d, 0xe7, 0xfd, 0xdc, 0x1a, 0x72, 0x1d, 0xe2,
0x1d, 0xe2, 0xf7, 0x89, 0x5f, 0xee, 0x7a, 0x64, 0x38, 0x28, 0x9f, 0x5c, 0x6d, 0x63, 0x6a, 0x5d, 0xf5, 0x89, 0x57, 0xec, 0xba, 0x64, 0x38, 0x28, 0x1e, 0x5e, 0x6b, 0x63, 0x6a, 0x5e, 0x2b, 0xd2,
0x2d, 0xd3, 0xd1, 0x00, 0xfb, 0xa5, 0x81, 0x47, 0x28, 0x41, 0x1b, 0x1c, 0x51, 0x62, 0x88, 0x92, 0xd1, 0x00, 0x7b, 0x85, 0x81, 0x4b, 0x28, 0x41, 0x6b, 0x1c, 0x51, 0x60, 0x88, 0x82, 0x40, 0x6c,
0x40, 0x6c, 0x6d, 0x74, 0x49, 0x97, 0x30, 0x40, 0x39, 0xf8, 0xc7, 0xb1, 0x5b, 0xb9, 0x2e, 0x21, 0xac, 0x75, 0x49, 0x97, 0x30, 0x40, 0xd1, 0xff, 0x8b, 0x63, 0x37, 0x32, 0x5d, 0x42, 0xba, 0x3d,
0xdd, 0x1e, 0x2e, 0xb3, 0x55, 0x7b, 0x78, 0xb7, 0x6c, 0x0f, 0x3d, 0x8b, 0x3a, 0xc4, 0x15, 0xf6, 0x5c, 0x64, 0xa7, 0xf6, 0xf0, 0x5e, 0xd1, 0x1a, 0xba, 0x26, 0xb5, 0x89, 0x23, 0xec, 0xd9, 0x59,
0xfc, 0xbc, 0x9d, 0x3a, 0x7d, 0xec, 0x53, 0xab, 0x3f, 0x10, 0x80, 0x4d, 0x1e, 0xcc, 0xe4, 0x27, 0x3b, 0xb5, 0xfb, 0xd8, 0xa3, 0x66, 0x7f, 0x20, 0x00, 0xeb, 0xfc, 0x31, 0x83, 0x7b, 0x16, 0x2f,
0x8b, 0xc8, 0xc2, 0x34, 0xef, 0x6b, 0xb9, 0x23, 0x6e, 0x2a, 0xfe, 0x2c, 0x41, 0xfc, 0x36, 0xee, 0x0b, 0xd3, 0x2c, 0xd7, 0x74, 0x46, 0xdc, 0x94, 0xff, 0x59, 0x82, 0xe8, 0x1d, 0xdc, 0x6f, 0x63,
0xb7, 0xb1, 0x87, 0xf6, 0x20, 0x61, 0xd9, 0xb6, 0x87, 0x7d, 0x5f, 0x96, 0x0a, 0xd2, 0x6e, 0xaa, 0x17, 0x95, 0x20, 0x66, 0x5a, 0x96, 0x8b, 0x3d, 0x4f, 0x96, 0x72, 0xd2, 0x76, 0xa2, 0x22, 0xff,
0x22, 0xff, 0xf6, 0xf8, 0x72, 0x98, 0x82, 0xc2, 0x2d, 0x3a, 0xf5, 0x1c, 0xb7, 0xab, 0x85, 0x40, 0xfe, 0xf8, 0x4a, 0x10, 0x42, 0x99, 0x5b, 0x34, 0xea, 0xda, 0x4e, 0x57, 0x0d, 0x80, 0xe8, 0x1c,
0x74, 0x0e, 0xe2, 0xf7, 0xb0, 0xd3, 0x3d, 0xa6, 0x72, 0x34, 0x70, 0xd1, 0xc4, 0x0a, 0x6d, 0x41, 0x44, 0x8f, 0xb0, 0xdd, 0x3d, 0xa0, 0x72, 0xd8, 0xa7, 0xa8, 0xe2, 0x84, 0x36, 0x20, 0xde, 0xc7,
0xb2, 0x8f, 0xa9, 0x65, 0x5b, 0xd4, 0x92, 0x57, 0x98, 0x65, 0xbc, 0x46, 0x1f, 0x43, 0xd2, 0xb2, 0xd4, 0xb4, 0x4c, 0x6a, 0xca, 0x4b, 0xcc, 0x32, 0x3e, 0xa3, 0x4f, 0x20, 0x6e, 0x5a, 0x16, 0xb6,
0x6d, 0x6c, 0x9b, 0x16, 0x95, 0x63, 0x05, 0x69, 0x37, 0xbd, 0xb7, 0x55, 0xe2, 0x04, 0x4b, 0x21, 0x0c, 0x93, 0xca, 0x91, 0x9c, 0xb4, 0x9d, 0x2c, 0x6d, 0x14, 0xb8, 0xc0, 0x42, 0x20, 0xb0, 0xa0,
0xc1, 0x92, 0x11, 0x26, 0x57, 0x49, 0x3e, 0xf9, 0x23, 0x1f, 0x79, 0xf8, 0x67, 0x5e, 0x62, 0x41, 0x07, 0xc1, 0x55, 0xe2, 0x4f, 0xfe, 0xcc, 0x86, 0x1e, 0xfe, 0x95, 0x95, 0xd8, 0xa3, 0xd8, 0x2a,
0xb1, 0xad, 0xd0, 0xe2, 0x2d, 0x48, 0x70, 0xca, 0x3e, 0xba, 0x0e, 0x89, 0x3e, 0xff, 0x2b, 0x4b, 0xd3, 0xfc, 0x2d, 0x88, 0x71, 0xc9, 0x1e, 0xba, 0x01, 0xb1, 0x3e, 0xff, 0x53, 0x96, 0x72, 0x4b,
0x85, 0x95, 0xdd, 0xf4, 0xde, 0x76, 0x69, 0x51, 0xcd, 0x4b, 0x1c, 0x5f, 0x89, 0x05, 0x87, 0x69, 0xdb, 0xc9, 0xd2, 0x66, 0x61, 0x5e, 0xce, 0x0b, 0x1c, 0x5f, 0x89, 0xf8, 0xce, 0xd4, 0x80, 0x92,
0xa1, 0x4b, 0xf1, 0x6b, 0x09, 0xce, 0x1b, 0xc7, 0x1e, 0xf6, 0x8f, 0x49, 0xcf, 0xde, 0xc7, 0x1d, 0xff, 0x46, 0x82, 0xf3, 0xfa, 0x81, 0x8b, 0xbd, 0x03, 0xd2, 0xb3, 0x76, 0x70, 0xc7, 0xf6, 0x6c,
0xc7, 0x77, 0x88, 0xdb, 0x24, 0x3d, 0xa7, 0x33, 0x42, 0xdb, 0x90, 0xa2, 0xa1, 0x89, 0xd7, 0x43, 0xe2, 0x34, 0x49, 0xcf, 0xee, 0x8c, 0xd0, 0x26, 0x24, 0x68, 0x60, 0xe2, 0xf9, 0x50, 0x8f, 0x2f,
0x9b, 0x6c, 0xa0, 0x8f, 0x20, 0x11, 0xd4, 0x9f, 0x0c, 0x79, 0xe2, 0xe9, 0xbd, 0xcd, 0x17, 0x52, 0x90, 0x02, 0xb1, 0x23, 0xdb, 0xb1, 0xc8, 0x91, 0xc7, 0x02, 0x4f, 0x96, 0x2e, 0xcf, 0x7f, 0x77,
0xd8, 0x17, 0xfd, 0xe3, 0x19, 0x7c, 0xcf, 0x32, 0x10, 0x3e, 0xd7, 0xd0, 0xaf, 0x8f, 0x2f, 0x67, 0xda, 0xe9, 0x3e, 0xa7, 0xa8, 0x01, 0xf7, 0x3a, 0xfa, 0xed, 0xf1, 0x95, 0xd4, 0x34, 0x26, 0xff,
0x66, 0x03, 0x16, 0xbf, 0x91, 0x40, 0x6e, 0x62, 0xaf, 0x83, 0x5d, 0x6a, 0x75, 0xf1, 0x1c, 0x9b, 0x9d, 0x04, 0x72, 0x13, 0xbb, 0x1d, 0xec, 0x50, 0xb3, 0x8b, 0x67, 0x54, 0x65, 0x00, 0x06, 0x63,
0x1c, 0xc0, 0x60, 0x6c, 0x13, 0x74, 0xa6, 0x76, 0xfe, 0x0f, 0x3e, 0x7f, 0x4b, 0x90, 0xba, 0x15, 0x9b, 0x90, 0x35, 0x71, 0xf3, 0x7f, 0xea, 0xfa, 0x45, 0x82, 0xb7, 0xe7, 0xd2, 0xd0, 0x6d, 0x38,
0x14, 0xb1, 0xe6, 0xde, 0x25, 0x28, 0x03, 0x51, 0x87, 0xd7, 0x21, 0xa6, 0x45, 0x1d, 0x1b, 0x95, 0x73, 0x48, 0xa8, 0xed, 0x74, 0x8d, 0x01, 0x76, 0x6d, 0xc2, 0xd3, 0x95, 0x2c, 0xad, 0xbf, 0x52,
0x60, 0xd5, 0xb2, 0xfb, 0x8e, 0xcb, 0xfb, 0xbe, 0x44, 0x2a, 0x1c, 0xb6, 0x54, 0x10, 0x32, 0x24, 0xd5, 0x1d, 0xd1, 0xd2, 0xbc, 0xa8, 0xdf, 0xfb, 0x45, 0x5d, 0xe1, 0xcc, 0x26, 0x23, 0xa2, 0x16,
0x4e, 0xb0, 0x17, 0x84, 0x66, 0x7a, 0x88, 0x69, 0xe1, 0x12, 0x5d, 0x84, 0x35, 0x4a, 0xa8, 0xd5, 0xac, 0xf5, 0x6d, 0xc7, 0xc0, 0xf7, 0x71, 0x67, 0xe8, 0x03, 0x03, 0x87, 0xe1, 0x93, 0x3b, 0x44,
0x33, 0x85, 0xc8, 0x56, 0x99, 0x67, 0x9a, 0xed, 0x1d, 0x71, 0xa5, 0x55, 0x01, 0x3a, 0x1e, 0xb6, 0x7d, 0xdb, 0x51, 0x02, 0x3e, 0x77, 0x9b, 0xff, 0x47, 0x82, 0xc4, 0x2d, 0x3f, 0xfe, 0x9a, 0x73,
0x28, 0xd7, 0x53, 0xfc, 0x35, 0xf4, 0x94, 0x12, 0x7e, 0x0a, 0x2d, 0x7e, 0x09, 0x69, 0x96, 0xaa, 0x8f, 0xa0, 0x14, 0x84, 0x6d, 0xae, 0x31, 0xa2, 0x86, 0x6d, 0x0b, 0x15, 0x60, 0xd9, 0xb4, 0xfa,
0xb8, 0x09, 0x9b, 0x90, 0x64, 0xf2, 0x31, 0xc7, 0x29, 0x27, 0xd8, 0xba, 0x66, 0xa3, 0xf7, 0x21, 0xb6, 0xc3, 0x5b, 0x78, 0x41, 0xd7, 0x73, 0xd8, 0xc2, 0xde, 0x96, 0x21, 0x76, 0x88, 0x5d, 0x3f,
0xce, 0xd5, 0x23, 0xea, 0xbc, 0x54, 0x6f, 0x9a, 0xc0, 0x16, 0x9f, 0x47, 0x61, 0x9d, 0x05, 0xe0, 0x45, 0xac, 0xb5, 0x23, 0x6a, 0x70, 0x44, 0x17, 0x61, 0x85, 0x12, 0x6a, 0xf6, 0x0c, 0x31, 0x2f,
0xb5, 0x65, 0x15, 0x7d, 0x93, 0xeb, 0x36, 0x4d, 0x2c, 0x3a, 0x4b, 0x6c, 0xdc, 0x90, 0x95, 0xd7, 0xcb, 0x8c, 0x99, 0x64, 0x77, 0xfb, 0x7c, 0x68, 0xaa, 0x00, 0x1d, 0x17, 0x9b, 0x94, 0x8f, 0x46,
0x6f, 0x48, 0xec, 0xe5, 0x0d, 0x59, 0x9d, 0x6d, 0xc8, 0x67, 0xb0, 0x6e, 0x0b, 0x99, 0x98, 0x03, 0xf4, 0x14, 0xa3, 0x91, 0x10, 0xbc, 0x32, 0xcd, 0x7f, 0x09, 0x49, 0x16, 0xaa, 0x18, 0xea, 0x75,
0x96, 0x8b, 0x28, 0xf9, 0xc6, 0x0b, 0x25, 0x57, 0xdc, 0x51, 0x65, 0x81, 0xae, 0xb4, 0x8c, 0x3d, 0x88, 0xb3, 0xca, 0x1b, 0xe3, 0x90, 0x63, 0xec, 0x5c, 0xb3, 0xd0, 0x07, 0x10, 0xe5, 0x83, 0x20,
0x2b, 0xed, 0xd9, 0x06, 0x26, 0xde, 0xa8, 0x81, 0xd7, 0x92, 0x0f, 0x1e, 0xe5, 0x23, 0xcf, 0x1f, 0xd2, 0xbb, 0x70, 0x74, 0x54, 0x81, 0xcd, 0xbf, 0x08, 0xc3, 0x2a, 0x7b, 0x80, 0xf7, 0x00, 0xcb,
0xe5, 0xa5, 0xe2, 0xe9, 0x2a, 0x24, 0x9b, 0x1e, 0x19, 0x10, 0xdf, 0xea, 0xbd, 0xa0, 0xda, 0xa9, 0xe8, 0x9b, 0x6c, 0x8e, 0x49, 0x61, 0xe1, 0x69, 0x61, 0xe3, 0x82, 0x2c, 0x9d, 0xbe, 0x20, 0x91,
0x9a, 0x47, 0x5f, 0xb5, 0xe6, 0xcb, 0x94, 0xfb, 0x01, 0xa4, 0x06, 0x2c, 0x56, 0x30, 0x80, 0x62, 0xd7, 0x17, 0x64, 0x79, 0xba, 0x20, 0x9f, 0xc1, 0xaa, 0x25, 0xda, 0xd9, 0x18, 0xb0, 0x58, 0x44,
0x85, 0x95, 0xa5, 0x27, 0x4e, 0xa0, 0x48, 0x85, 0xb4, 0x3f, 0x6c, 0xf7, 0x1d, 0x6a, 0x06, 0x37, 0xca, 0xd7, 0x5e, 0x49, 0x79, 0xd9, 0x19, 0x55, 0xe6, 0x8c, 0x84, 0x9a, 0xb2, 0xa6, 0xa7, 0x73,
0x90, 0x15, 0xf9, 0x55, 0x93, 0x06, 0xee, 0x18, 0x98, 0xd0, 0xdb, 0x70, 0x86, 0xcb, 0x21, 0xec, 0xba, 0x80, 0xb1, 0x37, 0x2a, 0xe0, 0xf5, 0xf8, 0x83, 0x47, 0xd9, 0xd0, 0x8b, 0x47, 0x59, 0x29,
0x56, 0x9c, 0x65, 0xba, 0xc6, 0x36, 0x0f, 0x45, 0xcb, 0xae, 0xc0, 0x06, 0x07, 0xf1, 0x7e, 0x8d, 0xff, 0x72, 0x19, 0xe2, 0x4d, 0x97, 0x0c, 0x88, 0x67, 0xf6, 0x5e, 0xe9, 0xda, 0x89, 0x9c, 0x87,
0xb1, 0x09, 0x86, 0x45, 0xdd, 0x89, 0x2c, 0x43, 0x8f, 0xeb, 0x10, 0xf7, 0xa9, 0x45, 0x87, 0xbe, 0x4f, 0x9a, 0xf3, 0x45, 0x9d, 0xfb, 0x21, 0x24, 0x06, 0xec, 0x2d, 0x7f, 0x97, 0x46, 0x72, 0x4b,
0x9c, 0x2c, 0x48, 0xbb, 0x99, 0xbd, 0x9d, 0xc5, 0x1a, 0x0f, 0xab, 0xac, 0x33, 0xac, 0x26, 0x7c, 0x0b, 0x3d, 0x1e, 0x43, 0x91, 0x02, 0x49, 0x6f, 0xd8, 0xee, 0xdb, 0xd4, 0xf0, 0x3f, 0x48, 0x2c,
0x02, 0x6f, 0x0f, 0xfb, 0xc3, 0x1e, 0x95, 0x53, 0xaf, 0xe2, 0xad, 0x31, 0xac, 0x26, 0x7c, 0x50, 0xc9, 0x27, 0x0d, 0x1a, 0x38, 0xd1, 0x37, 0xa1, 0x77, 0xe1, 0x0c, 0x6f, 0x87, 0xa0, 0x5a, 0x51,
0x0b, 0xd0, 0x5d, 0xc7, 0xb5, 0x7a, 0x26, 0xb5, 0x7a, 0xbd, 0x91, 0x29, 0x4e, 0x02, 0x56, 0xa0, 0x16, 0xe9, 0x0a, 0xbb, 0xdc, 0x13, 0x25, 0xbb, 0x0a, 0x6b, 0x1c, 0xc4, 0xeb, 0x35, 0xc6, 0xc6,
0x8b, 0x8b, 0x4f, 0x32, 0x02, 0x24, 0x3f, 0x46, 0x0c, 0xf8, 0x2c, 0x3b, 0x62, 0x6a, 0x1f, 0xdd, 0x18, 0x16, 0x75, 0x8f, 0xdb, 0x32, 0x60, 0xdc, 0x80, 0xa8, 0x47, 0x4d, 0x3a, 0xf4, 0xe4, 0x78,
0x98, 0xcc, 0xc7, 0xf4, 0xeb, 0x3c, 0x39, 0xc2, 0x09, 0xb5, 0x60, 0x1d, 0xdf, 0xc7, 0x9d, 0x21, 0x4e, 0xda, 0x4e, 0x95, 0xb6, 0xe6, 0xf7, 0x78, 0x90, 0x65, 0x8d, 0x61, 0x55, 0xc1, 0xf1, 0xd9,
0x25, 0x5e, 0xc8, 0x69, 0x8d, 0x65, 0xf7, 0xee, 0xf2, 0xec, 0x54, 0xe1, 0x24, 0xb2, 0xcc, 0xe0, 0x2e, 0xf6, 0x86, 0x3d, 0x2a, 0x27, 0x4e, 0xc2, 0x56, 0x19, 0x56, 0x15, 0x1c, 0xd4, 0x02, 0x74,
0x99, 0x35, 0xba, 0x12, 0x68, 0xcb, 0xf7, 0xad, 0x2e, 0xf6, 0xe5, 0x33, 0xec, 0xfd, 0x5a, 0x78, 0xcf, 0x76, 0xcc, 0x9e, 0x41, 0xcd, 0x5e, 0x6f, 0x64, 0x08, 0x4f, 0xc0, 0x12, 0x74, 0x71, 0xbe,
0x8f, 0xb4, 0x31, 0xea, 0x5a, 0x2c, 0x10, 0x7a, 0xf1, 0x07, 0x09, 0xd2, 0xd3, 0xe9, 0x5d, 0x80, 0x27, 0xdd, 0x47, 0x72, 0x37, 0xe2, 0x5b, 0x95, 0x66, 0x2e, 0x26, 0xee, 0x51, 0x13, 0xde, 0x9a,
0xd4, 0x08, 0xfb, 0x66, 0x87, 0x0c, 0x5d, 0x2a, 0x5e, 0x87, 0xe4, 0x08, 0xfb, 0xd5, 0x60, 0x1d, 0xda, 0xb6, 0x06, 0x76, 0x2c, 0x39, 0x79, 0x8a, 0xb4, 0xaf, 0x4e, 0xae, 0x5c, 0xc5, 0xf1, 0xb7,
0xa8, 0xc4, 0x6a, 0xfb, 0xd4, 0x72, 0x5c, 0x01, 0xe0, 0x4f, 0xf5, 0x9a, 0xd8, 0xe4, 0xa0, 0x4d, 0xee, 0x2a, 0xdf, 0xb8, 0xc4, 0x0d, 0x54, 0xae, 0xb0, 0x78, 0xdf, 0x5b, 0x1c, 0xaf, 0x22, 0x48,
0x48, 0xba, 0x44, 0xd8, 0xb9, 0xca, 0x13, 0x2e, 0xe1, 0xa6, 0x77, 0x00, 0xb9, 0xc4, 0xbc, 0xe7, 0x22, 0xee, 0x14, 0x9e, 0x3a, 0xa3, 0xab, 0x7e, 0xb7, 0x79, 0x9e, 0xd9, 0xc5, 0x9e, 0x7c, 0x86,
0xd0, 0x63, 0xf3, 0x04, 0xd3, 0x10, 0xc4, 0x67, 0xc6, 0xba, 0x4b, 0x8e, 0x1c, 0x7a, 0x7c, 0x88, 0x7d, 0x9c, 0xe7, 0x4e, 0x96, 0x3a, 0x46, 0x5d, 0x8f, 0xf8, 0xad, 0x9f, 0xff, 0x41, 0x82, 0xe4,
0x29, 0x07, 0x0b, 0x7e, 0xff, 0x48, 0x10, 0x3b, 0x24, 0x14, 0xa3, 0x3c, 0xa4, 0x07, 0xa2, 0x14, 0x64, 0xc0, 0x17, 0x20, 0x31, 0xc2, 0x9e, 0xd1, 0x21, 0x43, 0x87, 0x8a, 0x4f, 0x5e, 0x7c, 0x84,
0x93, 0x61, 0x0a, 0xe1, 0x16, 0x1f, 0x5b, 0x27, 0x84, 0x8a, 0x71, 0xba, 0x74, 0x6c, 0x31, 0x18, 0xbd, 0xaa, 0x7f, 0xf6, 0xfb, 0xc6, 0x6c, 0x7b, 0xd4, 0xb4, 0x1d, 0x01, 0xe0, 0xff, 0x0f, 0x59,
0xfa, 0x10, 0xe2, 0x64, 0x10, 0x3c, 0x63, 0x8c, 0x65, 0x66, 0xaf, 0xb0, 0xb8, 0xfe, 0x41, 0xf0, 0x11, 0x97, 0x1c, 0xb4, 0x0e, 0x71, 0x87, 0x08, 0x3b, 0xef, 0xfb, 0x98, 0x43, 0xb8, 0xe9, 0x32,
0x06, 0xc3, 0x69, 0x02, 0xbf, 0x74, 0xe0, 0xfd, 0x37, 0xf7, 0xf1, 0xd2, 0xb7, 0x12, 0xc0, 0x24, 0x20, 0x87, 0x18, 0x47, 0x36, 0x3d, 0x30, 0x0e, 0x31, 0x0d, 0x40, 0x7c, 0x8b, 0xac, 0x3a, 0x64,
0x32, 0xba, 0x00, 0xe7, 0x0f, 0x1b, 0x86, 0x6a, 0x36, 0x9a, 0x46, 0xad, 0x51, 0x37, 0x5b, 0x75, 0xdf, 0xa6, 0x07, 0x7b, 0x98, 0x72, 0xb0, 0xd0, 0xf7, 0x52, 0x82, 0xc8, 0x1e, 0xa1, 0x18, 0x65,
0xbd, 0xa9, 0x56, 0x6b, 0x37, 0x6b, 0xea, 0x7e, 0x36, 0x82, 0xce, 0xc2, 0xfa, 0xb4, 0xf1, 0x0b, 0x21, 0x39, 0x10, 0xa9, 0x38, 0x5e, 0xaf, 0x10, 0x5c, 0xf1, 0x45, 0x76, 0x48, 0xa8, 0x58, 0xb0,
0x55, 0xcf, 0x4a, 0xe8, 0x3c, 0x9c, 0x9d, 0xde, 0x54, 0x2a, 0xba, 0xa1, 0xd4, 0xea, 0xd9, 0x28, 0x0b, 0x17, 0x19, 0x83, 0xa1, 0x8f, 0x20, 0x4a, 0x06, 0xfe, 0x77, 0x8b, 0xa9, 0x4c, 0x95, 0x72,
0x42, 0x90, 0x99, 0x36, 0xd4, 0x1b, 0xd9, 0x15, 0xb4, 0x0d, 0xf2, 0xec, 0x9e, 0x79, 0x54, 0x33, 0xf3, 0xf3, 0xef, 0x3f, 0xde, 0x60, 0x38, 0x55, 0xe0, 0x17, 0xae, 0xc0, 0xff, 0x66, 0x42, 0x2f,
0x3e, 0x31, 0x0f, 0x55, 0xa3, 0x91, 0x8d, 0x6d, 0xc5, 0x1e, 0xfc, 0x98, 0x8b, 0x5c, 0xfa, 0x49, 0x7d, 0x2d, 0x01, 0x1c, 0xbf, 0x8c, 0x2e, 0xc0, 0xf9, 0xbd, 0x86, 0xae, 0x18, 0x8d, 0xa6, 0x5e,
0x82, 0xcc, 0xec, 0x3d, 0x45, 0x79, 0xb8, 0xd0, 0xd4, 0x1a, 0xcd, 0x86, 0xae, 0x1c, 0x98, 0xba, 0x6b, 0xd4, 0x8d, 0x56, 0x5d, 0x6b, 0x2a, 0xd5, 0xda, 0xcd, 0x9a, 0xb2, 0x93, 0x0e, 0xa1, 0xb3,
0xa1, 0x18, 0x2d, 0x7d, 0x8e, 0xd9, 0x5b, 0xb0, 0x39, 0x0f, 0xd0, 0x5b, 0x95, 0xdb, 0x35, 0xc3, 0xb0, 0x3a, 0x69, 0xfc, 0x42, 0xd1, 0xd2, 0x12, 0x3a, 0x0f, 0x67, 0x27, 0x2f, 0xcb, 0x15, 0x4d,
0x50, 0xf7, 0xb3, 0x12, 0xda, 0x82, 0x73, 0xf3, 0xe6, 0xea, 0x41, 0x43, 0x57, 0xf7, 0xb3, 0xd1, 0x2f, 0xd7, 0xea, 0xe9, 0x30, 0x42, 0x90, 0x9a, 0x34, 0xd4, 0x1b, 0xe9, 0x25, 0xb4, 0x09, 0xf2,
0x20, 0xe3, 0x79, 0x9b, 0x52, 0x69, 0x68, 0x81, 0xe3, 0xca, 0xa2, 0x73, 0x03, 0xc2, 0xfb, 0x9a, 0xf4, 0x9d, 0xb1, 0x5f, 0xd3, 0x6f, 0x1b, 0x7b, 0x8a, 0xde, 0x48, 0x47, 0x36, 0x22, 0x0f, 0x7e,
0x72, 0x54, 0x1f, 0x13, 0xfe, 0x6e, 0x8a, 0xb0, 0x10, 0xf7, 0x34, 0x61, 0x4d, 0xd5, 0x5b, 0x07, 0xcc, 0x84, 0x2e, 0xfd, 0x24, 0x41, 0x6a, 0x7a, 0x72, 0x51, 0x16, 0x2e, 0x34, 0xd5, 0x46, 0xb3,
0xc6, 0x1c, 0xe1, 0x85, 0x80, 0x9b, 0xb5, 0xba, 0x72, 0x50, 0xbb, 0xc3, 0x28, 0x6f, 0x83, 0x3c, 0xa1, 0x95, 0x77, 0x0d, 0x4d, 0x2f, 0xeb, 0x2d, 0x6d, 0x46, 0xd9, 0x3b, 0xb0, 0x3e, 0x0b, 0xd0,
0x0f, 0x50, 0xaa, 0x55, 0xb5, 0x69, 0x30, 0xd2, 0x0b, 0xac, 0x9a, 0xfa, 0xa9, 0x5a, 0x65, 0xac, 0x5a, 0x95, 0x3b, 0x35, 0x5d, 0x57, 0x76, 0xd2, 0x12, 0xda, 0x80, 0x73, 0xb3, 0xe6, 0xea, 0x6e,
0x05, 0xad, 0x5f, 0x24, 0x38, 0xb7, 0xf8, 0x4e, 0xa3, 0x5d, 0xd8, 0x19, 0xbb, 0xab, 0x9f, 0xab, 0x43, 0x53, 0x76, 0xd2, 0x61, 0x3f, 0xe2, 0x59, 0x5b, 0xb9, 0xd2, 0x50, 0x7d, 0xe2, 0xd2, 0x3c,
0xd5, 0x96, 0xd1, 0xd0, 0x16, 0xf3, 0xdc, 0x81, 0xc2, 0x4b, 0x91, 0xf5, 0x86, 0x61, 0x6a, 0xad, 0xbf, 0xbe, 0xe0, 0x1d, 0xb5, 0xbc, 0x5f, 0x1f, 0x0b, 0xfe, 0x76, 0x42, 0xb0, 0x68, 0xee, 0x49,
0x7a, 0x56, 0x5a, 0x8a, 0xd2, 0x5b, 0xd5, 0xaa, 0xaa, 0xeb, 0xd9, 0xe8, 0x52, 0xd4, 0x4d, 0xa5, 0xc1, 0xaa, 0xa2, 0xb5, 0x76, 0xf5, 0x19, 0xc1, 0x73, 0x01, 0x37, 0x6b, 0xf5, 0xf2, 0x6e, 0xed,
0x76, 0xd0, 0xd2, 0xd4, 0x90, 0x7c, 0xe5, 0xc6, 0x93, 0xd3, 0x9c, 0xf4, 0xf4, 0x34, 0x27, 0xfd, 0x2e, 0x93, 0xbc, 0x09, 0xf2, 0x2c, 0xa0, 0x5c, 0xad, 0x2a, 0x4d, 0x9d, 0x89, 0x9e, 0x63, 0x55,
0x75, 0x9a, 0x93, 0x1e, 0x3e, 0xcb, 0x45, 0x9e, 0x3e, 0xcb, 0x45, 0x7e, 0x7f, 0x96, 0x8b, 0xdc, 0x95, 0x4f, 0x95, 0x2a, 0x53, 0x2d, 0x64, 0xfd, 0x2a, 0xc1, 0xb9, 0xf9, 0x33, 0x8d, 0xb6, 0x61,
0xd9, 0xe9, 0x3a, 0xf4, 0x78, 0xd8, 0x2e, 0x75, 0x48, 0x5f, 0x7c, 0x31, 0x88, 0x9f, 0xcb, 0xbe, 0x6b, 0x4c, 0x57, 0x3e, 0x57, 0xaa, 0x2d, 0xbd, 0xa1, 0xce, 0xd7, 0xb9, 0x05, 0xb9, 0xd7, 0x22,
0xfd, 0x55, 0xf9, 0x3e, 0xff, 0xb4, 0x69, 0xc7, 0xd9, 0x05, 0x78, 0xef, 0xdf, 0x00, 0x00, 0x00, 0xeb, 0x0d, 0xdd, 0x50, 0x5b, 0xf5, 0xb4, 0xb4, 0x10, 0xa5, 0xb5, 0xaa, 0x55, 0x45, 0xd3, 0xd2,
0xff, 0xff, 0x7b, 0x95, 0x5a, 0x6f, 0xf1, 0x0c, 0x00, 0x00, 0xe1, 0x85, 0xa8, 0x9b, 0xe5, 0xda, 0x6e, 0x4b, 0x55, 0x02, 0xf1, 0x95, 0x8f, 0x9f, 0x3c, 0xcb,
0x48, 0x4f, 0x9f, 0x65, 0xa4, 0xbf, 0x9f, 0x65, 0xa4, 0x87, 0xcf, 0x33, 0xa1, 0xa7, 0xcf, 0x33,
0xa1, 0x3f, 0x9e, 0x67, 0x42, 0x77, 0xb7, 0xba, 0x36, 0x3d, 0x18, 0xb6, 0x0b, 0x1d, 0xd2, 0x17,
0x3f, 0x87, 0xc4, 0x3f, 0x57, 0x3c, 0xeb, 0xab, 0xe2, 0x7d, 0xfe, 0xbb, 0xad, 0x1d, 0x65, 0x03,
0xf0, 0xfe, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0xf7, 0x5a, 0xf3, 0xf9, 0xce, 0x0d, 0x00, 0x00,
} }
func (this *GroupPolicyInfo) Equal(that interface{}) bool { func (this *GroupPolicyInfo) Equal(that interface{}) bool {
@ -1072,14 +1142,18 @@ func (m *ThresholdDecisionPolicy) MarshalToSizedBuffer(dAtA []byte) (int, error)
_ = i _ = i
var l int var l int
_ = l _ = l
n2, err2 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.Timeout, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.Timeout):]) if m.Windows != nil {
if err2 != nil { {
return 0, err2 size, err := m.Windows.MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintTypes(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x12
} }
i -= n2
i = encodeVarintTypes(dAtA, i, uint64(n2))
i--
dAtA[i] = 0x12
if len(m.Threshold) > 0 { if len(m.Threshold) > 0 {
i -= len(m.Threshold) i -= len(m.Threshold)
copy(dAtA[i:], m.Threshold) copy(dAtA[i:], m.Threshold)
@ -1110,14 +1184,18 @@ func (m *PercentageDecisionPolicy) MarshalToSizedBuffer(dAtA []byte) (int, error
_ = i _ = i
var l int var l int
_ = l _ = l
n3, err3 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.Timeout, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.Timeout):]) if m.Windows != nil {
if err3 != nil { {
return 0, err3 size, err := m.Windows.MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintTypes(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x12
} }
i -= n3
i = encodeVarintTypes(dAtA, i, uint64(n3))
i--
dAtA[i] = 0x12
if len(m.Percentage) > 0 { if len(m.Percentage) > 0 {
i -= len(m.Percentage) i -= len(m.Percentage)
copy(dAtA[i:], m.Percentage) copy(dAtA[i:], m.Percentage)
@ -1128,6 +1206,45 @@ func (m *PercentageDecisionPolicy) MarshalToSizedBuffer(dAtA []byte) (int, error
return len(dAtA) - i, nil return len(dAtA) - i, nil
} }
func (m *DecisionPolicyWindows) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *DecisionPolicyWindows) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *DecisionPolicyWindows) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
n4, err4 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.MinExecutionPeriod, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.MinExecutionPeriod):])
if err4 != nil {
return 0, err4
}
i -= n4
i = encodeVarintTypes(dAtA, i, uint64(n4))
i--
dAtA[i] = 0x12
n5, err5 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.VotingPeriod, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.VotingPeriod):])
if err5 != nil {
return 0, err5
}
i -= n5
i = encodeVarintTypes(dAtA, i, uint64(n5))
i--
dAtA[i] = 0xa
return len(dAtA) - i, nil
}
func (m *GroupInfo) Marshal() (dAtA []byte, err error) { func (m *GroupInfo) Marshal() (dAtA []byte, err error) {
size := m.Size() size := m.Size()
dAtA = make([]byte, size) dAtA = make([]byte, size)
@ -1148,12 +1265,12 @@ func (m *GroupInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i _ = i
var l int var l int
_ = l _ = l
n4, err4 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.CreatedAt, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.CreatedAt):]) n6, err6 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.CreatedAt, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.CreatedAt):])
if err4 != nil { if err6 != nil {
return 0, err4 return 0, err6
} }
i -= n4 i -= n6
i = encodeVarintTypes(dAtA, i, uint64(n4)) i = encodeVarintTypes(dAtA, i, uint64(n6))
i-- i--
dAtA[i] = 0x32 dAtA[i] = 0x32
if len(m.TotalWeight) > 0 { if len(m.TotalWeight) > 0 {
@ -1250,12 +1367,12 @@ func (m *GroupPolicyInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i _ = i
var l int var l int
_ = l _ = l
n6, err6 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.CreatedAt, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.CreatedAt):]) n8, err8 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.CreatedAt, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.CreatedAt):])
if err6 != nil { if err8 != nil {
return 0, err6 return 0, err8
} }
i -= n6 i -= n8
i = encodeVarintTypes(dAtA, i, uint64(n6)) i = encodeVarintTypes(dAtA, i, uint64(n8))
i-- i--
dAtA[i] = 0x3a dAtA[i] = 0x3a
if m.DecisionPolicy != nil { if m.DecisionPolicy != nil {
@ -1343,12 +1460,12 @@ func (m *Proposal) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i-- i--
dAtA[i] = 0x60 dAtA[i] = 0x60
} }
n8, err8 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timeout, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timeout):]) n10, err10 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.VotingPeriodEnd, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.VotingPeriodEnd):])
if err8 != nil { if err10 != nil {
return 0, err8 return 0, err10
} }
i -= n8 i -= n10
i = encodeVarintTypes(dAtA, i, uint64(n8)) i = encodeVarintTypes(dAtA, i, uint64(n10))
i-- i--
dAtA[i] = 0x5a dAtA[i] = 0x5a
{ {
@ -1381,12 +1498,12 @@ func (m *Proposal) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i-- i--
dAtA[i] = 0x30 dAtA[i] = 0x30
} }
n10, err10 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.SubmitTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.SubmitTime):]) n12, err12 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.SubmitTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.SubmitTime):])
if err10 != nil { if err12 != nil {
return 0, err10 return 0, err12
} }
i -= n10 i -= n12
i = encodeVarintTypes(dAtA, i, uint64(n10)) i = encodeVarintTypes(dAtA, i, uint64(n12))
i-- i--
dAtA[i] = 0x2a dAtA[i] = 0x2a
if len(m.Proposers) > 0 { if len(m.Proposers) > 0 {
@ -1491,12 +1608,12 @@ func (m *Vote) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i _ = i
var l int var l int
_ = l _ = l
n11, err11 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.SubmitTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.SubmitTime):]) n13, err13 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.SubmitTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.SubmitTime):])
if err11 != nil { if err13 != nil {
return 0, err11 return 0, err13
} }
i -= n11 i -= n13
i = encodeVarintTypes(dAtA, i, uint64(n11)) i = encodeVarintTypes(dAtA, i, uint64(n13))
i-- i--
dAtA[i] = 0x2a dAtA[i] = 0x2a
if len(m.Metadata) > 0 { if len(m.Metadata) > 0 {
@ -1585,8 +1702,10 @@ func (m *ThresholdDecisionPolicy) Size() (n int) {
if l > 0 { if l > 0 {
n += 1 + l + sovTypes(uint64(l)) n += 1 + l + sovTypes(uint64(l))
} }
l = github_com_gogo_protobuf_types.SizeOfStdDuration(m.Timeout) if m.Windows != nil {
n += 1 + l + sovTypes(uint64(l)) l = m.Windows.Size()
n += 1 + l + sovTypes(uint64(l))
}
return n return n
} }
@ -1600,7 +1719,22 @@ func (m *PercentageDecisionPolicy) Size() (n int) {
if l > 0 { if l > 0 {
n += 1 + l + sovTypes(uint64(l)) n += 1 + l + sovTypes(uint64(l))
} }
l = github_com_gogo_protobuf_types.SizeOfStdDuration(m.Timeout) if m.Windows != nil {
l = m.Windows.Size()
n += 1 + l + sovTypes(uint64(l))
}
return n
}
func (m *DecisionPolicyWindows) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = github_com_gogo_protobuf_types.SizeOfStdDuration(m.VotingPeriod)
n += 1 + l + sovTypes(uint64(l))
l = github_com_gogo_protobuf_types.SizeOfStdDuration(m.MinExecutionPeriod)
n += 1 + l + sovTypes(uint64(l)) n += 1 + l + sovTypes(uint64(l))
return n return n
} }
@ -1722,7 +1856,7 @@ func (m *Proposal) Size() (n int) {
} }
l = m.FinalTallyResult.Size() l = m.FinalTallyResult.Size()
n += 1 + l + sovTypes(uint64(l)) n += 1 + l + sovTypes(uint64(l))
l = github_com_gogo_protobuf_types.SizeOfStdTime(m.Timeout) l = github_com_gogo_protobuf_types.SizeOfStdTime(m.VotingPeriodEnd)
n += 1 + l + sovTypes(uint64(l)) n += 1 + l + sovTypes(uint64(l))
if m.ExecutorResult != 0 { if m.ExecutorResult != 0 {
n += 1 + sovTypes(uint64(m.ExecutorResult)) n += 1 + sovTypes(uint64(m.ExecutorResult))
@ -2118,7 +2252,7 @@ func (m *ThresholdDecisionPolicy) Unmarshal(dAtA []byte) error {
iNdEx = postIndex iNdEx = postIndex
case 2: case 2:
if wireType != 2 { if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Timeout", wireType) return fmt.Errorf("proto: wrong wireType = %d for field Windows", wireType)
} }
var msglen int var msglen int
for shift := uint(0); ; shift += 7 { for shift := uint(0); ; shift += 7 {
@ -2145,7 +2279,10 @@ func (m *ThresholdDecisionPolicy) Unmarshal(dAtA []byte) error {
if postIndex > l { if postIndex > l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.Timeout, dAtA[iNdEx:postIndex]); err != nil { if m.Windows == nil {
m.Windows = &DecisionPolicyWindows{}
}
if err := m.Windows.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err return err
} }
iNdEx = postIndex iNdEx = postIndex
@ -2233,7 +2370,7 @@ func (m *PercentageDecisionPolicy) Unmarshal(dAtA []byte) error {
iNdEx = postIndex iNdEx = postIndex
case 2: case 2:
if wireType != 2 { if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Timeout", wireType) return fmt.Errorf("proto: wrong wireType = %d for field Windows", wireType)
} }
var msglen int var msglen int
for shift := uint(0); ; shift += 7 { for shift := uint(0); ; shift += 7 {
@ -2260,7 +2397,126 @@ func (m *PercentageDecisionPolicy) Unmarshal(dAtA []byte) error {
if postIndex > l { if postIndex > l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.Timeout, dAtA[iNdEx:postIndex]); err != nil { if m.Windows == nil {
m.Windows = &DecisionPolicyWindows{}
}
if err := m.Windows.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipTypes(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthTypes
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *DecisionPolicyWindows) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTypes
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: DecisionPolicyWindows: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: DecisionPolicyWindows: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field VotingPeriod", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTypes
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthTypes
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthTypes
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.VotingPeriod, dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field MinExecutionPeriod", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTypes
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthTypes
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthTypes
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.MinExecutionPeriod, dAtA[iNdEx:postIndex]); err != nil {
return err return err
} }
iNdEx = postIndex iNdEx = postIndex
@ -3148,7 +3404,7 @@ func (m *Proposal) Unmarshal(dAtA []byte) error {
iNdEx = postIndex iNdEx = postIndex
case 11: case 11:
if wireType != 2 { if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Timeout", wireType) return fmt.Errorf("proto: wrong wireType = %d for field VotingPeriodEnd", wireType)
} }
var msglen int var msglen int
for shift := uint(0); ; shift += 7 { for shift := uint(0); ; shift += 7 {
@ -3175,7 +3431,7 @@ func (m *Proposal) Unmarshal(dAtA []byte) error {
if postIndex > l { if postIndex > l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.Timeout, dAtA[iNdEx:postIndex]); err != nil { if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.VotingPeriodEnd, dAtA[iNdEx:postIndex]); err != nil {
return err return err
} }
iNdEx = postIndex iNdEx = postIndex

View File

@ -9,6 +9,105 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestThresholdDecisionPolicyValidate(t *testing.T) {
g := group.GroupInfo{
TotalWeight: "10",
}
config := group.DefaultConfig()
testCases := []struct {
name string
policy group.ThresholdDecisionPolicy
expErr bool
}{
{
"threshold bigger than total weight",
group.ThresholdDecisionPolicy{
Threshold: "12",
Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second,
},
},
true,
},
{
"min exec period too big",
group.ThresholdDecisionPolicy{
Threshold: "5",
Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second,
MinExecutionPeriod: time.Hour * 24 * 30,
},
},
true,
},
{
"all good",
group.ThresholdDecisionPolicy{
Threshold: "5",
Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Hour,
MinExecutionPeriod: time.Hour * 24,
},
},
false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := tc.policy.Validate(g, config)
if tc.expErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
func TestPercentageDecisionPolicyValidate(t *testing.T) {
g := group.GroupInfo{}
config := group.DefaultConfig()
testCases := []struct {
name string
policy group.PercentageDecisionPolicy
expErr bool
}{
{
"min exec period too big",
group.PercentageDecisionPolicy{
Percentage: "0.5",
Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second,
MinExecutionPeriod: time.Hour * 24 * 30,
},
},
true,
},
{
"all good",
group.PercentageDecisionPolicy{
Percentage: "0.5",
Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Hour,
MinExecutionPeriod: time.Hour * 24,
},
},
false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := tc.policy.Validate(g, config)
if tc.expErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
func TestPercentageDecisionPolicyAllow(t *testing.T) { func TestPercentageDecisionPolicyAllow(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
@ -17,12 +116,15 @@ func TestPercentageDecisionPolicyAllow(t *testing.T) {
totalPower string totalPower string
votingDuration time.Duration votingDuration time.Duration
result group.DecisionPolicyResult result group.DecisionPolicyResult
expErr bool
}{ }{
{ {
"YesCount percentage > decision policy percentage", "YesCount percentage > decision policy percentage",
&group.PercentageDecisionPolicy{ &group.PercentageDecisionPolicy{
Percentage: "0.5", Percentage: "0.5",
Timeout: time.Second * 100, Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second * 100,
},
}, },
&group.TallyResult{ &group.TallyResult{
YesCount: "2", YesCount: "2",
@ -36,12 +138,15 @@ func TestPercentageDecisionPolicyAllow(t *testing.T) {
Allow: true, Allow: true,
Final: true, Final: true,
}, },
false,
}, },
{ {
"YesCount percentage == decision policy percentage", "YesCount percentage == decision policy percentage",
&group.PercentageDecisionPolicy{ &group.PercentageDecisionPolicy{
Percentage: "0.5", Percentage: "0.5",
Timeout: time.Second * 100, Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second * 100,
},
}, },
&group.TallyResult{ &group.TallyResult{
YesCount: "2", YesCount: "2",
@ -55,12 +160,15 @@ func TestPercentageDecisionPolicyAllow(t *testing.T) {
Allow: true, Allow: true,
Final: true, Final: true,
}, },
false,
}, },
{ {
"YesCount percentage < decision policy percentage", "YesCount percentage < decision policy percentage",
&group.PercentageDecisionPolicy{ &group.PercentageDecisionPolicy{
Percentage: "0.5", Percentage: "0.5",
Timeout: time.Second * 100, Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second * 100,
},
}, },
&group.TallyResult{ &group.TallyResult{
YesCount: "1", YesCount: "1",
@ -74,12 +182,15 @@ func TestPercentageDecisionPolicyAllow(t *testing.T) {
Allow: false, Allow: false,
Final: false, Final: false,
}, },
false,
}, },
{ {
"sum percentage (YesCount + undecided votes percentage) < decision policy percentage", "sum percentage (YesCount + undecided votes percentage) < decision policy percentage",
&group.PercentageDecisionPolicy{ &group.PercentageDecisionPolicy{
Percentage: "0.5", Percentage: "0.5",
Timeout: time.Second * 100, Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second * 100,
},
}, },
&group.TallyResult{ &group.TallyResult{
YesCount: "1", YesCount: "1",
@ -93,12 +204,15 @@ func TestPercentageDecisionPolicyAllow(t *testing.T) {
Allow: false, Allow: false,
Final: true, Final: true,
}, },
false,
}, },
{ {
"sum percentage = decision policy percentage", "sum percentage = decision policy percentage",
&group.PercentageDecisionPolicy{ &group.PercentageDecisionPolicy{
Percentage: "0.5", Percentage: "0.5",
Timeout: time.Second * 100, Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second * 100,
},
}, },
&group.TallyResult{ &group.TallyResult{
YesCount: "1", YesCount: "1",
@ -112,12 +226,15 @@ func TestPercentageDecisionPolicyAllow(t *testing.T) {
Allow: false, Allow: false,
Final: false, Final: false,
}, },
false,
}, },
{ {
"sum percentage > decision policy percentage", "sum percentage > decision policy percentage",
&group.PercentageDecisionPolicy{ &group.PercentageDecisionPolicy{
Percentage: "0.5", Percentage: "0.5",
Timeout: time.Second * 100, Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second * 100,
},
}, },
&group.TallyResult{ &group.TallyResult{
YesCount: "1", YesCount: "1",
@ -131,12 +248,16 @@ func TestPercentageDecisionPolicyAllow(t *testing.T) {
Allow: false, Allow: false,
Final: false, Final: false,
}, },
false,
}, },
{ {
"decision policy timeout <= voting duration", "time since submission < min execution period",
&group.PercentageDecisionPolicy{ &group.PercentageDecisionPolicy{
Percentage: "0.5", Percentage: "0.5",
Timeout: time.Second * 10, Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second * 10,
MinExecutionPeriod: time.Minute,
},
}, },
&group.TallyResult{ &group.TallyResult{
YesCount: "2", YesCount: "2",
@ -146,17 +267,19 @@ func TestPercentageDecisionPolicyAllow(t *testing.T) {
}, },
"3", "3",
time.Duration(time.Second * 50), time.Duration(time.Second * 50),
group.DecisionPolicyResult{ group.DecisionPolicyResult{},
Allow: false, true,
Final: true,
},
}, },
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
policyResult, err := tc.policy.Allow(*tc.tally, tc.totalPower, tc.votingDuration) policyResult, err := tc.policy.Allow(*tc.tally, tc.totalPower, tc.votingDuration)
require.NoError(t, err) if tc.expErr {
require.Equal(t, tc.result, policyResult) require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, tc.result, policyResult)
}
}) })
} }
} }
@ -169,12 +292,15 @@ func TestThresholdDecisionPolicyAllow(t *testing.T) {
totalPower string totalPower string
votingDuration time.Duration votingDuration time.Duration
result group.DecisionPolicyResult result group.DecisionPolicyResult
expErr bool
}{ }{
{ {
"YesCount >= threshold decision policy", "YesCount >= threshold decision policy",
&group.ThresholdDecisionPolicy{ &group.ThresholdDecisionPolicy{
Threshold: "3", Threshold: "3",
Timeout: time.Second * 100, Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second * 100,
},
}, },
&group.TallyResult{ &group.TallyResult{
YesCount: "3", YesCount: "3",
@ -188,12 +314,15 @@ func TestThresholdDecisionPolicyAllow(t *testing.T) {
Allow: true, Allow: true,
Final: true, Final: true,
}, },
false,
}, },
{ {
"YesCount < threshold decision policy", "YesCount < threshold decision policy",
&group.ThresholdDecisionPolicy{ &group.ThresholdDecisionPolicy{
Threshold: "3", Threshold: "3",
Timeout: time.Second * 100, Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second * 100,
},
}, },
&group.TallyResult{ &group.TallyResult{
YesCount: "1", YesCount: "1",
@ -207,12 +336,15 @@ func TestThresholdDecisionPolicyAllow(t *testing.T) {
Allow: false, Allow: false,
Final: false, Final: false,
}, },
false,
}, },
{ {
"sum votes < threshold decision policy", "sum votes < threshold decision policy",
&group.ThresholdDecisionPolicy{ &group.ThresholdDecisionPolicy{
Threshold: "3", Threshold: "3",
Timeout: time.Second * 100, Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second * 100,
},
}, },
&group.TallyResult{ &group.TallyResult{
YesCount: "1", YesCount: "1",
@ -226,12 +358,15 @@ func TestThresholdDecisionPolicyAllow(t *testing.T) {
Allow: false, Allow: false,
Final: true, Final: true,
}, },
false,
}, },
{ {
"sum votes >= threshold decision policy", "sum votes >= threshold decision policy",
&group.ThresholdDecisionPolicy{ &group.ThresholdDecisionPolicy{
Threshold: "3", Threshold: "3",
Timeout: time.Second * 100, Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second * 100,
},
}, },
&group.TallyResult{ &group.TallyResult{
YesCount: "1", YesCount: "1",
@ -245,12 +380,16 @@ func TestThresholdDecisionPolicyAllow(t *testing.T) {
Allow: false, Allow: false,
Final: false, Final: false,
}, },
false,
}, },
{ {
"decision policy timeout <= voting duration", "time since submission < min execution period",
&group.ThresholdDecisionPolicy{ &group.ThresholdDecisionPolicy{
Threshold: "3", Threshold: "3",
Timeout: time.Second * 10, Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second * 10,
MinExecutionPeriod: time.Minute,
},
}, },
&group.TallyResult{ &group.TallyResult{
YesCount: "3", YesCount: "3",
@ -264,13 +403,18 @@ func TestThresholdDecisionPolicyAllow(t *testing.T) {
Allow: false, Allow: false,
Final: true, Final: true,
}, },
true,
}, },
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
policyResult, err := tc.policy.Allow(*tc.tally, tc.totalPower, tc.votingDuration) policyResult, err := tc.policy.Allow(*tc.tally, tc.totalPower, tc.votingDuration)
require.NoError(t, err) if tc.expErr {
require.Equal(t, tc.result, policyResult) require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, tc.result, policyResult)
}
}) })
} }
} }