From 7521bcc5f8dffcb37d605f480625548f0ba81a00 Mon Sep 17 00:00:00 2001 From: likhita-809 <78951027+likhita-809@users.noreply.github.com> Date: Tue, 4 Jan 2022 15:03:00 +0530 Subject: [PATCH] docs: Add docs for group module (#10800) ## Description Closes: #9902 Add docs for group module --- ### 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) --- x/group/spec/01_concepts.md | 74 ++ x/group/spec/02_state.md | 103 +++ x/group/spec/03_messages.md | 106 +++ x/group/spec/04_events.md | 56 ++ x/group/spec/05_client.md | 1428 +++++++++++++++++++++++++++++++++++ x/group/spec/README.md | 54 ++ 6 files changed, 1821 insertions(+) create mode 100644 x/group/spec/01_concepts.md create mode 100644 x/group/spec/02_state.md create mode 100644 x/group/spec/03_messages.md create mode 100644 x/group/spec/04_events.md create mode 100644 x/group/spec/05_client.md create mode 100644 x/group/spec/README.md diff --git a/x/group/spec/01_concepts.md b/x/group/spec/01_concepts.md new file mode 100644 index 000000000000..8400a2eec017 --- /dev/null +++ b/x/group/spec/01_concepts.md @@ -0,0 +1,74 @@ + + +# Concepts + +## Group + +A group is simply an aggregation of accounts with associated weights. It is not +an account and doesn't have a balance. It doesn't in and of itself have any +sort of voting or decision weight. It does have an "administrator" which has +the ability to add, remove and update members in the group. Note that a +group account could be an administrator of a group. + +## Group Account + +A group account is an account associated with a group and a decision policy. +Group accounts are abstracted from groups because a single group may have +multiple decision policies for different types of actions. Managing group +membership separately from decision policies results in the least overhead +and keeps membership consistent across different policies. The pattern that +is recommended is to have a single master group account for a given group, +and then to create separate group accounts with different decision policies +and delegate the desired permissions from the master account to +those "sub-accounts" using the `x/authz` module. + +## Decision Policy + +A decision policy is the mechanism by which members of a group can vote on +proposals. + +All decision policies generally would have a minimum and maximum voting window. +The minimum voting window is the minimum amount of time that must pass in order +for a proposal to potentially pass, and it may be set to 0. The maximum voting +window is the maximum time that a proposal may be voted on before it is closed. +Both of these values must be less than a chain-wide max voting window parameter. + +### Threshold decision policy + +A threshold decision policy defines a threshold of yes votes (based on a tally +of voter weights) that must be achieved in order for a proposal to pass. For +this decision policy, abstain and veto are simply treated as no's. + +## Proposal + +Any member of a group can submit a proposal for a group account to decide upon. +A proposal consists of a set of messages that will be executed if the proposal +passes as well as any metadata associated with the proposal. + +## Voting + +There are four choices to choose while voting - yes, no, abstain and veto. Not +all decision policies will support them. Votes can contain some optional metadata. +During the voting window, accounts that have already voted may change their vote. +In the current implementation, the voting window begins as soon as a proposal +is submitted. + +## Executing Proposals + +Proposals will not be automatically executed by the chain in this current design, +but rather a user must submit a `Msg/Exec` transaction to attempt to execute the +proposal based on the current votes and decision policy. +It's also possible to try to execute a proposal immediately on creation or on +new votes using the `Exec` field of `Msg/CreateProposal` and `Msg/Vote` requests. +In the former case, proposers signatures are considered as yes votes. +For now, if the proposal can't be executed, it'll still be opened for new votes and +could be executed later on. + +### Changing Group Membership + +In the current implementation, changing a group's membership (adding or removing members or changing their weight) +will cause all existing proposals for group accounts linked to this group +to be invalidated. They will simply fail if someone calls `Msg/Exec` and will +eventually be garbage collected. \ No newline at end of file diff --git a/x/group/spec/02_state.md b/x/group/spec/02_state.md new file mode 100644 index 000000000000..4b222a93a8a1 --- /dev/null +++ b/x/group/spec/02_state.md @@ -0,0 +1,103 @@ + + +# State + +The `group` module uses the `orm` package which provides table storage with support for +primary keys and secondary indexes. `orm` also defines `Sequence` which is a persistent unique key generator based on a counter that can be used along with `Table`s. + +Here's the list of tables and associated sequences and indexes stored as part of the `group` module. + +## Group Table + +The `groupTable` stores `GroupInfo`: `0x0 | BigEndian(GroupId) -> ProtocolBuffer(GroupInfo)`. + +### groupSeq + +The value of `groupSeq` is incremented when creating a new group and corresponds to the new `GroupId`: `0x1 | 0x1 -> BigEndian`. + +The second `0x1` corresponds to the ORM `sequenceStorageKey`. + +### groupByAdminIndex + +`groupByAdminIndex` allows to retrieve groups by admin address: +`0x2 | len([]byte(group.Admin)) | []byte(group.Admin) | BigEndian(GroupId) -> []byte()`. + +## Group Member Table + +The `groupMemberTable` stores `GroupMember`s: `0x10 | BigEndian(GroupId) | []byte(member.Address) -> ProtocolBuffer(GroupMember)`. + +The `groupMemberTable` is a primary key table and its `PrimaryKey` is given by +`BigEndian(GroupId) | []byte(member.Address)` which is used by the following indexes. + +### groupMemberByGroupIndex + +`groupMemberByGroupIndex` allows to retrieve group members by group id: +`0x11 | BigEndian(GroupId) | PrimaryKey -> []byte()`. + +### groupMemberByMemberIndex + +`groupMemberByMemberIndex` allows to retrieve group members by member address: +`0x12 | len([]byte(member.Address)) | []byte(member.Address) | PrimaryKey -> []byte()`. + +## Group Account Table + +The `groupAccountTable` stores `GroupAccountInfo`: `0x20 | len([]byte(Address)) | []byte(Address) -> ProtocolBuffer(GroupAccountInfo)`. + +The `groupAccountTable` is a primary key table and its `PrimaryKey` is given by +`len([]byte(Address)) | []byte(Address)` which is used by the following indexes. + +### groupAccountSeq + +The value of `groupAccountSeq` is incremented when creating a new group account and is used to generate the new group account `Address`: +`0x21 | 0x1 -> BigEndian`. + +The second `0x1` corresponds to the ORM `sequenceStorageKey`. + +### groupAccountByGroupIndex + +`groupAccountByGroupIndex` allows to retrieve group accounts by group id: +`0x22 | BigEndian(GroupId) | PrimaryKey -> []byte()`. + +### groupAccountByAdminIndex + +`groupAccountByAdminIndex` allows to retrieve group accounts by admin address: +`0x23 | len([]byte(Address)) | []byte(Address) | PrimaryKey -> []byte()`. + +## Proposal Table + +The `proposalTable` stores `Proposal`s: `0x30 | BigEndian(ProposalId) -> ProtocolBuffer(Proposal)`. + +### proposalSeq + +The value of `proposalSeq` is incremented when creating a new proposal and corresponds to the new `ProposalId`: `0x31 | 0x1 -> BigEndian`. + +The second `0x1` corresponds to the ORM `sequenceStorageKey`. + +### proposalByGroupAccountIndex + +`proposalByGroupAccountIndex` allows to retrieve proposals by group account address: +`0x32 | len([]byte(account.Address)) | []byte(account.Address) | BigEndian(ProposalId) -> []byte()`. + +### proposalByProposerIndex + +`proposalByProposerIndex` allows to retrieve proposals by proposer address: +`0x33 | len([]byte(proposer.Address)) | []byte(proposer.Address) | BigEndian(ProposalId) -> []byte()`. + +## Vote Table + +The `voteTable` stores `Vote`s: `0x40 | BigEndian(ProposalId) | []byte(voter.Address) -> ProtocolBuffer(Vote)`. + +The `voteTable` is a primary key table and its `PrimaryKey` is given by +`BigEndian(ProposalId) | []byte(voter.Address)` which is used by the following indexes. + +### voteByProposalIndex + +`voteByProposalIndex` allows to retrieve votes by proposal id: +`0x41 | BigEndian(ProposalId) | PrimaryKey -> []byte()`. + +### voteByVoterIndex + +`voteByVoterIndex` allows to retrieve votes by voter address: +`0x42 | len([]byte(voter.Address)) | []byte(voter.Address) | PrimaryKey -> []byte()`. diff --git a/x/group/spec/03_messages.md b/x/group/spec/03_messages.md new file mode 100644 index 000000000000..387f9145d055 --- /dev/null +++ b/x/group/spec/03_messages.md @@ -0,0 +1,106 @@ + + +# Msg Service + +## Msg/CreateGroup + +A new group can be created with the `MsgCreateGroup`, which has an admin address, a list of members and some optional metadata bytes. + ++++ https://github.com/cosmos/cosmos-sdk/blob/6f58963e7f6ce820e9b33f02f06f7b96f6d2e347/proto/cosmos/group/v1beta1/tx.proto#L54-L65 + +It's expecting to fail if metadata length is greater than some `MaxMetadataLength`. + +## Msg/UpdateGroupMembers + +Group members can be updated with the `UpdateGroupMembers`. + ++++ https://github.com/cosmos/cosmos-sdk/blob/6f58963e7f6ce820e9b33f02f06f7b96f6d2e347/proto/cosmos/group/v1beta1/tx.proto#L74-L86 + +In the list of `MemberUpdates`, an existing member can be removed by setting its weight to 0. + +It's expecting to fail if the signer is not the admin of the group. + +## Msg/UpdateGroupAdmin + +The `UpdateGroupAdmin` can be used to update a group admin. + ++++ https://github.com/cosmos/cosmos-sdk/blob/6f58963e7f6ce820e9b33f02f06f7b96f6d2e347/proto/cosmos/group/v1beta1/tx.proto#L91-L102 + +It's expecting to fail if the signer is not the admin of the group. + +## Msg/UpdateGroupMetadata + +The `UpdateGroupMetadata` can be used to update a group metadata. + ++++ https://github.com/cosmos/cosmos-sdk/blob/6f58963e7f6ce820e9b33f02f06f7b96f6d2e347/proto/cosmos/group/v1beta1/tx.proto#L107-L118 + +It's expecting to fail if: +- new metadata length is greater than some `MaxMetadataLength`. +- the signer is not the admin of the group. + +## Msg/CreateGroupAccount + +A new group account can be created with the `MsgCreateGroupAccount`, which has an admin address, a group id, a decision policy and some optional metadata bytes. + ++++ https://github.com/cosmos/cosmos-sdk/blob/6f58963e7f6ce820e9b33f02f06f7b96f6d2e347/proto/cosmos/group/v1beta1/tx.proto#L121-L142 + +It's expecting to fail if metadata length is greater than some `MaxMetadataLength`. + +## Msg/UpdateGroupAccountAdmin + +The `UpdateGroupAccountAdmin` can be used to update a group account admin. + ++++ https://github.com/cosmos/cosmos-sdk/blob/6f58963e7f6ce820e9b33f02f06f7b96f6d2e347/proto/cosmos/group/v1beta1/tx.proto#L151-L162 + +It's expecting to fail if the signer is not the admin of the group account. + +## Msg/UpdateGroupAccountDecisionPolicy + +The `UpdateGroupAccountDecisionPolicy` can be used to update a decision policy. + ++++ https://github.com/cosmos/cosmos-sdk/blob/6f58963e7f6ce820e9b33f02f06f7b96f6d2e347/proto/cosmos/group/v1beta1/tx.proto#L167-L179 + +It's expecting to fail if the signer is not the admin of the group account. + +## Msg/UpdateGroupAccountMetadata + +The `UpdateGroupAccountMetadata` can be used to update a group account metadata. + ++++ https://github.com/cosmos/cosmos-sdk/blob/6f58963e7f6ce820e9b33f02f06f7b96f6d2e347/proto/cosmos/group/v1beta1/tx.proto#L184-L195 + +It's expecting to fail if: +- new metadata length is greater than some `MaxMetadataLength`. +- the signer is not the admin of the group. + +## Msg/CreateProposal + +A new group account can be created with the `MsgCreateProposal`, which has a group account address, a list of proposers addresses, a list of messages to execute if the proposal is accepted and some optional metadata bytes. +An optional `Exec` value can be provided to try to execute the proposal immediately after proposal creation. Proposers signatures are considered as yes votes in this case. + ++++ https://github.com/cosmos/cosmos-sdk/blob/6f58963e7f6ce820e9b33f02f06f7b96f6d2e347/proto/cosmos/group/v1beta1/tx.proto#L218-L239 + +It's expecting to fail if metadata length is greater than some `MaxMetadataLength`. + +## Msg/Vote + +A new vote can be created with the `MsgVote`, given a proposal id, a voter address, a choice (yes, no, veto or abstain) and some optional metadata bytes. +An optional `Exec` value can be provided to try to execute the proposal immediately after voting. + ++++ https://github.com/cosmos/cosmos-sdk/blob/6f58963e7f6ce820e9b33f02f06f7b96f6d2e347/proto/cosmos/group/v1beta1/tx.proto#L248-L265 + +It's expecting to fail if metadata length is greater than some `MaxMetadataLength`. + +## Msg/Exec + +A proposal can be executed with the `MsgExec`. + ++++ https://github.com/cosmos/cosmos-sdk/blob/6f58963e7f6ce820e9b33f02f06f7b96f6d2e347/proto/cosmos/group/v1beta1/tx.proto#L270-L278 + +The messages that are part of this proposal won't be executed if: +- the group has been modified before tally. +- the group account has been modified before tally. +- the proposal has not been accepted. +- the proposal status is not closed. +- the proposal has already been successfully executed. \ No newline at end of file diff --git a/x/group/spec/04_events.md b/x/group/spec/04_events.md new file mode 100644 index 000000000000..8607a511e986 --- /dev/null +++ b/x/group/spec/04_events.md @@ -0,0 +1,56 @@ + + +# Events + +The group module emits the following events: + +## EventCreateGroup + +| Type | Attribute Key | Attribute Value | +|---------------------------------------|---------------|---------------------------------------| +| message | action | /cosmos.group.v1beta1.Msg/CreateGroup | +| cosmos.group.v1beta1.EventCreateGroup | group_id | {groupId} | + +## EventUpdateGroup + +| Type | Attribute Key | Attribute Value | +|---------------------------------------|---------------|-----------------------------------------------------------------| +| message | action | /cosmos.group.v1beta1.Msg/UpdateGroup{Admin\|Metadata\|Members} | +| cosmos.group.v1beta1.EventUpdateGroup | group_id | {groupId} | + +## EventCreateGroupAccount + +| Type | Attribute Key | Attribute Value | +|----------------------------------------------|---------------|----------------------------------------------| +| message | action | /cosmos.group.v1beta1.Msg/CreateGroupAccount | +| cosmos.group.v1beta1.EventCreateGroupAccount | address | {groupAccountAddress} | + +## EventUpdateGroupAccount + +| Type | Attribute Key | Attribute Value | +|----------------------------------------------|---------------|-------------------------------------------------------------------------------| +| message | action | /cosmos.group.v1beta1.Msg/UpdateGroupAccount{Admin\|Metadata\|DecisionPolicy} | +| cosmos.group.v1beta1.EventUpdateGroupAccount | address | {groupAccountAddress} | + +## EventCreateProposal + +| Type | Attribute Key | Attribute Value | +|------------------------------------------|---------------|------------------------------------------| +| message | action | /cosmos.group.v1beta1.Msg/CreateProposal | +| cosmos.group.v1beta1.EventCreateProposal | proposal_id | {proposalId} | + +## EventVote + +| Type | Attribute Key | Attribute Value | +|--------------------------------|---------------|--------------------------------| +| message | action | /cosmos.group.v1beta1.Msg/Vote | +| cosmos.group.v1beta1.EventVote | proposal_id | {proposalId} | + +## EventExec + +| Type | Attribute Key | Attribute Value | +|--------------------------------|---------------|--------------------------------| +| message | action | /cosmos.group.v1beta1.Msg/Exec | +| cosmos.group.v1beta1.EventExec | proposal_id | {proposalId} | \ No newline at end of file diff --git a/x/group/spec/05_client.md b/x/group/spec/05_client.md new file mode 100644 index 000000000000..d5ea498cec03 --- /dev/null +++ b/x/group/spec/05_client.md @@ -0,0 +1,1428 @@ + + +# Client + +## CLI + +A user can query and interact with the `group` module using the CLI. + +### Query + +The `query` commands allow users to query `group` state. + +```bash +simd query group --help +``` + +#### group-info + +The `group-info` command allows users to query for group info by given group id. + +```bash +simd query group group-info [id] [flags] +``` + +Example: + +```bash +simd query group group-info 1 +``` + +Example Output: + +```bash +admin: cosmos1.. +group_id: "1" +metadata: AQ== +total_weight: "3" +version: "1" +``` + +#### group-account-info + +The `group-account-info` command allows users to query for group account info by group account address. + +```bash +simd query group group-account-info [group-account] [flags] +``` + +Example: + +```bash +simd query group group-account-info cosmos1.. +``` + +Example Output: + +```bash +address: cosmos1.. +admin: cosmos1.. +decision_policy: + '@type': /cosmos.group.v1beta1.ThresholdDecisionPolicy + threshold: "1" + timeout: 600s +group_id: "1" +metadata: AQ== +version: "1" +``` + +#### group-members + +The `group-members` command allows users to query for group members by group id with pagination flags. + +```bash +simd query group group-members [id] [flags] +``` + +Example: + +```bash +simd query group group-members 1 +``` + +Example Output: + +```bash +members: +- group_id: "1" + member: + address: cosmos1.. + metadata: AQ== + weight: "2" +- group_id: "1" + member: + address: cosmos1.. + metadata: AQ== + weight: "1" +pagination: + next_key: null + total: "2" +``` + +#### groups-by-admin + +The `groups-by-admin` command allows users to query for groups by admin account address with pagination flags. + +```bash +simd query group groups-by-admin [admin] [flags] +``` + +Example: + +```bash +simd query group groups-by-admin cosmos1.. +``` + +Example Output: + +```bash +groups: +- admin: cosmos1.. + group_id: "1" + metadata: AQ== + total_weight: "3" + version: "1" +- admin: cosmos1.. + group_id: "2" + metadata: AQ== + total_weight: "3" + version: "1" +pagination: + next_key: null + total: "2" +``` + +#### group-accounts-by-group + +The `group-accounts-by-group` command allows users to query for group accounts by group id with pagination flags. + +```bash +simd query group group-accounts-by-group [group-id] [flags] +``` + +Example: + +```bash +simd query group group-accounts-by-group 1 +``` + +Example Output: + +```bash +group_accounts: +- address: cosmos1.. + admin: cosmos1.. + decision_policy: + '@type': /cosmos.group.v1beta1.ThresholdDecisionPolicy + threshold: "1" + timeout: 600s + group_id: "1" + metadata: AQ== + version: "1" +- address: cosmos1.. + admin: cosmos1.. + decision_policy: + '@type': /cosmos.group.v1beta1.ThresholdDecisionPolicy + threshold: "1" + timeout: 600s + group_id: "1" + metadata: AQ== + version: "1" +pagination: + next_key: null + total: "2" +``` + +#### group-accounts-by-admin + +The `group-accounts-by-admin` command allows users to query for group accounts by admin account address with pagination flags. + +```bash +simd query group group-accounts-by-admin [admin] [flags] +``` + +Example: + +```bash +simd query group group-accounts-by-admin cosmos1.. +``` + +Example Output: + +```bash +group_accounts: +- address: cosmos1.. + admin: cosmos1.. + decision_policy: + '@type': /cosmos.group.v1beta1.ThresholdDecisionPolicy + threshold: "1" + timeout: 600s + group_id: "1" + metadata: AQ== + version: "1" +- address: cosmos1.. + admin: cosmos1.. + decision_policy: + '@type': /cosmos.group.v1beta1.ThresholdDecisionPolicy + threshold: "1" + timeout: 600s + group_id: "1" + metadata: AQ== + version: "1" +pagination: + next_key: null + total: "2" +``` + +#### proposal + +The `proposal` command allows users to query for proposal by id. + +```bash +simd query group proposal [id] [flags] +``` + +Example: + +```bash +simd query group proposal 1 +``` + +Example Output: + +```bash +proposal: + address: cosmos1.. + executor_result: EXECUTOR_RESULT_NOT_RUN + group_account_version: "1" + group_version: "1" + metadata: AQ== + msgs: + - '@type': /cosmos.bank.v1beta1.MsgSend + amount: + - amount: "100000000" + denom: stake + from_address: cosmos1.. + to_address: cosmos1.. + proposal_id: "1" + proposers: + - cosmos1.. + result: RESULT_UNFINALIZED + status: STATUS_SUBMITTED + submitted_at: "2021-12-17T07:06:26.310638964Z" + timeout: "2021-12-17T07:06:27.310638964Z" + vote_state: + abstain_count: "0" + no_count: "0" + veto_count: "0" + yes_count: "0" +``` + +#### proposals-by-group-account + +The `proposals-by-group-account` command allows users to query for proposals by group account address with pagination flags. + +```bash +simd query group proposals-by-group-account [group-account] [flags] +``` + +Example: + +```bash +simd query group proposals-by-group-account cosmos1.. +``` + +Example Output: + +```bash +pagination: + next_key: null + total: "1" +proposals: +- address: cosmos1.. + executor_result: EXECUTOR_RESULT_NOT_RUN + group_account_version: "1" + group_version: "1" + metadata: AQ== + msgs: + - '@type': /cosmos.bank.v1beta1.MsgSend + amount: + - amount: "100000000" + denom: stake + from_address: cosmos1.. + to_address: cosmos1.. + proposal_id: "1" + proposers: + - cosmos1.. + result: RESULT_UNFINALIZED + status: STATUS_SUBMITTED + submitted_at: "2021-12-17T07:06:26.310638964Z" + timeout: "2021-12-17T07:06:27.310638964Z" + vote_state: + abstain_count: "0" + no_count: "0" + veto_count: "0" + yes_count: "0" +``` + +#### vote + +The `vote` command allows users to query for vote by proposal id and voter account address. + +```bash +simd query group vote [proposal-id] [voter] [flags] +``` + +Example: + +```bash +simd query group vote 1 cosmos1.. +``` + +Example Output: + +```bash +vote: + choice: CHOICE_YES + metadata: AQ== + proposal_id: "1" + submitted_at: "2021-12-17T08:05:02.490164009Z" + voter: cosmos1.. +``` + +#### votes-by-proposal + +The `votes-by-proposal` command allows users to query for votes by proposal id with pagination flags. + +```bash +simd query group votes-by-proposal [proposal-id] [flags] +``` + +Example: + +```bash +simd query group votes-by-proposal 1 +``` + +Example Output: + +```bash +pagination: + next_key: null + total: "1" +votes: +- choice: CHOICE_YES + metadata: AQ== + proposal_id: "1" + submitted_at: "2021-12-17T08:05:02.490164009Z" + voter: cosmos1.. +``` + +#### votes-by-voter + +The `votes-by-voter` command allows users to query for votes by voter account address with pagination flags. + +```bash +simd query group votes-by-voter [voter] [flags] +``` + +Example: + +```bash +simd query group votes-by-voter cosmos1.. +``` + +Example Output: + +```bash +pagination: + next_key: null + total: "1" +votes: +- choice: CHOICE_YES + metadata: AQ== + proposal_id: "1" + submitted_at: "2021-12-17T08:05:02.490164009Z" + voter: cosmos1.. +``` + +### Transactions + +The `tx` commands allow users to interact with the `group` module. + +```bash +simd tx group --help +``` + +#### create-group + +The `create-group` command allows users to create a group which is an aggregation of member accounts with associated weights and +an administrator account. + +```bash +simd tx group create-group [admin] [metadata] [members-json-file] +``` + +Example: + +```bash +simd tx group create-group cosmos1.. "AQ==" members.json +``` + +#### update-group-admin + +The `update-group-admin` command allows users to update a group's admin. + +```bash +simd tx group update-group-admin [admin] [group-id] [new-admin] [flags] +``` + +Example: + +```bash +simd tx group update-group-admin cosmos1.. 1 cosmos1.. +``` + +#### update-group-members + +The `update-group-members` command allows users to update a group's members. + +```bash +simd tx group update-group-members [admin] [group-id] [members-json-file] [flags] +``` + +Example: + +```bash +simd tx group update-group-members cosmos1.. 1 members.json +``` + +#### update-group-metadata + +The `update-group-metadata` command allows users to update a group's metadata. + +```bash +simd tx group update-group-metadata [admin] [group-id] [metadata] [flags] +``` + +Example: + +```bash +simd tx group update-group-metadata cosmos1.. 1 "AQ==" +``` + +#### create-group-account + +The `create-group-account` command allows users to create a group account which is an account associated with a group and a decision policy. + +```bash +simd tx group create-group-account [admin] [group-id] [metadata] [decision-policy] [flags] +``` + +Example: + +```bash +simd tx group create-group-account cosmos1.. 1 "AQ==" '{"@type":"/cosmos.group.v1beta1.ThresholdDecisionPolicy", "threshold":"1", "timeout":"600s"}' +``` + +#### update-group-account-admin + +The `update-group-account-admin` command allows users to update a group account admin. + +```bash +simd tx group update-group-account-admin [admin] [group-account] [new-admin] [flags] +``` + +Example: + +```bash +simd tx group update-group-account-admin cosmos1.. cosmos1.. cosmos1.. +``` + +#### update-group-account-metadata + +The `update-group-account-metadata` command allows users to update a group account metadata. + +```bash +simd tx group update-group-account-metadata [admin] [group-account] [new-metadata] [flags] +``` + +Example: + +```bash +simd tx group update-group-account-metadata cosmos1.. cosmos1.. "AQ==" +``` + +#### update-group-account-policy + +The `update-group-account-policy` command allows users to update a group account decision policy. + +```bash +simd tx group update-group-account-policy [admin] [group-account] [decision-policy] [flags] +``` + +Example: + +```bash +simd tx group update-group-account-policy cosmos1.. cosmos1.. '{"@type":"/cosmos.group.v1beta1.ThresholdDecisionPolicy", "threshold":"2", "timeout":"1000s"}' +``` + +#### create-proposal + +The `create-proposal` command allows users to submit a new proposal. + +```bash +simd tx group create-proposal [group-account] [proposer[,proposer]*] [msg_tx_json_file] [metadata] [flags] +``` + +Example: + +```bash +simd tx group create-proposal cosmos1.. cosmos1.. msg_tx.json "AQ==" +``` + +#### vote + +The `vote` command allows users to vote on a proposal. + +```bash +simd tx group vote proposal-id] [voter] [choice] [metadata] [flags] +``` + +Example: + +```bash +simd tx group vote 1 cosmos1.. CHOICE_YES "AQ==" +``` + +#### exec + +The `exec` command allows users to execute a proposal. + +```bash +simd tx group exec [proposal-id] [flags] +``` + +Example: + +```bash +simd tx group exec 1 +``` + +## gRPC + +A user can query the `group` module using gRPC endpoints. + +### GroupInfo + +The `GroupInfo` endpoint allows users to query for group info by given group id. + +```bash +cosmos.group.v1beta1.Query/GroupInfo +``` + +Example: + +```bash +grpcurl -plaintext \ + -d '{"group_id":1}' localhost:9090 cosmos.group.v1beta1.Query/GroupInfo +``` + +Example Output: + +```bash +{ + "info": { + "groupId": "1", + "admin": "cosmos1..", + "metadata": "AQ==", + "version": "1", + "totalWeight": "3" + } +} +``` + +### GroupAccountInfo + +The `GroupAccountInfo` endpoint allows users to query for group account info by group account address. + +```bash +cosmos.group.v1beta1.Query/GroupAccountInfo +``` + +Example: + +```bash +grpcurl -plaintext \ + -d '{"address":"cosmos1.."}' localhost:9090 cosmos.group.v1beta1.Query/GroupAccountInfo +``` + +Example Output: + +```bash +{ + "info": { + "address": "cosmos1..", + "groupId": "1", + "admin": "cosmos1..", + "version": "1", + "decisionPolicy": {"@type":"/cosmos.group.v1beta1.ThresholdDecisionPolicy","threshold":"1","timeout":"600s"}, + } +} +``` + +### GroupMembers + +The `GroupMembers` endpoint allows users to query for group members by group id with pagination flags. + +```bash +cosmos.group.v1beta1.Query/GroupMembers +``` + +Example: + +```bash +grpcurl -plaintext \ + -d '{"group_id":"1"}' localhost:9090 cosmos.group.v1beta1.Query/GroupMembers +``` + +Example Output: + +```bash +{ + "members": [ + { + "groupId": "1", + "member": { + "address": "cosmos1..", + "weight": "1" + } + }, + { + "groupId": "1", + "member": { + "address": "cosmos1..", + "weight": "2" + } + } + ], + "pagination": { + "total": "2" + } +} +``` + +### GroupsByAdmin + +The `GroupsByAdmin` endpoint allows users to query for groups by admin account address with pagination flags. + +```bash +cosmos.group.v1beta1.Query/GroupsByAdmin +``` + +Example: + +```bash +grpcurl -plaintext \ + -d '{"admin":"cosmos1.."}' localhost:9090 cosmos.group.v1beta1.Query/GroupsByAdmin +``` + +Example Output: + +```bash +{ + "groups": [ + { + "groupId": "1", + "admin": "cosmos1..", + "metadata": "AQ==", + "version": "1", + "totalWeight": "3" + }, + { + "groupId": "2", + "admin": "cosmos1..", + "metadata": "AQ==", + "version": "1", + "totalWeight": "3" + } + ], + "pagination": { + "total": "2" + } +} +``` + +### GroupAccountsByGroup + +The `GroupAccountsByGroup` endpoint allows users to query for group accounts by group id with pagination flags. + +```bash +cosmos.group.v1beta1.Query/GroupAccountsByGroup +``` + +Example: + +```bash +grpcurl -plaintext \ + -d '{"group_id":"1"}' localhost:9090 cosmos.group.v1beta1.Query/GroupAccountsByGroup +``` + +Example Output: + +```bash +{ + "groupAccounts": [ + { + "address": "cosmos1..", + "groupId": "1", + "admin": "cosmos1..", + "version": "1", + "decisionPolicy": {"@type":"/cosmos.group.v1beta1.ThresholdDecisionPolicy","threshold":"1","timeout":"600s"}, + }, + { + "address": "cosmos1..", + "groupId": "1", + "admin": "cosmos1..", + "version": "1", + "decisionPolicy": {"@type":"/cosmos.group.v1beta1.ThresholdDecisionPolicy","threshold":"1","timeout":"600s"}, + } + ], + "pagination": { + "total": "2" + } +} +``` + +### GroupAccountsByAdmin + +The `GroupAccountsByAdmin` endpoint allows users to query for group accounts by admin account address with pagination flags. + +```bash +cosmos.group.v1beta1.Query/GroupAccountsByAdmin +``` + +Example: + +```bash +grpcurl -plaintext \ + -d '{"admin":"cosmos1.."}' localhost:9090 cosmos.group.v1beta1.Query/GroupAccountsByAdmin +``` + +Example Output: + +```bash +{ + "groupAccounts": [ + { + "address": "cosmos1..", + "groupId": "1", + "admin": "cosmos1..", + "version": "1", + "decisionPolicy": {"@type":"/cosmos.group.v1beta1.ThresholdDecisionPolicy","threshold":"1","timeout":"600s"}, + }, + { + "address": "cosmos1..", + "groupId": "1", + "admin": "cosmos1..", + "version": "1", + "decisionPolicy": {"@type":"/cosmos.group.v1beta1.ThresholdDecisionPolicy","threshold":"1","timeout":"600s"}, + } + ], + "pagination": { + "total": "2" + } +} +``` + +### Proposal + +The `Proposal` endpoint allows users to query for proposal by id. + +```bash +cosmos.group.v1beta1.Query/Proposal +``` + +Example: + +```bash +grpcurl -plaintext \ + -d '{"proposal_id":"1"}' localhost:9090 cosmos.group.v1beta1.Query/Proposal +``` + +Example Output: + +```bash +{ + "proposal": { + "proposalId": "1", + "address": "cosmos1..", + "proposers": [ + "cosmos1.." + ], + "submittedAt": "2021-12-17T07:06:26.310638964Z", + "groupVersion": "1", + "groupAccountVersion": "1", + "status": "STATUS_SUBMITTED", + "result": "RESULT_UNFINALIZED", + "voteState": { + "yesCount": "0", + "noCount": "0", + "abstainCount": "0", + "vetoCount": "0" + }, + "timeout": "2021-12-17T07:06:27.310638964Z", + "executorResult": "EXECUTOR_RESULT_NOT_RUN", + "msgs": [ + {"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"stake","amount":"100000000"}],"fromAddress":"cosmos1..","toAddress":"cosmos1.."} + ] + } +} +``` + +### ProposalsByGroupAccount + +The `ProposalsByGroupAccount` endpoint allows users to query for proposals by group account address with pagination flags. + +```bash +cosmos.group.v1beta1.Query/ProposalsByGroupAccount +``` + +Example: + +```bash +grpcurl -plaintext \ + -d '{"address":"cosmos1.."}' localhost:9090 cosmos.group.v1beta1.Query/ProposalsByGroupAccount +``` + +Example Output: + +```bash +{ + "proposals": [ + { + "proposalId": "1", + "address": "cosmos1..", + "proposers": [ + "cosmos1.." + ], + "submittedAt": "2021-12-17T08:03:27.099649352Z", + "groupVersion": "1", + "groupAccountVersion": "1", + "status": "STATUS_CLOSED", + "result": "RESULT_ACCEPTED", + "voteState": { + "yesCount": "1", + "noCount": "0", + "abstainCount": "0", + "vetoCount": "0" + }, + "timeout": "2021-12-17T08:13:27.099649352Z", + "executorResult": "EXECUTOR_RESULT_NOT_RUN", + "msgs": [ + {"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"stake","amount":"100000000"}],"fromAddress":"cosmos1..","toAddress":"cosmos1.."} + ] + } + ], + "pagination": { + "total": "1" + } +} +``` + +### VoteByProposalVoter + +The `VoteByProposalVoter` endpoint allows users to query for vote by proposal id and voter account address. + +```bash +cosmos.group.v1beta1.Query/VoteByProposalVoter +``` + +Example: + +```bash +grpcurl -plaintext \ + -d '{"proposal_id":"1","voter":"cosmos1.."}' localhost:9090 cosmos.group.v1beta1.Query/VoteByProposalVoter +``` + +Example Output: + +```bash +{ + "vote": { + "proposalId": "1", + "voter": "cosmos1..", + "choice": "CHOICE_YES", + "submittedAt": "2021-12-17T08:05:02.490164009Z" + } +} +``` + +### VotesByProposal + +The `VotesByProposal` endpoint allows users to query for votes by proposal id with pagination flags. + +```bash +cosmos.group.v1beta1.Query/VotesByProposal +``` + +Example: + +```bash +grpcurl -plaintext \ + -d '{"proposal_id":"1"}' localhost:9090 cosmos.group.v1beta1.Query/VotesByProposal +``` + +Example Output: + +```bash +{ + "votes": [ + { + "proposalId": "1", + "voter": "cosmos1..", + "choice": "CHOICE_YES", + "submittedAt": "2021-12-17T08:05:02.490164009Z" + } + ], + "pagination": { + "total": "1" + } +} +``` + +### VotesByVoter + +The `VotesByVoter` endpoint allows users to query for votes by voter account address with pagination flags. + +```bash +cosmos.group.v1beta1.Query/VotesByVoter +``` + +Example: + +```bash +grpcurl -plaintext \ + -d '{"voter":"cosmos1.."}' localhost:9090 cosmos.group.v1beta1.Query/VotesByVoter +``` + +Example Output: + +```bash +{ + "votes": [ + { + "proposalId": "1", + "voter": "cosmos1..", + "choice": "CHOICE_YES", + "submittedAt": "2021-12-17T08:05:02.490164009Z" + } + ], + "pagination": { + "total": "1" + } +} +``` + +## REST + +A user can query the `group` module using REST endpoints. + +### GroupInfo + +The `GroupInfo` endpoint allows users to query for group info by given group id. + +```bash +/cosmos/group/v1beta1/group_info/{group_id} +``` + +Example: + +```bash +curl localhost:1317/cosmos/group/v1beta1/group_info/1 +``` + +Example Output: + +```bash +{ + "info": { + "group_id": "1", + "admin": "cosmos1..", + "metadata": "AQ==", + "version": "1", + "total_weight": "3" + } +} +``` + +### GroupAccountInfo + +The `GroupAccountInfo` endpoint allows users to query for group account info by group account address. + +```bash +/cosmos/group/v1beta1/group_account_info/{address} +``` + +Example: + +```bash +curl localhost:1317/cosmos/group/v1beta1/group_account_info/cosmos1.. +``` + +Example Output: + +```bash +{ + "info": { + "address": "cosmos1..", + "group_id": "1", + "admin": "cosmos1..", + "metadata": "AQ==", + "version": "1", + "decision_policy": { + "@type": "/cosmos.group.v1beta1.ThresholdDecisionPolicy", + "threshold": "1", + "timeout": "600s" + }, + } +} +``` + +### GroupMembers + +The `GroupMembers` endpoint allows users to query for group members by group id with pagination flags. + +```bash +/cosmos/group/v1beta1/group_members/{group_id} +``` + +Example: + +```bash +curl localhost:1317/cosmos/group/v1beta1/group_members/1 +``` + +Example Output: + +```bash +{ + "members": [ + { + "group_id": "1", + "member": { + "address": "cosmos1..", + "weight": "1", + "metadata": "AQ==" + } + }, + { + "group_id": "1", + "member": { + "address": "cosmos1..", + "weight": "2", + "metadata": "AQ==" + } + ], + "pagination": { + "next_key": null, + "total": "2" + } +} +``` + +### GroupsByAdmin + +The `GroupsByAdmin` endpoint allows users to query for groups by admin account address with pagination flags. + +```bash +/cosmos/group/v1beta1/groups_by_admin/{admin} +``` + +Example: + +```bash +curl localhost:1317/cosmos/group/v1beta1/groups_by_admin/cosmos1.. +``` + +Example Output: + +```bash +{ + "groups": [ + { + "group_id": "1", + "admin": "cosmos1..", + "metadata": "AQ==", + "version": "1", + "total_weight": "3" + }, + { + "group_id": "2", + "admin": "cosmos1..", + "metadata": "AQ==", + "version": "1", + "total_weight": "3" + } + ], + "pagination": { + "next_key": null, + "total": "2" + } +} +``` + +### GroupAccountsByGroup + +The `GroupAccountsByGroup` endpoint allows users to query for group accounts by group id with pagination flags. + +```bash +/cosmos/group/v1beta1/group_accounts_by_group/{group_id} +``` + +Example: + +```bash +curl localhost:1317/cosmos/group/v1beta1/group_accounts_by_group/1 +``` + +Example Output: + +```bash +{ + "group_accounts": [ + { + "address": "cosmos1..", + "group_id": "1", + "admin": "cosmos1..", + "metadata": "AQ==", + "version": "1", + "decision_policy": { + "@type": "/cosmos.group.v1beta1.ThresholdDecisionPolicy", + "threshold": "1", + "timeout": "600s" + }, + }, + { + "address": "cosmos1..", + "group_id": "1", + "admin": "cosmos1..", + "metadata": "AQ==", + "version": "1", + "decision_policy": { + "@type": "/cosmos.group.v1beta1.ThresholdDecisionPolicy", + "threshold": "1", + "timeout": "600s" + }, + } + ], + "pagination": { + "next_key": null, + "total": "2" + } +} +``` + +### GroupAccountsByAdmin + +The `GroupAccountsByAdmin` endpoint allows users to query for group accounts by admin account address with pagination flags. + +```bash +/cosmos/group/v1beta1/group_accounts_by_admin/{admin} +``` + +Example: + +```bash +curl localhost:1317/cosmos/group/v1beta1/group_accounts_by_admin/cosmos1.. +``` + +Example Output: + +```bash +{ + "group_accounts": [ + { + "address": "cosmos1..", + "group_id": "1", + "admin": "cosmos1..", + "metadata": "AQ==", + "version": "1", + "decision_policy": { + "@type": "/cosmos.group.v1beta1.ThresholdDecisionPolicy", + "threshold": "1", + "timeout": "600s" + }, + }, + { + "address": "cosmos1..", + "group_id": "1", + "admin": "cosmos1..", + "metadata": "AQ==", + "version": "1", + "decision_policy": { + "@type": "/cosmos.group.v1beta1.ThresholdDecisionPolicy", + "threshold": "1", + "timeout": "600s" + }, + } + ], + "pagination": { + "next_key": null, + "total": "2" + } +``` + +### Proposal + +The `Proposal` endpoint allows users to query for proposal by id. + +```bash +/cosmos/group/v1beta1/proposal/{proposal_id} +``` + +Example: + +```bash +curl localhost:1317/cosmos/group/v1beta1/proposal/1 +``` + +Example Output: + +```bash +{ + "proposal": { + "proposal_id": "1", + "address": "cosmos1..", + "metadata": "AQ==", + "proposers": [ + "cosmos1.." + ], + "submitted_at": "2021-12-17T07:06:26.310638964Z", + "group_version": "1", + "group_account_version": "1", + "status": "STATUS_SUBMITTED", + "result": "RESULT_UNFINALIZED", + "vote_state": { + "yes_count": "0", + "no_count": "0", + "abstain_count": "0", + "veto_count": "0" + }, + "timeout": "2021-12-17T07:06:27.310638964Z", + "executor_result": "EXECUTOR_RESULT_NOT_RUN", + "msgs": [ + { + "@type": "/cosmos.bank.v1beta1.MsgSend", + "from_address": "cosmos1..", + "to_address": "cosmos1..", + "amount": [ + { + "denom": "stake", + "amount": "100000000" + } + ] + } + ] + } +} +``` + +### ProposalsByGroupAccount + +The `ProposalsByGroupAccount` endpoint allows users to query for proposals by group account address with pagination flags. + +```bash +/cosmos/group/v1beta1/proposals_by_group_account/{address} +``` + +Example: + +```bash +curl localhost:1317/cosmos/group/v1beta1/proposals_by_group_account/cosmos1.. +``` + +Example Output: + +```bash +{ + "proposals": [ + { + "proposal_id": "1", + "address": "cosmos1..", + "metadata": "AQ==", + "proposers": [ + "cosmos1.." + ], + "submitted_at": "2021-12-17T08:03:27.099649352Z", + "group_version": "1", + "group_account_version": "1", + "status": "STATUS_CLOSED", + "result": "RESULT_ACCEPTED", + "vote_state": { + "yes_count": "1", + "no_count": "0", + "abstain_count": "0", + "veto_count": "0" + }, + "timeout": "2021-12-17T08:13:27.099649352Z", + "executor_result": "EXECUTOR_RESULT_NOT_RUN", + "msgs": [ + { + "@type": "/cosmos.bank.v1beta1.MsgSend", + "from_address": "cosmos1..", + "to_address": "cosmos1..", + "amount": [ + { + "denom": "stake", + "amount": "100000000" + } + ] + } + ] + } + ], + "pagination": { + "next_key": null, + "total": "1" + } +} +``` + +### VoteByProposalVoter + +The `VoteByProposalVoter` endpoint allows users to query for vote by proposal id and voter account address. + +```bash +/cosmos/group/v1beta1/vote_by_proposal_voter/{proposal_id}/{voter} +``` + +Example: + +```bash +curl localhost:1317/cosmos/group/v1beta1/vote_by_proposal_voter/1/cosmos1.. +``` + +Example Output: + +```bash +{ + "vote": { + "proposal_id": "1", + "voter": "cosmos1..", + "choice": "CHOICE_YES", + "metadata": "AQ==", + "submitted_at": "2021-12-17T08:05:02.490164009Z" + } +} +``` + +### VotesByProposal + +The `VotesByProposal` endpoint allows users to query for votes by proposal id with pagination flags. + +```bash +/cosmos/group/v1beta1/votes_by_proposal/{proposal_id} +``` + +Example: + +```bash +curl localhost:1317/cosmos/group/v1beta1/votes_by_proposal/1 +``` + +Example Output: + +```bash +{ + "votes": [ + { + "proposal_id": "1", + "voter": "cosmos1..", + "choice": "CHOICE_YES", + "metadata": "AQ==", + "submitted_at": "2021-12-17T08:05:02.490164009Z" + } + ], + "pagination": { + "next_key": null, + "total": "1" + } +} +``` + +### VotesByVoter + +The `VotesByVoter` endpoint allows users to query for votes by voter account address with pagination flags. + +```bash +/cosmos/group/v1beta1/votes_by_voter/{voter} +``` + +Example: + +```bash +curl localhost:1317/cosmos/group/v1beta1/votes_by_voter/cosmos1.. +``` + +Example Output: + +```bash +{ + "votes": [ + { + "proposal_id": "1", + "voter": "cosmos1..", + "choice": "CHOICE_YES", + "metadata": "AQ==", + "submitted_at": "2021-12-17T08:05:02.490164009Z" + } + ], + "pagination": { + "next_key": null, + "total": "1" + } +} +``` diff --git a/x/group/spec/README.md b/x/group/spec/README.md new file mode 100644 index 000000000000..a27393775231 --- /dev/null +++ b/x/group/spec/README.md @@ -0,0 +1,54 @@ + + +# Group Module + +## Abstract + +The following documents specify the group module. + +This module allows the creation and management of on-chain multisig accounts and enables voting for message execution based on configurable decision policies. + +## Contents + +1. **[Concepts](01_concepts.md)** + - [Group](01_concepts.md#group) + - [Group Account](01_concepts.md#group-account) + - [Decision Policy](01_concepts.md#decision-policy) + - [Proposal](01_concepts.md#proposal) + - [Voting](01_concepts.md#voting) + - [Executing Proposals](01_concepts.md#executing-proposals) +2. **[State](02_state.md)** + - [Group Table](02_state.md#group-table) + - [Group Member Table](02_state.md#group-member-table) + - [Group Account Table](02_state.md#group-account-table) + - [Proposal](02_state.md#proposal-table) + - [Vote Table](02_state.md#vote-table) +3. **[Msg Service](03_messages.md)** + - [Msg/CreateGroup](03_messages.md#msgcreategroup) + - [Msg/UpdateGroupMembers](03_messages.md#msgupdategroupmembers) + - [Msg/UpdateGroupAdmin](03_messages.md#msgupdategroupadmin) + - [Msg/UpdateGroupMetadata](03_messages.md#msgupdategroupmetadata) + - [Msg/CreateGroupAccount](03_messages.md#msgcreategroupaccount) + - [Msg/UpdateGroupAccountAdmin](03_messages.md#msgupdategroupaccountadmin) + - [Msg/UpdateGroupAccountDecisionPolicy](03_messages.md#msgupdategroupaccountdecisionpolicy) + - [Msg/UpdateGroupAccountMetadata](03_messages.md#msgupdategroupaccountmetadata) + - [Msg/CreateProposal](03_messages.md#msgcreateproposal) + - [Msg/Vote](03_messages.md#msgvote) + - [Msg/Exec](03_messages.md#msgexec) +4. **[Events](04_events.md)** + - [EventCreateGroup](04_events.md#eventcreategroup) + - [EventUpdateGroup](04_events.md#eventupdategroup) + - [EventCreateGroupAccount](04_events.md#eventcreategroupaccount) + - [EventUpdateGroupAccount](04_events.md#eventupdategroupaccount) + - [EventCreateProposal](04_events.md#eventcreateproposal) + - [EventVote](04_events.md#eventvote) + - [EventExec](04_events.md#eventexec) +5. **[Client](05_client.md)** + - [CLI](05_client.md#cli) + - [gRPC](05_client.md#grpc) + - [REST](05_client.md#rest)