feat(group): Prune expired proposals and votes (#11315)
## Description Closes: #11245 ### 1. Pruning proposal. Proposals are pruned: - after a successful MsgExec (or try_exec) - on voting_period_end + max_execution_period whichever happens first. ### 2. Pruning votes. Votes are pruned: - on MsgExec (or try_exec) if tally is final - on voting_period_end whichever happens first. ### 3. Various group fixes See #11404 for details TODO: - [x] Add vote pruning after Tally, once #11310 is merged - [x] Add tests --- ### 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:
parent
73c9f42fc0
commit
5491be27d0
|
@ -2876,12 +2876,14 @@ func (x *fastReflection_EventVote) ProtoMethods() *protoiface.Methods {
|
|||
var (
|
||||
md_EventExec protoreflect.MessageDescriptor
|
||||
fd_EventExec_proposal_id protoreflect.FieldDescriptor
|
||||
fd_EventExec_result protoreflect.FieldDescriptor
|
||||
)
|
||||
|
||||
func init() {
|
||||
file_cosmos_group_v1_events_proto_init()
|
||||
md_EventExec = File_cosmos_group_v1_events_proto.Messages().ByName("EventExec")
|
||||
fd_EventExec_proposal_id = md_EventExec.Fields().ByName("proposal_id")
|
||||
fd_EventExec_result = md_EventExec.Fields().ByName("result")
|
||||
}
|
||||
|
||||
var _ protoreflect.Message = (*fastReflection_EventExec)(nil)
|
||||
|
@ -2955,6 +2957,12 @@ func (x *fastReflection_EventExec) Range(f func(protoreflect.FieldDescriptor, pr
|
|||
return
|
||||
}
|
||||
}
|
||||
if x.Result != 0 {
|
||||
value := protoreflect.ValueOfEnum((protoreflect.EnumNumber)(x.Result))
|
||||
if !f(fd_EventExec_result, value) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Has reports whether a field is populated.
|
||||
|
@ -2972,6 +2980,8 @@ func (x *fastReflection_EventExec) Has(fd protoreflect.FieldDescriptor) bool {
|
|||
switch fd.FullName() {
|
||||
case "cosmos.group.v1.EventExec.proposal_id":
|
||||
return x.ProposalId != uint64(0)
|
||||
case "cosmos.group.v1.EventExec.result":
|
||||
return x.Result != 0
|
||||
default:
|
||||
if fd.IsExtension() {
|
||||
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec"))
|
||||
|
@ -2990,6 +3000,8 @@ func (x *fastReflection_EventExec) Clear(fd protoreflect.FieldDescriptor) {
|
|||
switch fd.FullName() {
|
||||
case "cosmos.group.v1.EventExec.proposal_id":
|
||||
x.ProposalId = uint64(0)
|
||||
case "cosmos.group.v1.EventExec.result":
|
||||
x.Result = 0
|
||||
default:
|
||||
if fd.IsExtension() {
|
||||
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec"))
|
||||
|
@ -3009,6 +3021,9 @@ func (x *fastReflection_EventExec) Get(descriptor protoreflect.FieldDescriptor)
|
|||
case "cosmos.group.v1.EventExec.proposal_id":
|
||||
value := x.ProposalId
|
||||
return protoreflect.ValueOfUint64(value)
|
||||
case "cosmos.group.v1.EventExec.result":
|
||||
value := x.Result
|
||||
return protoreflect.ValueOfEnum((protoreflect.EnumNumber)(value))
|
||||
default:
|
||||
if descriptor.IsExtension() {
|
||||
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec"))
|
||||
|
@ -3031,6 +3046,8 @@ func (x *fastReflection_EventExec) Set(fd protoreflect.FieldDescriptor, value pr
|
|||
switch fd.FullName() {
|
||||
case "cosmos.group.v1.EventExec.proposal_id":
|
||||
x.ProposalId = value.Uint()
|
||||
case "cosmos.group.v1.EventExec.result":
|
||||
x.Result = (ProposalExecutorResult)(value.Enum())
|
||||
default:
|
||||
if fd.IsExtension() {
|
||||
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec"))
|
||||
|
@ -3053,6 +3070,8 @@ func (x *fastReflection_EventExec) Mutable(fd protoreflect.FieldDescriptor) prot
|
|||
switch fd.FullName() {
|
||||
case "cosmos.group.v1.EventExec.proposal_id":
|
||||
panic(fmt.Errorf("field proposal_id of message cosmos.group.v1.EventExec is not mutable"))
|
||||
case "cosmos.group.v1.EventExec.result":
|
||||
panic(fmt.Errorf("field result of message cosmos.group.v1.EventExec is not mutable"))
|
||||
default:
|
||||
if fd.IsExtension() {
|
||||
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec"))
|
||||
|
@ -3068,6 +3087,8 @@ func (x *fastReflection_EventExec) NewField(fd protoreflect.FieldDescriptor) pro
|
|||
switch fd.FullName() {
|
||||
case "cosmos.group.v1.EventExec.proposal_id":
|
||||
return protoreflect.ValueOfUint64(uint64(0))
|
||||
case "cosmos.group.v1.EventExec.result":
|
||||
return protoreflect.ValueOfEnum(0)
|
||||
default:
|
||||
if fd.IsExtension() {
|
||||
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec"))
|
||||
|
@ -3140,6 +3161,9 @@ func (x *fastReflection_EventExec) ProtoMethods() *protoiface.Methods {
|
|||
if x.ProposalId != 0 {
|
||||
n += 1 + runtime.Sov(uint64(x.ProposalId))
|
||||
}
|
||||
if x.Result != 0 {
|
||||
n += 1 + runtime.Sov(uint64(x.Result))
|
||||
}
|
||||
if x.unknownFields != nil {
|
||||
n += len(x.unknownFields)
|
||||
}
|
||||
|
@ -3169,6 +3193,11 @@ func (x *fastReflection_EventExec) ProtoMethods() *protoiface.Methods {
|
|||
i -= len(x.unknownFields)
|
||||
copy(dAtA[i:], x.unknownFields)
|
||||
}
|
||||
if x.Result != 0 {
|
||||
i = runtime.EncodeVarint(dAtA, i, uint64(x.Result))
|
||||
i--
|
||||
dAtA[i] = 0x10
|
||||
}
|
||||
if x.ProposalId != 0 {
|
||||
i = runtime.EncodeVarint(dAtA, i, uint64(x.ProposalId))
|
||||
i--
|
||||
|
@ -3242,6 +3271,25 @@ func (x *fastReflection_EventExec) ProtoMethods() *protoiface.Methods {
|
|||
break
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
if wireType != 0 {
|
||||
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field Result", wireType)
|
||||
}
|
||||
x.Result = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
x.Result |= ProposalExecutorResult(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := runtime.Skip(dAtA[iNdEx:])
|
||||
|
@ -4025,6 +4073,8 @@ type EventExec struct {
|
|||
|
||||
// proposal_id is the unique ID of the proposal.
|
||||
ProposalId uint64 `protobuf:"varint,1,opt,name=proposal_id,json=proposalId,proto3" json:"proposal_id,omitempty"`
|
||||
// result is the proposal execution result.
|
||||
Result ProposalExecutorResult `protobuf:"varint,2,opt,name=result,proto3,enum=cosmos.group.v1.ProposalExecutorResult" json:"result,omitempty"`
|
||||
}
|
||||
|
||||
func (x *EventExec) Reset() {
|
||||
|
@ -4054,6 +4104,13 @@ func (x *EventExec) GetProposalId() uint64 {
|
|||
return 0
|
||||
}
|
||||
|
||||
func (x *EventExec) GetResult() ProposalExecutorResult {
|
||||
if x != nil {
|
||||
return x.Result
|
||||
}
|
||||
return ProposalExecutorResult_PROPOSAL_EXECUTOR_RESULT_UNSPECIFIED
|
||||
}
|
||||
|
||||
// EventLeaveGroup is an event emitted when group member leaves the group.
|
||||
type EventLeaveGroup struct {
|
||||
state protoimpl.MessageState
|
||||
|
@ -4107,54 +4164,60 @@ var file_cosmos_group_v1_events_proto_rawDesc = []byte{
|
|||
0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f,
|
||||
0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x76, 0x31, 0x1a,
|
||||
0x19, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f,
|
||||
0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2d, 0x0a, 0x10, 0x45, 0x76,
|
||||
0x65, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19,
|
||||
0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
|
||||
0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x2d, 0x0a, 0x10, 0x45, 0x76, 0x65,
|
||||
0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a,
|
||||
0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52,
|
||||
0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x4c, 0x0a, 0x16, 0x45, 0x76, 0x65, 0x6e,
|
||||
0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x6f, 0x6c, 0x69,
|
||||
0x63, 0x79, 0x12, 0x32, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e,
|
||||
0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61,
|
||||
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x4c, 0x0a, 0x16, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55,
|
||||
0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79,
|
||||
0x12, 0x32, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64,
|
||||
0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64,
|
||||
0x72, 0x65, 0x73, 0x73, 0x22, 0x36, 0x0a, 0x13, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62,
|
||||
0x6d, 0x69, 0x74, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70,
|
||||
0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
|
||||
0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x49, 0x64, 0x22, 0x38, 0x0a, 0x15,
|
||||
0x45, 0x76, 0x65, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x50, 0x72, 0x6f,
|
||||
0x70, 0x6f, 0x73, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61,
|
||||
0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70,
|
||||
0x6f, 0x73, 0x61, 0x6c, 0x49, 0x64, 0x22, 0x2c, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x56,
|
||||
0x6f, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73,
|
||||
0x61, 0x6c, 0x49, 0x64, 0x22, 0x2c, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x45, 0x78, 0x65,
|
||||
0x63, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c,
|
||||
0x49, 0x64, 0x22, 0x60, 0x0a, 0x0f, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x61, 0x76, 0x65,
|
||||
0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64,
|
||||
0x12, 0x32, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64,
|
||||
0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64,
|
||||
0x72, 0x65, 0x73, 0x73, 0x42, 0xba, 0x01, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x73,
|
||||
0x6d, 0x6f, 0x73, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x76, 0x31, 0x42, 0x0b, 0x45, 0x76,
|
||||
0x65, 0x6e, 0x74, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x38, 0x67, 0x69, 0x74,
|
||||
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x63,
|
||||
0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f,
|
||||
0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2f, 0x76, 0x31, 0x3b, 0x67, 0x72,
|
||||
0x6f, 0x75, 0x70, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x47, 0x58, 0xaa, 0x02, 0x0f, 0x43, 0x6f,
|
||||
0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0f,
|
||||
0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x5c, 0x56, 0x31, 0xe2,
|
||||
0x02, 0x1b, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x5c, 0x56,
|
||||
0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x11,
|
||||
0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x3a, 0x3a, 0x56,
|
||||
0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x63, 0x6f, 0x73, 0x6d,
|
||||
0x6f, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x79, 0x70, 0x65,
|
||||
0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2d, 0x0a, 0x10, 0x45, 0x76, 0x65, 0x6e, 0x74,
|
||||
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x67,
|
||||
0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67,
|
||||
0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x2d, 0x0a, 0x10, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55,
|
||||
0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72,
|
||||
0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67, 0x72,
|
||||
0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x4c, 0x0a, 0x16, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x43, 0x72,
|
||||
0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12,
|
||||
0x32, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64,
|
||||
0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72,
|
||||
0x65, 0x73, 0x73, 0x22, 0x4c, 0x0a, 0x16, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61,
|
||||
0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x32, 0x0a,
|
||||
0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18,
|
||||
0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65,
|
||||
0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
|
||||
0x73, 0x22, 0x36, 0x0a, 0x13, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74,
|
||||
0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70,
|
||||
0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70,
|
||||
0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x49, 0x64, 0x22, 0x38, 0x0a, 0x15, 0x45, 0x76, 0x65,
|
||||
0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73,
|
||||
0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61,
|
||||
0x6c, 0x49, 0x64, 0x22, 0x2c, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x56, 0x6f, 0x74, 0x65,
|
||||
0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x49,
|
||||
0x64, 0x22, 0x6d, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x45, 0x78, 0x65, 0x63, 0x12, 0x1f,
|
||||
0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x49, 0x64, 0x12,
|
||||
0x3f, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
||||
0x27, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x76,
|
||||
0x31, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74,
|
||||
0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74,
|
||||
0x22, 0x60, 0x0a, 0x0f, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x47, 0x72,
|
||||
0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x32,
|
||||
0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42,
|
||||
0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72,
|
||||
0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65,
|
||||
0x73, 0x73, 0x42, 0xba, 0x01, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f,
|
||||
0x73, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x76, 0x31, 0x42, 0x0b, 0x45, 0x76, 0x65, 0x6e,
|
||||
0x74, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75,
|
||||
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x63, 0x6f, 0x73,
|
||||
0x6d, 0x6f, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x73, 0x6d,
|
||||
0x6f, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2f, 0x76, 0x31, 0x3b, 0x67, 0x72, 0x6f, 0x75,
|
||||
0x70, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x47, 0x58, 0xaa, 0x02, 0x0f, 0x43, 0x6f, 0x73, 0x6d,
|
||||
0x6f, 0x73, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0f, 0x43, 0x6f,
|
||||
0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x1b,
|
||||
0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x5c, 0x56, 0x31, 0x5c,
|
||||
0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x11, 0x43, 0x6f,
|
||||
0x73, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x3a, 0x3a, 0x56, 0x31, 0x62,
|
||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -4180,13 +4243,15 @@ var file_cosmos_group_v1_events_proto_goTypes = []interface{}{
|
|||
(*EventVote)(nil), // 6: cosmos.group.v1.EventVote
|
||||
(*EventExec)(nil), // 7: cosmos.group.v1.EventExec
|
||||
(*EventLeaveGroup)(nil), // 8: cosmos.group.v1.EventLeaveGroup
|
||||
(ProposalExecutorResult)(0), // 9: cosmos.group.v1.ProposalExecutorResult
|
||||
}
|
||||
var file_cosmos_group_v1_events_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
9, // 0: cosmos.group.v1.EventExec.result:type_name -> cosmos.group.v1.ProposalExecutorResult
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_cosmos_group_v1_events_proto_init() }
|
||||
|
@ -4194,6 +4259,7 @@ func file_cosmos_group_v1_events_proto_init() {
|
|||
if File_cosmos_group_v1_events_proto != nil {
|
||||
return
|
||||
}
|
||||
file_cosmos_group_v1_types_proto_init()
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_cosmos_group_v1_events_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*EventCreateGroup); i {
|
||||
|
|
|
@ -3,6 +3,7 @@ syntax = "proto3";
|
|||
package cosmos.group.v1;
|
||||
|
||||
import "cosmos_proto/cosmos.proto";
|
||||
import "cosmos/group/v1/types.proto";
|
||||
|
||||
option go_package = "github.com/cosmos/cosmos-sdk/x/group";
|
||||
|
||||
|
@ -60,6 +61,9 @@ message EventExec {
|
|||
|
||||
// proposal_id is the unique ID of the proposal.
|
||||
uint64 proposal_id = 1;
|
||||
|
||||
// result is the proposal execution result.
|
||||
ProposalExecutorResult result = 2;
|
||||
}
|
||||
|
||||
// EventLeaveGroup is an event emitted when group member leaves the group.
|
||||
|
|
|
@ -349,6 +349,8 @@ func (m *EventVote) GetProposalId() uint64 {
|
|||
type EventExec struct {
|
||||
// proposal_id is the unique ID of the proposal.
|
||||
ProposalId uint64 `protobuf:"varint,1,opt,name=proposal_id,json=proposalId,proto3" json:"proposal_id,omitempty"`
|
||||
// result is the proposal execution result.
|
||||
Result ProposalExecutorResult `protobuf:"varint,2,opt,name=result,proto3,enum=cosmos.group.v1.ProposalExecutorResult" json:"result,omitempty"`
|
||||
}
|
||||
|
||||
func (m *EventExec) Reset() { *m = EventExec{} }
|
||||
|
@ -391,6 +393,13 @@ func (m *EventExec) GetProposalId() uint64 {
|
|||
return 0
|
||||
}
|
||||
|
||||
func (m *EventExec) GetResult() ProposalExecutorResult {
|
||||
if m != nil {
|
||||
return m.Result
|
||||
}
|
||||
return PROPOSAL_EXECUTOR_RESULT_UNSPECIFIED
|
||||
}
|
||||
|
||||
// EventLeaveGroup is an event emitted when group member leaves the group.
|
||||
type EventLeaveGroup struct {
|
||||
// group_id is the unique ID of the group.
|
||||
|
@ -461,27 +470,30 @@ func init() {
|
|||
func init() { proto.RegisterFile("cosmos/group/v1/events.proto", fileDescriptor_e8d753981546f032) }
|
||||
|
||||
var fileDescriptor_e8d753981546f032 = []byte{
|
||||
// 314 bytes of a gzipped FileDescriptorProto
|
||||
// 366 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x49, 0xce, 0x2f, 0xce,
|
||||
0xcd, 0x2f, 0xd6, 0x4f, 0x2f, 0xca, 0x2f, 0x2d, 0xd0, 0x2f, 0x33, 0xd4, 0x4f, 0x2d, 0x4b, 0xcd,
|
||||
0x2b, 0x29, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x87, 0xc8, 0xea, 0x81, 0x65, 0xf5,
|
||||
0xca, 0x0c, 0xa5, 0x24, 0x21, 0x02, 0xf1, 0x60, 0x69, 0x7d, 0xa8, 0x2c, 0x98, 0xa3, 0xa4, 0xcb,
|
||||
0x25, 0xe0, 0x0a, 0xd2, 0xeb, 0x5c, 0x94, 0x9a, 0x58, 0x92, 0xea, 0x0e, 0xd2, 0x21, 0x24, 0xc9,
|
||||
0xc5, 0x01, 0xd6, 0x1a, 0x9f, 0x99, 0x22, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x12, 0xc4, 0x0e, 0xe6,
|
||||
0x7b, 0xa6, 0xc0, 0x95, 0x87, 0x16, 0xa4, 0x10, 0xa3, 0xdc, 0x87, 0x4b, 0x0c, 0xdd, 0xf4, 0x80,
|
||||
0xfc, 0x9c, 0xcc, 0xe4, 0x4a, 0x21, 0x23, 0x2e, 0xf6, 0xc4, 0x94, 0x94, 0xa2, 0xd4, 0xe2, 0x62,
|
||||
0xb0, 0x1e, 0x4e, 0x27, 0x89, 0x4b, 0x5b, 0x74, 0x45, 0xa0, 0x4e, 0x73, 0x84, 0xc8, 0x04, 0x97,
|
||||
0x14, 0x65, 0xe6, 0xa5, 0x07, 0xc1, 0x14, 0xc2, 0x4d, 0x43, 0xb2, 0x9c, 0x02, 0xd3, 0xcc, 0xb8,
|
||||
0x84, 0xc1, 0xa6, 0x05, 0x97, 0x26, 0xe5, 0x66, 0x96, 0x04, 0x14, 0xe5, 0x17, 0xe4, 0x17, 0x27,
|
||||
0xe6, 0x08, 0xc9, 0x73, 0x71, 0x17, 0x40, 0xd9, 0x08, 0x0f, 0x71, 0xc1, 0x84, 0x3c, 0x53, 0x94,
|
||||
0x2c, 0xb8, 0x44, 0xc1, 0xfa, 0xc2, 0x33, 0x4b, 0x32, 0x52, 0x8a, 0x12, 0xcb, 0x89, 0xd7, 0xa9,
|
||||
0xc3, 0xc5, 0x09, 0xd6, 0x19, 0x96, 0x5f, 0x92, 0x4a, 0xbc, 0x6a, 0xd7, 0x8a, 0xd4, 0x64, 0xc2,
|
||||
0xaa, 0x13, 0xb8, 0xf8, 0xc1, 0xaa, 0x7d, 0x52, 0x13, 0xcb, 0x08, 0xc6, 0x0b, 0x72, 0x78, 0x31,
|
||||
0x11, 0x19, 0x5e, 0x4e, 0x76, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91,
|
||||
0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0xa5,
|
||||
0x92, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0x0b, 0x4d, 0x5c, 0x50, 0x4a, 0xb7,
|
||||
0x38, 0x25, 0x5b, 0xbf, 0x02, 0x92, 0x4a, 0x93, 0xd8, 0xc0, 0x09, 0xce, 0x18, 0x10, 0x00, 0x00,
|
||||
0xff, 0xff, 0xa2, 0x42, 0x09, 0x69, 0xbc, 0x02, 0x00, 0x00,
|
||||
0xca, 0x0c, 0xa5, 0x24, 0x21, 0x02, 0xf1, 0x60, 0x69, 0x7d, 0xa8, 0x2c, 0x98, 0x23, 0x25, 0x8d,
|
||||
0x6e, 0x52, 0x49, 0x65, 0x41, 0x2a, 0x54, 0x52, 0x49, 0x97, 0x4b, 0xc0, 0x15, 0x64, 0xb0, 0x73,
|
||||
0x51, 0x6a, 0x62, 0x49, 0xaa, 0x3b, 0x48, 0x89, 0x90, 0x24, 0x17, 0x07, 0x58, 0x6d, 0x7c, 0x66,
|
||||
0x8a, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x4b, 0x10, 0x3b, 0x98, 0xef, 0x99, 0x02, 0x57, 0x1e, 0x5a,
|
||||
0x90, 0x42, 0x8c, 0x72, 0x1f, 0x2e, 0x31, 0x74, 0xd3, 0x03, 0xf2, 0x73, 0x32, 0x93, 0x2b, 0x85,
|
||||
0x8c, 0xb8, 0xd8, 0x13, 0x53, 0x52, 0x8a, 0x52, 0x8b, 0x8b, 0xc1, 0x7a, 0x38, 0x9d, 0x24, 0x2e,
|
||||
0x6d, 0xd1, 0x15, 0x81, 0xba, 0xdb, 0x11, 0x22, 0x13, 0x5c, 0x52, 0x94, 0x99, 0x97, 0x1e, 0x04,
|
||||
0x53, 0x08, 0x37, 0x0d, 0xc9, 0x72, 0x0a, 0x4c, 0x33, 0xe3, 0x12, 0x06, 0x9b, 0x16, 0x5c, 0x9a,
|
||||
0x94, 0x9b, 0x59, 0x12, 0x50, 0x94, 0x5f, 0x90, 0x5f, 0x9c, 0x98, 0x23, 0x24, 0xcf, 0xc5, 0x5d,
|
||||
0x00, 0x65, 0x23, 0x3c, 0xc4, 0x05, 0x13, 0xf2, 0x4c, 0x51, 0xb2, 0xe0, 0x12, 0x05, 0xeb, 0x0b,
|
||||
0xcf, 0x2c, 0xc9, 0x48, 0x29, 0x4a, 0x2c, 0x27, 0x5e, 0xa7, 0x0e, 0x17, 0x27, 0x58, 0x67, 0x58,
|
||||
0x7e, 0x49, 0x2a, 0x61, 0xd5, 0xb9, 0x50, 0xd5, 0xae, 0x15, 0xa9, 0xc9, 0x04, 0x55, 0x0b, 0xd9,
|
||||
0x73, 0xb1, 0x15, 0xa5, 0x16, 0x97, 0xe6, 0x94, 0x48, 0x30, 0x29, 0x30, 0x6a, 0xf0, 0x19, 0xa9,
|
||||
0xeb, 0xa1, 0xa5, 0x10, 0x3d, 0x98, 0x3b, 0x41, 0xe6, 0x95, 0x96, 0xe4, 0x17, 0x05, 0x81, 0x95,
|
||||
0x07, 0x41, 0xb5, 0x29, 0x25, 0x70, 0xf1, 0x83, 0xad, 0xf3, 0x49, 0x4d, 0x2c, 0x23, 0x18, 0xb1,
|
||||
0xc8, 0x01, 0xce, 0x44, 0x64, 0x80, 0x3b, 0xd9, 0x9d, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c,
|
||||
0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1,
|
||||
0x1c, 0x43, 0x94, 0x4a, 0x7a, 0x66, 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0x2e, 0x34, 0xe9,
|
||||
0x42, 0x29, 0xdd, 0xe2, 0x94, 0x6c, 0xfd, 0x0a, 0x48, 0xca, 0x4d, 0x62, 0x03, 0xa7, 0x58, 0x63,
|
||||
0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe6, 0x94, 0x89, 0xaf, 0x1a, 0x03, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *EventCreateGroup) Marshal() (dAtA []byte, err error) {
|
||||
|
@ -704,6 +716,11 @@ func (m *EventExec) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
|||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.Result != 0 {
|
||||
i = encodeVarintEvents(dAtA, i, uint64(m.Result))
|
||||
i--
|
||||
dAtA[i] = 0x10
|
||||
}
|
||||
if m.ProposalId != 0 {
|
||||
i = encodeVarintEvents(dAtA, i, uint64(m.ProposalId))
|
||||
i--
|
||||
|
@ -853,6 +870,9 @@ func (m *EventExec) Size() (n int) {
|
|||
if m.ProposalId != 0 {
|
||||
n += 1 + sovEvents(uint64(m.ProposalId))
|
||||
}
|
||||
if m.Result != 0 {
|
||||
n += 1 + sovEvents(uint64(m.Result))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
|
@ -1435,6 +1455,25 @@ func (m *EventExec) Unmarshal(dAtA []byte) error {
|
|||
break
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Result", wireType)
|
||||
}
|
||||
m.Result = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEvents
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Result |= ProposalExecutorResult(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipEvents(dAtA[iNdEx:])
|
||||
|
|
|
@ -20,24 +20,10 @@ const (
|
|||
|
||||
// RegisterInvariants registers all group invariants
|
||||
func RegisterInvariants(ir sdk.InvariantRegistry, keeper Keeper) {
|
||||
ir.RegisterRoute(group.ModuleName, votesInvariant, TallyVotesInvariant(keeper))
|
||||
ir.RegisterRoute(group.ModuleName, weightInvariant, GroupTotalWeightInvariant(keeper))
|
||||
ir.RegisterRoute(group.ModuleName, votesSumInvariant, TallyVotesSumInvariant(keeper))
|
||||
}
|
||||
|
||||
// TallyVotesInvariant checks that vote tally sums must never have less than the block before.
|
||||
func TallyVotesInvariant(keeper Keeper) sdk.Invariant {
|
||||
return func(ctx sdk.Context) (string, bool) {
|
||||
if ctx.BlockHeight()-1 < 0 {
|
||||
return sdk.FormatInvariant(group.ModuleName, votesInvariant, "Not enough blocks to perform TallyVotesInvariant"), false
|
||||
}
|
||||
prevCtx, _ := ctx.CacheContext()
|
||||
prevCtx = prevCtx.WithBlockHeight(ctx.BlockHeight() - 1)
|
||||
msg, broken := TallyVotesInvariantHelper(ctx, prevCtx, keeper.key, keeper.proposalTable)
|
||||
return sdk.FormatInvariant(group.ModuleName, votesInvariant, msg), broken
|
||||
}
|
||||
}
|
||||
|
||||
// GroupTotalWeightInvariant checks that group's TotalWeight must be equal to the sum of its members.
|
||||
func GroupTotalWeightInvariant(keeper Keeper) sdk.Invariant {
|
||||
return func(ctx sdk.Context) (string, bool) {
|
||||
|
@ -46,105 +32,20 @@ func GroupTotalWeightInvariant(keeper Keeper) sdk.Invariant {
|
|||
}
|
||||
}
|
||||
|
||||
// TallyVotesSumInvariant checks that proposal FinalTallyResult must correspond to the vote option.
|
||||
// TallyVotesSumInvariant checks that proposal FinalTallyResult must correspond to the vote option,
|
||||
// for proposals with PROPOSAL_STATUS_CLOSED status.
|
||||
func TallyVotesSumInvariant(keeper Keeper) sdk.Invariant {
|
||||
return func(ctx sdk.Context) (string, bool) {
|
||||
msg, broken := TallyVotesSumInvariantHelper(ctx, keeper.key, keeper.groupTable, keeper.proposalTable, keeper.groupMemberTable, keeper.voteByProposalIndex, keeper.groupPolicyTable)
|
||||
msg, broken := TallyVotesSumInvariantHelper(ctx, keeper.key, keeper.proposalTable, keeper.groupMemberTable, keeper.voteByProposalIndex, keeper.groupPolicyTable)
|
||||
return sdk.FormatInvariant(group.ModuleName, votesSumInvariant, msg), broken
|
||||
}
|
||||
}
|
||||
|
||||
func TallyVotesInvariantHelper(ctx sdk.Context, prevCtx sdk.Context, key storetypes.StoreKey, proposalTable orm.AutoUInt64Table) (string, bool) {
|
||||
|
||||
var msg string
|
||||
var broken bool
|
||||
|
||||
prevIt, err := proposalTable.PrefixScan(prevCtx.KVStore(key), 1, math.MaxUint64)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("PrefixScan failure on proposal table at block height %d\n%v\n", prevCtx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
curIt, err := proposalTable.PrefixScan(ctx.KVStore(key), 1, math.MaxUint64)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("PrefixScan failure on proposal table at block height %d\n%v\n", ctx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
var curProposals []*group.Proposal
|
||||
_, err = orm.ReadAll(curIt, &curProposals)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting all the proposals at block height %d\n%v\n", ctx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
var prevProposals []*group.Proposal
|
||||
_, err = orm.ReadAll(prevIt, &prevProposals)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting all the proposals at block height %d\n%v\n", prevCtx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
for i := 0; i < len(prevProposals); i++ {
|
||||
if prevProposals[i].Id == curProposals[i].Id {
|
||||
prevYesCount, err := prevProposals[i].FinalTallyResult.GetYesCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting yes votes weight of proposal at block height %d\n%v\n", prevCtx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
curYesCount, err := curProposals[i].FinalTallyResult.GetYesCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting yes votes weight of proposal at block height %d\n%v\n", ctx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
prevNoCount, err := prevProposals[i].FinalTallyResult.GetNoCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting no votes weight of proposal at block height %d\n%v\n", prevCtx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
curNoCount, err := curProposals[i].FinalTallyResult.GetNoCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting no votes weight of proposal at block height %d\n%v\n", ctx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
prevAbstainCount, err := prevProposals[i].FinalTallyResult.GetAbstainCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting abstain votes weight of proposal at block height %d\n%v\n", prevCtx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
curAbstainCount, err := curProposals[i].FinalTallyResult.GetAbstainCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting abstain votes weight of proposal at block height %d\n%v\n", ctx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
prevVetoCount, err := prevProposals[i].FinalTallyResult.GetNoWithVetoCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting veto votes weight of proposal at block height %d\n%v\n", prevCtx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
curVetoCount, err := curProposals[i].FinalTallyResult.GetNoWithVetoCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting veto votes weight of proposal at block height %d\n%v\n", ctx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
if (curYesCount.Cmp(prevYesCount) == -1) || (curNoCount.Cmp(prevNoCount) == -1) || (curAbstainCount.Cmp(prevAbstainCount) == -1) || (curVetoCount.Cmp(prevVetoCount) == -1) {
|
||||
broken = true
|
||||
msg += "vote tally sums must never have less than the block before\n"
|
||||
return msg, broken
|
||||
}
|
||||
}
|
||||
}
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
func GroupTotalWeightInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, groupTable orm.AutoUInt64Table, groupMemberByGroupIndex orm.Index) (string, bool) {
|
||||
|
||||
var msg string
|
||||
var broken bool
|
||||
|
||||
var groupInfo group.GroupInfo
|
||||
var groupMember group.GroupMember
|
||||
|
||||
groupIt, err := groupTable.PrefixScan(ctx.KVStore(key), 1, math.MaxUint64)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("PrefixScan failure on group table\n%v\n", err)
|
||||
|
@ -158,10 +59,16 @@ func GroupTotalWeightInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, g
|
|||
msg += fmt.Sprintf("error while parsing positive dec zero for group member\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
var groupInfo group.GroupInfo
|
||||
_, err = groupIt.LoadNext(&groupInfo)
|
||||
if errors.ErrORMIteratorDone.Is(err) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("LoadNext failure on group table iterator\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
memIt, err := groupMemberByGroupIndex.Get(ctx.KVStore(key), groupInfo.Id)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while returning group member iterator for group with ID %d\n%v\n", groupInfo.Id, err)
|
||||
|
@ -170,10 +77,16 @@ func GroupTotalWeightInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, g
|
|||
defer memIt.Close()
|
||||
|
||||
for {
|
||||
var groupMember group.GroupMember
|
||||
_, err = memIt.LoadNext(&groupMember)
|
||||
if errors.ErrORMIteratorDone.Is(err) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("LoadNext failure on member table iterator\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
curMemWeight, err := groupmath.NewNonNegativeDecFromString(groupMember.GetMember().GetWeight())
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while parsing non-nengative decimal for group member %s\n%v\n", groupMember.Member.Address, err)
|
||||
|
@ -201,25 +114,32 @@ func GroupTotalWeightInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, g
|
|||
return msg, broken
|
||||
}
|
||||
|
||||
func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, groupTable orm.AutoUInt64Table, proposalTable orm.AutoUInt64Table, groupMemberTable orm.PrimaryKeyTable, voteByProposalIndex orm.Index, groupPolicyTable orm.PrimaryKeyTable) (string, bool) {
|
||||
func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, proposalTable orm.AutoUInt64Table, groupMemberTable orm.PrimaryKeyTable, voteByProposalIndex orm.Index, groupPolicyTable orm.PrimaryKeyTable) (string, bool) {
|
||||
var msg string
|
||||
var broken bool
|
||||
|
||||
var groupInfo group.GroupInfo
|
||||
var proposal group.Proposal
|
||||
var groupPolicy group.GroupPolicyInfo
|
||||
var groupMem group.GroupMember
|
||||
var vote group.Vote
|
||||
|
||||
proposalIt, err := proposalTable.PrefixScan(ctx.KVStore(key), 1, math.MaxUint64)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
msg += fmt.Sprintf("PrefixScan failure on proposal table\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
defer proposalIt.Close()
|
||||
|
||||
for {
|
||||
var proposal group.Proposal
|
||||
_, err = proposalIt.LoadNext(&proposal)
|
||||
if errors.ErrORMIteratorDone.Is(err) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("LoadNext failure on proposal table iterator\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
// Only look at proposals that are closed, i.e. for which FinalTallyResult has been computed
|
||||
if proposal.Status != group.PROPOSAL_STATUS_CLOSED {
|
||||
continue
|
||||
}
|
||||
|
||||
totalVotingWeight, err := groupmath.NewNonNegativeDecFromString("0")
|
||||
if err != nil {
|
||||
|
@ -247,11 +167,7 @@ func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, grou
|
|||
return msg, broken
|
||||
}
|
||||
|
||||
_, err = proposalIt.LoadNext(&proposal)
|
||||
if errors.ErrORMIteratorDone.Is(err) {
|
||||
break
|
||||
}
|
||||
|
||||
var groupPolicy group.GroupPolicyInfo
|
||||
err = groupPolicyTable.GetOne(ctx.KVStore(key), orm.PrimaryKey(&group.GroupPolicyInfo{Address: proposal.Address}), &groupPolicy)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("group policy not found for address: %s\n%v\n", proposal.Address, err)
|
||||
|
@ -263,29 +179,25 @@ func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, grou
|
|||
return msg, broken
|
||||
}
|
||||
|
||||
_, err = groupTable.GetOne(ctx.KVStore(key), groupPolicy.GroupId, &groupInfo)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("group info not found for group id %d\n%v\n", groupPolicy.GroupId, err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
if groupInfo.Version != proposal.GroupVersion {
|
||||
msg += fmt.Sprintf("group with id %d was modified\n", groupInfo.Id)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
voteIt, err := voteByProposalIndex.Get(ctx.KVStore(key), proposal.Id)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while returning vote iterator for proposal with ID %d\n%v\n", proposal.Id, err)
|
||||
return msg, broken
|
||||
}
|
||||
defer voteIt.Close()
|
||||
|
||||
for {
|
||||
var vote group.Vote
|
||||
_, err := voteIt.LoadNext(&vote)
|
||||
if errors.ErrORMIteratorDone.Is(err) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("LoadNext failure on voteByProposalIndex index iterator\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
var groupMem group.GroupMember
|
||||
err = groupMemberTable.GetOne(ctx.KVStore(key), orm.PrimaryKey(&group.GroupMember{GroupId: groupPolicy.GroupId, Member: &group.Member{Address: vote.Voter}}), &groupMem)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("group member not found with group ID %d and group member %s\n%v\n", groupPolicy.GroupId, vote.Voter, err)
|
||||
|
@ -330,7 +242,6 @@ func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, grou
|
|||
}
|
||||
}
|
||||
}
|
||||
voteIt.Close()
|
||||
|
||||
totalProposalVotes, err := proposal.FinalTallyResult.TotalCounts()
|
||||
if err != nil {
|
||||
|
@ -366,7 +277,7 @@ func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, grou
|
|||
|
||||
if (yesVoteWeight.Cmp(proposalYesCount) != 0) || (noVoteWeight.Cmp(proposalNoCount) != 0) || (abstainVoteWeight.Cmp(proposalAbstainCount) != 0) || (vetoVoteWeight.Cmp(proposalVetoCount) != 0) {
|
||||
broken = true
|
||||
msg += fmt.Sprintf("proposal FinalTallyResult must correspond to the vote option\nProposal with ID %d and voter address %s must correspond to the vote option\n", proposal.Id, vote.Voter)
|
||||
msg += fmt.Sprintf("proposal FinalTallyResult must correspond to the sum of all votes\nProposal with ID %d FinalTallyResult must correspond to the sum of all votes\n", proposal.Id)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,189 +51,6 @@ func (s *invariantTestSuite) SetupSuite() {
|
|||
|
||||
}
|
||||
|
||||
func (s *invariantTestSuite) TestTallyVotesInvariant() {
|
||||
sdkCtx, _ := s.ctx.CacheContext()
|
||||
curCtx, cdc, key := sdkCtx, s.cdc, s.key
|
||||
prevCtx, _ := curCtx.CacheContext()
|
||||
prevCtx = prevCtx.WithBlockHeight(curCtx.BlockHeight() - 1)
|
||||
|
||||
// Proposal Table
|
||||
proposalTable, err := orm.NewAutoUInt64Table([2]byte{keeper.ProposalTablePrefix}, keeper.ProposalTableSeqPrefix, &group.Proposal{}, cdc)
|
||||
s.Require().NoError(err)
|
||||
|
||||
_, _, addr1 := testdata.KeyTestPubAddr()
|
||||
_, _, addr2 := testdata.KeyTestPubAddr()
|
||||
|
||||
specs := map[string]struct {
|
||||
prevProposal *group.Proposal
|
||||
curProposal *group.Proposal
|
||||
expBroken bool
|
||||
}{
|
||||
"invariant not broken": {
|
||||
prevProposal: &group.Proposal{
|
||||
Id: 1,
|
||||
Address: addr1.String(),
|
||||
Proposers: []string{addr1.String()},
|
||||
SubmitTime: prevCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupPolicyVersion: 1,
|
||||
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
||||
Result: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
FinalTallyResult: group.TallyResult{YesCount: "1", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
|
||||
VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
|
||||
curProposal: &group.Proposal{
|
||||
Id: 1,
|
||||
Address: addr2.String(),
|
||||
Proposers: []string{addr2.String()},
|
||||
SubmitTime: curCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupPolicyVersion: 1,
|
||||
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
||||
Result: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
FinalTallyResult: group.TallyResult{YesCount: "2", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
|
||||
VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
},
|
||||
"current block yes vote count must be greater than previous block yes vote count": {
|
||||
prevProposal: &group.Proposal{
|
||||
Id: 1,
|
||||
Address: addr1.String(),
|
||||
Proposers: []string{addr1.String()},
|
||||
SubmitTime: prevCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupPolicyVersion: 1,
|
||||
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
||||
Result: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
FinalTallyResult: group.TallyResult{YesCount: "2", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
|
||||
VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
curProposal: &group.Proposal{
|
||||
Id: 1,
|
||||
Address: addr2.String(),
|
||||
Proposers: []string{addr2.String()},
|
||||
SubmitTime: curCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupPolicyVersion: 1,
|
||||
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
||||
Result: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
FinalTallyResult: group.TallyResult{YesCount: "1", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
|
||||
VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
expBroken: true,
|
||||
},
|
||||
"current block no vote count must be greater than previous block no vote count": {
|
||||
prevProposal: &group.Proposal{
|
||||
Id: 1,
|
||||
Address: addr1.String(),
|
||||
Proposers: []string{addr1.String()},
|
||||
SubmitTime: prevCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupPolicyVersion: 1,
|
||||
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
||||
Result: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "2", AbstainCount: "0", NoWithVetoCount: "0"},
|
||||
VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
curProposal: &group.Proposal{
|
||||
Id: 1,
|
||||
Address: addr2.String(),
|
||||
Proposers: []string{addr2.String()},
|
||||
SubmitTime: curCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupPolicyVersion: 1,
|
||||
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
||||
Result: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "1", AbstainCount: "0", NoWithVetoCount: "0"},
|
||||
VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
expBroken: true,
|
||||
},
|
||||
"current block abstain vote count must be greater than previous block abstain vote count": {
|
||||
prevProposal: &group.Proposal{
|
||||
Id: 1,
|
||||
Address: addr1.String(),
|
||||
Proposers: []string{addr1.String()},
|
||||
SubmitTime: prevCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupPolicyVersion: 1,
|
||||
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
||||
Result: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "2", NoWithVetoCount: "0"},
|
||||
VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
curProposal: &group.Proposal{
|
||||
Id: 1,
|
||||
Address: addr2.String(),
|
||||
Proposers: []string{addr2.String()},
|
||||
SubmitTime: curCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupPolicyVersion: 1,
|
||||
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
||||
Result: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "1", NoWithVetoCount: "0"},
|
||||
VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
expBroken: true,
|
||||
},
|
||||
"current block veto vote count must be greater than previous block veto vote count": {
|
||||
prevProposal: &group.Proposal{
|
||||
Id: 1,
|
||||
Address: addr1.String(),
|
||||
Proposers: []string{addr1.String()},
|
||||
SubmitTime: prevCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupPolicyVersion: 1,
|
||||
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
||||
Result: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "2"},
|
||||
VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
curProposal: &group.Proposal{
|
||||
Id: 1,
|
||||
Address: addr2.String(),
|
||||
Proposers: []string{addr2.String()},
|
||||
SubmitTime: curCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupPolicyVersion: 1,
|
||||
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
||||
Result: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "1"},
|
||||
VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
expBroken: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, spec := range specs {
|
||||
|
||||
prevProposal := spec.prevProposal
|
||||
curProposal := spec.curProposal
|
||||
|
||||
cachePrevCtx, _ := prevCtx.CacheContext()
|
||||
cacheCurCtx, _ := curCtx.CacheContext()
|
||||
|
||||
_, err = proposalTable.Create(cachePrevCtx.KVStore(key), prevProposal)
|
||||
s.Require().NoError(err)
|
||||
_, err = proposalTable.Create(cacheCurCtx.KVStore(key), curProposal)
|
||||
s.Require().NoError(err)
|
||||
|
||||
_, broken := keeper.TallyVotesInvariantHelper(cacheCurCtx, cachePrevCtx, key, *proposalTable)
|
||||
s.Require().Equal(spec.expBroken, broken)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *invariantTestSuite) TestGroupTotalWeightInvariant() {
|
||||
sdkCtx, _ := s.ctx.CacheContext()
|
||||
curCtx, cdc, key := sdkCtx, s.cdc, s.key
|
||||
|
@ -365,6 +182,8 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
|
|||
_, _, addr1 := testdata.KeyTestPubAddr()
|
||||
_, _, addr2 := testdata.KeyTestPubAddr()
|
||||
|
||||
votingPeriodEnd := curCtx.BlockTime().Add(time.Second * 600)
|
||||
|
||||
specs := map[string]struct {
|
||||
groupsInfo *group.GroupInfo
|
||||
groupPolicy *group.GroupPolicyInfo
|
||||
|
@ -409,10 +228,10 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
|
|||
SubmitTime: curCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupPolicyVersion: 1,
|
||||
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
||||
Result: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
Status: group.PROPOSAL_STATUS_CLOSED,
|
||||
Result: group.PROPOSAL_RESULT_ACCEPTED,
|
||||
FinalTallyResult: group.TallyResult{YesCount: "4", NoCount: "3", AbstainCount: "0", NoWithVetoCount: "0"},
|
||||
VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
|
||||
VotingPeriodEnd: votingPeriodEnd,
|
||||
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
votes: []*group.Vote{
|
||||
|
@ -431,6 +250,58 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
|
|||
},
|
||||
expBroken: false,
|
||||
},
|
||||
"proposal not closed ignored": {
|
||||
groupsInfo: &group.GroupInfo{
|
||||
Id: 1,
|
||||
Admin: adminAddr.String(),
|
||||
Version: 1,
|
||||
TotalWeight: "7",
|
||||
},
|
||||
groupPolicy: &group.GroupPolicyInfo{
|
||||
Address: addr1.String(),
|
||||
GroupId: 1,
|
||||
Admin: adminAddr.String(),
|
||||
Version: 1,
|
||||
},
|
||||
groupMembers: []*group.GroupMember{
|
||||
{
|
||||
GroupId: 1,
|
||||
Member: &group.Member{
|
||||
Address: addr1.String(),
|
||||
Weight: "4",
|
||||
},
|
||||
},
|
||||
{
|
||||
GroupId: 1,
|
||||
Member: &group.Member{
|
||||
Address: addr2.String(),
|
||||
Weight: "3",
|
||||
},
|
||||
},
|
||||
},
|
||||
proposal: &group.Proposal{
|
||||
Id: 1,
|
||||
Address: addr1.String(),
|
||||
Proposers: []string{addr1.String()},
|
||||
SubmitTime: curCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupPolicyVersion: 1,
|
||||
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
||||
Result: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
|
||||
VotingPeriodEnd: votingPeriodEnd,
|
||||
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
votes: []*group.Vote{
|
||||
{
|
||||
ProposalId: 1,
|
||||
Voter: addr1.String(),
|
||||
Option: group.VOTE_OPTION_YES,
|
||||
SubmitTime: curCtx.BlockTime(),
|
||||
},
|
||||
},
|
||||
expBroken: false,
|
||||
},
|
||||
"proposal tally must correspond to the sum of vote weights": {
|
||||
groupsInfo: &group.GroupInfo{
|
||||
Id: 1,
|
||||
|
@ -467,10 +338,10 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
|
|||
SubmitTime: curCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupPolicyVersion: 1,
|
||||
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
||||
Result: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
Status: group.PROPOSAL_STATUS_CLOSED,
|
||||
Result: group.PROPOSAL_RESULT_ACCEPTED,
|
||||
FinalTallyResult: group.TallyResult{YesCount: "6", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
|
||||
VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
|
||||
VotingPeriodEnd: votingPeriodEnd,
|
||||
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
votes: []*group.Vote{
|
||||
|
@ -525,10 +396,10 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
|
|||
SubmitTime: curCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupPolicyVersion: 1,
|
||||
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
||||
Result: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
Status: group.PROPOSAL_STATUS_CLOSED,
|
||||
Result: group.PROPOSAL_RESULT_ACCEPTED,
|
||||
FinalTallyResult: group.TallyResult{YesCount: "4", NoCount: "3", AbstainCount: "0", NoWithVetoCount: "0"},
|
||||
VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
|
||||
VotingPeriodEnd: votingPeriodEnd,
|
||||
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
votes: []*group.Vote{
|
||||
|
@ -578,7 +449,7 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
|
|||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
_, broken := keeper.TallyVotesSumInvariantHelper(cacheCurCtx, key, *groupTable, *proposalTable, *groupMemberTable, voteByProposalIndex, *groupPolicyTable)
|
||||
_, broken := keeper.TallyVotesSumInvariantHelper(cacheCurCtx, key, *proposalTable, *groupMemberTable, voteByProposalIndex, *groupPolicyTable)
|
||||
s.Require().Equal(spec.expBroken, broken)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,14 @@ package keeper
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
storetypes "github.com/cosmos/cosmos-sdk/store/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
authmiddleware "github.com/cosmos/cosmos-sdk/x/auth/middleware"
|
||||
"github.com/cosmos/cosmos-sdk/x/group"
|
||||
"github.com/cosmos/cosmos-sdk/x/group/errors"
|
||||
|
@ -35,8 +37,7 @@ const (
|
|||
ProposalTablePrefix byte = 0x30
|
||||
ProposalTableSeqPrefix byte = 0x31
|
||||
ProposalByGroupPolicyIndexPrefix byte = 0x32
|
||||
ProposalByProposerIndexPrefix byte = 0x33
|
||||
ProposalsByVotingPeriodEndPrefix byte = 0x34
|
||||
ProposalsByVotingPeriodEndPrefix byte = 0x33
|
||||
|
||||
// Vote Table
|
||||
VoteTablePrefix byte = 0x40
|
||||
|
@ -67,7 +68,6 @@ type Keeper struct {
|
|||
// Proposal Table
|
||||
proposalTable orm.AutoUInt64Table
|
||||
proposalByGroupPolicyIndex orm.Index
|
||||
proposalByProposerIndex orm.Index
|
||||
proposalsByVotingPeriodEnd orm.Index
|
||||
|
||||
// Vote Table
|
||||
|
@ -169,21 +169,6 @@ func NewKeeper(storeKey storetypes.StoreKey, cdc codec.Codec, router *authmiddle
|
|||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
k.proposalByProposerIndex, err = orm.NewIndex(proposalTable, ProposalByProposerIndexPrefix, func(value interface{}) ([]interface{}, error) {
|
||||
proposers := value.(*group.Proposal).Proposers
|
||||
r := make([]interface{}, len(proposers))
|
||||
for i := range proposers {
|
||||
addr, err := sdk.AccAddressFromBech32(proposers[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r[i] = addr.Bytes()
|
||||
}
|
||||
return r, nil
|
||||
}, []byte{})
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
k.proposalsByVotingPeriodEnd, err = orm.NewIndex(proposalTable, ProposalsByVotingPeriodEndPrefix, func(value interface{}) ([]interface{}, error) {
|
||||
votingPeriodEnd := value.(*group.Proposal).VotingPeriodEnd
|
||||
return []interface{}{sdk.FormatTimeBytes(votingPeriodEnd)}, nil
|
||||
|
@ -233,16 +218,14 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger {
|
|||
return ctx.Logger().With("module", fmt.Sprintf("x/%s", group.ModuleName))
|
||||
}
|
||||
|
||||
// MaxMetadataLength returns the max length of the metadata bytes field for various entities within the group module.
|
||||
func (k Keeper) MaxMetadataLength() uint64 { return k.config.MaxMetadataLen }
|
||||
|
||||
// GetGroupSequence returns the current value of the group table sequence
|
||||
func (k Keeper) GetGroupSequence(ctx sdk.Context) uint64 {
|
||||
return k.groupTable.Sequence().CurVal(ctx.KVStore(k.key))
|
||||
}
|
||||
|
||||
func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, cb func(proposal group.Proposal) (bool, error)) error {
|
||||
timeBytes := sdk.FormatTimeBytes(ctx.BlockTime())
|
||||
// iterateProposalsByVPEnd iterates over all proposals whose voting_period_end is after the `endTime` time argument.
|
||||
func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, endTime time.Time, cb func(proposal group.Proposal) (bool, error)) error {
|
||||
timeBytes := sdk.FormatTimeBytes(endTime)
|
||||
it, err := k.proposalsByVotingPeriodEnd.PrefixScan(ctx.KVStore(k.key), nil, timeBytes)
|
||||
|
||||
if err != nil {
|
||||
|
@ -250,8 +233,17 @@ func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, cb func(proposal group.
|
|||
}
|
||||
defer it.Close()
|
||||
|
||||
var proposal group.Proposal
|
||||
for {
|
||||
// Important: this following line cannot outside the for loop.
|
||||
// It seems that when one unmarshals into the same `group.Proposal`
|
||||
// reference, then gogoproto somehow "adds" the new bytes to the old
|
||||
// object for some fields. When running simulations, for proposals with
|
||||
// each 1-2 proposers, after a couple of loop iterations we got to a
|
||||
// proposal with 60k+ proposers.
|
||||
// So we're declaring a local variable that gets GCed.
|
||||
//
|
||||
// Also see `x/group/types/proposal_test.go`, TestGogoUnmarshalProposal().
|
||||
var proposal group.Proposal
|
||||
_, err := it.LoadNext(&proposal)
|
||||
if errors.ErrORMIteratorDone.Is(err) {
|
||||
break
|
||||
|
@ -272,30 +264,87 @@ func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, cb func(proposal group.
|
|||
return nil
|
||||
}
|
||||
|
||||
func (k Keeper) UpdateTallyOfVPEndProposals(ctx sdk.Context) error {
|
||||
k.iterateProposalsByVPEnd(ctx, func(proposal group.Proposal) (bool, error) {
|
||||
// pruneProposal deletes a proposal from state.
|
||||
func (k Keeper) pruneProposal(ctx sdk.Context, proposalID uint64) error {
|
||||
store := ctx.KVStore(k.key)
|
||||
|
||||
policyInfo, err := k.getGroupPolicyInfo(ctx, proposal.Address)
|
||||
err := k.proposalTable.Delete(store, proposalID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
k.Logger(ctx).Debug(fmt.Sprintf("Pruned proposal %d", proposalID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// pruneVotes prunes all votes for a proposal from state.
|
||||
func (k Keeper) pruneVotes(ctx sdk.Context, proposalID uint64) error {
|
||||
store := ctx.KVStore(k.key)
|
||||
it, err := k.voteByProposalIndex.Get(store, proposalID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer it.Close()
|
||||
|
||||
for {
|
||||
var vote group.Vote
|
||||
_, err = it.LoadNext(&vote)
|
||||
if errors.ErrORMIteratorDone.Is(err) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return true, err
|
||||
return err
|
||||
}
|
||||
|
||||
electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId)
|
||||
err = k.voteTable.Delete(store, &vote)
|
||||
if err != nil {
|
||||
return true, err
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = k.doTallyAndUpdate(ctx, &proposal, electorate, policyInfo)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PruneProposals prunes all proposals that are expired, i.e. whose
|
||||
// `voting_period + max_execution_period` is greater than the current block
|
||||
// time.
|
||||
func (k Keeper) PruneProposals(ctx sdk.Context) error {
|
||||
err := k.iterateProposalsByVPEnd(ctx, ctx.BlockTime().Add(-k.config.MaxExecutionPeriod), func(proposal group.Proposal) (bool, error) {
|
||||
err := k.pruneProposal(ctx, proposal.Id)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
if err := k.proposalTable.Update(ctx.KVStore(k.key), proposal.Id, &proposal); err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k Keeper) UpdateTallyOfVPEndProposals(ctx sdk.Context) error {
|
||||
return k.iterateProposalsByVPEnd(ctx, ctx.BlockTime(), func(proposal group.Proposal) (bool, error) {
|
||||
policyInfo, err := k.getGroupPolicyInfo(ctx, proposal.Address)
|
||||
if err != nil {
|
||||
return true, sdkerrors.Wrap(err, "group policy")
|
||||
}
|
||||
|
||||
electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId)
|
||||
if err != nil {
|
||||
return true, sdkerrors.Wrap(err, "group")
|
||||
}
|
||||
|
||||
err = k.doTallyAndUpdate(ctx, &proposal, electorate, policyInfo)
|
||||
if err != nil {
|
||||
return true, sdkerrors.Wrap(err, "doTallyAndUpdate")
|
||||
}
|
||||
|
||||
if err := k.proposalTable.Update(ctx.KVStore(k.key), proposal.Id, &proposal); err != nil {
|
||||
return true, sdkerrors.Wrap(err, "proposal update")
|
||||
}
|
||||
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1598,27 +1598,29 @@ func (s *TestSuite) TestSubmitProposal() {
|
|||
s.Require().NoError(err)
|
||||
id := res.ProposalId
|
||||
|
||||
// then all data persisted
|
||||
proposalRes, err := s.keeper.Proposal(s.ctx, &group.QueryProposalRequest{ProposalId: id})
|
||||
s.Require().NoError(err)
|
||||
proposal := proposalRes.Proposal
|
||||
if !(spec.expProposal.ExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS) {
|
||||
// then all data persisted
|
||||
proposalRes, err := s.keeper.Proposal(s.ctx, &group.QueryProposalRequest{ProposalId: id})
|
||||
s.Require().NoError(err)
|
||||
proposal := proposalRes.Proposal
|
||||
|
||||
s.Assert().Equal(spec.expProposal.Address, proposal.Address)
|
||||
s.Assert().Equal(spec.req.Metadata, proposal.Metadata)
|
||||
s.Assert().Equal(spec.req.Proposers, proposal.Proposers)
|
||||
s.Assert().Equal(s.blockTime, proposal.SubmitTime)
|
||||
s.Assert().Equal(uint64(1), proposal.GroupVersion)
|
||||
s.Assert().Equal(uint64(1), proposal.GroupPolicyVersion)
|
||||
s.Assert().Equal(spec.expProposal.Status, proposal.Status)
|
||||
s.Assert().Equal(spec.expProposal.Result, proposal.Result)
|
||||
s.Assert().Equal(spec.expProposal.FinalTallyResult, proposal.FinalTallyResult)
|
||||
s.Assert().Equal(spec.expProposal.ExecutorResult, proposal.ExecutorResult)
|
||||
s.Assert().Equal(s.blockTime.Add(time.Second), proposal.VotingPeriodEnd)
|
||||
s.Assert().Equal(spec.expProposal.Address, proposal.Address)
|
||||
s.Assert().Equal(spec.req.Metadata, proposal.Metadata)
|
||||
s.Assert().Equal(spec.req.Proposers, proposal.Proposers)
|
||||
s.Assert().Equal(s.blockTime, proposal.SubmitTime)
|
||||
s.Assert().Equal(uint64(1), proposal.GroupVersion)
|
||||
s.Assert().Equal(uint64(1), proposal.GroupPolicyVersion)
|
||||
s.Assert().Equal(spec.expProposal.Status, proposal.Status)
|
||||
s.Assert().Equal(spec.expProposal.Result, proposal.Result)
|
||||
s.Assert().Equal(spec.expProposal.FinalTallyResult, proposal.FinalTallyResult)
|
||||
s.Assert().Equal(spec.expProposal.ExecutorResult, proposal.ExecutorResult)
|
||||
s.Assert().Equal(s.blockTime.Add(time.Second), proposal.VotingPeriodEnd)
|
||||
|
||||
if spec.msgs == nil { // then empty list is ok
|
||||
s.Assert().Len(proposal.GetMsgs(), 0)
|
||||
} else {
|
||||
s.Assert().Equal(spec.msgs, proposal.GetMsgs())
|
||||
if spec.msgs == nil { // then empty list is ok
|
||||
s.Assert().Len(proposal.GetMsgs(), 0)
|
||||
} else {
|
||||
s.Assert().Equal(spec.msgs, proposal.GetMsgs())
|
||||
}
|
||||
}
|
||||
|
||||
spec.postRun(s.sdkCtx)
|
||||
|
@ -2042,47 +2044,6 @@ func (s *TestSuite) TestVote() {
|
|||
expErr: true,
|
||||
postRun: func(sdkCtx sdk.Context) {},
|
||||
},
|
||||
"with group modified": {
|
||||
req: &group.MsgVote{
|
||||
ProposalId: myProposalID,
|
||||
Voter: addr4.String(),
|
||||
Option: group.VOTE_OPTION_NO,
|
||||
},
|
||||
doBefore: func(ctx context.Context) {
|
||||
_, err = s.keeper.UpdateGroupMetadata(ctx, &group.MsgUpdateGroupMetadata{
|
||||
GroupId: myGroupID,
|
||||
Admin: addr1.String(),
|
||||
})
|
||||
s.Require().NoError(err)
|
||||
},
|
||||
expErr: true,
|
||||
postRun: func(sdkCtx sdk.Context) {},
|
||||
},
|
||||
"with policy modified": {
|
||||
req: &group.MsgVote{
|
||||
ProposalId: myProposalID,
|
||||
Voter: addr4.String(),
|
||||
Option: group.VOTE_OPTION_NO,
|
||||
},
|
||||
doBefore: func(ctx context.Context) {
|
||||
m, err := group.NewMsgUpdateGroupPolicyDecisionPolicyRequest(
|
||||
addr1,
|
||||
groupPolicy,
|
||||
&group.ThresholdDecisionPolicy{
|
||||
Threshold: "1",
|
||||
Windows: &group.DecisionPolicyWindows{
|
||||
VotingPeriod: time.Second,
|
||||
},
|
||||
},
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
_, err = s.keeper.UpdateGroupPolicyDecisionPolicy(ctx, m)
|
||||
s.Require().NoError(err)
|
||||
},
|
||||
expErr: true,
|
||||
postRun: func(sdkCtx sdk.Context) {},
|
||||
},
|
||||
}
|
||||
for msg, spec := range specs {
|
||||
spec := spec
|
||||
|
@ -2105,66 +2066,69 @@ func (s *TestSuite) TestVote() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
s.Require().NoError(err)
|
||||
// vote is stored and all data persisted
|
||||
res, err := s.keeper.VoteByProposalVoter(ctx, &group.QueryVoteByProposalVoterRequest{
|
||||
ProposalId: spec.req.ProposalId,
|
||||
Voter: spec.req.Voter,
|
||||
})
|
||||
s.Require().NoError(err)
|
||||
loaded := res.Vote
|
||||
s.Assert().Equal(spec.req.ProposalId, loaded.ProposalId)
|
||||
s.Assert().Equal(spec.req.Voter, loaded.Voter)
|
||||
s.Assert().Equal(spec.req.Option, loaded.Option)
|
||||
s.Assert().Equal(spec.req.Metadata, loaded.Metadata)
|
||||
s.Assert().Equal(s.blockTime, loaded.SubmitTime)
|
||||
|
||||
// query votes by proposal
|
||||
votesByProposalRes, err := s.keeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{
|
||||
ProposalId: spec.req.ProposalId,
|
||||
})
|
||||
s.Require().NoError(err)
|
||||
votesByProposal := votesByProposalRes.Votes
|
||||
s.Require().Equal(1, len(votesByProposal))
|
||||
vote := votesByProposal[0]
|
||||
s.Assert().Equal(spec.req.ProposalId, vote.ProposalId)
|
||||
s.Assert().Equal(spec.req.Voter, vote.Voter)
|
||||
s.Assert().Equal(spec.req.Option, vote.Option)
|
||||
s.Assert().Equal(spec.req.Metadata, vote.Metadata)
|
||||
s.Assert().Equal(s.blockTime, vote.SubmitTime)
|
||||
if !(spec.expExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS) {
|
||||
// vote is stored and all data persisted
|
||||
res, err := s.keeper.VoteByProposalVoter(ctx, &group.QueryVoteByProposalVoterRequest{
|
||||
ProposalId: spec.req.ProposalId,
|
||||
Voter: spec.req.Voter,
|
||||
})
|
||||
s.Require().NoError(err)
|
||||
loaded := res.Vote
|
||||
s.Assert().Equal(spec.req.ProposalId, loaded.ProposalId)
|
||||
s.Assert().Equal(spec.req.Voter, loaded.Voter)
|
||||
s.Assert().Equal(spec.req.Option, loaded.Option)
|
||||
s.Assert().Equal(spec.req.Metadata, loaded.Metadata)
|
||||
s.Assert().Equal(s.blockTime, loaded.SubmitTime)
|
||||
|
||||
// query votes by voter
|
||||
voter := spec.req.Voter
|
||||
votesByVoterRes, err := s.keeper.VotesByVoter(ctx, &group.QueryVotesByVoterRequest{
|
||||
Voter: voter,
|
||||
})
|
||||
s.Require().NoError(err)
|
||||
votesByVoter := votesByVoterRes.Votes
|
||||
s.Require().Equal(1, len(votesByVoter))
|
||||
s.Assert().Equal(spec.req.ProposalId, votesByVoter[0].ProposalId)
|
||||
s.Assert().Equal(voter, votesByVoter[0].Voter)
|
||||
s.Assert().Equal(spec.req.Option, votesByVoter[0].Option)
|
||||
s.Assert().Equal(spec.req.Metadata, votesByVoter[0].Metadata)
|
||||
s.Assert().Equal(s.blockTime, votesByVoter[0].SubmitTime)
|
||||
// query votes by proposal
|
||||
votesByProposalRes, err := s.keeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{
|
||||
ProposalId: spec.req.ProposalId,
|
||||
})
|
||||
s.Require().NoError(err)
|
||||
votesByProposal := votesByProposalRes.Votes
|
||||
s.Require().Equal(1, len(votesByProposal))
|
||||
vote := votesByProposal[0]
|
||||
s.Assert().Equal(spec.req.ProposalId, vote.ProposalId)
|
||||
s.Assert().Equal(spec.req.Voter, vote.Voter)
|
||||
s.Assert().Equal(spec.req.Option, vote.Option)
|
||||
s.Assert().Equal(spec.req.Metadata, vote.Metadata)
|
||||
s.Assert().Equal(s.blockTime, vote.SubmitTime)
|
||||
|
||||
proposalRes, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{
|
||||
ProposalId: spec.req.ProposalId,
|
||||
})
|
||||
s.Require().NoError(err)
|
||||
// query votes by voter
|
||||
voter := spec.req.Voter
|
||||
votesByVoterRes, err := s.keeper.VotesByVoter(ctx, &group.QueryVotesByVoterRequest{
|
||||
Voter: voter,
|
||||
})
|
||||
s.Require().NoError(err)
|
||||
votesByVoter := votesByVoterRes.Votes
|
||||
s.Require().Equal(1, len(votesByVoter))
|
||||
s.Assert().Equal(spec.req.ProposalId, votesByVoter[0].ProposalId)
|
||||
s.Assert().Equal(voter, votesByVoter[0].Voter)
|
||||
s.Assert().Equal(spec.req.Option, votesByVoter[0].Option)
|
||||
s.Assert().Equal(spec.req.Metadata, votesByVoter[0].Metadata)
|
||||
s.Assert().Equal(s.blockTime, votesByVoter[0].SubmitTime)
|
||||
|
||||
proposal := proposalRes.Proposal
|
||||
if spec.isFinal {
|
||||
s.Assert().Equal(spec.expTallyResult, proposal.FinalTallyResult)
|
||||
s.Assert().Equal(spec.expResult, proposal.Result)
|
||||
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)
|
||||
proposalRes, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{
|
||||
ProposalId: spec.req.ProposalId,
|
||||
})
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Assert().Equal(spec.expTallyResult, tallyResult)
|
||||
proposal := proposalRes.Proposal
|
||||
if spec.isFinal {
|
||||
s.Assert().Equal(spec.expTallyResult, proposal.FinalTallyResult)
|
||||
s.Assert().Equal(spec.expResult, proposal.Result)
|
||||
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)
|
||||
|
@ -2329,36 +2293,6 @@ func (s *TestSuite) TestExecProposal() {
|
|||
expProposalResult: group.PROPOSAL_RESULT_REJECTED,
|
||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
"with group modified before tally": {
|
||||
setupProposal: func(ctx context.Context) uint64 {
|
||||
myProposalID := submitProposal(ctx, s, []sdk.Msg{msgSend1}, proposers)
|
||||
|
||||
// then modify group
|
||||
_, err := s.keeper.UpdateGroupMetadata(ctx, &group.MsgUpdateGroupMetadata{
|
||||
Admin: addr1.String(),
|
||||
GroupId: s.groupID,
|
||||
})
|
||||
s.Require().NoError(err)
|
||||
return myProposalID
|
||||
},
|
||||
expProposalStatus: group.PROPOSAL_STATUS_ABORTED,
|
||||
expProposalResult: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
"with group policy modified before tally": {
|
||||
setupProposal: func(ctx context.Context) uint64 {
|
||||
myProposalID := submitProposal(ctx, s, []sdk.Msg{msgSend1}, proposers)
|
||||
_, err := s.keeper.UpdateGroupPolicyMetadata(ctx, &group.MsgUpdateGroupPolicyMetadata{
|
||||
Admin: addr1.String(),
|
||||
Address: s.groupPolicyAddr.String(),
|
||||
})
|
||||
s.Require().NoError(err)
|
||||
return myProposalID
|
||||
},
|
||||
expProposalStatus: group.PROPOSAL_STATUS_ABORTED,
|
||||
expProposalResult: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
"prevent double execution when successful": {
|
||||
setupProposal: func(ctx context.Context) uint64 {
|
||||
myProposalID := submitProposalAndVote(ctx, s, []sdk.Msg{msgSend1}, proposers, group.VOTE_OPTION_YES)
|
||||
|
@ -2367,6 +2301,7 @@ func (s *TestSuite) TestExecProposal() {
|
|||
s.Require().NoError(err)
|
||||
return myProposalID
|
||||
},
|
||||
expErr: true, // since proposal is pruned after a successful MsgExec
|
||||
expProposalStatus: group.PROPOSAL_STATUS_CLOSED,
|
||||
expProposalResult: group.PROPOSAL_RESULT_ACCEPTED,
|
||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
|
||||
|
@ -2419,22 +2354,25 @@ func (s *TestSuite) TestExecProposal() {
|
|||
}
|
||||
s.Require().NoError(err)
|
||||
|
||||
// and proposal is updated
|
||||
res, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID})
|
||||
s.Require().NoError(err)
|
||||
proposal := res.Proposal
|
||||
if !(spec.expExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS) {
|
||||
|
||||
exp := group.ProposalResult_name[int32(spec.expProposalResult)]
|
||||
got := group.ProposalResult_name[int32(proposal.Result)]
|
||||
s.Assert().Equal(exp, got)
|
||||
// and proposal is updated
|
||||
res, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID})
|
||||
s.Require().NoError(err)
|
||||
proposal := res.Proposal
|
||||
|
||||
exp = group.ProposalStatus_name[int32(spec.expProposalStatus)]
|
||||
got = group.ProposalStatus_name[int32(proposal.Status)]
|
||||
s.Assert().Equal(exp, got)
|
||||
exp := group.ProposalResult_name[int32(spec.expProposalResult)]
|
||||
got := group.ProposalResult_name[int32(proposal.Result)]
|
||||
s.Assert().Equal(exp, got)
|
||||
|
||||
exp = group.ProposalExecutorResult_name[int32(spec.expExecutorResult)]
|
||||
got = group.ProposalExecutorResult_name[int32(proposal.ExecutorResult)]
|
||||
s.Assert().Equal(exp, got)
|
||||
exp = group.ProposalStatus_name[int32(spec.expProposalStatus)]
|
||||
got = group.ProposalStatus_name[int32(proposal.Status)]
|
||||
s.Assert().Equal(exp, got)
|
||||
|
||||
exp = group.ProposalExecutorResult_name[int32(spec.expExecutorResult)]
|
||||
got = group.ProposalExecutorResult_name[int32(proposal.ExecutorResult)]
|
||||
s.Assert().Equal(exp, got)
|
||||
}
|
||||
|
||||
if spec.expBalance {
|
||||
fromBalances := s.app.BankKeeper.GetAllBalances(sdkCtx, s.groupPolicyAddr)
|
||||
|
@ -2446,6 +2384,150 @@ func (s *TestSuite) TestExecProposal() {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestExecPrunedProposalsAndVotes() {
|
||||
addrs := s.addrs
|
||||
addr1 := addrs[0]
|
||||
addr2 := addrs[1]
|
||||
|
||||
msgSend1 := &banktypes.MsgSend{
|
||||
FromAddress: s.groupPolicyAddr.String(),
|
||||
ToAddress: addr2.String(),
|
||||
Amount: sdk.Coins{sdk.NewInt64Coin("test", 100)},
|
||||
}
|
||||
msgSend2 := &banktypes.MsgSend{
|
||||
FromAddress: s.groupPolicyAddr.String(),
|
||||
ToAddress: addr2.String(),
|
||||
Amount: sdk.Coins{sdk.NewInt64Coin("test", 10001)},
|
||||
}
|
||||
proposers := []string{addr2.String()}
|
||||
specs := map[string]struct {
|
||||
srcBlockTime time.Time
|
||||
setupProposal func(ctx context.Context) uint64
|
||||
expErr bool
|
||||
expErrMsg string
|
||||
expExecutorResult group.ProposalExecutorResult
|
||||
}{
|
||||
"proposal pruned after executor result success": {
|
||||
setupProposal: func(ctx context.Context) uint64 {
|
||||
msgs := []sdk.Msg{msgSend1}
|
||||
return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_YES)
|
||||
},
|
||||
expErrMsg: "load proposal: not found",
|
||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
|
||||
},
|
||||
"proposal with multiple messages pruned when executed with result success": {
|
||||
setupProposal: func(ctx context.Context) uint64 {
|
||||
msgs := []sdk.Msg{msgSend1, msgSend1}
|
||||
return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_YES)
|
||||
},
|
||||
expErrMsg: "load proposal: not found",
|
||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
|
||||
},
|
||||
"proposal not pruned when not executed and rejected": {
|
||||
setupProposal: func(ctx context.Context) uint64 {
|
||||
msgs := []sdk.Msg{msgSend1}
|
||||
return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_NO)
|
||||
},
|
||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
"open proposal is not pruned which must not fail ": {
|
||||
setupProposal: func(ctx context.Context) uint64 {
|
||||
return submitProposal(ctx, s, []sdk.Msg{msgSend1}, proposers)
|
||||
},
|
||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
"proposal not pruned with group modified before tally": {
|
||||
setupProposal: func(ctx context.Context) uint64 {
|
||||
myProposalID := submitProposal(ctx, s, []sdk.Msg{msgSend1}, proposers)
|
||||
|
||||
// then modify group
|
||||
_, err := s.keeper.UpdateGroupMetadata(ctx, &group.MsgUpdateGroupMetadata{
|
||||
Admin: addr1.String(),
|
||||
GroupId: s.groupID,
|
||||
})
|
||||
s.Require().NoError(err)
|
||||
return myProposalID
|
||||
},
|
||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
"proposal not pruned with group policy modified before tally": {
|
||||
setupProposal: func(ctx context.Context) uint64 {
|
||||
myProposalID := submitProposal(ctx, s, []sdk.Msg{msgSend1}, proposers)
|
||||
_, err := s.keeper.UpdateGroupPolicyMetadata(ctx, &group.MsgUpdateGroupPolicyMetadata{
|
||||
Admin: addr1.String(),
|
||||
Address: s.groupPolicyAddr.String(),
|
||||
})
|
||||
s.Require().NoError(err)
|
||||
return myProposalID
|
||||
},
|
||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
"proposal exists when rollback all msg updates on failure": {
|
||||
setupProposal: func(ctx context.Context) uint64 {
|
||||
msgs := []sdk.Msg{msgSend1, msgSend2}
|
||||
return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_YES)
|
||||
},
|
||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_FAILURE,
|
||||
},
|
||||
"pruned when proposal is executable when failed before": {
|
||||
setupProposal: func(ctx context.Context) uint64 {
|
||||
msgs := []sdk.Msg{msgSend2}
|
||||
myProposalID := submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_YES)
|
||||
|
||||
_, err := s.keeper.Exec(ctx, &group.MsgExec{Signer: addr1.String(), ProposalId: myProposalID})
|
||||
s.Require().NoError(err)
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
s.Require().NoError(testutil.FundAccount(s.app.BankKeeper, sdkCtx, s.groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10002)}))
|
||||
|
||||
return myProposalID
|
||||
},
|
||||
expErrMsg: "load proposal: not found",
|
||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
|
||||
},
|
||||
}
|
||||
for msg, spec := range specs {
|
||||
spec := spec
|
||||
s.Run(msg, func() {
|
||||
sdkCtx, _ := s.sdkCtx.CacheContext()
|
||||
ctx := sdk.WrapSDKContext(sdkCtx)
|
||||
proposalID := spec.setupProposal(ctx)
|
||||
|
||||
if !spec.srcBlockTime.IsZero() {
|
||||
sdkCtx = sdkCtx.WithBlockTime(spec.srcBlockTime)
|
||||
}
|
||||
|
||||
ctx = sdk.WrapSDKContext(sdkCtx)
|
||||
_, err := s.keeper.Exec(ctx, &group.MsgExec{Signer: addr1.String(), ProposalId: proposalID})
|
||||
if spec.expErr {
|
||||
s.Require().Error(err)
|
||||
return
|
||||
}
|
||||
s.Require().NoError(err)
|
||||
|
||||
if spec.expExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS {
|
||||
// Make sure proposal is deleted from state
|
||||
_, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID})
|
||||
s.Require().Contains(err.Error(), spec.expErrMsg)
|
||||
res, err := s.keeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{ProposalId: proposalID})
|
||||
s.Require().NoError(err)
|
||||
s.Require().Empty(res.GetVotes())
|
||||
|
||||
} else {
|
||||
// Check that proposal and votes exists
|
||||
res, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID})
|
||||
s.Require().NoError(err)
|
||||
_, err = s.keeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{ProposalId: res.Proposal.Id})
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal("", spec.expErrMsg)
|
||||
|
||||
exp := group.ProposalExecutorResult_name[int32(spec.expExecutorResult)]
|
||||
got := group.ProposalExecutorResult_name[int32(res.Proposal.ExecutorResult)]
|
||||
s.Assert().Equal(exp, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuite) TestProposalsByVPEnd() {
|
||||
addrs := s.addrs
|
||||
addr2 := addrs[1]
|
||||
|
|
|
@ -637,22 +637,14 @@ func (k Keeper) Vote(goCtx context.Context, req *group.MsgVote) (*group.MsgVoteR
|
|||
|
||||
var policyInfo group.GroupPolicyInfo
|
||||
|
||||
// Ensure that group policy hasn't been modified since the proposal submission.
|
||||
if policyInfo, err = k.getGroupPolicyInfo(ctx, proposal.Address); err != nil {
|
||||
return nil, sdkerrors.Wrap(err, "load group policy")
|
||||
}
|
||||
if proposal.GroupPolicyVersion != policyInfo.Version {
|
||||
return nil, sdkerrors.Wrap(errors.ErrModified, "group policy was modified")
|
||||
}
|
||||
|
||||
// Ensure that group hasn't been modified since the proposal submission.
|
||||
electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if electorate.Version != proposal.GroupVersion {
|
||||
return nil, sdkerrors.Wrap(errors.ErrModified, "group was modified")
|
||||
}
|
||||
|
||||
// Count and store votes.
|
||||
voterAddr := req.Voter
|
||||
|
@ -711,17 +703,22 @@ func (k Keeper) doTallyAndUpdate(ctx sdk.Context, p *group.Proposal, electorate
|
|||
return err
|
||||
}
|
||||
|
||||
switch result, err := policy.Allow(tallyResult, electorate.TotalWeight, ctx.BlockTime().Sub(submittedAt)); {
|
||||
result, err := policy.Allow(tallyResult, electorate.TotalWeight, ctx.BlockTime().Sub(submittedAt))
|
||||
switch {
|
||||
case err != nil:
|
||||
return sdkerrors.Wrap(err, "policy execution")
|
||||
case result.Allow && result.Final:
|
||||
return sdkerrors.Wrap(err, "policy allow")
|
||||
case result.Final:
|
||||
if err := k.pruneVotes(ctx, p.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
p.FinalTallyResult = tallyResult
|
||||
p.Result = group.PROPOSAL_RESULT_ACCEPTED
|
||||
p.Status = group.PROPOSAL_STATUS_CLOSED
|
||||
case !result.Allow && result.Final:
|
||||
p.FinalTallyResult = tallyResult
|
||||
p.Result = group.PROPOSAL_RESULT_REJECTED
|
||||
p.Status = group.PROPOSAL_STATUS_CLOSED
|
||||
if result.Allow {
|
||||
p.Result = group.PROPOSAL_RESULT_ACCEPTED
|
||||
p.Status = group.PROPOSAL_STATUS_CLOSED
|
||||
} else {
|
||||
p.Result = group.PROPOSAL_RESULT_REJECTED
|
||||
p.Status = group.PROPOSAL_STATUS_CLOSED
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -747,32 +744,28 @@ func (k Keeper) Exec(goCtx context.Context, req *group.MsgExec) (*group.MsgExecR
|
|||
}
|
||||
|
||||
storeUpdates := func() (*group.MsgExecResponse, error) {
|
||||
if err := k.proposalTable.Update(ctx.KVStore(k.key), id, &proposal); err != nil {
|
||||
return nil, err
|
||||
store := ctx.KVStore(k.key)
|
||||
|
||||
// If proposal has successfully run, delete it from state.
|
||||
if proposal.ExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS {
|
||||
if err := k.pruneProposal(ctx, proposal.Id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := k.proposalTable.Update(store, id, &proposal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &group.MsgExecResponse{}, nil
|
||||
}
|
||||
|
||||
if proposal.Status == group.PROPOSAL_STATUS_SUBMITTED {
|
||||
// Ensure that group policy hasn't been modified before tally.
|
||||
if proposal.GroupPolicyVersion != policyInfo.Version {
|
||||
proposal.Result = group.PROPOSAL_RESULT_UNFINALIZED
|
||||
proposal.Status = group.PROPOSAL_STATUS_ABORTED
|
||||
return storeUpdates()
|
||||
}
|
||||
|
||||
electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(err, "load group")
|
||||
}
|
||||
|
||||
// Ensure that group hasn't been modified before tally.
|
||||
if electorate.Version != proposal.GroupVersion {
|
||||
proposal.Result = group.PROPOSAL_RESULT_UNFINALIZED
|
||||
proposal.Status = group.PROPOSAL_STATUS_ABORTED
|
||||
return storeUpdates()
|
||||
}
|
||||
|
||||
if err := k.doTallyAndUpdate(ctx, &proposal, electorate, policyInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -805,7 +798,10 @@ func (k Keeper) Exec(goCtx context.Context, req *group.MsgExec) (*group.MsgExecR
|
|||
return nil, err
|
||||
}
|
||||
|
||||
err = ctx.EventManager().EmitTypedEvent(&group.EventExec{ProposalId: id})
|
||||
err = ctx.EventManager().EmitTypedEvent(&group.EventExec{
|
||||
ProposalId: id,
|
||||
Result: proposal.ExecutorResult,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -983,8 +979,8 @@ func (k Keeper) validateDecisionPolicies(ctx sdk.Context, g group.GroupInfo) err
|
|||
}
|
||||
defer it.Close()
|
||||
|
||||
var groupPolicy group.GroupPolicyInfo
|
||||
for {
|
||||
var groupPolicy group.GroupPolicyInfo
|
||||
_, err = it.LoadNext(&groupPolicy)
|
||||
if errors.ErrORMIteratorDone.Is(err) {
|
||||
break
|
||||
|
|
|
@ -25,8 +25,8 @@ func (q Keeper) Tally(ctx sdk.Context, p group.Proposal, groupId uint64) (group.
|
|||
|
||||
tallyResult := group.DefaultTallyResult()
|
||||
|
||||
var vote group.Vote
|
||||
for {
|
||||
var vote group.Vote
|
||||
_, err = it.LoadNext(&vote)
|
||||
if errors.ErrORMIteratorDone.Is(err) {
|
||||
break
|
||||
|
|
|
@ -9,4 +9,12 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) {
|
|||
if err := k.UpdateTallyOfVPEndProposals(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pruneProposals(ctx, k)
|
||||
}
|
||||
|
||||
func pruneProposals(ctx sdk.Context, k keeper.Keeper) {
|
||||
err := k.PruneProposals(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,18 +6,196 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/simapp"
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank/testutil"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/group"
|
||||
"github.com/cosmos/cosmos-sdk/x/group/module"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
)
|
||||
|
||||
func TestEndBlockerPruning(t *testing.T) {
|
||||
app := simapp.Setup(t, false)
|
||||
ctx := app.BaseApp.NewContext(false, tmproto.Header{})
|
||||
addrs := simapp.AddTestAddrsIncremental(app, ctx, 3, sdk.NewInt(30000000))
|
||||
addr1 := addrs[0]
|
||||
addr2 := addrs[1]
|
||||
addr3 := addrs[2]
|
||||
|
||||
// Initial group, group policy and balance setup
|
||||
members := []group.Member{
|
||||
{Address: addr1.String(), Weight: "1"}, {Address: addr2.String(), Weight: "2"},
|
||||
}
|
||||
|
||||
groupRes, err := app.GroupKeeper.CreateGroup(ctx, &group.MsgCreateGroup{
|
||||
Admin: addr1.String(),
|
||||
Members: members,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
groupID := groupRes.GroupId
|
||||
|
||||
policy := group.NewThresholdDecisionPolicy(
|
||||
"2",
|
||||
time.Second,
|
||||
0,
|
||||
)
|
||||
|
||||
policyReq := &group.MsgCreateGroupPolicy{
|
||||
Admin: addr1.String(),
|
||||
GroupId: groupID,
|
||||
}
|
||||
|
||||
err = policyReq.SetDecisionPolicy(policy)
|
||||
require.NoError(t, err)
|
||||
policyRes, err := app.GroupKeeper.CreateGroupPolicy(ctx, policyReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
groupPolicyAddr, err := sdk.AccAddressFromBech32(policyRes.Address)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, testutil.FundAccount(app.BankKeeper, ctx, groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10000)}))
|
||||
|
||||
msgSend1 := &banktypes.MsgSend{
|
||||
FromAddress: groupPolicyAddr.String(),
|
||||
ToAddress: addr2.String(),
|
||||
Amount: sdk.Coins{sdk.NewInt64Coin("test", 100)},
|
||||
}
|
||||
proposers := []string{addr2.String()}
|
||||
|
||||
specs := map[string]struct {
|
||||
srcBlockTime time.Time
|
||||
setupProposal func(ctx context.Context) uint64
|
||||
expErr bool
|
||||
expErrMsg string
|
||||
expExecutorResult group.ProposalExecutorResult
|
||||
}{
|
||||
"proposal pruned after executor result success": {
|
||||
setupProposal: func(ctx context.Context) uint64 {
|
||||
msgs := []sdk.Msg{msgSend1}
|
||||
pID, err := submitProposalAndVote(app, ctx, msgs, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
|
||||
require.NoError(t, err)
|
||||
_, err = app.GroupKeeper.Exec(ctx, &group.MsgExec{Signer: addr3.String(), ProposalId: pID})
|
||||
require.NoError(t, err)
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
require.NoError(t, testutil.FundAccount(app.BankKeeper, sdkCtx, groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10002)}))
|
||||
|
||||
return pID
|
||||
},
|
||||
expErrMsg: "load proposal: not found",
|
||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
|
||||
},
|
||||
"proposal with multiple messages pruned when executed with result success": {
|
||||
setupProposal: func(ctx context.Context) uint64 {
|
||||
msgs := []sdk.Msg{msgSend1, msgSend1}
|
||||
pID, err := submitProposalAndVote(app, ctx, msgs, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
|
||||
require.NoError(t, err)
|
||||
_, err = app.GroupKeeper.Exec(ctx, &group.MsgExec{Signer: addr3.String(), ProposalId: pID})
|
||||
require.NoError(t, err)
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
require.NoError(t, testutil.FundAccount(app.BankKeeper, sdkCtx, groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10002)}))
|
||||
|
||||
return pID
|
||||
},
|
||||
expErrMsg: "load proposal: not found",
|
||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
|
||||
},
|
||||
"proposal not pruned when not executed and rejected": {
|
||||
setupProposal: func(ctx context.Context) uint64 {
|
||||
msgs := []sdk.Msg{msgSend1}
|
||||
pID, err := submitProposalAndVote(app, ctx, msgs, proposers, groupPolicyAddr, group.VOTE_OPTION_NO)
|
||||
require.NoError(t, err)
|
||||
_, err = app.GroupKeeper.Exec(ctx, &group.MsgExec{Signer: addr3.String(), ProposalId: pID})
|
||||
require.NoError(t, err)
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
require.NoError(t, testutil.FundAccount(app.BankKeeper, sdkCtx, groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10002)}))
|
||||
|
||||
return pID
|
||||
},
|
||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
"open proposal is not pruned which must not fail ": {
|
||||
setupProposal: func(ctx context.Context) uint64 {
|
||||
pID, err := submitProposal(app, ctx, []sdk.Msg{msgSend1}, proposers, groupPolicyAddr)
|
||||
require.NoError(t, err)
|
||||
_, err = app.GroupKeeper.Exec(ctx, &group.MsgExec{Signer: addr3.String(), ProposalId: pID})
|
||||
require.NoError(t, err)
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
require.NoError(t, testutil.FundAccount(app.BankKeeper, sdkCtx, groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10002)}))
|
||||
|
||||
return pID
|
||||
},
|
||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
"proposal not pruned with group policy modified before tally": {
|
||||
setupProposal: func(ctx context.Context) uint64 {
|
||||
pID, err := submitProposal(app, ctx, []sdk.Msg{msgSend1}, proposers, groupPolicyAddr)
|
||||
require.NoError(t, err)
|
||||
_, err = app.GroupKeeper.UpdateGroupPolicyMetadata(ctx, &group.MsgUpdateGroupPolicyMetadata{
|
||||
Admin: addr1.String(),
|
||||
Address: groupPolicyAddr.String(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = app.GroupKeeper.Exec(ctx, &group.MsgExec{Signer: addr3.String(), ProposalId: pID})
|
||||
require.NoError(t, err)
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
require.NoError(t, testutil.FundAccount(app.BankKeeper, sdkCtx, groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10002)}))
|
||||
|
||||
return pID
|
||||
},
|
||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||
},
|
||||
"pruned when proposal is executable when failed before": {
|
||||
setupProposal: func(ctx context.Context) uint64 {
|
||||
msgs := []sdk.Msg{msgSend1}
|
||||
pID, err := submitProposalAndVote(app, ctx, msgs, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
|
||||
require.NoError(t, err)
|
||||
_, err = app.GroupKeeper.Exec(ctx, &group.MsgExec{Signer: addrs[2].String(), ProposalId: pID})
|
||||
require.NoError(t, err)
|
||||
return pID
|
||||
},
|
||||
expErrMsg: "load proposal: not found",
|
||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
|
||||
},
|
||||
}
|
||||
for msg, spec := range specs {
|
||||
spec := spec
|
||||
t.Run(msg, func(t *testing.T) {
|
||||
proposalID := spec.setupProposal(ctx)
|
||||
|
||||
module.EndBlocker(ctx, app.GroupKeeper)
|
||||
|
||||
if spec.expExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS {
|
||||
// Make sure proposal is deleted from state
|
||||
_, err = app.GroupKeeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID})
|
||||
require.Contains(t, err.Error(), spec.expErrMsg)
|
||||
res, err := app.GroupKeeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{ProposalId: proposalID})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, res.GetVotes())
|
||||
} else {
|
||||
// Check that proposal and votes exists
|
||||
res, err := app.GroupKeeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID})
|
||||
require.NoError(t, err)
|
||||
_, err = app.GroupKeeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{ProposalId: res.Proposal.Id})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "", spec.expErrMsg)
|
||||
|
||||
exp := group.ProposalExecutorResult_name[int32(spec.expExecutorResult)]
|
||||
got := group.ProposalExecutorResult_name[int32(res.Proposal.ExecutorResult)]
|
||||
assert.Equal(t, exp, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEndBlocker(t *testing.T) {
|
||||
app := simapp.Setup(t, false)
|
||||
ctx := app.BaseApp.NewContext(false, tmproto.Header{})
|
||||
addrs := simapp.AddTestAddrsIncremental(app, ctx, 4, types.NewInt(30000000))
|
||||
|
||||
addrs := simapp.AddTestAddrsIncremental(app, ctx, 4, sdk.NewInt(30000000))
|
||||
|
||||
// Initial group, group policy and balance setup
|
||||
members := []group.Member{
|
||||
|
@ -48,45 +226,44 @@ func TestEndBlocker(t *testing.T) {
|
|||
policyRes, err := app.GroupKeeper.CreateGroupPolicy(ctx, policyReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
groupPolicyAddr, err := types.AccAddressFromBech32(policyRes.Address)
|
||||
groupPolicyAddr, err := sdk.AccAddressFromBech32(policyRes.Address)
|
||||
require.NoError(t, err)
|
||||
|
||||
votingPeriod := policy.GetVotingPeriod()
|
||||
now := time.Now()
|
||||
|
||||
msgSend := &banktypes.MsgSend{
|
||||
FromAddress: groupPolicyAddr.String(),
|
||||
ToAddress: addrs[3].String(),
|
||||
Amount: types.Coins{types.NewInt64Coin("test", 100)},
|
||||
Amount: sdk.Coins{sdk.NewInt64Coin("test", 100)},
|
||||
}
|
||||
|
||||
proposers := []string{addrs[2].String()}
|
||||
|
||||
specs := map[string]struct {
|
||||
preRun func(sdkCtx types.Context) uint64
|
||||
preRun func(sdkCtx sdk.Context) uint64
|
||||
proposalId uint64
|
||||
admin string
|
||||
expErrMsg string
|
||||
newCtx types.Context
|
||||
newCtx sdk.Context
|
||||
tallyRes group.TallyResult
|
||||
expStatus group.ProposalStatus
|
||||
expExecutorResult group.ProposalResult
|
||||
}{
|
||||
"tally updated after voting power end": {
|
||||
preRun: func(sdkCtx types.Context) uint64 {
|
||||
pId, err := submitProposal(app, sdkCtx, []types.Msg{msgSend}, proposers, groupPolicyAddr)
|
||||
preRun: func(sdkCtx sdk.Context) uint64 {
|
||||
pId, err := submitProposal(app, sdkCtx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr)
|
||||
require.NoError(t, err)
|
||||
return pId
|
||||
},
|
||||
admin: proposers[0],
|
||||
newCtx: ctx.WithBlockTime(now.Add(votingPeriod).Add(time.Hour)),
|
||||
newCtx: ctx.WithBlockTime(ctx.BlockTime().Add(votingPeriod).Add(time.Hour)),
|
||||
tallyRes: group.DefaultTallyResult(),
|
||||
expStatus: group.PROPOSAL_STATUS_SUBMITTED,
|
||||
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
},
|
||||
"tally within voting period": {
|
||||
preRun: func(sdkCtx types.Context) uint64 {
|
||||
pId, err := submitProposal(app, sdkCtx, []types.Msg{msgSend}, proposers, groupPolicyAddr)
|
||||
preRun: func(sdkCtx sdk.Context) uint64 {
|
||||
pId, err := submitProposal(app, sdkCtx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
return pId
|
||||
|
@ -98,8 +275,8 @@ func TestEndBlocker(t *testing.T) {
|
|||
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
},
|
||||
"tally within voting period(with votes)": {
|
||||
preRun: func(sdkCtx types.Context) uint64 {
|
||||
pId, err := submitProposalAndVote(app, ctx, []types.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
|
||||
preRun: func(sdkCtx sdk.Context) uint64 {
|
||||
pId, err := submitProposalAndVote(app, ctx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
|
||||
require.NoError(t, err)
|
||||
|
||||
return pId
|
||||
|
@ -111,14 +288,14 @@ func TestEndBlocker(t *testing.T) {
|
|||
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
},
|
||||
"tally after voting period(with votes)": {
|
||||
preRun: func(sdkCtx types.Context) uint64 {
|
||||
pId, err := submitProposalAndVote(app, ctx, []types.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
|
||||
preRun: func(sdkCtx sdk.Context) uint64 {
|
||||
pId, err := submitProposalAndVote(app, ctx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
|
||||
require.NoError(t, err)
|
||||
|
||||
return pId
|
||||
},
|
||||
admin: proposers[0],
|
||||
newCtx: ctx.WithBlockTime(now.Add(votingPeriod).Add(time.Hour)),
|
||||
newCtx: ctx.WithBlockTime(ctx.BlockTime().Add(votingPeriod).Add(time.Hour)),
|
||||
tallyRes: group.TallyResult{
|
||||
YesCount: "2",
|
||||
NoCount: "0",
|
||||
|
@ -129,8 +306,8 @@ func TestEndBlocker(t *testing.T) {
|
|||
expExecutorResult: group.PROPOSAL_RESULT_ACCEPTED,
|
||||
},
|
||||
"tally of closed proposal": {
|
||||
preRun: func(sdkCtx types.Context) uint64 {
|
||||
pId, err := submitProposal(app, sdkCtx, []types.Msg{msgSend}, proposers, groupPolicyAddr)
|
||||
preRun: func(sdkCtx sdk.Context) uint64 {
|
||||
pId, err := submitProposal(app, sdkCtx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = app.GroupKeeper.WithdrawProposal(ctx, &group.MsgWithdrawProposal{
|
||||
|
@ -148,8 +325,8 @@ func TestEndBlocker(t *testing.T) {
|
|||
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||
},
|
||||
"tally of closed proposal (with votes)": {
|
||||
preRun: func(sdkCtx types.Context) uint64 {
|
||||
pId, err := submitProposalAndVote(app, ctx, []types.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
|
||||
preRun: func(sdkCtx sdk.Context) uint64 {
|
||||
pId, err := submitProposalAndVote(app, ctx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = app.GroupKeeper.WithdrawProposal(ctx, &group.MsgWithdrawProposal{
|
||||
|
@ -193,8 +370,8 @@ func TestEndBlocker(t *testing.T) {
|
|||
}
|
||||
|
||||
func submitProposal(
|
||||
app *simapp.SimApp, ctx context.Context, msgs []types.Msg,
|
||||
proposers []string, groupPolicyAddr types.AccAddress) (uint64, error) {
|
||||
app *simapp.SimApp, ctx context.Context, msgs []sdk.Msg,
|
||||
proposers []string, groupPolicyAddr sdk.AccAddress) (uint64, error) {
|
||||
proposalReq := &group.MsgSubmitProposal{
|
||||
Address: groupPolicyAddr.String(),
|
||||
Proposers: proposers,
|
||||
|
@ -213,13 +390,12 @@ func submitProposal(
|
|||
}
|
||||
|
||||
func submitProposalAndVote(
|
||||
app *simapp.SimApp, ctx context.Context, msgs []types.Msg,
|
||||
proposers []string, groupPolicyAddr types.AccAddress, voteOption group.VoteOption) (uint64, error) {
|
||||
app *simapp.SimApp, ctx context.Context, msgs []sdk.Msg,
|
||||
proposers []string, groupPolicyAddr sdk.AccAddress, voteOption group.VoteOption) (uint64, error) {
|
||||
myProposalID, err := submitProposal(app, ctx, msgs, proposers, groupPolicyAddr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, err = app.GroupKeeper.Vote(ctx, &group.MsgVote{
|
||||
ProposalId: myProposalID,
|
||||
Voter: proposers[0],
|
||||
|
@ -228,6 +404,5 @@ func submitProposalAndVote(
|
|||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return myProposalID, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package group_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/simapp"
|
||||
"github.com/cosmos/cosmos-sdk/x/group"
|
||||
)
|
||||
|
||||
// TestGogoUnmarshalProposal tests some weird behavior in gogoproto
|
||||
// unmarshalling.
|
||||
// This test serves as a showcase that we need to be careful when unmarshalling
|
||||
// multiple times into the same reference.
|
||||
func TestGogoUnmarshalProposal(t *testing.T) {
|
||||
cdc := simapp.MakeTestEncodingConfig().Codec
|
||||
p1 := group.Proposal{Proposers: []string{"foo"}}
|
||||
p2 := group.Proposal{Proposers: []string{"bar"}}
|
||||
|
||||
p1Bz, err := cdc.Marshal(&p1)
|
||||
require.NoError(t, err)
|
||||
p2Bz, err := cdc.Marshal(&p2)
|
||||
require.NoError(t, err)
|
||||
|
||||
var p group.Proposal
|
||||
err = cdc.Unmarshal(p1Bz, &p)
|
||||
require.NoError(t, err)
|
||||
err = cdc.Unmarshal(p2Bz, &p)
|
||||
require.NoError(t, err)
|
||||
|
||||
// One would expect that unmarshalling into the same `&p` reference would
|
||||
// clear the previous `p` value. But it seems that (at least for array
|
||||
// fields), the values are not replaced, but concatenated, which
|
||||
// is not an intuitive behavior.
|
||||
require.Len(t, p.Proposers, 2)
|
||||
}
|
|
@ -72,13 +72,13 @@ func getGroupPolicies(r *rand.Rand, simState *module.SimulationState) []*group.G
|
|||
return groupPolicies
|
||||
}
|
||||
|
||||
func getProposals(r *rand.Rand, simState *module.SimulationState) []*group.Proposal {
|
||||
func getProposals(r *rand.Rand, simState *module.SimulationState, groupPolicies []*group.GroupPolicyInfo) []*group.Proposal {
|
||||
proposals := make([]*group.Proposal, 3)
|
||||
proposers := []string{simState.Accounts[0].Address.String(), simState.Accounts[1].Address.String()}
|
||||
for i := 0; i < 3; i++ {
|
||||
from, _ := simtypes.RandomAcc(r, simState.Accounts)
|
||||
idx := r.Intn(len(groupPolicies))
|
||||
groupPolicyAddress := groupPolicies[idx].Address
|
||||
to, _ := simtypes.RandomAcc(r, simState.Accounts)
|
||||
fromAddr := from.Address.String()
|
||||
|
||||
submittedAt := time.Unix(0, 0)
|
||||
timeout := submittedAt.Add(time.Second * 1000).UTC()
|
||||
|
@ -86,7 +86,7 @@ func getProposals(r *rand.Rand, simState *module.SimulationState) []*group.Propo
|
|||
proposal := &group.Proposal{
|
||||
Id: uint64(i + 1),
|
||||
Proposers: proposers,
|
||||
Address: fromAddr,
|
||||
Address: groupPolicyAddress,
|
||||
GroupVersion: uint64(i + 1),
|
||||
GroupPolicyVersion: uint64(i + 1),
|
||||
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
||||
|
@ -103,7 +103,7 @@ func getProposals(r *rand.Rand, simState *module.SimulationState) []*group.Propo
|
|||
VotingPeriodEnd: timeout,
|
||||
}
|
||||
err := proposal.SetMsgs([]sdk.Msg{&banktypes.MsgSend{
|
||||
FromAddress: fromAddr,
|
||||
FromAddress: groupPolicyAddress,
|
||||
ToAddress: to.Address.String(),
|
||||
Amount: sdk.NewCoins(sdk.NewInt64Coin("test", 10)),
|
||||
}})
|
||||
|
@ -163,7 +163,7 @@ func RandomizedGenState(simState *module.SimulationState) {
|
|||
func(r *rand.Rand) { members = getGroupMembers(r, simState.Accounts) },
|
||||
)
|
||||
|
||||
// group accounts
|
||||
// group policies
|
||||
var groupPolicies []*group.GroupPolicyInfo
|
||||
simState.AppParams.GetOrGenerate(
|
||||
simState.Cdc, GroupPolicyInfo, &groupPolicies, simState.Rand,
|
||||
|
@ -174,7 +174,7 @@ func RandomizedGenState(simState *module.SimulationState) {
|
|||
var proposals []*group.Proposal
|
||||
simState.AppParams.GetOrGenerate(
|
||||
simState.Cdc, GroupProposals, &proposals, simState.Rand,
|
||||
func(r *rand.Rand) { proposals = getProposals(r, simState) },
|
||||
func(r *rand.Rand) { proposals = getProposals(r, simState, groupPolicies) },
|
||||
)
|
||||
|
||||
// votes
|
||||
|
|
|
@ -873,16 +873,6 @@ func SimulateMsgWithdrawProposal(ak group.AccountKeeper,
|
|||
return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "no proposals found"), nil, nil
|
||||
}
|
||||
|
||||
// Ensure that group and group policy haven't been modified since the proposal submission.
|
||||
if proposal.GroupPolicyVersion != groupPolicy.Version {
|
||||
return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "group policy has been modified"), nil, nil
|
||||
}
|
||||
|
||||
// Ensure the group hasn't been modified.
|
||||
if proposal.GroupVersion != g.Version {
|
||||
return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "group has been modified"), nil, nil
|
||||
}
|
||||
|
||||
// select a random proposer
|
||||
proposers := proposal.Proposers
|
||||
n := randIntInRange(r, len(proposers))
|
||||
|
@ -972,13 +962,11 @@ func SimulateMsgVote(ak group.AccountKeeper,
|
|||
return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "no proposals found"), nil, nil
|
||||
}
|
||||
|
||||
var proposal *group.Proposal
|
||||
proposalID := -1
|
||||
|
||||
for _, p := range proposals {
|
||||
if p.Status == group.PROPOSAL_STATUS_SUBMITTED {
|
||||
timeout := p.VotingPeriodEnd
|
||||
proposal = p
|
||||
proposalID = int(p.Id)
|
||||
if timeout.Before(sdkCtx.BlockTime()) || timeout.Equal(sdkCtx.BlockTime()) {
|
||||
return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "voting period ended: skipping"), nil, nil
|
||||
|
@ -992,14 +980,6 @@ func SimulateMsgVote(ak group.AccountKeeper,
|
|||
return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "no proposals found"), nil, nil
|
||||
}
|
||||
|
||||
// Ensure that group and group policy haven't been modified since the proposal submission.
|
||||
if proposal.GroupPolicyVersion != groupPolicy.Version {
|
||||
return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "group policy has been modified"), nil, nil
|
||||
}
|
||||
if proposal.GroupVersion != g.Version {
|
||||
return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "group has been modified"), nil, nil
|
||||
}
|
||||
|
||||
// Ensure member hasn't already voted
|
||||
res, _ := k.VoteByProposalVoter(ctx, &group.QueryVoteByProposalVoterRequest{
|
||||
Voter: acc.Address.String(),
|
||||
|
|
|
@ -26,14 +26,19 @@ those "sub-accounts" using the `x/authz` module.
|
|||
|
||||
## Decision Policy
|
||||
|
||||
A decision policy is the mechanism by which members of a group can vote on
|
||||
proposals.
|
||||
A decision policy is the mechanism by which members of a group can vote on
|
||||
proposals, as well as the rules that dictate whether a proposal should pass
|
||||
or not based on its tally outcome.
|
||||
|
||||
All decision policies generally would have a minimum and maximum voting window.
|
||||
The minimum voting window is the minimum amount of time that must pass in order
|
||||
for a proposal to potentially pass, and it may be set to 0. The maximum voting
|
||||
window is the maximum time that a proposal may be voted on before it is closed.
|
||||
Both of these values must be less than a chain-wide max voting window parameter.
|
||||
All decision policies generally would have a mininum execution perdio and a
|
||||
maximum voting window. The minimum execution period is the minimum amount of time
|
||||
that must pass in order for a proposal to potentially be executed, and it may
|
||||
be set to 0. The maximum voting window is the maximum time that a proposal may
|
||||
be voted on before it is closed.
|
||||
|
||||
The chain developer also defines an app-wide maximum execution period, which is
|
||||
the maximum amount of time after a proposal's voting period end where users are
|
||||
allowed to execute a proposal.
|
||||
|
||||
### Threshold decision policy
|
||||
|
||||
|
@ -41,6 +46,14 @@ A threshold decision policy defines a threshold of yes votes (based on a tally
|
|||
of voter weights) that must be achieved in order for a proposal to pass. For
|
||||
this decision policy, abstain and veto are simply treated as no's.
|
||||
|
||||
### Percentage decision policy
|
||||
|
||||
A percentage decision policy is similar to a threshold decision policy, except
|
||||
that the threshold is not defined as a constant weight, but as a percentage.
|
||||
It's more suited for groups where the group members' weights can be updated, as
|
||||
the percentage threshold stays the same, and doesn't depend on how those member
|
||||
weights get updated.
|
||||
|
||||
## Proposal
|
||||
|
||||
Any member of a group can submit a proposal for a group policy account to decide upon.
|
||||
|
@ -51,24 +64,56 @@ passes as well as any metadata associated with the proposal.
|
|||
|
||||
There are four choices to choose while voting - yes, no, abstain and veto. Not
|
||||
all decision policies will support them. Votes can contain some optional metadata.
|
||||
During the voting window, accounts that have already voted may change their vote.
|
||||
In the current implementation, the voting window begins as soon as a proposal
|
||||
is submitted.
|
||||
|
||||
## Tallying
|
||||
|
||||
Tallying is the counting of all votes on a proposal. It happens only once in
|
||||
the lifecycle of a proposal, but can be triggered by two factors, whichever
|
||||
happens first:
|
||||
|
||||
- either someone tries to execute the proposal (see next section), which can
|
||||
happen on a `Msg/Exec` transaction, or a `Msg/{SubmitProposal,Vote}`
|
||||
transaction with the `Exec` field set. When a proposal execution is attempted,
|
||||
a tally is done first to make sure the proposal passes.
|
||||
- or on `EndBlock` when the proposal's voting period end just passed.
|
||||
|
||||
If the tally result passes the decision policy's rules, then the proposal is
|
||||
marked as `STATUS_CLOSED`, so no more voting is allowed anymore, and the tally
|
||||
result is persisted to state.
|
||||
|
||||
## Executing Proposals
|
||||
|
||||
Proposals are executed only when the tallying is done, and the group account's
|
||||
decision policy allows the proposal to pass based on the tally outcome.
|
||||
|
||||
Proposals will not be automatically executed by the chain in this current design,
|
||||
but rather a user must submit a `Msg/Exec` transaction to attempt to execute the
|
||||
proposal based on the current votes and decision policy.
|
||||
It's also possible to try to execute a proposal immediately on creation or on
|
||||
new votes using the `Exec` field of `Msg/CreateProposal` and `Msg/Vote` requests.
|
||||
new votes using the `Exec` field of `Msg/SubmitProposal` and `Msg/Vote` requests.
|
||||
In the former case, proposers signatures are considered as yes votes.
|
||||
For now, if the proposal can't be executed, it'll still be opened for new votes and
|
||||
could be executed later on.
|
||||
|
||||
### Changing Group Membership
|
||||
## Pruning
|
||||
|
||||
In the current implementation, changing a group's membership (adding or removing members or changing their weight)
|
||||
will cause all existing proposals for group policy accounts linked to this group
|
||||
to be invalidated. They will simply fail if someone calls `Msg/Exec` and will
|
||||
eventually be garbage collected.
|
||||
Proposals and votes are automatically pruned to avoid state bloat.
|
||||
|
||||
Votes are pruned:
|
||||
|
||||
- either after a successful tally, i.e. a tally whose result passes the decision
|
||||
policy's rules, which can be trigged by a `Msg/Exec` or a
|
||||
`Msg/{SubmitProposal,Vote}` with the `Exec` field,
|
||||
- or on `EndBlock` right after the proposal's voting period end,
|
||||
|
||||
whichever happens first.
|
||||
|
||||
Proposals are pruned:
|
||||
|
||||
- either after a successful proposal execution,
|
||||
- or on `EndBlock` right after the proposal's `voting_period_end` +
|
||||
`max_execution_period` (defined as an app-wide configuration) is passed,
|
||||
|
||||
whichever happens first.
|
||||
|
|
|
@ -80,10 +80,12 @@ The second `0x1` corresponds to the ORM `sequenceStorageKey`.
|
|||
`proposalByGroupPolicyIndex` allows to retrieve proposals by group policy account address:
|
||||
`0x32 | len([]byte(account.Address)) | []byte(account.Address) | BigEndian(ProposalId) -> []byte()`.
|
||||
|
||||
### proposalByProposerIndex
|
||||
### ProposalsByVotingPeriodEndIndex
|
||||
|
||||
`proposalByProposerIndex` allows to retrieve proposals by proposer address:
|
||||
`0x33 | len([]byte(proposer.Address)) | []byte(proposer.Address) | BigEndian(ProposalId) -> []byte()`.
|
||||
`proposalsByVotingPeriodEndIndex` allows to retrieve proposals sorted by chronological `voting_period_end`:
|
||||
`0x33 | sdk.FormatTimeBytes(proposal.VotingPeriodEnd) | BigEndian(ProposalId) -> []byte()`.
|
||||
|
||||
This index is used when tallying the proposal votes at the end of the voting period, and for pruning proposals at `VotingPeriodEnd + MaxExecutionPeriod`.
|
||||
|
||||
## Vote Table
|
||||
|
||||
|
|
|
@ -124,8 +124,6 @@ A proposal can be executed with the `MsgExec`.
|
|||
|
||||
The messages that are part of this proposal won't be executed if:
|
||||
|
||||
* the group has been modified before tally.
|
||||
* the group policy has been modified before tally.
|
||||
* the proposal has not been accepted.
|
||||
* the proposal status is not closed.
|
||||
* the proposal has already been successfully executed.
|
||||
|
|
Loading…
Reference in New Issue