Skip to content

Commit

Permalink
feat(x/gov): add constitution amendment and law proposals with specif…
Browse files Browse the repository at this point in the history
…ic quorum and pass thresholds (#11)

* draft new prop types for law and amendments

* add also quorum

* improvements

* seems to work

* refine sim params

* fix tests

* add tests for tally and simulation of the two prop types

* fix simulation test

* fix merge mistakes and fmt pass
  • Loading branch information
giunatale authored Sep 18, 2024
1 parent 7a102b4 commit 1d342cc
Show file tree
Hide file tree
Showing 18 changed files with 1,758 additions and 258 deletions.
29 changes: 25 additions & 4 deletions proto/atomone/gov/v1/gov.proto
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,18 @@ message TallyParams {

// Minimum proportion of Yes votes for proposal to pass. Default value: 2/3.
string threshold = 2 [(cosmos_proto.scalar) = "cosmos.Dec"];

// quorum for constitution amendment proposals
string constitution_amendment_quorum = 3 [(cosmos_proto.scalar) = "cosmos.Dec"];

// Minimum proportion of Yes votes for a Constitution Amendment proposal to pass. Default value: 0.9.
string constitution_amendment_threshold = 4 [(cosmos_proto.scalar) = "cosmos.Dec"];

// quorum for law proposals
string law_quorum = 5 [(cosmos_proto.scalar) = "cosmos.Dec"];

// Minimum proportion of Yes votes for a Law proposal to pass. Default value: 0.9.
string law_threshold = 6 [(cosmos_proto.scalar) = "cosmos.Dec"];
}

// Params defines the parameters for the x/gov module.
Expand Down Expand Up @@ -213,15 +225,24 @@ message Params {
// burn deposits if the proposal does not enter voting period
bool burn_proposal_deposit_prevote = 14;

// burn deposits if quorum with vote type no_veto is met
bool burn_vote_veto = 15;

// The ratio representing the proportion of the deposit value minimum that
// must be met when making a deposit. Default value: 0.01. Meaning that for a
// chain with a min_deposit of 100stake, a deposit of 1stake would be
// required.
//
// Since: cosmos-sdk 0.50
// NOTE: backported from v50 (https://github.com/cosmos/cosmos-sdk/pull/18146)
string min_deposit_ratio = 16 [ (cosmos_proto.scalar) = "cosmos.Dec" ];
string min_deposit_ratio = 15 [ (cosmos_proto.scalar) = "cosmos.Dec" ];

// quorum for constitution amendment proposals
string constitution_amendment_quorum = 16 [(cosmos_proto.scalar) = "cosmos.Dec"];

// Minimum proportion of Yes votes for a Constitution Amendment proposal to pass. Default value: 0.9.
string constitution_amendment_threshold = 17 [(cosmos_proto.scalar) = "cosmos.Dec"];

// quorum for law proposals
string law_quorum = 18 [(cosmos_proto.scalar) = "cosmos.Dec"];

// Minimum proportion of Yes votes for a Law proposal to pass. Default value: 0.9.
string law_threshold = 19 [(cosmos_proto.scalar) = "cosmos.Dec"];
}
26 changes: 26 additions & 0 deletions proto/atomone/gov/v1beta1/gov.proto
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,32 @@ message TextProposal {
string description = 2;
}

message LawProposal {
option (cosmos_proto.implements_interface) = "atomone.gov.v1beta1.Content";
option (amino.name) = "atomone/LawProposal";

option (gogoproto.equal) = true;

// title of the proposal.
string title = 1;

// description associated with the proposal.
string description = 2;
}

message ConstitutionAmendmentProposal {
option (cosmos_proto.implements_interface) = "atomone.gov.v1beta1.Content";
option (amino.name) = "atomone/ConstitutionAmendmentProposal";

option (gogoproto.equal) = true;

// title of the proposal.
string title = 1;

// description associated with the proposal.
string description = 2;
}

// Deposit defines an amount deposited by an account address to an active
// proposal.
message Deposit {
Expand Down
5 changes: 5 additions & 0 deletions tests/e2e/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ func modifyGenesis(path, moniker, amountStr string, addrAll []sdk.AccAddress, de
amnt := sdk.NewInt(10000)
quorum, _ := sdk.NewDecFromStr("0.000000000000000001")
threshold, _ := sdk.NewDecFromStr("0.000000000000000001")
lawQuorum, _ := sdk.NewDecFromStr("0.000000000000000001")
lawThreshold, _ := sdk.NewDecFromStr("0.000000000000000001")
amendmentsQuorum, _ := sdk.NewDecFromStr("0.000000000000000001")
amendmentsThreshold, _ := sdk.NewDecFromStr("0.000000000000000001")

maxDepositPeriod := 10 * time.Minute
votingPeriod := 15 * time.Second
Expand All @@ -133,6 +137,7 @@ func modifyGenesis(path, moniker, amountStr string, addrAll []sdk.AccAddress, de
sdk.NewCoins(sdk.NewCoin(denom, amnt)), maxDepositPeriod,
votingPeriod,
quorum.String(), threshold.String(),
amendmentsQuorum.String(), amendmentsThreshold.String(), lawQuorum.String(), lawThreshold.String(),
sdk.ZeroDec().String(),
false, false, govv1.DefaultMinDepositRatio.String(),
),
Expand Down
34 changes: 31 additions & 3 deletions x/gov/keeper/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ import (
)

var (
_, _, addr = testdata.KeyTestPubAddr()
govAcct = authtypes.NewModuleAddress(types.ModuleName)
TestProposal = getTestProposal()
_, _, addr = testdata.KeyTestPubAddr()
govAcct = authtypes.NewModuleAddress(types.ModuleName)
TestProposal = getTestProposal()
TestAmendmentProposal = getTestConstitutionAmendmentProposal()
TestLawProposal = getTestLawProposal()
)

// getTestProposal creates and returns a test proposal message.
Expand All @@ -46,6 +48,32 @@ func getTestProposal() []sdk.Msg {
}
}

// getTestConstitutionAmendmentProposal creates and returns a test constitution amendment proposal message.
func getTestConstitutionAmendmentProposal() []sdk.Msg {
legacyProposalMsg, err := v1.NewLegacyContent(v1beta1.NewConstitutionAmendmentProposal("Title", "description"), authtypes.NewModuleAddress(types.ModuleName).String())
if err != nil {
panic(err)
}

return []sdk.Msg{
banktypes.NewMsgSend(govAcct, addr, sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(1000)))),
legacyProposalMsg,
}
}

// getTestLawProposal creates and returns a test law proposal message.
func getTestLawProposal() []sdk.Msg {
legacyProposalMsg, err := v1.NewLegacyContent(v1beta1.NewLawProposal("Title", "description"), authtypes.NewModuleAddress(types.ModuleName).String())
if err != nil {
panic(err)
}

return []sdk.Msg{
banktypes.NewMsgSend(govAcct, addr, sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(1000)))),
legacyProposalMsg,
}
}

type mocks struct {
acctKeeper *govtestutil.MockAccountKeeper
bankKeeper *govtestutil.MockBankKeeper
Expand Down
116 changes: 114 additions & 2 deletions x/gov/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -905,7 +905,7 @@ func (suite *KeeperTestSuite) TestMsgUpdateParams() {
}
},
expErr: true,
expErrMsg: "quorom cannot be negative",
expErrMsg: "quorum must be positive",
},
{
name: "quorum > 1",
Expand All @@ -919,7 +919,7 @@ func (suite *KeeperTestSuite) TestMsgUpdateParams() {
}
},
expErr: true,
expErrMsg: "quorom too large",
expErrMsg: "quorum too large",
},
{
name: "invalid threshold",
Expand Down Expand Up @@ -963,6 +963,118 @@ func (suite *KeeperTestSuite) TestMsgUpdateParams() {
expErr: true,
expErrMsg: "vote threshold too large",
},
{
name: "negative constitution amendment quorum",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.ConstitutionAmendmentQuorum = "-0.1"

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "constitution amendment quorum must be positive",
},
{
name: "constitution amendments quorum > 1",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.ConstitutionAmendmentQuorum = "2"

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "constitution amendment quorum too large",
},
{
name: "negative constitution amendment threshold",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.ConstitutionAmendmentThreshold = "-0.1"

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "constitution amendment threshold must be positive",
},
{
name: "constitution amendments threshold > 1",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.ConstitutionAmendmentThreshold = "2"

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "constitution amendment threshold too large",
},
{
name: "negative law quorum",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.LawQuorum = "-0.1"

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "law quorum must be positive",
},
{
name: "law quorum > 1",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.LawQuorum = "2"

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "law quorum too large",
},
{
name: "negative law threshold",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.LawThreshold = "-0.1"

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "law threshold must be positive",
},
{
name: "law threshold > 1",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.LawThreshold = "2"

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "law threshold too large",
},
{
name: "invalid voting period",
input: func() *v1.MsgUpdateParams {
Expand Down
44 changes: 43 additions & 1 deletion x/gov/keeper/tally.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

v1 "github.com/atomone-hub/atomone/x/gov/types/v1"
"github.com/atomone-hub/atomone/x/gov/types/v1beta1"
)

// TODO: Break into several smaller functions for clarity
Expand Down Expand Up @@ -85,6 +86,48 @@ func (keeper Keeper) Tally(ctx sdk.Context, proposal v1.Proposal) (passes bool,
// If there is not enough quorum of votes, the proposal fails
percentVoting := totalVotingPower.Quo(sdk.NewDecFromInt(totalBondedTokens))
quorum, _ := sdk.NewDecFromStr(params.Quorum)
threshold, _ := sdk.NewDecFromStr(params.Threshold)

// Check if a proposal message is an ExecLegacyContent message
if len(proposal.Messages) > 0 {
var sdkMsg sdk.Msg
for _, msg := range proposal.Messages {
if err := keeper.cdc.UnpackAny(msg, &sdkMsg); err == nil {
execMsg, ok := sdkMsg.(*v1.MsgExecLegacyContent)
if !ok {
continue
}
var content v1beta1.Content
if err := keeper.cdc.UnpackAny(execMsg.Content, &content); err != nil {
return false, false, tallyResults
}

// Check if proposal is a law or constitution amendment and adjust the
// quorum and threshold accordingly
switch content.(type) {
case *v1beta1.ConstitutionAmendmentProposal:
q, _ := sdk.NewDecFromStr(params.ConstitutionAmendmentQuorum)
if quorum.LT(q) {
quorum = q
}
t, _ := sdk.NewDecFromStr(params.ConstitutionAmendmentThreshold)
if threshold.LT(t) {
threshold = t
}
case *v1beta1.LawProposal:
q, _ := sdk.NewDecFromStr(params.LawQuorum)
if quorum.LT(q) {
quorum = q
}
t, _ := sdk.NewDecFromStr(params.LawThreshold)
if threshold.LT(t) {
threshold = t
}
}
}
}
}

if percentVoting.LT(quorum) {
return false, params.BurnVoteQuorum, tallyResults
}
Expand All @@ -95,7 +138,6 @@ func (keeper Keeper) Tally(ctx sdk.Context, proposal v1.Proposal) (passes bool,
}

// If more than 2/3 of non-abstaining voters vote Yes, proposal passes
threshold, _ := sdk.NewDecFromStr(params.Threshold)
if results[v1.OptionYes].Quo(totalVotingPower.Sub(results[v1.OptionAbstain])).GT(threshold) {
return true, false, tallyResults
}
Expand Down
Loading

0 comments on commit 1d342cc

Please sign in to comment.