Skip to content

Commit

Permalink
feat(x/gov): add expedited proposal quorum param (#19741)
Browse files Browse the repository at this point in the history
Co-authored-by: Likhita Polavarapu <[email protected]>
  • Loading branch information
julienrbrt and likhita-809 authored Mar 13, 2024
1 parent 10c1d7c commit 9c47292
Show file tree
Hide file tree
Showing 13 changed files with 516 additions and 229 deletions.
220 changes: 151 additions & 69 deletions api/cosmos/gov/v1/gov.pulsar.go

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion x/gov/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Improvements

* [#19741](https://github.com/cosmos/cosmos-sdk/pull/19741) Add `ExpeditedQuorum` parameter specifying a minimum quorum for expedited proposals, that can differ from the regular quorum.
* [#19352](https://github.com/cosmos/cosmos-sdk/pull/19352) `TallyResult` include vote options counts. Those counts replicates the now deprecated (but not removed) yes, no, abstain and veto count fields.
* [#18976](https://github.com/cosmos/cosmos-sdk/pull/18976) Log and send an event when a proposal deposit refund or burn has failed.
* [#18856](https://github.com/cosmos/cosmos-sdk/pull/18856) Add `ProposalCancelMaxPeriod` parameter for modifying how long a proposal can be cancelled after it has been submitted.
Expand All @@ -61,7 +62,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### API Breaking Changes

* [#19481](https://github.com/cosmos/cosmos-sdk/pull/19481) Migrate module to use `appmodule.Environment`; `NewKeeper` now takes `appmodule.Environment` instead of a store service and no `baseapp.MessageRouter` anymore.
* [#19481](https://github.com/cosmos/cosmos-sdk/pull/19481) v1beta1 proposal handlers now take a `context.Context` instead of an `sdk.Context`.
* [#19481](https://github.com/cosmos/cosmos-sdk/pull/19481) v1beta1 proposal handlers now take a `context.Context` instead of an `sdk.Context`.
* [#19592](https://github.com/cosmos/cosmos-sdk/pull/19592) `types.Config` and `types.DefaultConfig` have been moved to the keeper package in order to support the custom tallying function.
* [#19349](https://github.com/cosmos/cosmos-sdk/pull/19349) Simplify state management in `x/gov`. Note `k.VotingPeriodProposals` and `k.SetProposal` are no longer needed and have been removed.
* [#18532](https://github.com/cosmos/cosmos-sdk/pull/18532) All functions that were taking an expedited bool parameter now take a `ProposalType` parameter instead.
Expand Down
20 changes: 13 additions & 7 deletions x/gov/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,18 @@ For a weighted vote to be valid, the `options` field must not contain duplicate
Quorum is defined as the minimum percentage of voting power that needs to be
cast on a proposal for the result to be valid.

### Expedited Quorum

Expedited Quorum is defined as the minimum percentage of voting power that needs to be
cast on an **expedited** proposal for the result to be valid.

### Yes Quorum

Yes quorum is a more restrictive quorum that is used to determine if a proposal passes.
It is defined as the minimum percentage of voting power that needs to have voted `Yes` for the proposal to pass.
It differs from `Threshold` as it takes the whole voting power into account, not only `Yes` and `No` votes.
By default, `YesQuorum` is set to 0, which means no minimum.

### Proposal Types

Proposal types have been introduced in ADR-069.
Expand Down Expand Up @@ -235,13 +247,6 @@ This means that proposals are accepted iff:

For expedited proposals, by default, the threshold is higher than with a *normal proposal*, namely, 66.7%.

### Yes Quorum

Yes quorum is a more restrictive quorum that is used to determine if a proposal passes.
It is defined as the minimum percentage of voting power that needs to have voted `Yes` for the proposal to pass.
It differs from `Threshold` as it takes the whole voting power into account, not only `Yes` and `No` votes.
By default, `YesQuorum` is set to 0, which means no minimum.

#### Inheritance

If a delegator does not vote, it will inherit its validator vote.
Expand Down Expand Up @@ -616,6 +621,7 @@ The governance module contains the following parameters:
| expedited_threshold | string (time ns) | "0.667000000000000000" |
| expedited_voting_period | string (time ns) | "86400000000000" (8600s) |
| expedited_min_deposit | array (coins) | [{"denom":"uatom","amount":"50000000"}] |
| expedited_quorum | string (dec) | "0.5" |
| burn_proposal_deposit_prevote | bool | false |
| burn_vote_quorum | bool | false |
| burn_vote_veto | bool | true |
Expand Down
84 changes: 84 additions & 0 deletions x/gov/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1727,6 +1727,90 @@ func (suite *KeeperTestSuite) TestMsgUpdateParams() {
expErr: true,
expErrMsg: "quorum too large",
},
{
name: "invalid yes quorum",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.YesQuorum = abc

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "invalid yes_quorum string",
},
{
name: "negative yes quorum",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.YesQuorum = o1

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "yes_quorum cannot be negative",
},
{
name: "yes quorum > 1",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.YesQuorum = "2"

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "yes_quorum too large",
},
{
name: "invalid expedited quorum",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.ExpeditedQuorum = abc

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "invalid expedited_quorum string",
},
{
name: "negative expedited quorum",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.ExpeditedQuorum = o1

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "expedited_quorum cannot be negative",
},
{
name: "expedited quorum > 1",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.ExpeditedQuorum = "2"

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "expedited_quorum too large",
},
{
name: "invalid threshold",
input: func() *v1.MsgUpdateParams {
Expand Down
6 changes: 3 additions & 3 deletions x/gov/keeper/tally.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func (k Keeper) tallyStandard(ctx context.Context, proposal v1.Proposal, totalVo
}

// tallyExpedited tallies the votes of an expedited proposal
// If there is not enough quorum of votes, the proposal fails
// If there is not enough expedited quorum of votes, the proposal fails
// If no one votes (everyone abstains), proposal fails
// If more than 1/3 of voters veto, proposal fails
// If more than 2/3 of non-abstaining voters vote Yes, proposal passes
Expand All @@ -136,8 +136,8 @@ func (k Keeper) tallyExpedited(totalVoterPower math.LegacyDec, totalBonded math.

// If there is not enough quorum of votes, the proposal fails
percentVoting := totalVoterPower.Quo(math.LegacyNewDecFromInt(totalBonded))
quorum, _ := math.LegacyNewDecFromStr(params.Quorum)
if percentVoting.LT(quorum) {
expeditedQuorum, _ := math.LegacyNewDecFromStr(params.ExpeditedQuorum)
if percentVoting.LT(expeditedQuorum) {
return false, params.BurnVoteQuorum, tallyResults, nil
}

Expand Down
17 changes: 10 additions & 7 deletions x/gov/keeper/tally_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -712,16 +712,17 @@ func TestTally_Expedited(t *testing.T) {
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_TWO)
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_TWO)
validatorVote(s, s.valAddrs[3], v1.VoteOption_VOTE_OPTION_TWO)
validatorVote(s, s.valAddrs[4], v1.VoteOption_VOTE_OPTION_TWO)
},
expectedPass: false,
expectedBurn: false,
expectedTally: v1.TallyResult{
YesCount: "0",
AbstainCount: "4000000",
AbstainCount: "5000000",
NoCount: "0",
NoWithVetoCount: "0",
OptionOneCount: "0",
OptionTwoCount: "4000000",
OptionTwoCount: "5000000",
OptionThreeCount: "0",
OptionFourCount: "0",
SpamCount: "0",
Expand Down Expand Up @@ -759,19 +760,21 @@ func TestTally_Expedited(t *testing.T) {
setTotalBonded(s, 10000000)
validatorVote(s, s.valAddrs[0], v1.VoteOption_VOTE_OPTION_ONE)
validatorVote(s, s.valAddrs[1], v1.VoteOption_VOTE_OPTION_ONE)
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_THREE)
validatorVote(s, s.valAddrs[2], v1.VoteOption_VOTE_OPTION_ONE)
validatorVote(s, s.valAddrs[3], v1.VoteOption_VOTE_OPTION_THREE)
validatorVote(s, s.valAddrs[4], v1.VoteOption_VOTE_OPTION_THREE)
validatorVote(s, s.valAddrs[5], v1.VoteOption_VOTE_OPTION_THREE)
},
expectedPass: false,
expectedBurn: false,
expectedTally: v1.TallyResult{
YesCount: "2000000",
YesCount: "3000000",
AbstainCount: "0",
NoCount: "2000000",
NoCount: "3000000",
NoWithVetoCount: "0",
OptionOneCount: "2000000",
OptionOneCount: "3000000",
OptionTwoCount: "0",
OptionThreeCount: "2000000",
OptionThreeCount: "3000000",
OptionFourCount: "0",
SpamCount: "0",
},
Expand Down
1 change: 1 addition & 0 deletions x/gov/migrations/v6/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func MigrateStore(ctx context.Context, storeService corestoretypes.KVStoreServic
if err != nil {
return fmt.Errorf("failed to get gov params: %w", err)
}
govParams.ExpeditedQuorum = govParams.Quorum

defaultParams := v1.DefaultParams()
govParams.YesQuorum = defaultParams.YesQuorum
Expand Down
2 changes: 2 additions & 0 deletions x/gov/migrations/v6/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func TestMigrateStore(t *testing.T) {
// set defaults without newly added fields
previousParams := v1.DefaultParams()
previousParams.YesQuorum = ""
previousParams.ExpeditedQuorum = ""
previousParams.ProposalCancelMaxPeriod = ""
previousParams.OptimisticAuthorizedAddresses = nil
previousParams.OptimisticRejectedThreshold = ""
Expand All @@ -45,6 +46,7 @@ func TestMigrateStore(t *testing.T) {
newParams, err := paramsCollection.Get(ctx)
require.NoError(t, err)
require.Equal(t, v1.DefaultParams().YesQuorum, newParams.YesQuorum)
require.Equal(t, v1.DefaultParams().Quorum, newParams.ExpeditedQuorum) // ExpeditedQuorum is set to Quorum during the migration instead of the default SDK value, for avoiding behavior change.
require.Equal(t, v1.DefaultParams().ProposalCancelMaxPeriod, newParams.ProposalCancelMaxPeriod)
require.Equal(t, v1.DefaultParams().OptimisticAuthorizedAddresses, newParams.OptimisticAuthorizedAddresses)
require.Equal(t, v1.DefaultParams().OptimisticRejectedThreshold, newParams.OptimisticRejectedThreshold)
Expand Down
6 changes: 6 additions & 0 deletions x/gov/proto/cosmos/gov/v1/gov.proto
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,12 @@ message Params {
//
// Since: x/gov v1.0.0
string yes_quorum = 20 [(cosmos_proto.scalar) = "cosmos.Dec"];

// Minimum percentage of total stake needed to vote for a result to be
// considered valid for an expedited proposal.
//
// Since: x/gov v1.0.0
string expedited_quorum = 21 [(cosmos_proto.scalar) = "cosmos.Dec"];
}

// MessageBasedParams defines the parameters of specific messages in a proposal.
Expand Down
32 changes: 29 additions & 3 deletions x/gov/simulation/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const (
VotingPeriod = "voting_period"
ExpeditedVotingPeriod = "expedited_voting_period"
Quorum = "quorum"
ExpeditedQuorum = "expedited_quorum"
YesQuorum = "yes_quorum"
Threshold = "threshold"
ExpeditedThreshold = "expedited_threshold"
Expand Down Expand Up @@ -151,11 +152,14 @@ func RandomizedGenState(simState *module.SimulationState) {
var yesQuorum sdkmath.LegacyDec
simState.AppParams.GetOrGenerate(YesQuorum, &yesQuorum, simState.Rand, func(r *rand.Rand) { yesQuorum = GenQuorum(r) })

var expeditedQuorum sdkmath.LegacyDec
simState.AppParams.GetOrGenerate(ExpeditedQuorum, &expeditedQuorum, simState.Rand, func(r *rand.Rand) { expeditedQuorum = GenQuorum(r) })

var threshold sdkmath.LegacyDec
simState.AppParams.GetOrGenerate(Threshold, &threshold, simState.Rand, func(r *rand.Rand) { threshold = GenThreshold(r) })

var expitedVotingThreshold sdkmath.LegacyDec
simState.AppParams.GetOrGenerate(ExpeditedThreshold, &expitedVotingThreshold, simState.Rand, func(r *rand.Rand) { expitedVotingThreshold = GenExpeditedThreshold(r) })
var expeditedVotingThreshold sdkmath.LegacyDec
simState.AppParams.GetOrGenerate(ExpeditedThreshold, &expeditedVotingThreshold, simState.Rand, func(r *rand.Rand) { expeditedVotingThreshold = GenExpeditedThreshold(r) })

var veto sdkmath.LegacyDec
simState.AppParams.GetOrGenerate(Veto, &veto, simState.Rand, func(r *rand.Rand) { veto = GenVeto(r) })
Expand All @@ -168,7 +172,29 @@ func RandomizedGenState(simState *module.SimulationState) {

govGenesis := v1.NewGenesisState(
startingProposalID,
v1.NewParams(minDeposit, expeditedMinDeposit, depositPeriod, votingPeriod, expeditedVotingPeriod, quorum.String(), yesQuorum.String(), threshold.String(), expitedVotingThreshold.String(), veto.String(), minInitialDepositRatio.String(), proposalCancelRate.String(), "", proposalMaxCancelVotingPeriod.String(), simState.Rand.Intn(2) == 0, simState.Rand.Intn(2) == 0, simState.Rand.Intn(2) == 0, minDepositRatio.String(), optimisticRejectedThreshold.String(), []string{}),
v1.NewParams(
minDeposit,
expeditedMinDeposit,
depositPeriod,
votingPeriod,
expeditedVotingPeriod,
quorum.String(),
yesQuorum.String(),
expeditedQuorum.String(),
threshold.String(),
expeditedVotingThreshold.String(),
veto.String(),
minInitialDepositRatio.String(),
proposalCancelRate.String(),
"",
proposalMaxCancelVotingPeriod.String(),
simState.Rand.Intn(2) == 0,
simState.Rand.Intn(2) == 0,
simState.Rand.Intn(2) == 0,
minDepositRatio.String(),
optimisticRejectedThreshold.String(),
[]string{},
),
)

bz, err := json.MarshalIndent(&govGenesis, "", " ")
Expand Down
39 changes: 20 additions & 19 deletions x/gov/simulation/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"testing"

"github.com/stretchr/testify/require"
"gotest.tools/v3/assert"

sdkmath "cosmossdk.io/math"
"cosmossdk.io/x/gov/simulation"
Expand Down Expand Up @@ -48,28 +47,30 @@ func TestRandomizedGenState(t *testing.T) {
const (
tallyQuorum = "0.387000000000000000"
tallyYesQuorum = "0.449000000000000000"
tallyThreshold = "0.464000000000000000"
tallyExpeditedThreshold = "0.506000000000000000"
tallyVetoThreshold = "0.309000000000000000"
tallyExpeditedQuorum = "0.457000000000000000"
tallyThreshold = "0.479000000000000000"
tallyExpeditedThreshold = "0.545000000000000000"
tallyVetoThreshold = "0.280000000000000000"
minInitialDepositDec = "0.880000000000000000"
proposalCancelMaxPeriod = "0.110000000000000000"
)

assert.Equal(t, "272stake", govGenesis.Params.MinDeposit[0].String())
assert.Equal(t, "800stake", govGenesis.Params.ExpeditedMinDeposit[0].String())
assert.Equal(t, "41h11m36s", govGenesis.Params.MaxDepositPeriod.String())
assert.Equal(t, float64(291928), govGenesis.Params.VotingPeriod.Seconds())
assert.Equal(t, float64(33502), govGenesis.Params.ExpeditedVotingPeriod.Seconds())
assert.Equal(t, tallyQuorum, govGenesis.Params.Quorum)
assert.Equal(t, tallyYesQuorum, govGenesis.Params.YesQuorum)
assert.Equal(t, tallyThreshold, govGenesis.Params.Threshold)
assert.Equal(t, tallyExpeditedThreshold, govGenesis.Params.ExpeditedThreshold)
assert.Equal(t, tallyVetoThreshold, govGenesis.Params.VetoThreshold)
assert.Equal(t, proposalCancelMaxPeriod, govGenesis.Params.ProposalCancelMaxPeriod)
assert.Equal(t, uint64(0x28), govGenesis.StartingProposalId)
assert.DeepEqual(t, []*v1.Deposit{}, govGenesis.Deposits)
assert.DeepEqual(t, []*v1.Vote{}, govGenesis.Votes)
assert.DeepEqual(t, []*v1.Proposal{}, govGenesis.Proposals)
require.Equal(t, "272stake", govGenesis.Params.MinDeposit[0].String())
require.Equal(t, "800stake", govGenesis.Params.ExpeditedMinDeposit[0].String())
require.Equal(t, "41h11m36s", govGenesis.Params.MaxDepositPeriod.String())
require.Equal(t, float64(291928), govGenesis.Params.VotingPeriod.Seconds())
require.Equal(t, float64(33502), govGenesis.Params.ExpeditedVotingPeriod.Seconds())
require.Equal(t, tallyQuorum, govGenesis.Params.Quorum)
require.Equal(t, tallyYesQuorum, govGenesis.Params.YesQuorum)
require.Equal(t, tallyExpeditedQuorum, govGenesis.Params.ExpeditedQuorum)
require.Equal(t, tallyThreshold, govGenesis.Params.Threshold)
require.Equal(t, tallyExpeditedThreshold, govGenesis.Params.ExpeditedThreshold)
require.Equal(t, tallyVetoThreshold, govGenesis.Params.VetoThreshold)
require.Equal(t, proposalCancelMaxPeriod, govGenesis.Params.ProposalCancelMaxPeriod)
require.Equal(t, uint64(0x28), govGenesis.StartingProposalId)
require.Equal(t, []*v1.Deposit{}, govGenesis.Deposits)
require.Equal(t, []*v1.Vote{}, govGenesis.Votes)
require.Equal(t, []*v1.Proposal{}, govGenesis.Proposals)
}

// TestRandomizedGenState tests abnormal scenarios of applying RandomizedGenState.
Expand Down
Loading

0 comments on commit 9c47292

Please sign in to comment.