-
Notifications
You must be signed in to change notification settings - Fork 352
/
committee.go
161 lines (134 loc) · 5.14 KB
/
committee.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package types
import (
"fmt"
"time"
yaml "gopkg.in/yaml.v2"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
)
const MaxCommitteeDescriptionLength int = 512
// ------------------------------------------
// Committees
// ------------------------------------------
// A Committee is a collection of addresses that are allowed to vote and enact any governance proposal that passes their permissions.
type Committee struct {
ID uint64 `json:"id" yaml:"id"`
Description string `json:"description" yaml:"description"`
Members []sdk.AccAddress `json:"members" yaml:"members"`
Permissions []Permission `json:"permissions" yaml:"permissions"`
VoteThreshold sdk.Dec `json:"vote_threshold" yaml:"vote_threshold"` // Smallest percentage of members that must vote for a proposal to pass.
ProposalDuration time.Duration `json:"proposal_duration" yaml:"proposal_duration"` // The length of time a proposal remains active for. Proposals will close earlier if they get enough votes.
}
func NewCommittee(id uint64, description string, members []sdk.AccAddress, permissions []Permission, threshold sdk.Dec, duration time.Duration) Committee {
return Committee{
ID: id,
Description: description,
Members: members,
Permissions: permissions,
VoteThreshold: threshold,
ProposalDuration: duration,
}
}
func (c Committee) HasMember(addr sdk.AccAddress) bool {
for _, m := range c.Members {
if m.Equals(addr) {
return true
}
}
return false
}
// HasPermissionsFor returns whether the committee is authorized to enact a proposal.
// As long as one permission allows the proposal then it goes through. Its the OR of all permissions.
func (c Committee) HasPermissionsFor(ctx sdk.Context, appCdc *codec.Codec, pk ParamKeeper, proposal PubProposal) bool {
for _, p := range c.Permissions {
if p.Allows(ctx, appCdc, pk, proposal) {
return true
}
}
return false
}
func (c Committee) Validate() error {
addressMap := make(map[string]bool, len(c.Members))
for _, m := range c.Members {
// check there are no duplicate members
if _, ok := addressMap[m.String()]; ok {
return fmt.Errorf("committe cannot have duplicate members, %s", m)
}
// check for valid addresses
if m.Empty() {
return fmt.Errorf("committee cannot have empty member address")
}
addressMap[m.String()] = true
}
if len(c.Members) == 0 {
return fmt.Errorf("committee cannot have zero members")
}
if len(c.Description) > MaxCommitteeDescriptionLength {
return fmt.Errorf("description length %d longer than max allowed %d", len(c.Description), MaxCommitteeDescriptionLength)
}
for _, p := range c.Permissions {
if p == nil {
return fmt.Errorf("committee cannot have a nil permission")
}
}
// threshold must be in the range (0,1]
if c.VoteThreshold.IsNil() || c.VoteThreshold.LTE(sdk.ZeroDec()) || c.VoteThreshold.GT(sdk.NewDec(1)) {
return fmt.Errorf("invalid threshold: %s", c.VoteThreshold)
}
if c.ProposalDuration < 0 {
return fmt.Errorf("invalid proposal duration: %s", c.ProposalDuration)
}
return nil
}
// ------------------------------------------
// Proposals
// ------------------------------------------
// PubProposal is the interface that all proposals must fulfill to be submitted to a committee.
// Proposal types can be created external to this module. For example a ParamChangeProposal, or CommunityPoolSpendProposal.
// It is pinned to the equivalent type in the gov module to create compatibility between proposal types.
type PubProposal govtypes.Content
// Proposal is an internal record of a governance proposal submitted to a committee.
type Proposal struct {
PubProposal `json:"pub_proposal" yaml:"pub_proposal"`
ID uint64 `json:"id" yaml:"id"`
CommitteeID uint64 `json:"committee_id" yaml:"committee_id"`
Deadline time.Time `json:"deadline" yaml:"deadline"`
}
func NewProposal(pubProposal PubProposal, id uint64, committeeID uint64, deadline time.Time) Proposal {
return Proposal{
PubProposal: pubProposal,
ID: id,
CommitteeID: committeeID,
Deadline: deadline,
}
}
// HasExpiredBy calculates if the proposal will have expired by a certain time.
// All votes must be cast before deadline, those cast at time == deadline are not valid
func (p Proposal) HasExpiredBy(time time.Time) bool {
return !time.Before(p.Deadline)
}
// String implements the fmt.Stringer interface, and importantly overrides the String methods inherited from the embedded PubProposal type.
func (p Proposal) String() string {
bz, _ := yaml.Marshal(p)
return string(bz)
}
// ------------------------------------------
// Votes
// ------------------------------------------
type Vote struct {
ProposalID uint64 `json:"proposal_id" yaml:"proposal_id"`
Voter sdk.AccAddress `json:"voter" yaml:"voter"`
}
func NewVote(proposalID uint64, voter sdk.AccAddress) Vote {
return Vote{
ProposalID: proposalID,
Voter: voter,
}
}
func (v Vote) Validate() error {
if v.Voter.Empty() {
return fmt.Errorf("voter address cannot be empty")
}
return nil
}