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 (
|
var (
|
||||||
md_EventExec protoreflect.MessageDescriptor
|
md_EventExec protoreflect.MessageDescriptor
|
||||||
fd_EventExec_proposal_id protoreflect.FieldDescriptor
|
fd_EventExec_proposal_id protoreflect.FieldDescriptor
|
||||||
|
fd_EventExec_result protoreflect.FieldDescriptor
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
file_cosmos_group_v1_events_proto_init()
|
file_cosmos_group_v1_events_proto_init()
|
||||||
md_EventExec = File_cosmos_group_v1_events_proto.Messages().ByName("EventExec")
|
md_EventExec = File_cosmos_group_v1_events_proto.Messages().ByName("EventExec")
|
||||||
fd_EventExec_proposal_id = md_EventExec.Fields().ByName("proposal_id")
|
fd_EventExec_proposal_id = md_EventExec.Fields().ByName("proposal_id")
|
||||||
|
fd_EventExec_result = md_EventExec.Fields().ByName("result")
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ protoreflect.Message = (*fastReflection_EventExec)(nil)
|
var _ protoreflect.Message = (*fastReflection_EventExec)(nil)
|
||||||
|
@ -2955,6 +2957,12 @@ func (x *fastReflection_EventExec) Range(f func(protoreflect.FieldDescriptor, pr
|
||||||
return
|
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.
|
// Has reports whether a field is populated.
|
||||||
|
@ -2972,6 +2980,8 @@ func (x *fastReflection_EventExec) Has(fd protoreflect.FieldDescriptor) bool {
|
||||||
switch fd.FullName() {
|
switch fd.FullName() {
|
||||||
case "cosmos.group.v1.EventExec.proposal_id":
|
case "cosmos.group.v1.EventExec.proposal_id":
|
||||||
return x.ProposalId != uint64(0)
|
return x.ProposalId != uint64(0)
|
||||||
|
case "cosmos.group.v1.EventExec.result":
|
||||||
|
return x.Result != 0
|
||||||
default:
|
default:
|
||||||
if fd.IsExtension() {
|
if fd.IsExtension() {
|
||||||
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec"))
|
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() {
|
switch fd.FullName() {
|
||||||
case "cosmos.group.v1.EventExec.proposal_id":
|
case "cosmos.group.v1.EventExec.proposal_id":
|
||||||
x.ProposalId = uint64(0)
|
x.ProposalId = uint64(0)
|
||||||
|
case "cosmos.group.v1.EventExec.result":
|
||||||
|
x.Result = 0
|
||||||
default:
|
default:
|
||||||
if fd.IsExtension() {
|
if fd.IsExtension() {
|
||||||
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec"))
|
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":
|
case "cosmos.group.v1.EventExec.proposal_id":
|
||||||
value := x.ProposalId
|
value := x.ProposalId
|
||||||
return protoreflect.ValueOfUint64(value)
|
return protoreflect.ValueOfUint64(value)
|
||||||
|
case "cosmos.group.v1.EventExec.result":
|
||||||
|
value := x.Result
|
||||||
|
return protoreflect.ValueOfEnum((protoreflect.EnumNumber)(value))
|
||||||
default:
|
default:
|
||||||
if descriptor.IsExtension() {
|
if descriptor.IsExtension() {
|
||||||
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec"))
|
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() {
|
switch fd.FullName() {
|
||||||
case "cosmos.group.v1.EventExec.proposal_id":
|
case "cosmos.group.v1.EventExec.proposal_id":
|
||||||
x.ProposalId = value.Uint()
|
x.ProposalId = value.Uint()
|
||||||
|
case "cosmos.group.v1.EventExec.result":
|
||||||
|
x.Result = (ProposalExecutorResult)(value.Enum())
|
||||||
default:
|
default:
|
||||||
if fd.IsExtension() {
|
if fd.IsExtension() {
|
||||||
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec"))
|
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() {
|
switch fd.FullName() {
|
||||||
case "cosmos.group.v1.EventExec.proposal_id":
|
case "cosmos.group.v1.EventExec.proposal_id":
|
||||||
panic(fmt.Errorf("field proposal_id of message cosmos.group.v1.EventExec is not mutable"))
|
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:
|
default:
|
||||||
if fd.IsExtension() {
|
if fd.IsExtension() {
|
||||||
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec"))
|
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() {
|
switch fd.FullName() {
|
||||||
case "cosmos.group.v1.EventExec.proposal_id":
|
case "cosmos.group.v1.EventExec.proposal_id":
|
||||||
return protoreflect.ValueOfUint64(uint64(0))
|
return protoreflect.ValueOfUint64(uint64(0))
|
||||||
|
case "cosmos.group.v1.EventExec.result":
|
||||||
|
return protoreflect.ValueOfEnum(0)
|
||||||
default:
|
default:
|
||||||
if fd.IsExtension() {
|
if fd.IsExtension() {
|
||||||
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec"))
|
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 {
|
if x.ProposalId != 0 {
|
||||||
n += 1 + runtime.Sov(uint64(x.ProposalId))
|
n += 1 + runtime.Sov(uint64(x.ProposalId))
|
||||||
}
|
}
|
||||||
|
if x.Result != 0 {
|
||||||
|
n += 1 + runtime.Sov(uint64(x.Result))
|
||||||
|
}
|
||||||
if x.unknownFields != nil {
|
if x.unknownFields != nil {
|
||||||
n += len(x.unknownFields)
|
n += len(x.unknownFields)
|
||||||
}
|
}
|
||||||
|
@ -3169,6 +3193,11 @@ func (x *fastReflection_EventExec) ProtoMethods() *protoiface.Methods {
|
||||||
i -= len(x.unknownFields)
|
i -= len(x.unknownFields)
|
||||||
copy(dAtA[i:], 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 {
|
if x.ProposalId != 0 {
|
||||||
i = runtime.EncodeVarint(dAtA, i, uint64(x.ProposalId))
|
i = runtime.EncodeVarint(dAtA, i, uint64(x.ProposalId))
|
||||||
i--
|
i--
|
||||||
|
@ -3242,6 +3271,25 @@ func (x *fastReflection_EventExec) ProtoMethods() *protoiface.Methods {
|
||||||
break
|
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:
|
default:
|
||||||
iNdEx = preIndex
|
iNdEx = preIndex
|
||||||
skippy, err := runtime.Skip(dAtA[iNdEx:])
|
skippy, err := runtime.Skip(dAtA[iNdEx:])
|
||||||
|
@ -4025,6 +4073,8 @@ type EventExec struct {
|
||||||
|
|
||||||
// proposal_id is the unique ID of the proposal.
|
// 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"`
|
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() {
|
func (x *EventExec) Reset() {
|
||||||
|
@ -4054,6 +4104,13 @@ func (x *EventExec) GetProposalId() uint64 {
|
||||||
return 0
|
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.
|
// EventLeaveGroup is an event emitted when group member leaves the group.
|
||||||
type EventLeaveGroup struct {
|
type EventLeaveGroup struct {
|
||||||
state protoimpl.MessageState
|
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,
|
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,
|
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,
|
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,
|
0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x63, 0x6f, 0x73, 0x6d,
|
||||||
0x65, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19,
|
0x6f, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x79, 0x70, 0x65,
|
||||||
0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
|
0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2d, 0x0a, 0x10, 0x45, 0x76, 0x65, 0x6e, 0x74,
|
||||||
0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x2d, 0x0a, 0x10, 0x45, 0x76, 0x65,
|
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x67,
|
||||||
0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a,
|
0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67,
|
||||||
0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52,
|
0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x2d, 0x0a, 0x10, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55,
|
||||||
0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x4c, 0x0a, 0x16, 0x45, 0x76, 0x65, 0x6e,
|
0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72,
|
||||||
0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x6f, 0x6c, 0x69,
|
0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67, 0x72,
|
||||||
0x63, 0x79, 0x12, 0x32, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20,
|
0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x4c, 0x0a, 0x16, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x43, 0x72,
|
||||||
0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e,
|
0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12,
|
||||||
0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61,
|
0x32, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||||
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x4c, 0x0a, 0x16, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55,
|
0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64,
|
||||||
0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79,
|
0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72,
|
||||||
0x12, 0x32, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
|
0x65, 0x73, 0x73, 0x22, 0x4c, 0x0a, 0x16, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61,
|
||||||
0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64,
|
0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x32, 0x0a,
|
||||||
0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64,
|
0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18,
|
||||||
0x72, 0x65, 0x73, 0x73, 0x22, 0x36, 0x0a, 0x13, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62,
|
0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65,
|
||||||
0x6d, 0x69, 0x74, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70,
|
0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
|
||||||
0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
|
0x73, 0x22, 0x36, 0x0a, 0x13, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74,
|
||||||
0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x49, 0x64, 0x22, 0x38, 0x0a, 0x15,
|
0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70,
|
||||||
0x45, 0x76, 0x65, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x50, 0x72, 0x6f,
|
0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70,
|
||||||
0x70, 0x6f, 0x73, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61,
|
0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x49, 0x64, 0x22, 0x38, 0x0a, 0x15, 0x45, 0x76, 0x65,
|
||||||
0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70,
|
0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73,
|
||||||
0x6f, 0x73, 0x61, 0x6c, 0x49, 0x64, 0x22, 0x2c, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x56,
|
0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69,
|
||||||
0x6f, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f,
|
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61,
|
||||||
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73,
|
0x6c, 0x49, 0x64, 0x22, 0x2c, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x56, 0x6f, 0x74, 0x65,
|
||||||
0x61, 0x6c, 0x49, 0x64, 0x22, 0x2c, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x45, 0x78, 0x65,
|
0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18,
|
||||||
0x63, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64,
|
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x49,
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c,
|
0x64, 0x22, 0x6d, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x45, 0x78, 0x65, 0x63, 0x12, 0x1f,
|
||||||
0x49, 0x64, 0x22, 0x60, 0x0a, 0x0f, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x61, 0x76, 0x65,
|
0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
|
||||||
0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69,
|
0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x49, 0x64, 0x12,
|
||||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64,
|
0x3f, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
||||||
0x12, 0x32, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
|
0x27, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x76,
|
||||||
0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64,
|
0x31, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74,
|
||||||
0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64,
|
0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74,
|
||||||
0x72, 0x65, 0x73, 0x73, 0x42, 0xba, 0x01, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x73,
|
0x22, 0x60, 0x0a, 0x0f, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x47, 0x72,
|
||||||
0x6d, 0x6f, 0x73, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x76, 0x31, 0x42, 0x0b, 0x45, 0x76,
|
0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18,
|
||||||
0x65, 0x6e, 0x74, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x38, 0x67, 0x69, 0x74,
|
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x32,
|
||||||
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x63,
|
0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42,
|
||||||
0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f,
|
0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72,
|
||||||
0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2f, 0x76, 0x31, 0x3b, 0x67, 0x72,
|
0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65,
|
||||||
0x6f, 0x75, 0x70, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x47, 0x58, 0xaa, 0x02, 0x0f, 0x43, 0x6f,
|
0x73, 0x73, 0x42, 0xba, 0x01, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f,
|
||||||
0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0f,
|
0x73, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x76, 0x31, 0x42, 0x0b, 0x45, 0x76, 0x65, 0x6e,
|
||||||
0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x5c, 0x56, 0x31, 0xe2,
|
0x74, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75,
|
||||||
0x02, 0x1b, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x5c, 0x56,
|
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x63, 0x6f, 0x73,
|
||||||
0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x11,
|
0x6d, 0x6f, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x73, 0x6d,
|
||||||
0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x3a, 0x3a, 0x56,
|
0x6f, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2f, 0x76, 0x31, 0x3b, 0x67, 0x72, 0x6f, 0x75,
|
||||||
0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
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 (
|
var (
|
||||||
|
@ -4180,13 +4243,15 @@ var file_cosmos_group_v1_events_proto_goTypes = []interface{}{
|
||||||
(*EventVote)(nil), // 6: cosmos.group.v1.EventVote
|
(*EventVote)(nil), // 6: cosmos.group.v1.EventVote
|
||||||
(*EventExec)(nil), // 7: cosmos.group.v1.EventExec
|
(*EventExec)(nil), // 7: cosmos.group.v1.EventExec
|
||||||
(*EventLeaveGroup)(nil), // 8: cosmos.group.v1.EventLeaveGroup
|
(*EventLeaveGroup)(nil), // 8: cosmos.group.v1.EventLeaveGroup
|
||||||
|
(ProposalExecutorResult)(0), // 9: cosmos.group.v1.ProposalExecutorResult
|
||||||
}
|
}
|
||||||
var file_cosmos_group_v1_events_proto_depIdxs = []int32{
|
var file_cosmos_group_v1_events_proto_depIdxs = []int32{
|
||||||
0, // [0:0] is the sub-list for method output_type
|
9, // 0: cosmos.group.v1.EventExec.result:type_name -> cosmos.group.v1.ProposalExecutorResult
|
||||||
0, // [0:0] is the sub-list for method input_type
|
1, // [1:1] is the sub-list for method output_type
|
||||||
0, // [0:0] is the sub-list for extension type_name
|
1, // [1:1] is the sub-list for method input_type
|
||||||
0, // [0:0] is the sub-list for extension extendee
|
1, // [1:1] is the sub-list for extension type_name
|
||||||
0, // [0:0] is the sub-list for field 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() }
|
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 {
|
if File_cosmos_group_v1_events_proto != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
file_cosmos_group_v1_types_proto_init()
|
||||||
if !protoimpl.UnsafeEnabled {
|
if !protoimpl.UnsafeEnabled {
|
||||||
file_cosmos_group_v1_events_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
file_cosmos_group_v1_events_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*EventCreateGroup); i {
|
switch v := v.(*EventCreateGroup); i {
|
||||||
|
|
|
@ -3,6 +3,7 @@ syntax = "proto3";
|
||||||
package cosmos.group.v1;
|
package cosmos.group.v1;
|
||||||
|
|
||||||
import "cosmos_proto/cosmos.proto";
|
import "cosmos_proto/cosmos.proto";
|
||||||
|
import "cosmos/group/v1/types.proto";
|
||||||
|
|
||||||
option go_package = "github.com/cosmos/cosmos-sdk/x/group";
|
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.
|
// proposal_id is the unique ID of the proposal.
|
||||||
uint64 proposal_id = 1;
|
uint64 proposal_id = 1;
|
||||||
|
|
||||||
|
// result is the proposal execution result.
|
||||||
|
ProposalExecutorResult result = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// EventLeaveGroup is an event emitted when group member leaves the group.
|
// EventLeaveGroup is an event emitted when group member leaves the group.
|
||||||
|
|
|
@ -349,6 +349,8 @@ func (m *EventVote) GetProposalId() uint64 {
|
||||||
type EventExec struct {
|
type EventExec struct {
|
||||||
// proposal_id is the unique ID of the proposal.
|
// 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"`
|
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{} }
|
func (m *EventExec) Reset() { *m = EventExec{} }
|
||||||
|
@ -391,6 +393,13 @@ func (m *EventExec) GetProposalId() uint64 {
|
||||||
return 0
|
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.
|
// EventLeaveGroup is an event emitted when group member leaves the group.
|
||||||
type EventLeaveGroup struct {
|
type EventLeaveGroup struct {
|
||||||
// group_id is the unique ID of the group.
|
// 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) }
|
func init() { proto.RegisterFile("cosmos/group/v1/events.proto", fileDescriptor_e8d753981546f032) }
|
||||||
|
|
||||||
var fileDescriptor_e8d753981546f032 = []byte{
|
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,
|
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,
|
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,
|
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,
|
0xca, 0x0c, 0xa5, 0x24, 0x21, 0x02, 0xf1, 0x60, 0x69, 0x7d, 0xa8, 0x2c, 0x98, 0x23, 0x25, 0x8d,
|
||||||
0x25, 0xe0, 0x0a, 0xd2, 0xeb, 0x5c, 0x94, 0x9a, 0x58, 0x92, 0xea, 0x0e, 0xd2, 0x21, 0x24, 0xc9,
|
0x6e, 0x52, 0x49, 0x65, 0x41, 0x2a, 0x54, 0x52, 0x49, 0x97, 0x4b, 0xc0, 0x15, 0x64, 0xb0, 0x73,
|
||||||
0xc5, 0x01, 0xd6, 0x1a, 0x9f, 0x99, 0x22, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x12, 0xc4, 0x0e, 0xe6,
|
0x51, 0x6a, 0x62, 0x49, 0xaa, 0x3b, 0x48, 0x89, 0x90, 0x24, 0x17, 0x07, 0x58, 0x6d, 0x7c, 0x66,
|
||||||
0x7b, 0xa6, 0xc0, 0x95, 0x87, 0x16, 0xa4, 0x10, 0xa3, 0xdc, 0x87, 0x4b, 0x0c, 0xdd, 0xf4, 0x80,
|
0x8a, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x4b, 0x10, 0x3b, 0x98, 0xef, 0x99, 0x02, 0x57, 0x1e, 0x5a,
|
||||||
0xfc, 0x9c, 0xcc, 0xe4, 0x4a, 0x21, 0x23, 0x2e, 0xf6, 0xc4, 0x94, 0x94, 0xa2, 0xd4, 0xe2, 0x62,
|
0x90, 0x42, 0x8c, 0x72, 0x1f, 0x2e, 0x31, 0x74, 0xd3, 0x03, 0xf2, 0x73, 0x32, 0x93, 0x2b, 0x85,
|
||||||
0xb0, 0x1e, 0x4e, 0x27, 0x89, 0x4b, 0x5b, 0x74, 0x45, 0xa0, 0x4e, 0x73, 0x84, 0xc8, 0x04, 0x97,
|
0x8c, 0xb8, 0xd8, 0x13, 0x53, 0x52, 0x8a, 0x52, 0x8b, 0x8b, 0xc1, 0x7a, 0x38, 0x9d, 0x24, 0x2e,
|
||||||
0x14, 0x65, 0xe6, 0xa5, 0x07, 0xc1, 0x14, 0xc2, 0x4d, 0x43, 0xb2, 0x9c, 0x02, 0xd3, 0xcc, 0xb8,
|
0x6d, 0xd1, 0x15, 0x81, 0xba, 0xdb, 0x11, 0x22, 0x13, 0x5c, 0x52, 0x94, 0x99, 0x97, 0x1e, 0x04,
|
||||||
0x84, 0xc1, 0xa6, 0x05, 0x97, 0x26, 0xe5, 0x66, 0x96, 0x04, 0x14, 0xe5, 0x17, 0xe4, 0x17, 0x27,
|
0x53, 0x08, 0x37, 0x0d, 0xc9, 0x72, 0x0a, 0x4c, 0x33, 0xe3, 0x12, 0x06, 0x9b, 0x16, 0x5c, 0x9a,
|
||||||
0xe6, 0x08, 0xc9, 0x73, 0x71, 0x17, 0x40, 0xd9, 0x08, 0x0f, 0x71, 0xc1, 0x84, 0x3c, 0x53, 0x94,
|
0x94, 0x9b, 0x59, 0x12, 0x50, 0x94, 0x5f, 0x90, 0x5f, 0x9c, 0x98, 0x23, 0x24, 0xcf, 0xc5, 0x5d,
|
||||||
0x2c, 0xb8, 0x44, 0xc1, 0xfa, 0xc2, 0x33, 0x4b, 0x32, 0x52, 0x8a, 0x12, 0xcb, 0x89, 0xd7, 0xa9,
|
0x00, 0x65, 0x23, 0x3c, 0xc4, 0x05, 0x13, 0xf2, 0x4c, 0x51, 0xb2, 0xe0, 0x12, 0x05, 0xeb, 0x0b,
|
||||||
0xc3, 0xc5, 0x09, 0xd6, 0x19, 0x96, 0x5f, 0x92, 0x4a, 0xbc, 0x6a, 0xd7, 0x8a, 0xd4, 0x64, 0xc2,
|
0xcf, 0x2c, 0xc9, 0x48, 0x29, 0x4a, 0x2c, 0x27, 0x5e, 0xa7, 0x0e, 0x17, 0x27, 0x58, 0x67, 0x58,
|
||||||
0xaa, 0x13, 0xb8, 0xf8, 0xc1, 0xaa, 0x7d, 0x52, 0x13, 0xcb, 0x08, 0xc6, 0x0b, 0x72, 0x78, 0x31,
|
0x7e, 0x49, 0x2a, 0x61, 0xd5, 0xb9, 0x50, 0xd5, 0xae, 0x15, 0xa9, 0xc9, 0x04, 0x55, 0x0b, 0xd9,
|
||||||
0x11, 0x19, 0x5e, 0x4e, 0x76, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91,
|
0x73, 0xb1, 0x15, 0xa5, 0x16, 0x97, 0xe6, 0x94, 0x48, 0x30, 0x29, 0x30, 0x6a, 0xf0, 0x19, 0xa9,
|
||||||
0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0xa5,
|
0xeb, 0xa1, 0xa5, 0x10, 0x3d, 0x98, 0x3b, 0x41, 0xe6, 0x95, 0x96, 0xe4, 0x17, 0x05, 0x81, 0x95,
|
||||||
0x92, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0x0b, 0x4d, 0x5c, 0x50, 0x4a, 0xb7,
|
0x07, 0x41, 0xb5, 0x29, 0x25, 0x70, 0xf1, 0x83, 0xad, 0xf3, 0x49, 0x4d, 0x2c, 0x23, 0x18, 0xb1,
|
||||||
0x38, 0x25, 0x5b, 0xbf, 0x02, 0x92, 0x4a, 0x93, 0xd8, 0xc0, 0x09, 0xce, 0x18, 0x10, 0x00, 0x00,
|
0xc8, 0x01, 0xce, 0x44, 0x64, 0x80, 0x3b, 0xd9, 0x9d, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c,
|
||||||
0xff, 0xff, 0xa2, 0x42, 0x09, 0x69, 0xbc, 0x02, 0x00, 0x00,
|
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) {
|
func (m *EventCreateGroup) Marshal() (dAtA []byte, err error) {
|
||||||
|
@ -704,6 +716,11 @@ func (m *EventExec) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||||
_ = i
|
_ = i
|
||||||
var l int
|
var l int
|
||||||
_ = l
|
_ = l
|
||||||
|
if m.Result != 0 {
|
||||||
|
i = encodeVarintEvents(dAtA, i, uint64(m.Result))
|
||||||
|
i--
|
||||||
|
dAtA[i] = 0x10
|
||||||
|
}
|
||||||
if m.ProposalId != 0 {
|
if m.ProposalId != 0 {
|
||||||
i = encodeVarintEvents(dAtA, i, uint64(m.ProposalId))
|
i = encodeVarintEvents(dAtA, i, uint64(m.ProposalId))
|
||||||
i--
|
i--
|
||||||
|
@ -853,6 +870,9 @@ func (m *EventExec) Size() (n int) {
|
||||||
if m.ProposalId != 0 {
|
if m.ProposalId != 0 {
|
||||||
n += 1 + sovEvents(uint64(m.ProposalId))
|
n += 1 + sovEvents(uint64(m.ProposalId))
|
||||||
}
|
}
|
||||||
|
if m.Result != 0 {
|
||||||
|
n += 1 + sovEvents(uint64(m.Result))
|
||||||
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1435,6 +1455,25 @@ func (m *EventExec) Unmarshal(dAtA []byte) error {
|
||||||
break
|
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:
|
default:
|
||||||
iNdEx = preIndex
|
iNdEx = preIndex
|
||||||
skippy, err := skipEvents(dAtA[iNdEx:])
|
skippy, err := skipEvents(dAtA[iNdEx:])
|
||||||
|
|
|
@ -20,24 +20,10 @@ const (
|
||||||
|
|
||||||
// RegisterInvariants registers all group invariants
|
// RegisterInvariants registers all group invariants
|
||||||
func RegisterInvariants(ir sdk.InvariantRegistry, keeper Keeper) {
|
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, weightInvariant, GroupTotalWeightInvariant(keeper))
|
||||||
ir.RegisterRoute(group.ModuleName, votesSumInvariant, TallyVotesSumInvariant(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.
|
// GroupTotalWeightInvariant checks that group's TotalWeight must be equal to the sum of its members.
|
||||||
func GroupTotalWeightInvariant(keeper Keeper) sdk.Invariant {
|
func GroupTotalWeightInvariant(keeper Keeper) sdk.Invariant {
|
||||||
return func(ctx sdk.Context) (string, bool) {
|
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 {
|
func TallyVotesSumInvariant(keeper Keeper) sdk.Invariant {
|
||||||
return func(ctx sdk.Context) (string, bool) {
|
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
|
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) {
|
func GroupTotalWeightInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, groupTable orm.AutoUInt64Table, groupMemberByGroupIndex orm.Index) (string, bool) {
|
||||||
|
|
||||||
var msg string
|
var msg string
|
||||||
var broken bool
|
var broken bool
|
||||||
|
|
||||||
var groupInfo group.GroupInfo
|
|
||||||
var groupMember group.GroupMember
|
|
||||||
|
|
||||||
groupIt, err := groupTable.PrefixScan(ctx.KVStore(key), 1, math.MaxUint64)
|
groupIt, err := groupTable.PrefixScan(ctx.KVStore(key), 1, math.MaxUint64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg += fmt.Sprintf("PrefixScan failure on group table\n%v\n", err)
|
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)
|
msg += fmt.Sprintf("error while parsing positive dec zero for group member\n%v\n", err)
|
||||||
return msg, broken
|
return msg, broken
|
||||||
}
|
}
|
||||||
|
var groupInfo group.GroupInfo
|
||||||
_, err = groupIt.LoadNext(&groupInfo)
|
_, err = groupIt.LoadNext(&groupInfo)
|
||||||
if errors.ErrORMIteratorDone.Is(err) {
|
if errors.ErrORMIteratorDone.Is(err) {
|
||||||
break
|
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)
|
memIt, err := groupMemberByGroupIndex.Get(ctx.KVStore(key), groupInfo.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg += fmt.Sprintf("error while returning group member iterator for group with ID %d\n%v\n", groupInfo.Id, err)
|
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()
|
defer memIt.Close()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
var groupMember group.GroupMember
|
||||||
_, err = memIt.LoadNext(&groupMember)
|
_, err = memIt.LoadNext(&groupMember)
|
||||||
if errors.ErrORMIteratorDone.Is(err) {
|
if errors.ErrORMIteratorDone.Is(err) {
|
||||||
break
|
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())
|
curMemWeight, err := groupmath.NewNonNegativeDecFromString(groupMember.GetMember().GetWeight())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg += fmt.Sprintf("error while parsing non-nengative decimal for group member %s\n%v\n", groupMember.Member.Address, err)
|
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
|
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 msg string
|
||||||
var broken bool
|
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)
|
proposalIt, err := proposalTable.PrefixScan(ctx.KVStore(key), 1, math.MaxUint64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
|
||||||
msg += fmt.Sprintf("PrefixScan failure on proposal table\n%v\n", err)
|
msg += fmt.Sprintf("PrefixScan failure on proposal table\n%v\n", err)
|
||||||
return msg, broken
|
return msg, broken
|
||||||
}
|
}
|
||||||
defer proposalIt.Close()
|
defer proposalIt.Close()
|
||||||
|
|
||||||
for {
|
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")
|
totalVotingWeight, err := groupmath.NewNonNegativeDecFromString("0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -247,11 +167,7 @@ func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, grou
|
||||||
return msg, broken
|
return msg, broken
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = proposalIt.LoadNext(&proposal)
|
var groupPolicy group.GroupPolicyInfo
|
||||||
if errors.ErrORMIteratorDone.Is(err) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
err = groupPolicyTable.GetOne(ctx.KVStore(key), orm.PrimaryKey(&group.GroupPolicyInfo{Address: proposal.Address}), &groupPolicy)
|
err = groupPolicyTable.GetOne(ctx.KVStore(key), orm.PrimaryKey(&group.GroupPolicyInfo{Address: proposal.Address}), &groupPolicy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg += fmt.Sprintf("group policy not found for address: %s\n%v\n", proposal.Address, err)
|
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
|
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)
|
voteIt, err := voteByProposalIndex.Get(ctx.KVStore(key), proposal.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg += fmt.Sprintf("error while returning vote iterator for proposal with ID %d\n%v\n", proposal.Id, err)
|
msg += fmt.Sprintf("error while returning vote iterator for proposal with ID %d\n%v\n", proposal.Id, err)
|
||||||
return msg, broken
|
return msg, broken
|
||||||
}
|
}
|
||||||
|
defer voteIt.Close()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
var vote group.Vote
|
||||||
_, err := voteIt.LoadNext(&vote)
|
_, err := voteIt.LoadNext(&vote)
|
||||||
if errors.ErrORMIteratorDone.Is(err) {
|
if errors.ErrORMIteratorDone.Is(err) {
|
||||||
break
|
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)
|
err = groupMemberTable.GetOne(ctx.KVStore(key), orm.PrimaryKey(&group.GroupMember{GroupId: groupPolicy.GroupId, Member: &group.Member{Address: vote.Voter}}), &groupMem)
|
||||||
if err != nil {
|
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)
|
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()
|
totalProposalVotes, err := proposal.FinalTallyResult.TotalCounts()
|
||||||
if err != nil {
|
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) {
|
if (yesVoteWeight.Cmp(proposalYesCount) != 0) || (noVoteWeight.Cmp(proposalNoCount) != 0) || (abstainVoteWeight.Cmp(proposalAbstainCount) != 0) || (vetoVoteWeight.Cmp(proposalVetoCount) != 0) {
|
||||||
broken = true
|
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
|
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() {
|
func (s *invariantTestSuite) TestGroupTotalWeightInvariant() {
|
||||||
sdkCtx, _ := s.ctx.CacheContext()
|
sdkCtx, _ := s.ctx.CacheContext()
|
||||||
curCtx, cdc, key := sdkCtx, s.cdc, s.key
|
curCtx, cdc, key := sdkCtx, s.cdc, s.key
|
||||||
|
@ -365,6 +182,8 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
|
||||||
_, _, addr1 := testdata.KeyTestPubAddr()
|
_, _, addr1 := testdata.KeyTestPubAddr()
|
||||||
_, _, addr2 := testdata.KeyTestPubAddr()
|
_, _, addr2 := testdata.KeyTestPubAddr()
|
||||||
|
|
||||||
|
votingPeriodEnd := curCtx.BlockTime().Add(time.Second * 600)
|
||||||
|
|
||||||
specs := map[string]struct {
|
specs := map[string]struct {
|
||||||
groupsInfo *group.GroupInfo
|
groupsInfo *group.GroupInfo
|
||||||
groupPolicy *group.GroupPolicyInfo
|
groupPolicy *group.GroupPolicyInfo
|
||||||
|
@ -409,10 +228,10 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
|
||||||
SubmitTime: curCtx.BlockTime(),
|
SubmitTime: curCtx.BlockTime(),
|
||||||
GroupVersion: 1,
|
GroupVersion: 1,
|
||||||
GroupPolicyVersion: 1,
|
GroupPolicyVersion: 1,
|
||||||
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
Status: group.PROPOSAL_STATUS_CLOSED,
|
||||||
Result: group.PROPOSAL_RESULT_UNFINALIZED,
|
Result: group.PROPOSAL_RESULT_ACCEPTED,
|
||||||
FinalTallyResult: group.TallyResult{YesCount: "4", NoCount: "3", AbstainCount: "0", NoWithVetoCount: "0"},
|
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,
|
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||||
},
|
},
|
||||||
votes: []*group.Vote{
|
votes: []*group.Vote{
|
||||||
|
@ -431,6 +250,58 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
|
||||||
},
|
},
|
||||||
expBroken: false,
|
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": {
|
"proposal tally must correspond to the sum of vote weights": {
|
||||||
groupsInfo: &group.GroupInfo{
|
groupsInfo: &group.GroupInfo{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
|
@ -467,10 +338,10 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
|
||||||
SubmitTime: curCtx.BlockTime(),
|
SubmitTime: curCtx.BlockTime(),
|
||||||
GroupVersion: 1,
|
GroupVersion: 1,
|
||||||
GroupPolicyVersion: 1,
|
GroupPolicyVersion: 1,
|
||||||
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
Status: group.PROPOSAL_STATUS_CLOSED,
|
||||||
Result: group.PROPOSAL_RESULT_UNFINALIZED,
|
Result: group.PROPOSAL_RESULT_ACCEPTED,
|
||||||
FinalTallyResult: group.TallyResult{YesCount: "6", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
|
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,
|
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||||
},
|
},
|
||||||
votes: []*group.Vote{
|
votes: []*group.Vote{
|
||||||
|
@ -525,10 +396,10 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
|
||||||
SubmitTime: curCtx.BlockTime(),
|
SubmitTime: curCtx.BlockTime(),
|
||||||
GroupVersion: 1,
|
GroupVersion: 1,
|
||||||
GroupPolicyVersion: 1,
|
GroupPolicyVersion: 1,
|
||||||
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
Status: group.PROPOSAL_STATUS_CLOSED,
|
||||||
Result: group.PROPOSAL_RESULT_UNFINALIZED,
|
Result: group.PROPOSAL_RESULT_ACCEPTED,
|
||||||
FinalTallyResult: group.TallyResult{YesCount: "4", NoCount: "3", AbstainCount: "0", NoWithVetoCount: "0"},
|
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,
|
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
||||||
},
|
},
|
||||||
votes: []*group.Vote{
|
votes: []*group.Vote{
|
||||||
|
@ -578,7 +449,7 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
|
||||||
s.Require().NoError(err)
|
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)
|
s.Require().Equal(spec.expBroken, broken)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,14 @@ package keeper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
storetypes "github.com/cosmos/cosmos-sdk/store/types"
|
storetypes "github.com/cosmos/cosmos-sdk/store/types"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/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"
|
authmiddleware "github.com/cosmos/cosmos-sdk/x/auth/middleware"
|
||||||
"github.com/cosmos/cosmos-sdk/x/group"
|
"github.com/cosmos/cosmos-sdk/x/group"
|
||||||
"github.com/cosmos/cosmos-sdk/x/group/errors"
|
"github.com/cosmos/cosmos-sdk/x/group/errors"
|
||||||
|
@ -35,8 +37,7 @@ const (
|
||||||
ProposalTablePrefix byte = 0x30
|
ProposalTablePrefix byte = 0x30
|
||||||
ProposalTableSeqPrefix byte = 0x31
|
ProposalTableSeqPrefix byte = 0x31
|
||||||
ProposalByGroupPolicyIndexPrefix byte = 0x32
|
ProposalByGroupPolicyIndexPrefix byte = 0x32
|
||||||
ProposalByProposerIndexPrefix byte = 0x33
|
ProposalsByVotingPeriodEndPrefix byte = 0x33
|
||||||
ProposalsByVotingPeriodEndPrefix byte = 0x34
|
|
||||||
|
|
||||||
// Vote Table
|
// Vote Table
|
||||||
VoteTablePrefix byte = 0x40
|
VoteTablePrefix byte = 0x40
|
||||||
|
@ -67,7 +68,6 @@ type Keeper struct {
|
||||||
// Proposal Table
|
// Proposal Table
|
||||||
proposalTable orm.AutoUInt64Table
|
proposalTable orm.AutoUInt64Table
|
||||||
proposalByGroupPolicyIndex orm.Index
|
proposalByGroupPolicyIndex orm.Index
|
||||||
proposalByProposerIndex orm.Index
|
|
||||||
proposalsByVotingPeriodEnd orm.Index
|
proposalsByVotingPeriodEnd orm.Index
|
||||||
|
|
||||||
// Vote Table
|
// Vote Table
|
||||||
|
@ -169,21 +169,6 @@ func NewKeeper(storeKey storetypes.StoreKey, cdc codec.Codec, router *authmiddle
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err.Error())
|
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) {
|
k.proposalsByVotingPeriodEnd, err = orm.NewIndex(proposalTable, ProposalsByVotingPeriodEndPrefix, func(value interface{}) ([]interface{}, error) {
|
||||||
votingPeriodEnd := value.(*group.Proposal).VotingPeriodEnd
|
votingPeriodEnd := value.(*group.Proposal).VotingPeriodEnd
|
||||||
return []interface{}{sdk.FormatTimeBytes(votingPeriodEnd)}, nil
|
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))
|
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
|
// GetGroupSequence returns the current value of the group table sequence
|
||||||
func (k Keeper) GetGroupSequence(ctx sdk.Context) uint64 {
|
func (k Keeper) GetGroupSequence(ctx sdk.Context) uint64 {
|
||||||
return k.groupTable.Sequence().CurVal(ctx.KVStore(k.key))
|
return k.groupTable.Sequence().CurVal(ctx.KVStore(k.key))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, cb func(proposal group.Proposal) (bool, error)) error {
|
// iterateProposalsByVPEnd iterates over all proposals whose voting_period_end is after the `endTime` time argument.
|
||||||
timeBytes := sdk.FormatTimeBytes(ctx.BlockTime())
|
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)
|
it, err := k.proposalsByVotingPeriodEnd.PrefixScan(ctx.KVStore(k.key), nil, timeBytes)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -250,8 +233,17 @@ func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, cb func(proposal group.
|
||||||
}
|
}
|
||||||
defer it.Close()
|
defer it.Close()
|
||||||
|
|
||||||
var proposal group.Proposal
|
|
||||||
for {
|
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)
|
_, err := it.LoadNext(&proposal)
|
||||||
if errors.ErrORMIteratorDone.Is(err) {
|
if errors.ErrORMIteratorDone.Is(err) {
|
||||||
break
|
break
|
||||||
|
@ -272,30 +264,87 @@ func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, cb func(proposal group.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k Keeper) UpdateTallyOfVPEndProposals(ctx sdk.Context) error {
|
// pruneProposal deletes a proposal from state.
|
||||||
k.iterateProposalsByVPEnd(ctx, func(proposal group.Proposal) (bool, error) {
|
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 {
|
if err != nil {
|
||||||
return true, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId)
|
err = k.voteTable.Delete(store, &vote)
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := k.proposalTable.Update(ctx.KVStore(k.key), proposal.Id, &proposal); err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
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)
|
s.Require().NoError(err)
|
||||||
id := res.ProposalId
|
id := res.ProposalId
|
||||||
|
|
||||||
// then all data persisted
|
if !(spec.expProposal.ExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS) {
|
||||||
proposalRes, err := s.keeper.Proposal(s.ctx, &group.QueryProposalRequest{ProposalId: id})
|
// then all data persisted
|
||||||
s.Require().NoError(err)
|
proposalRes, err := s.keeper.Proposal(s.ctx, &group.QueryProposalRequest{ProposalId: id})
|
||||||
proposal := proposalRes.Proposal
|
s.Require().NoError(err)
|
||||||
|
proposal := proposalRes.Proposal
|
||||||
|
|
||||||
s.Assert().Equal(spec.expProposal.Address, proposal.Address)
|
s.Assert().Equal(spec.expProposal.Address, proposal.Address)
|
||||||
s.Assert().Equal(spec.req.Metadata, proposal.Metadata)
|
s.Assert().Equal(spec.req.Metadata, proposal.Metadata)
|
||||||
s.Assert().Equal(spec.req.Proposers, proposal.Proposers)
|
s.Assert().Equal(spec.req.Proposers, proposal.Proposers)
|
||||||
s.Assert().Equal(s.blockTime, proposal.SubmitTime)
|
s.Assert().Equal(s.blockTime, proposal.SubmitTime)
|
||||||
s.Assert().Equal(uint64(1), proposal.GroupVersion)
|
s.Assert().Equal(uint64(1), proposal.GroupVersion)
|
||||||
s.Assert().Equal(uint64(1), proposal.GroupPolicyVersion)
|
s.Assert().Equal(uint64(1), proposal.GroupPolicyVersion)
|
||||||
s.Assert().Equal(spec.expProposal.Status, proposal.Status)
|
s.Assert().Equal(spec.expProposal.Status, proposal.Status)
|
||||||
s.Assert().Equal(spec.expProposal.Result, proposal.Result)
|
s.Assert().Equal(spec.expProposal.Result, proposal.Result)
|
||||||
s.Assert().Equal(spec.expProposal.FinalTallyResult, proposal.FinalTallyResult)
|
s.Assert().Equal(spec.expProposal.FinalTallyResult, proposal.FinalTallyResult)
|
||||||
s.Assert().Equal(spec.expProposal.ExecutorResult, proposal.ExecutorResult)
|
s.Assert().Equal(spec.expProposal.ExecutorResult, proposal.ExecutorResult)
|
||||||
s.Assert().Equal(s.blockTime.Add(time.Second), proposal.VotingPeriodEnd)
|
s.Assert().Equal(s.blockTime.Add(time.Second), proposal.VotingPeriodEnd)
|
||||||
|
|
||||||
if spec.msgs == nil { // then empty list is ok
|
if spec.msgs == nil { // then empty list is ok
|
||||||
s.Assert().Len(proposal.GetMsgs(), 0)
|
s.Assert().Len(proposal.GetMsgs(), 0)
|
||||||
} else {
|
} else {
|
||||||
s.Assert().Equal(spec.msgs, proposal.GetMsgs())
|
s.Assert().Equal(spec.msgs, proposal.GetMsgs())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spec.postRun(s.sdkCtx)
|
spec.postRun(s.sdkCtx)
|
||||||
|
@ -2042,47 +2044,6 @@ func (s *TestSuite) TestVote() {
|
||||||
expErr: true,
|
expErr: true,
|
||||||
postRun: func(sdkCtx sdk.Context) {},
|
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 {
|
for msg, spec := range specs {
|
||||||
spec := spec
|
spec := spec
|
||||||
|
@ -2105,66 +2066,69 @@ func (s *TestSuite) TestVote() {
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
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
|
if !(spec.expExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS) {
|
||||||
votesByProposalRes, err := s.keeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{
|
// vote is stored and all data persisted
|
||||||
ProposalId: spec.req.ProposalId,
|
res, err := s.keeper.VoteByProposalVoter(ctx, &group.QueryVoteByProposalVoterRequest{
|
||||||
})
|
ProposalId: spec.req.ProposalId,
|
||||||
s.Require().NoError(err)
|
Voter: spec.req.Voter,
|
||||||
votesByProposal := votesByProposalRes.Votes
|
})
|
||||||
s.Require().Equal(1, len(votesByProposal))
|
s.Require().NoError(err)
|
||||||
vote := votesByProposal[0]
|
loaded := res.Vote
|
||||||
s.Assert().Equal(spec.req.ProposalId, vote.ProposalId)
|
s.Assert().Equal(spec.req.ProposalId, loaded.ProposalId)
|
||||||
s.Assert().Equal(spec.req.Voter, vote.Voter)
|
s.Assert().Equal(spec.req.Voter, loaded.Voter)
|
||||||
s.Assert().Equal(spec.req.Option, vote.Option)
|
s.Assert().Equal(spec.req.Option, loaded.Option)
|
||||||
s.Assert().Equal(spec.req.Metadata, vote.Metadata)
|
s.Assert().Equal(spec.req.Metadata, loaded.Metadata)
|
||||||
s.Assert().Equal(s.blockTime, vote.SubmitTime)
|
s.Assert().Equal(s.blockTime, loaded.SubmitTime)
|
||||||
|
|
||||||
// query votes by voter
|
// query votes by proposal
|
||||||
voter := spec.req.Voter
|
votesByProposalRes, err := s.keeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{
|
||||||
votesByVoterRes, err := s.keeper.VotesByVoter(ctx, &group.QueryVotesByVoterRequest{
|
ProposalId: spec.req.ProposalId,
|
||||||
Voter: voter,
|
})
|
||||||
})
|
s.Require().NoError(err)
|
||||||
s.Require().NoError(err)
|
votesByProposal := votesByProposalRes.Votes
|
||||||
votesByVoter := votesByVoterRes.Votes
|
s.Require().Equal(1, len(votesByProposal))
|
||||||
s.Require().Equal(1, len(votesByVoter))
|
vote := votesByProposal[0]
|
||||||
s.Assert().Equal(spec.req.ProposalId, votesByVoter[0].ProposalId)
|
s.Assert().Equal(spec.req.ProposalId, vote.ProposalId)
|
||||||
s.Assert().Equal(voter, votesByVoter[0].Voter)
|
s.Assert().Equal(spec.req.Voter, vote.Voter)
|
||||||
s.Assert().Equal(spec.req.Option, votesByVoter[0].Option)
|
s.Assert().Equal(spec.req.Option, vote.Option)
|
||||||
s.Assert().Equal(spec.req.Metadata, votesByVoter[0].Metadata)
|
s.Assert().Equal(spec.req.Metadata, vote.Metadata)
|
||||||
s.Assert().Equal(s.blockTime, votesByVoter[0].SubmitTime)
|
s.Assert().Equal(s.blockTime, vote.SubmitTime)
|
||||||
|
|
||||||
proposalRes, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{
|
// query votes by voter
|
||||||
ProposalId: spec.req.ProposalId,
|
voter := spec.req.Voter
|
||||||
})
|
votesByVoterRes, err := s.keeper.VotesByVoter(ctx, &group.QueryVotesByVoterRequest{
|
||||||
s.Require().NoError(err)
|
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
|
proposalRes, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{
|
||||||
if spec.isFinal {
|
ProposalId: spec.req.ProposalId,
|
||||||
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.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)
|
spec.postRun(sdkCtx)
|
||||||
|
@ -2329,36 +2293,6 @@ func (s *TestSuite) TestExecProposal() {
|
||||||
expProposalResult: group.PROPOSAL_RESULT_REJECTED,
|
expProposalResult: group.PROPOSAL_RESULT_REJECTED,
|
||||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
|
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": {
|
"prevent double execution when successful": {
|
||||||
setupProposal: func(ctx context.Context) uint64 {
|
setupProposal: func(ctx context.Context) uint64 {
|
||||||
myProposalID := submitProposalAndVote(ctx, s, []sdk.Msg{msgSend1}, proposers, group.VOTE_OPTION_YES)
|
myProposalID := submitProposalAndVote(ctx, s, []sdk.Msg{msgSend1}, proposers, group.VOTE_OPTION_YES)
|
||||||
|
@ -2367,6 +2301,7 @@ func (s *TestSuite) TestExecProposal() {
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
return myProposalID
|
return myProposalID
|
||||||
},
|
},
|
||||||
|
expErr: true, // since proposal is pruned after a successful MsgExec
|
||||||
expProposalStatus: group.PROPOSAL_STATUS_CLOSED,
|
expProposalStatus: group.PROPOSAL_STATUS_CLOSED,
|
||||||
expProposalResult: group.PROPOSAL_RESULT_ACCEPTED,
|
expProposalResult: group.PROPOSAL_RESULT_ACCEPTED,
|
||||||
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
|
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
|
||||||
|
@ -2419,22 +2354,25 @@ func (s *TestSuite) TestExecProposal() {
|
||||||
}
|
}
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
// and proposal is updated
|
if !(spec.expExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS) {
|
||||||
res, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID})
|
|
||||||
s.Require().NoError(err)
|
|
||||||
proposal := res.Proposal
|
|
||||||
|
|
||||||
exp := group.ProposalResult_name[int32(spec.expProposalResult)]
|
// and proposal is updated
|
||||||
got := group.ProposalResult_name[int32(proposal.Result)]
|
res, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID})
|
||||||
s.Assert().Equal(exp, got)
|
s.Require().NoError(err)
|
||||||
|
proposal := res.Proposal
|
||||||
|
|
||||||
exp = group.ProposalStatus_name[int32(spec.expProposalStatus)]
|
exp := group.ProposalResult_name[int32(spec.expProposalResult)]
|
||||||
got = group.ProposalStatus_name[int32(proposal.Status)]
|
got := group.ProposalResult_name[int32(proposal.Result)]
|
||||||
s.Assert().Equal(exp, got)
|
s.Assert().Equal(exp, got)
|
||||||
|
|
||||||
exp = group.ProposalExecutorResult_name[int32(spec.expExecutorResult)]
|
exp = group.ProposalStatus_name[int32(spec.expProposalStatus)]
|
||||||
got = group.ProposalExecutorResult_name[int32(proposal.ExecutorResult)]
|
got = group.ProposalStatus_name[int32(proposal.Status)]
|
||||||
s.Assert().Equal(exp, got)
|
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 {
|
if spec.expBalance {
|
||||||
fromBalances := s.app.BankKeeper.GetAllBalances(sdkCtx, s.groupPolicyAddr)
|
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() {
|
func (s *TestSuite) TestProposalsByVPEnd() {
|
||||||
addrs := s.addrs
|
addrs := s.addrs
|
||||||
addr2 := addrs[1]
|
addr2 := addrs[1]
|
||||||
|
|
|
@ -637,22 +637,14 @@ func (k Keeper) Vote(goCtx context.Context, req *group.MsgVote) (*group.MsgVoteR
|
||||||
|
|
||||||
var policyInfo group.GroupPolicyInfo
|
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 {
|
if policyInfo, err = k.getGroupPolicyInfo(ctx, proposal.Address); err != nil {
|
||||||
return nil, sdkerrors.Wrap(err, "load group policy")
|
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)
|
electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if electorate.Version != proposal.GroupVersion {
|
|
||||||
return nil, sdkerrors.Wrap(errors.ErrModified, "group was modified")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count and store votes.
|
// Count and store votes.
|
||||||
voterAddr := req.Voter
|
voterAddr := req.Voter
|
||||||
|
@ -711,17 +703,22 @@ func (k Keeper) doTallyAndUpdate(ctx sdk.Context, p *group.Proposal, electorate
|
||||||
return err
|
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:
|
case err != nil:
|
||||||
return sdkerrors.Wrap(err, "policy execution")
|
return sdkerrors.Wrap(err, "policy allow")
|
||||||
case result.Allow && result.Final:
|
case result.Final:
|
||||||
|
if err := k.pruneVotes(ctx, p.Id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
p.FinalTallyResult = tallyResult
|
p.FinalTallyResult = tallyResult
|
||||||
p.Result = group.PROPOSAL_RESULT_ACCEPTED
|
if result.Allow {
|
||||||
p.Status = group.PROPOSAL_STATUS_CLOSED
|
p.Result = group.PROPOSAL_RESULT_ACCEPTED
|
||||||
case !result.Allow && result.Final:
|
p.Status = group.PROPOSAL_STATUS_CLOSED
|
||||||
p.FinalTallyResult = tallyResult
|
} else {
|
||||||
p.Result = group.PROPOSAL_RESULT_REJECTED
|
p.Result = group.PROPOSAL_RESULT_REJECTED
|
||||||
p.Status = group.PROPOSAL_STATUS_CLOSED
|
p.Status = group.PROPOSAL_STATUS_CLOSED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -747,32 +744,28 @@ func (k Keeper) Exec(goCtx context.Context, req *group.MsgExec) (*group.MsgExecR
|
||||||
}
|
}
|
||||||
|
|
||||||
storeUpdates := func() (*group.MsgExecResponse, error) {
|
storeUpdates := func() (*group.MsgExecResponse, error) {
|
||||||
if err := k.proposalTable.Update(ctx.KVStore(k.key), id, &proposal); err != nil {
|
store := ctx.KVStore(k.key)
|
||||||
return nil, err
|
|
||||||
|
// 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
|
return &group.MsgExecResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if proposal.Status == group.PROPOSAL_STATUS_SUBMITTED {
|
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)
|
electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, sdkerrors.Wrap(err, "load group")
|
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 {
|
if err := k.doTallyAndUpdate(ctx, &proposal, electorate, policyInfo); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -805,7 +798,10 @@ func (k Keeper) Exec(goCtx context.Context, req *group.MsgExec) (*group.MsgExecR
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -983,8 +979,8 @@ func (k Keeper) validateDecisionPolicies(ctx sdk.Context, g group.GroupInfo) err
|
||||||
}
|
}
|
||||||
defer it.Close()
|
defer it.Close()
|
||||||
|
|
||||||
var groupPolicy group.GroupPolicyInfo
|
|
||||||
for {
|
for {
|
||||||
|
var groupPolicy group.GroupPolicyInfo
|
||||||
_, err = it.LoadNext(&groupPolicy)
|
_, err = it.LoadNext(&groupPolicy)
|
||||||
if errors.ErrORMIteratorDone.Is(err) {
|
if errors.ErrORMIteratorDone.Is(err) {
|
||||||
break
|
break
|
||||||
|
|
|
@ -25,8 +25,8 @@ func (q Keeper) Tally(ctx sdk.Context, p group.Proposal, groupId uint64) (group.
|
||||||
|
|
||||||
tallyResult := group.DefaultTallyResult()
|
tallyResult := group.DefaultTallyResult()
|
||||||
|
|
||||||
var vote group.Vote
|
|
||||||
for {
|
for {
|
||||||
|
var vote group.Vote
|
||||||
_, err = it.LoadNext(&vote)
|
_, err = it.LoadNext(&vote)
|
||||||
if errors.ErrORMIteratorDone.Is(err) {
|
if errors.ErrORMIteratorDone.Is(err) {
|
||||||
break
|
break
|
||||||
|
|
|
@ -9,4 +9,12 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) {
|
||||||
if err := k.UpdateTallyOfVPEndProposals(ctx); err != nil {
|
if err := k.UpdateTallyOfVPEndProposals(ctx); err != nil {
|
||||||
panic(err)
|
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"
|
"time"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/simapp"
|
"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"
|
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/group"
|
"github.com/cosmos/cosmos-sdk/x/group"
|
||||||
"github.com/cosmos/cosmos-sdk/x/group/module"
|
"github.com/cosmos/cosmos-sdk/x/group/module"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
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) {
|
func TestEndBlocker(t *testing.T) {
|
||||||
app := simapp.Setup(t, false)
|
app := simapp.Setup(t, false)
|
||||||
ctx := app.BaseApp.NewContext(false, tmproto.Header{})
|
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
|
// Initial group, group policy and balance setup
|
||||||
members := []group.Member{
|
members := []group.Member{
|
||||||
|
@ -48,45 +226,44 @@ func TestEndBlocker(t *testing.T) {
|
||||||
policyRes, err := app.GroupKeeper.CreateGroupPolicy(ctx, policyReq)
|
policyRes, err := app.GroupKeeper.CreateGroupPolicy(ctx, policyReq)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
groupPolicyAddr, err := types.AccAddressFromBech32(policyRes.Address)
|
groupPolicyAddr, err := sdk.AccAddressFromBech32(policyRes.Address)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
votingPeriod := policy.GetVotingPeriod()
|
votingPeriod := policy.GetVotingPeriod()
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
msgSend := &banktypes.MsgSend{
|
msgSend := &banktypes.MsgSend{
|
||||||
FromAddress: groupPolicyAddr.String(),
|
FromAddress: groupPolicyAddr.String(),
|
||||||
ToAddress: addrs[3].String(),
|
ToAddress: addrs[3].String(),
|
||||||
Amount: types.Coins{types.NewInt64Coin("test", 100)},
|
Amount: sdk.Coins{sdk.NewInt64Coin("test", 100)},
|
||||||
}
|
}
|
||||||
|
|
||||||
proposers := []string{addrs[2].String()}
|
proposers := []string{addrs[2].String()}
|
||||||
|
|
||||||
specs := map[string]struct {
|
specs := map[string]struct {
|
||||||
preRun func(sdkCtx types.Context) uint64
|
preRun func(sdkCtx sdk.Context) uint64
|
||||||
proposalId uint64
|
proposalId uint64
|
||||||
admin string
|
admin string
|
||||||
expErrMsg string
|
expErrMsg string
|
||||||
newCtx types.Context
|
newCtx sdk.Context
|
||||||
tallyRes group.TallyResult
|
tallyRes group.TallyResult
|
||||||
expStatus group.ProposalStatus
|
expStatus group.ProposalStatus
|
||||||
expExecutorResult group.ProposalResult
|
expExecutorResult group.ProposalResult
|
||||||
}{
|
}{
|
||||||
"tally updated after voting power end": {
|
"tally updated after voting power end": {
|
||||||
preRun: func(sdkCtx types.Context) uint64 {
|
preRun: func(sdkCtx sdk.Context) uint64 {
|
||||||
pId, err := submitProposal(app, sdkCtx, []types.Msg{msgSend}, proposers, groupPolicyAddr)
|
pId, err := submitProposal(app, sdkCtx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return pId
|
return pId
|
||||||
},
|
},
|
||||||
admin: proposers[0],
|
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(),
|
tallyRes: group.DefaultTallyResult(),
|
||||||
expStatus: group.PROPOSAL_STATUS_SUBMITTED,
|
expStatus: group.PROPOSAL_STATUS_SUBMITTED,
|
||||||
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
|
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||||
},
|
},
|
||||||
"tally within voting period": {
|
"tally within voting period": {
|
||||||
preRun: func(sdkCtx types.Context) uint64 {
|
preRun: func(sdkCtx sdk.Context) uint64 {
|
||||||
pId, err := submitProposal(app, sdkCtx, []types.Msg{msgSend}, proposers, groupPolicyAddr)
|
pId, err := submitProposal(app, sdkCtx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
return pId
|
return pId
|
||||||
|
@ -98,8 +275,8 @@ func TestEndBlocker(t *testing.T) {
|
||||||
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
|
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||||
},
|
},
|
||||||
"tally within voting period(with votes)": {
|
"tally within voting period(with votes)": {
|
||||||
preRun: func(sdkCtx types.Context) uint64 {
|
preRun: func(sdkCtx sdk.Context) uint64 {
|
||||||
pId, err := submitProposalAndVote(app, ctx, []types.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
|
pId, err := submitProposalAndVote(app, ctx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
return pId
|
return pId
|
||||||
|
@ -111,14 +288,14 @@ func TestEndBlocker(t *testing.T) {
|
||||||
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
|
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||||
},
|
},
|
||||||
"tally after voting period(with votes)": {
|
"tally after voting period(with votes)": {
|
||||||
preRun: func(sdkCtx types.Context) uint64 {
|
preRun: func(sdkCtx sdk.Context) uint64 {
|
||||||
pId, err := submitProposalAndVote(app, ctx, []types.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
|
pId, err := submitProposalAndVote(app, ctx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
return pId
|
return pId
|
||||||
},
|
},
|
||||||
admin: proposers[0],
|
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{
|
tallyRes: group.TallyResult{
|
||||||
YesCount: "2",
|
YesCount: "2",
|
||||||
NoCount: "0",
|
NoCount: "0",
|
||||||
|
@ -129,8 +306,8 @@ func TestEndBlocker(t *testing.T) {
|
||||||
expExecutorResult: group.PROPOSAL_RESULT_ACCEPTED,
|
expExecutorResult: group.PROPOSAL_RESULT_ACCEPTED,
|
||||||
},
|
},
|
||||||
"tally of closed proposal": {
|
"tally of closed proposal": {
|
||||||
preRun: func(sdkCtx types.Context) uint64 {
|
preRun: func(sdkCtx sdk.Context) uint64 {
|
||||||
pId, err := submitProposal(app, sdkCtx, []types.Msg{msgSend}, proposers, groupPolicyAddr)
|
pId, err := submitProposal(app, sdkCtx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = app.GroupKeeper.WithdrawProposal(ctx, &group.MsgWithdrawProposal{
|
_, err = app.GroupKeeper.WithdrawProposal(ctx, &group.MsgWithdrawProposal{
|
||||||
|
@ -148,8 +325,8 @@ func TestEndBlocker(t *testing.T) {
|
||||||
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
|
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
|
||||||
},
|
},
|
||||||
"tally of closed proposal (with votes)": {
|
"tally of closed proposal (with votes)": {
|
||||||
preRun: func(sdkCtx types.Context) uint64 {
|
preRun: func(sdkCtx sdk.Context) uint64 {
|
||||||
pId, err := submitProposalAndVote(app, ctx, []types.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
|
pId, err := submitProposalAndVote(app, ctx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = app.GroupKeeper.WithdrawProposal(ctx, &group.MsgWithdrawProposal{
|
_, err = app.GroupKeeper.WithdrawProposal(ctx, &group.MsgWithdrawProposal{
|
||||||
|
@ -193,8 +370,8 @@ func TestEndBlocker(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func submitProposal(
|
func submitProposal(
|
||||||
app *simapp.SimApp, ctx context.Context, msgs []types.Msg,
|
app *simapp.SimApp, ctx context.Context, msgs []sdk.Msg,
|
||||||
proposers []string, groupPolicyAddr types.AccAddress) (uint64, error) {
|
proposers []string, groupPolicyAddr sdk.AccAddress) (uint64, error) {
|
||||||
proposalReq := &group.MsgSubmitProposal{
|
proposalReq := &group.MsgSubmitProposal{
|
||||||
Address: groupPolicyAddr.String(),
|
Address: groupPolicyAddr.String(),
|
||||||
Proposers: proposers,
|
Proposers: proposers,
|
||||||
|
@ -213,13 +390,12 @@ func submitProposal(
|
||||||
}
|
}
|
||||||
|
|
||||||
func submitProposalAndVote(
|
func submitProposalAndVote(
|
||||||
app *simapp.SimApp, ctx context.Context, msgs []types.Msg,
|
app *simapp.SimApp, ctx context.Context, msgs []sdk.Msg,
|
||||||
proposers []string, groupPolicyAddr types.AccAddress, voteOption group.VoteOption) (uint64, error) {
|
proposers []string, groupPolicyAddr sdk.AccAddress, voteOption group.VoteOption) (uint64, error) {
|
||||||
myProposalID, err := submitProposal(app, ctx, msgs, proposers, groupPolicyAddr)
|
myProposalID, err := submitProposal(app, ctx, msgs, proposers, groupPolicyAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = app.GroupKeeper.Vote(ctx, &group.MsgVote{
|
_, err = app.GroupKeeper.Vote(ctx, &group.MsgVote{
|
||||||
ProposalId: myProposalID,
|
ProposalId: myProposalID,
|
||||||
Voter: proposers[0],
|
Voter: proposers[0],
|
||||||
|
@ -228,6 +404,5 @@ func submitProposalAndVote(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return myProposalID, nil
|
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
|
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)
|
proposals := make([]*group.Proposal, 3)
|
||||||
proposers := []string{simState.Accounts[0].Address.String(), simState.Accounts[1].Address.String()}
|
proposers := []string{simState.Accounts[0].Address.String(), simState.Accounts[1].Address.String()}
|
||||||
for i := 0; i < 3; i++ {
|
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)
|
to, _ := simtypes.RandomAcc(r, simState.Accounts)
|
||||||
fromAddr := from.Address.String()
|
|
||||||
|
|
||||||
submittedAt := time.Unix(0, 0)
|
submittedAt := time.Unix(0, 0)
|
||||||
timeout := submittedAt.Add(time.Second * 1000).UTC()
|
timeout := submittedAt.Add(time.Second * 1000).UTC()
|
||||||
|
@ -86,7 +86,7 @@ func getProposals(r *rand.Rand, simState *module.SimulationState) []*group.Propo
|
||||||
proposal := &group.Proposal{
|
proposal := &group.Proposal{
|
||||||
Id: uint64(i + 1),
|
Id: uint64(i + 1),
|
||||||
Proposers: proposers,
|
Proposers: proposers,
|
||||||
Address: fromAddr,
|
Address: groupPolicyAddress,
|
||||||
GroupVersion: uint64(i + 1),
|
GroupVersion: uint64(i + 1),
|
||||||
GroupPolicyVersion: uint64(i + 1),
|
GroupPolicyVersion: uint64(i + 1),
|
||||||
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
Status: group.PROPOSAL_STATUS_SUBMITTED,
|
||||||
|
@ -103,7 +103,7 @@ func getProposals(r *rand.Rand, simState *module.SimulationState) []*group.Propo
|
||||||
VotingPeriodEnd: timeout,
|
VotingPeriodEnd: timeout,
|
||||||
}
|
}
|
||||||
err := proposal.SetMsgs([]sdk.Msg{&banktypes.MsgSend{
|
err := proposal.SetMsgs([]sdk.Msg{&banktypes.MsgSend{
|
||||||
FromAddress: fromAddr,
|
FromAddress: groupPolicyAddress,
|
||||||
ToAddress: to.Address.String(),
|
ToAddress: to.Address.String(),
|
||||||
Amount: sdk.NewCoins(sdk.NewInt64Coin("test", 10)),
|
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) },
|
func(r *rand.Rand) { members = getGroupMembers(r, simState.Accounts) },
|
||||||
)
|
)
|
||||||
|
|
||||||
// group accounts
|
// group policies
|
||||||
var groupPolicies []*group.GroupPolicyInfo
|
var groupPolicies []*group.GroupPolicyInfo
|
||||||
simState.AppParams.GetOrGenerate(
|
simState.AppParams.GetOrGenerate(
|
||||||
simState.Cdc, GroupPolicyInfo, &groupPolicies, simState.Rand,
|
simState.Cdc, GroupPolicyInfo, &groupPolicies, simState.Rand,
|
||||||
|
@ -174,7 +174,7 @@ func RandomizedGenState(simState *module.SimulationState) {
|
||||||
var proposals []*group.Proposal
|
var proposals []*group.Proposal
|
||||||
simState.AppParams.GetOrGenerate(
|
simState.AppParams.GetOrGenerate(
|
||||||
simState.Cdc, GroupProposals, &proposals, simState.Rand,
|
simState.Cdc, GroupProposals, &proposals, simState.Rand,
|
||||||
func(r *rand.Rand) { proposals = getProposals(r, simState) },
|
func(r *rand.Rand) { proposals = getProposals(r, simState, groupPolicies) },
|
||||||
)
|
)
|
||||||
|
|
||||||
// votes
|
// votes
|
||||||
|
|
|
@ -873,16 +873,6 @@ func SimulateMsgWithdrawProposal(ak group.AccountKeeper,
|
||||||
return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "no proposals found"), nil, nil
|
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
|
// select a random proposer
|
||||||
proposers := proposal.Proposers
|
proposers := proposal.Proposers
|
||||||
n := randIntInRange(r, len(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
|
return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "no proposals found"), nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var proposal *group.Proposal
|
|
||||||
proposalID := -1
|
proposalID := -1
|
||||||
|
|
||||||
for _, p := range proposals {
|
for _, p := range proposals {
|
||||||
if p.Status == group.PROPOSAL_STATUS_SUBMITTED {
|
if p.Status == group.PROPOSAL_STATUS_SUBMITTED {
|
||||||
timeout := p.VotingPeriodEnd
|
timeout := p.VotingPeriodEnd
|
||||||
proposal = p
|
|
||||||
proposalID = int(p.Id)
|
proposalID = int(p.Id)
|
||||||
if timeout.Before(sdkCtx.BlockTime()) || timeout.Equal(sdkCtx.BlockTime()) {
|
if timeout.Before(sdkCtx.BlockTime()) || timeout.Equal(sdkCtx.BlockTime()) {
|
||||||
return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "voting period ended: skipping"), nil, nil
|
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
|
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
|
// Ensure member hasn't already voted
|
||||||
res, _ := k.VoteByProposalVoter(ctx, &group.QueryVoteByProposalVoterRequest{
|
res, _ := k.VoteByProposalVoter(ctx, &group.QueryVoteByProposalVoterRequest{
|
||||||
Voter: acc.Address.String(),
|
Voter: acc.Address.String(),
|
||||||
|
|
|
@ -26,14 +26,19 @@ those "sub-accounts" using the `x/authz` module.
|
||||||
|
|
||||||
## Decision Policy
|
## Decision Policy
|
||||||
|
|
||||||
A decision policy is the mechanism by which members of a group can vote on
|
A decision policy is the mechanism by which members of a group can vote on
|
||||||
proposals.
|
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.
|
All decision policies generally would have a mininum execution perdio and a
|
||||||
The minimum voting window is the minimum amount of time that must pass in order
|
maximum voting window. The minimum execution period is the minimum amount of time
|
||||||
for a proposal to potentially pass, and it may be set to 0. The maximum voting
|
that must pass in order for a proposal to potentially be executed, and it may
|
||||||
window is the maximum time that a proposal may be voted on before it is closed.
|
be set to 0. The maximum voting window is the maximum time that a proposal may
|
||||||
Both of these values must be less than a chain-wide max voting window parameter.
|
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
|
### 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
|
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.
|
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
|
## Proposal
|
||||||
|
|
||||||
Any member of a group can submit a proposal for a group policy account to decide upon.
|
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
|
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.
|
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
|
In the current implementation, the voting window begins as soon as a proposal
|
||||||
is submitted.
|
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
|
## 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,
|
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
|
but rather a user must submit a `Msg/Exec` transaction to attempt to execute the
|
||||||
proposal based on the current votes and decision policy.
|
proposal based on the current votes and decision policy.
|
||||||
It's also possible to try to execute a proposal immediately on creation or on
|
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.
|
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
|
For now, if the proposal can't be executed, it'll still be opened for new votes and
|
||||||
could be executed later on.
|
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)
|
Proposals and votes are automatically pruned to avoid state bloat.
|
||||||
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
|
Votes are pruned:
|
||||||
eventually be garbage collected.
|
|
||||||
|
- 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:
|
`proposalByGroupPolicyIndex` allows to retrieve proposals by group policy account address:
|
||||||
`0x32 | len([]byte(account.Address)) | []byte(account.Address) | BigEndian(ProposalId) -> []byte()`.
|
`0x32 | len([]byte(account.Address)) | []byte(account.Address) | BigEndian(ProposalId) -> []byte()`.
|
||||||
|
|
||||||
### proposalByProposerIndex
|
### ProposalsByVotingPeriodEndIndex
|
||||||
|
|
||||||
`proposalByProposerIndex` allows to retrieve proposals by proposer address:
|
`proposalsByVotingPeriodEndIndex` allows to retrieve proposals sorted by chronological `voting_period_end`:
|
||||||
`0x33 | len([]byte(proposer.Address)) | []byte(proposer.Address) | BigEndian(ProposalId) -> []byte()`.
|
`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
|
## 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 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 has not been accepted.
|
||||||
* the proposal status is not closed.
|
* the proposal status is not closed.
|
||||||
* the proposal has already been successfully executed.
|
* the proposal has already been successfully executed.
|
||||||
|
|
Loading…
Reference in New Issue