diff --git a/x/committee/spec/01_concepts.md b/x/committee/spec/01_concepts.md index 0a6762a4..8c6dbec9 100644 --- a/x/committee/spec/01_concepts.md +++ b/x/committee/spec/01_concepts.md @@ -6,4 +6,6 @@ order: 1 For a general introduction to governance using the Comsos-SDK, see [x/gov](https://github.com/cosmos/cosmos-sdk/blob/v0.38.3/x/gov/spec/01_concepts.md). -This module provides companion governance functionality to `x/gov` by allowing the creation of committees, or groups of addresses that can vote on proposals for which they have permission and which bypass the usual on-chain governance structures. Permissions scope the types of proposals that committees can submit and vote on. This allows for committees with unlimited breadth (ie, a committee can have permission to perform any governance action), or narrowly scoped abilities (ie, a committee can only change a single parameter of a single module within a specified range). Further, vote tallying is "first-past-the-post", so proposals can be enacted more rapidly and with greater flexibility than permitted by `x/gov`. +This module provides companion governance functionality to `x/gov` by allowing the creation of committees, or groups of addresses that can vote on proposals for which they have permission and which bypass the usual on-chain governance structures. Permissions scope the types of proposals that committees can submit and vote on. This allows for committees with unlimited breadth (ie, a committee can have permission to perform any governance action), or narrowly scoped abilities (ie, a committee can only change a single parameter of a single module within a specified range). + +Committees are either member committees governed by a set of whitelisted addresses or token committees whose votes are weighted by token balance. For example, the [Kava Stability Committee](https://medium.com/kava-labs/kava-improves-governance-enabling-faster-response-to-volatile-markets-2d0fff6e5fa9) is a member committee that has the ability to protect critical protocol infrastructure by briefly pausing certain functionality; while the Hard Token Committee allows HARD token holders to participate in governance related to HARD protocol on the Kava blockchain. Further, committees can tally votes by either the "first-past-the-post" or "deadline" tallying procedure. Committees with "first-past-the-post" vote tallying enact proposals immediately once they pass, allowing greater flexibility than permitted by `x/gov`. Committees with "deadline" vote tallying evaluate proposals at their deadline, allowing time for all stakeholders to vote before a proposal is enacted or rejected. diff --git a/x/committee/spec/02_state.md b/x/committee/spec/02_state.md index 97505500..e21ce5ac 100644 --- a/x/committee/spec/02_state.md +++ b/x/committee/spec/02_state.md @@ -18,6 +18,61 @@ order: 2 } ``` +## Committees + +Each committee conforms to the `Committee` interface and is defined as either a `MemberCommittee` or a `TokenCommittee`: + +```go +// Committee is an interface for handling common actions on committees +type Committee interface { + GetID() uint64 + GetType() string + GetDescription() string + + GetMembers() []sdk.AccAddress + SetMembers([]sdk.AccAddress) BaseCommittee + HasMember(addr sdk.AccAddress) bool + + GetPermissions() []Permission + SetPermissions([]Permission) Committee + HasPermissionsFor(ctx sdk.Context, appCdc *codec.Codec, pk ParamKeeper, proposal PubProposal) bool + + GetProposalDuration() time.Duration + SetProposalDuration(time.Duration) BaseCommittee + + GetVoteThreshold() sdk.Dec + SetVoteThreshold(sdk.Dec) BaseCommittee + + GetTallyOption() TallyOption + Validate() error +} + +// BaseCommittee is a common type shared by all Committees +type BaseCommittee 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 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. + TallyOption TallyOption `json:"tally_option" yaml:"tally_option"` +} + +// MemberCommittee is an alias of BaseCommittee +type MemberCommittee struct { + BaseCommittee `json:"base_committee" yaml:"base_committee"` +} + +// TokenCommittee supports voting on proposals by token holders +type TokenCommittee struct { + BaseCommittee `json:"base_committee" yaml:"base_committee"` + Quorum sdk.Dec `json:"quorum" yaml:"quorum"` + TallyDenom string `json:"tally_denom" yaml:"tally_denom"` +} +``` + + + ## Store For complete implementation details for how items are stored, see [keys.go](../types/keys.go). The committee module store state consists of committees, proposals, and votes. When a proposal expires or passes, the proposal and associated votes are deleted from state. diff --git a/x/committee/spec/03_messages.md b/x/committee/spec/03_messages.md index fd00a3bc..9e320645 100644 --- a/x/committee/spec/03_messages.md +++ b/x/committee/spec/03_messages.md @@ -17,22 +17,23 @@ type MsgSubmitProposal struct { ## State Modifications -* Generate new `ProposalID` -* Create new `Proposal` with deadline equal to the time that the proposal will expire. +- Generate new `ProposalID` +- Create new `Proposal` with deadline equal to the time that the proposal will expire. -Committee members vote 'yes' on a proposal using a `MsgVote` +Valid votes include 'yes', 'no', and 'abstain'. ```go // MsgVote is submitted by committee members to vote on proposals. type MsgVote struct { - ProposalID uint64 `json:"proposal_id" yaml:"proposal_id"` - Voter sdk.AccAddress `json:"voter" yaml:"voter"` + ProposalID uint64 `json:"proposal_id" yaml:"proposal_id"` + Voter sdk.AccAddress `json:"voter" yaml:"voter"` + VoteType VoteType `json:"vote_type" yaml:"vote_type"` } ``` ## State Modifications -* Create a new `Vote` -* If the proposal is over the threshold: - * Enact the proposal (proposals may cause state modifications) - * Delete the proposal and associated votes \ No newline at end of file +- Create a new `Vote` +- When the proposal is evaluated: + - Enact the proposal (passed proposals may cause state modifications) + - Delete the proposal and associated votes diff --git a/x/committee/spec/04_events.md b/x/committee/spec/04_events.md index 0411b351..9d5c8d70 100644 --- a/x/committee/spec/04_events.md +++ b/x/committee/spec/04_events.md @@ -8,27 +8,29 @@ The `x/committee` module emits the following events: ## MsgSubmitProposal -| Type | Attribute Key | Attribute Value | -|----------------------|---------------------|--------------------| -| proposal_submit | committee_id | {'committee ID}' | -| proposal_submit | proposal_id | {'proposal ID}' | -| message | module | committee | -| message | sender | {'sender address}' | +| Type | Attribute Key | Attribute Value | +| --------------- | ------------- | ------------------ | +| proposal_submit | committee_id | {'committee ID}' | +| proposal_submit | proposal_id | {'proposal ID}' | +| message | module | committee | +| message | sender | {'sender address}' | ## MsgVote -| Type | Attribute Key | Attribute Value | -|----------------------|---------------------|--------------------| -| proposal_vote | committee_id | {'committee ID}' | -| proposal_vote | proposal_id | {'proposal ID}' | -| proposal_vote | voter | {'voter address}' | -| message | module | committee | -| message | sender | {'sender address}' | +| Type | Attribute Key | Attribute Value | +| ------------- | ------------- | ------------------ | +| proposal_vote | committee_id | {'committee ID}' | +| proposal_vote | proposal_id | {'proposal ID}' | +| proposal_vote | voter | {'voter address}' | +| proposal_vote | vote | {'vote type}' | +| message | module | committee | +| message | sender | {'sender address}' | ## BeginBlock -| Type | Attribute Key | Attribute Value | -|----------------------|---------------------|--------------------| -| proposal_close | committee_id | {'committee ID}' | -| proposal_close | proposal_id | {'proposal ID}' | -| proposal_close | status | {'outcome}' | +| Type | Attribute Key | Attribute Value | +| -------------- | ---------------- | ----------------------- | +| proposal_close | committee_id | {'committee ID}' | +| proposal_close | proposal_id | {'proposal ID}' | +| proposal_close | proposal_tally | {'proposal vote tally}' | +| proposal_close | proposal_outcome | {'proposal result}' | diff --git a/x/committee/spec/06_begin_block.md b/x/committee/spec/06_begin_block.md index baeae944..0cf5fa30 100644 --- a/x/committee/spec/06_begin_block.md +++ b/x/committee/spec/06_begin_block.md @@ -4,11 +4,11 @@ order: 6 # Begin Block -At the start of each block, expired proposals are deleted. The logic is as follows: +At the start of each block, proposals are processed. Active proposals with "first-past-the-post" vote tallying are evaluated and if they meet quorum and voting threshold requirements are enacted, resulting in the deletion of the proposal and any associated votes. If a "first-past-the-post" proposal doesn't meet quorum and voting threshold requirements by its deadline it is not enacted and is deleted. Proposals with "deadline" vote tallying are evaluated at their deadline before being deleted. ```go // BeginBlocker runs at the start of every block. func BeginBlocker(ctx sdk.Context, _ abci.RequestBeginBlock, k Keeper) { - k.CloseExpiredProposals(ctx) + k.ProcessProposals(ctx) } ```