Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: ADR 069 - x/gov modularity, multiple choice and optimistic proposal #18498

Merged
merged 12 commits into from
Nov 30, 2023
1 change: 1 addition & 0 deletions docs/architecture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ When writing ADRs, follow the same best practices for writing RFCs. When writing
* [ADR 063: Core Module API](./adr-063-core-module-api.md)
* [ADR 065: Store v2](./adr-065-store-v2.md)
* [ADR 067: Simulator v2](./adr-067-simulator-v2.md)
* [ADR 069: `x/gov` modularity, multiple choice and optimisic proposals](./adr-069-gov-improvements.md)

### Draft

Expand Down
228 changes: 228 additions & 0 deletions docs/architecture/adr-069-gov-improvements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
# ADR 069: `x/gov` modularity, multiple choice and optimistic proposals

## Changelog

* 2023-11-17: Initial draft (@julienrbrt, @tac0turtle)

## Status

PROPOSED

## Abstract

Governance is an important aspect of Cosmos SDK chains.

This ADR aimed to extend the `x/gov` module functionalities by adding two different kinds of proposals, as well as making `x/gov` more composable and extendable.

Those two types are, namely: multiple choice proposals and optimistic proposals.

## Context

`x/gov` is the center of Cosmos governance, and has already been improved from its first version `v1beta1`, with a second version [`v1`][5].
This second iteration on gov unlocked many possibilities by letting governance proposals contain any number of proposals.
The last addition of gov has been expedited proposals (proposals that have a shorter voting period and a higher quorum, approval threshold).

The community requested ([1], [4]) two additional proposals for improving governance choices. Those proposals would be useful when having protocol decisions made on specific choices or simplifying regular proposals that do not require high community involvement.

Additionally, the SDK should allow chains to customize the tallying method of proposals (if they want to count the votes in another way). Currently, the Cosmos SDK counts votes proportionally to the voting power/stake. However, custom tallying could allow counting votes with a quadratic function instead.

## Decision

`x/gov` will integrate these functions and extract helpers and interfaces for extending the `x/gov` module capabilities.

### Proposals

Currently, all proposals are [`v1.Proposal`][5]. Optimistic and multiple choice proposals require a different tally logic, but the rest of the proposal stays the same to not create other proposal types, `v1.Proposal` will have an extra field:

```protobuf
// ProposalType enumerates the valid proposal types.
// All proposal types are v1.Proposal which have different voting periods or tallying logic.
enum ProposalType {
// PROPOSAL_TYPE_UNSPECIFIED defines no proposal type, which fallback to PROPOSAL_TYPE_STANDARD.
PROPOSAL_TYPE_UNSPECIFIED = 0;
// PROPOSAL_TYPE_STANDARD defines the type for a standard proposal.
PROPOSAL_TYPE_STANDARD = 1;
// PROPOSAL_TYPE_MULTIPLE_CHOICE defines the type for a multiple choice proposal.
PROPOSAL_TYPE_MULTIPLE_CHOICE = 2;
// PROPOSAL_TYPE_OPTIMISTIC defines the type for an optimistic proposal.
PROPOSAL_TYPE_OPTIMISTIC = 3;
// PROPOSAL_TYPE_EXPEDITED defines the type for an expedited proposal.
PROPOSAL_TYPE_EXPEDITED = 4;
}
```

Note, that expedited becomes a proposal type itself instead of a boolean on the `v1.Proposal` struct.

> An expedited proposal is by design a standard proposal with a quicker voting period and higher threshold. When an expedited proposal fails, it gets converted to a standard proposal.

An expedited optimistic proposal and an expedited multiple choice proposal do not make sense based on the definition above and is a proposal type instead of a proposal characteristic.

#### Optimistic Proposal

An optimistic proposal is a proposal that passes unless a threshold a NO votes is reached.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the intended use case?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A chain can decide how they want to use it (given the possibility to restrict proposers).
However, we can think of it as a way to alleviate governance burden for recurring proposals or any boring proposals where no governance discussion needs to be involved. In the case of Osmosis, that would be periodic incentive updates for example. I have no example for the Hub however 😅


Voter can only vote NO on the proposal. If the NO threshold is reached, the optimistic proposal is converted to a standard proposal.

Two governance parameters will be in added [`v1.Params`][5] to support optimistic proposals:

```protobuf
// optimistic_authorized_addreses is an optional governance parameter that limits the authorized accounts than can submit optimisitc proposals
repeated string optimistic_authorized_addreses = 17 [(cosmos_proto.scalar) = "cosmos.AddressString"];
tac0turtle marked this conversation as resolved.
Show resolved Hide resolved
tac0turtle marked this conversation as resolved.
Show resolved Hide resolved

// Optimistic rejected threshold defines at which percentage of NO votes, the optimistic proposal should fail and be converted to a standard proposal.
string optimistic_rejected_threshold = 18 [(cosmos_proto.scalar) = "cosmos.Dec"];
```

#### Multiple Choice Proposal

A multiple choice proposal is a proposal where the voting options can be defined by the proposer.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not really a fan of the fact that the options can only be set by the proposer. It relies on the proposer being fair in setting them. But definitely doing anything different would add a lot of complexity and it's not guaranteed to have any benefit.

A voter that is not agreeing with any option - and want none of them to pass - but does not want to abuse the SPAM vote can only choose to not vote, and try to keep the quorum lower than the threshold.
Maybe this is enough, but maybe not?

Copy link
Member Author

@julienrbrt julienrbrt Nov 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we first thought about it I thought the same. We thought of adding a blank vote option, but eventually decided to let that as well for the proposer to specify.

Usually, chains have an off chain process for before submitting a proposal. The voting option discovery should be done there and if someone decides to skip that process, it makes sense to consider the proposal as spam.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the voting option discovery should be done on the forum, completely agree with that, and ideally it's what makes the most sense.
I am just a little skeptical that it'll work like this all the times, and it might be very controversial when it doesn't.


The number of voting option will be limited to a maximum of 4.
tac0turtle marked this conversation as resolved.
Show resolved Hide resolved
A new vote option `SPAM` will be added and distinguished of those voting options. `SPAM` will be used to mark a proposal as spam and is explained further below.

Multiple choice proposals, contrary to any other proposal type, cannot have messages to execute. They are only text proposals.

Submitting a new multiple choice proposal will use a different message than the [`v1.MsgSubmitProposal`][5]. This is done in order to simplify the proposal submittion and allow defining the voting options directly.


```protobuf
message MsgSubmitMultipleChoiceProposal {
repeated cosmos.base.v1beta1.Coin initial_deposit = 1
string proposer = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string metadata = 3;
string title = 4;
string summary = 5;
string option_one = 6;
string option_two = 7;
string option_three = 8;
string option_four = 9;
}
```

Voters can only vote on the defined options in the proposal.

To maintain compatibility with the existing endpoints, the voting options will not be stored in the proposal itself and each option will be mapped to [`v1.VoteOption`][5]. A multiple choice proposal will be stored as a [`v1.Proposal`][5]. A query will be available for multiple choice proposal types to get the voting options.

### Votes

As mentioned above [multiple choice proposal](#multiple-choice-proposal) will introduce an additional vote option: `SPAM`.

This vote option will be supported by all proposal types.
At the end of the voting period, if a proposal is voted as `SPAM`, it fails and its deposit is burned.

`SPAM` differs from the `No with Veto` vote as its threshold is dynamic.
A proposal is marked as `SPAM` when the total of weighted votes for all options is lower than the amount of weighted vote on `SPAM`
(`spam` > `option_one + option_two + option_three + option_four` = proposal marked as spam).
julienrbrt marked this conversation as resolved.
Show resolved Hide resolved
Comment on lines +114 to +115
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is tallying done at the end of the VP (EndBlock)?

Copy link
Member Author

@julienrbrt julienrbrt Nov 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Tallying stays in end blocker in this ADR. Future improvements will attempt to make it lazy.
As it's an implementation detail I don't think we need to cover it here.

However we do need methods in the tally interface for that.

This allows clear spam proposals to be marked as spam easily, even with low participation from validators.

To avoid voters wrongfully voting down a proposal as `SPAM`, voters will be slashed `x`% (default 0%) of their voting stake if they voted `SPAM` on a proposal that wasn't a spam proposal. The parameter allows to incentivise voters to only vote `SPAM` on actual spam proposals and not use `SPAM` as a way to vote `No with Veto` with a different threshold.
julienrbrt marked this conversation as resolved.
Show resolved Hide resolved

This leads to the addition of the following governance parameter in [`v1.Params`][5]:

```protobuf
// burn_spam_amount defines the percentage of the voting stake that will be burned if a voter votes SPAM on a proposal that is not marked as SPAM.
string burn_spam_amount = 8 [(cosmos_proto.scalar) = "cosmos.Dec"];
```

Additionally, the current vote options will be aliased to better accommodate the multiple choice proposal:

```protobuf
// VoteOption enumerates the valid vote options for a given governance proposal.
enum VoteOption {
option allow_alias = true;

// VOTE_OPTION_UNSPECIFIED defines a no-op vote option.
VOTE_OPTION_UNSPECIFIED = 0;
// VOTE_OPTION_ONE defines the first proposal vote option.
VOTE_OPTION_ONE = 1;
// VOTE_OPTION_YES defines the yes proposal vote option.
VOTE_OPTION_YES = 1;
// VOTE_OPTION_TWO defines the second proposal vote option.
VOTE_OPTION_TWO = 2;
// VOTE_OPTION_ABSTAIN defines the abstain proposal vote option.
VOTE_OPTION_ABSTAIN = 2;
// VOTE_OPTION_THREE defines the third proposal vote option.
VOTE_OPTION_THREE = 3;
// VOTE_OPTION_NO defines the no proposal vote option.
VOTE_OPTION_NO = 3;
// VOTE_OPTION_FOUR defines the fourth proposal vote option.
VOTE_OPTION_FOUR = 4;
// VOTE_OPTION_NO_WITH_VETO defines the no with veto proposal vote option.
VOTE_OPTION_NO_WITH_VETO = 4;
// VOTE_OPTION_SPAM defines the spam proposal vote option.
VOTE_OPTION_SPAM = 5;
}
```

The order does not change for a standard proposal (1 = yes, 2 = abstain, 3 = no, 4 = no with veto as it was) and the aliased enum can be used interchangeably.

Updating vote options means updating [`v1.TallyResult`][5] as well.

#### Tally

Due to the vote option change, each proposal can have the same tallying method.

However, chains may want to change the tallying function (weighted vote per voting power) of `x/gov` for a different algorithm (using a quadratic function on the voter stake, for instance).

The custom tallying function can be passed to the `x/gov` keeper with the following interface:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is extremely useful


```go
type Tally interface{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to think a bit more of what exactly we need but at a high level you should be able to process a custom result with the voting power that has voted so far

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at a high level you should be able to process a custom result with the voting power that has voted so far

could you elaborate here? I dont understand what you mean

// to be decided

// Calculate calculates the tally result
Calculate(proposal v1.Proposal, govKeeper GovKeeper, stakingKeeper StakingKeeper) govv1.TallyResult
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Tally could be simplified to Tally(ctx context.Context, govKeeper GovKeeper, prop v1.Proposal) (govv1.TallyResult, error)

I assume Tally could be a module's keeper implementing this interface and already holding within it other keepers besides gov. Passing GovKeeper in instead of having it in the tally implementer avoids circular dependencies between the tally interface and gov itself.

Copy link
Member Author

@julienrbrt julienrbrt Nov 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GovKeeper does not have the other keepers it uses public.
Which I think is good, we will be able to remove staking from the gov keeper as well altogether because it is only used for tallying.

// IsAccepted returns true if the proposal passes/is accepted
IsAccepted() bool
// BurnDeposit returns true if the proposal deposit should be burned
BurnDeposit() bool
}
```

## Consequences

Changing voting possibilities has a direct consequence for the clients. Clients, like Keplr or Mintscan, need to implement logic for multiple choice proposals.

That logic consists of querying multiple choice proposals vote mapping their vote options.

### Backwards Compatibility

Legacy proposals (`v1beta1`) endpoints will not be supporting the new proposal types.

Voting on a gov v1 proposal having a different type than [`standard` or `expedited`](#proposals) via the `v1beta1` will not be supported.
This is already the case for the expedited proposals.

### Positive

* Extended governance features
* Extended governance customization

### Negative

* Increase gov wiring complexity

### Neutral

* Increases the number of parameters available

## Further Discussions

This ADR starts the `x/gov` overhaul for the `cosmossdk.io/x/gov` v1.0.0 release.
Further internal improvements of `x/gov` will happen soon after, in order to simplify its state management and making gov calculation in a more "lazy"-fashion.

Those improvements may change the tallying api.

* https://github.com/cosmos/cosmos-sdk/issues/16270

## References

* [https://github.com/cosmos/cosmos-sdk/issues/16270][1]
* [https://github.com/cosmos/cosmos-sdk/issues/17781][2]
* [https://github.com/cosmos/cosmos-sdk/issues/14403][3]
* [https://github.com/decentralists/DAO/issues/28][4]

[1]: https://grants.osmosis.zone/blog/rfp-cosmos-sdk-governance-module-improvements
[2]: https://github.com/cosmos/cosmos-sdk/issues/17781
[3]: https://github.com/cosmos/cosmos-sdk/issues/14403
[4]: https://github.com/decentralists/DAO/issues/28
[5]: https://buf.build/cosmos/cosmos-sdk/docs/main:cosmos.gov.v1