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

feat(x/gov): burn deposit if no votes > BurnDepositNoThreshold #90

Open
wants to merge 12 commits into
base: giunatale/gov/dynamic-deposit
Choose a base branch
from
3 changes: 3 additions & 0 deletions proto/atomone/gov/v1/gov.proto
Original file line number Diff line number Diff line change
Expand Up @@ -346,4 +346,7 @@ message Params {
MinDepositThrottler min_deposit_throttler = 23;

MinInitialDepositThrottler min_initial_deposit_throttler = 24;

// Minimum proportion of No Votes for a proposal deposit to be burnt.
string burn_deposit_no_threshold = 25 [(cosmos_proto.scalar) = "cosmos.Dec"];
}
75 changes: 68 additions & 7 deletions tests/e2e/e2e_gov_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ func (s *IntegrationTestSuite) testGovCommunityPoolSpend() {
sendAmount := sdk.NewInt64Coin(uatoneDenom, 10_000_000) // 10atone
s.writeGovCommunitySpendProposal(s.chainA, sendAmount, recipient)

beforeSenderBalance, err := getSpecificBalance(chainAAPIEndpoint, sender, uatoneDenom)
s.Require().NoError(err)
beforeRecipientBalance, err := getSpecificBalance(chainAAPIEndpoint, recipient, uatoneDenom)
s.Require().NoError(err)

Expand All @@ -140,8 +142,20 @@ func (s *IntegrationTestSuite) testGovCommunityPoolSpend() {
submitGovFlags := []string{configFile(proposalCommunitySpendFilename)}
depositGovFlags := []string{strconv.Itoa(proposalCounter), depositAmount.String()}
voteGovFlags := []string{strconv.Itoa(proposalCounter), "yes"}
s.submitGovProposal(chainAAPIEndpoint, sender, proposalCounter, "CommunityPoolSpend", submitGovFlags, depositGovFlags, voteGovFlags, "vote")
s.submitGovProposal(chainAAPIEndpoint, sender, proposalCounter, "CommunityPoolSpend", submitGovFlags, depositGovFlags, voteGovFlags, "vote", govtypesv1beta1.StatusPassed)

// Check that sender is refunded with the proposal deposit
s.Require().Eventually(
func() bool {
afterSenderBalance, err := getSpecificBalance(chainAAPIEndpoint, sender, uatoneDenom)
s.Require().NoError(err)

return afterSenderBalance.IsEqual(beforeSenderBalance)
},
10*time.Second,
time.Second,
)
// Check that recipient received the community pool spend
s.Require().Eventually(
func() bool {
afterRecipientBalance, err := getSpecificBalance(chainAAPIEndpoint, recipient, uatoneDenom)
Expand All @@ -153,6 +167,53 @@ func (s *IntegrationTestSuite) testGovCommunityPoolSpend() {
time.Second,
)
})
s.Run("community pool spend with number of no votes exceeds threshold", func() {
s.fundCommunityPool()
chainAAPIEndpoint := fmt.Sprintf("http://%s", s.valResources[s.chainA.id][0].GetHostPort("1317/tcp"))
senderAddress, _ := s.chainA.validators[0].keyInfo.GetAddress()
sender := senderAddress.String()
recipientAddress, _ := s.chainA.validators[1].keyInfo.GetAddress()
recipient := recipientAddress.String()
sendAmount := sdk.NewInt64Coin(uatoneDenom, 10_000_000) // 10atone
s.writeGovCommunitySpendProposal(s.chainA, sendAmount, recipient)

beforeSenderBalance, err := getSpecificBalance(chainAAPIEndpoint, sender, uatoneDenom)
s.Require().NoError(err)
beforeRecipientBalance, err := getSpecificBalance(chainAAPIEndpoint, recipient, uatoneDenom)
s.Require().NoError(err)

// Gov tests may be run in arbitrary order, each test must increment proposalCounter to have the correct proposal id to submit and query
proposalCounter++
submitGovFlags := []string{configFile(proposalCommunitySpendFilename)}
depositGovFlags := []string{strconv.Itoa(proposalCounter), depositAmount.String()}
voteGovFlags := []string{strconv.Itoa(proposalCounter), "no"}
s.submitGovProposal(chainAAPIEndpoint, sender, proposalCounter, "CommunityPoolSpend", submitGovFlags, depositGovFlags, voteGovFlags, "vote", govtypesv1beta1.StatusRejected)

// Check that sender is not refunded with the proposal deposit
s.Require().Eventually(
func() bool {
afterSenderBalance, err := getSpecificBalance(chainAAPIEndpoint, sender, uatoneDenom)
s.Require().NoError(err)

return afterSenderBalance.Add(depositAmount).Add(initialDepositAmount).
IsEqual(beforeSenderBalance)
},
10*time.Second,
time.Second,
)
// Check that recipient didnt receive the community pool spend since the
// proposal was rejected
s.Require().Eventually(
func() bool {
afterRecipientBalance, err := getSpecificBalance(chainAAPIEndpoint, recipient, uatoneDenom)
s.Require().NoError(err)

return afterRecipientBalance.IsEqual(beforeRecipientBalance)
},
10*time.Second,
time.Second,
)
})
}

// testGovParamChange tests passing a param change proposal.
Expand All @@ -173,7 +234,7 @@ func (s *IntegrationTestSuite) testGovParamChange() {
submitGovFlags := []string{configFile(proposalParamChangeFilename)}
depositGovFlags := []string{strconv.Itoa(proposalCounter), depositAmount.String()}
voteGovFlags := []string{strconv.Itoa(proposalCounter), "yes"}
s.submitGovProposal(chainAAPIEndpoint, sender, proposalCounter, "cosmos.staking.v1beta1.MsgUpdateParams", submitGovFlags, depositGovFlags, voteGovFlags, "vote")
s.submitGovProposal(chainAAPIEndpoint, sender, proposalCounter, "cosmos.staking.v1beta1.MsgUpdateParams", submitGovFlags, depositGovFlags, voteGovFlags, "vote", govtypesv1beta1.StatusPassed)

newParams := s.queryStakingParams(chainAAPIEndpoint)
s.Assert().NotEqual(oldMaxValidator, newParams.Params.MaxValidators)
Expand All @@ -196,7 +257,7 @@ func (s *IntegrationTestSuite) testGovParamChange() {
submitGovFlags := []string{configFile(proposalParamChangeFilename)}
depositGovFlags := []string{strconv.Itoa(proposalCounter), depositAmount.String()}
voteGovFlags := []string{strconv.Itoa(proposalCounter), "yes"}
s.submitGovProposal(chainAAPIEndpoint, sender, proposalCounter, "atomone.photon.v1.MsgUpdateParams", submitGovFlags, depositGovFlags, voteGovFlags, "vote")
s.submitGovProposal(chainAAPIEndpoint, sender, proposalCounter, "atomone.photon.v1.MsgUpdateParams", submitGovFlags, depositGovFlags, voteGovFlags, "vote", govtypesv1beta1.StatusPassed)

newParams := s.queryPhotonParams(chainAAPIEndpoint)
s.Assert().True(newParams.Params.MintDisabled, "expected photon param mint disabled to be true")
Expand All @@ -207,7 +268,7 @@ func (s *IntegrationTestSuite) testGovParamChange() {
depositGovFlags = []string{strconv.Itoa(proposalCounter), depositAmount.String()}
voteGovFlags = []string{strconv.Itoa(proposalCounter), "yes"}
s.writePhotonParamChangeProposal(s.chainA, params.Params)
s.submitGovProposal(chainAAPIEndpoint, sender, proposalCounter, "atomone.photon.v1.MsgUpdateParams", submitGovFlags, depositGovFlags, voteGovFlags, "vote")
s.submitGovProposal(chainAAPIEndpoint, sender, proposalCounter, "atomone.photon.v1.MsgUpdateParams", submitGovFlags, depositGovFlags, voteGovFlags, "vote", govtypesv1beta1.StatusPassed)

newParams = s.queryPhotonParams(chainAAPIEndpoint)
s.Require().False(newParams.Params.MintDisabled, "expected photon param mint disabled to be false")
Expand All @@ -229,7 +290,7 @@ func (s *IntegrationTestSuite) testGovConstitutionAmendment() {
submitGovFlags := []string{configFile(proposalConstitutionAmendmentFilename)}
depositGovFlags := []string{strconv.Itoa(proposalCounter), depositAmount.String()}
voteGovFlags := []string{strconv.Itoa(proposalCounter), "yes"}
s.submitGovProposal(chainAAPIEndpoint, sender, proposalCounter, "gov/MsgSubmitProposal", submitGovFlags, depositGovFlags, voteGovFlags, "vote")
s.submitGovProposal(chainAAPIEndpoint, sender, proposalCounter, "gov/MsgSubmitProposal", submitGovFlags, depositGovFlags, voteGovFlags, "vote", govtypesv1beta1.StatusPassed)

s.Require().Eventually(
func() bool {
Expand Down Expand Up @@ -260,14 +321,14 @@ func (s *IntegrationTestSuite) submitLegacyGovProposal(chainAAPIEndpoint, sender
// Instead, the deposit is added to the "deposit" field of the proposal JSON (usually stored as a file)
// you can use `atomoned tx gov draft-proposal` to create a proposal file that you can use
// min initial deposit of 100uatone is required in e2e tests, otherwise the proposal would be dropped
func (s *IntegrationTestSuite) submitGovProposal(chainAAPIEndpoint, sender string, proposalID int, proposalType string, submitFlags []string, depositFlags []string, voteFlags []string, voteCommand string) {
func (s *IntegrationTestSuite) submitGovProposal(chainAAPIEndpoint, sender string, proposalID int, proposalType string, submitFlags []string, depositFlags []string, voteFlags []string, voteCommand string, expectedStatusAfterVote govtypesv1beta1.ProposalStatus) {
s.T().Logf("Submitting Gov Proposal: %s", proposalType)
sflags := submitFlags
s.submitGovCommand(chainAAPIEndpoint, sender, proposalID, "submit-proposal", sflags, govtypesv1beta1.StatusDepositPeriod)
s.T().Logf("Depositing Gov Proposal: %s", proposalType)
s.submitGovCommand(chainAAPIEndpoint, sender, proposalID, "deposit", depositFlags, govtypesv1beta1.StatusVotingPeriod)
s.T().Logf("Voting Gov Proposal: %s", proposalType)
s.submitGovCommand(chainAAPIEndpoint, sender, proposalID, voteCommand, voteFlags, govtypesv1beta1.StatusPassed)
s.submitGovCommand(chainAAPIEndpoint, sender, proposalID, voteCommand, voteFlags, expectedStatusAfterVote)
}

func (s *IntegrationTestSuite) verifyChainHaltedAtUpgradeHeight(c *chain, valIdx int, upgradeHeight int64) {
Expand Down
1 change: 1 addition & 0 deletions tests/e2e/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ func modifyGenesis(path, moniker, amountStr string, addrAll []sdk.AccAddress, de
govv1.DefaultTargetActiveProposals, sdk.NewCoins(sdk.NewCoin(denom, initialDepositAmount.Amount)), govv1.DefaultMinInitialDepositUpdatePeriod,
govv1.DefaultMinInitialDepositSensitivityTargetDistance, govv1.DefaultMinInitialDepositIncreaseRatio.String(),
govv1.DefaultMinInitialDepositDecreaseRatio.String(), govv1.DefaultTargetProposalsInDepositPeriod,
govv1.DefaultBurnDepositNoThreshold.String(),
),
)
govGenState.Constitution = "This is a test constitution"
Expand Down
16 changes: 13 additions & 3 deletions x/gov/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,18 @@ staking token of the chain.
* [Proposal submission](#proposal-submission)
* [Deposit](#deposit)
* [Vote](#vote)
* [Software Upgrade](#software-upgrade)
* [Quorum](#quorum)
* [State](#state)
* [Proposals](#proposals)
* [Parameters and base types](#parameters-and-base-types)
* [Deposit](#deposit-1)
* [ValidatorGovInfo](#validatorgovinfo)
* [Stores](#stores)
* [Stores](#stores)
* [Proposal Processing Queue](#proposal-processing-queue)
* [Legacy Proposal](#legacy-proposal)
* [Quorum Checks and Voting Period Extension](#quorum-checks-and-voting-period-extension)
* [Constitution](#constitution)
* [Law and Constitution Amendment Proposals](#law-and-constitution-amendment-proposals)
* [Last Min Deposit and Last Min Initial Deposit](#last-min-deposit-and-last-min-initial-deposit)
* [Messages](#messages)
* [Proposal Submission](#proposal-submission-1)
* [Deposit](#deposit-2)
Expand All @@ -61,6 +61,8 @@ staking token of the chain.
* [EndBlocker](#endblocker)
* [Handlers](#handlers)
* [Parameters](#parameters)
* [MinDepositThrottler (dynamic MinDeposit)](#mindepositthrottler-dynamic-mindeposit)
* [MinInitialDepositThrottler (dynamic MinInitialDeposit)](#mininitialdepositthrottler-dynamic-mininitialdeposit)
* [Client](#client)
* [CLI](#cli)
* [gRPC](#grpc)
Expand Down Expand Up @@ -199,6 +201,11 @@ The initial option set includes the following options:
`Abstain` option allows voters to signal that they do not intend to vote in
favor or against the proposal but accept the result of the vote.

At the end of the voting period, if the percentage of `No` votes (excluding
`Abstain` votes) is greater than a specific threshold (see [Burnable
Params](#burnable-params) section), then the proposal is considered as SPAM and
its deposit is burned.

#### Weighted Votes

[ADR-037](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-037-gov-split-vote.md)
Expand Down Expand Up @@ -270,6 +277,9 @@ have to sign governance transactions with the sensitive CometBFT PrivKey.
There are three parameters that define if the deposit of a proposal should
be burned or returned to the depositors.

* `BurnDepositNoThreshold` burns the proposal deposit at the end of the voting
period if the percentage of `No` votes (excluding `Abstain` votes) exceeds
the threshold.
* `BurnVoteQuorum` burns the proposal deposit if the proposal deposit if the vote does not reach quorum.
* `BurnProposalDepositPrevote` burns the proposal deposit if it does not enter the voting phase.

Expand Down
5 changes: 1 addition & 4 deletions x/gov/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,7 @@ func EndBlocker(ctx sdk.Context, keeper *keeper.Keeper) {
// remove from queue
keeper.RemoveFromQuorumCheckQueue(ctx, proposal.Id, endTime)
// check if proposal passed quorum
quorum, err := keeper.HasReachedQuorum(ctx, proposal)
if err != nil {
return false
}
quorum := keeper.HasReachedQuorum(ctx, proposal)
logMsg := "proposal did not pass quorum after timeout, but was removed from quorum check queue"
tagValue := types.AttributeValueProposalQuorumNotMet

Expand Down
6 changes: 1 addition & 5 deletions x/gov/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,7 @@ func InitGenesis(ctx sdk.Context, ak types.AccountKeeper, bk types.BankKeeper, k
quorumCheckEntry := v1.NewQuorumCheckQueueEntry(quorumTimeoutTime, data.Params.QuorumCheckCount)
quorum := false
if ctx.BlockTime().After(quorumTimeoutTime) {
var err error
quorum, err = k.HasReachedQuorum(ctx, *proposal)
if err != nil {
panic(err)
}
quorum = k.HasReachedQuorum(ctx, *proposal)
if !quorum {
// since we don't export the state of the quorum check queue, we can't know how many checks were actually
// done. However, in order to trigger a vote time extension, it is enough to have QuorumChecksDone > 0 to
Expand Down
119 changes: 119 additions & 0 deletions x/gov/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,20 @@ func (suite *KeeperTestSuite) TestMsgUpdateParams() {
expErr: true,
expErrMsg: "quorum too large",
},
{
name: "empty threshold",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.Threshold = ""

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "invalid threshold string: decimal string cannot be empty",
},
{
name: "invalid threshold",
input: func() *v1.MsgUpdateParams {
Expand Down Expand Up @@ -994,6 +1008,20 @@ func (suite *KeeperTestSuite) TestMsgUpdateParams() {
expErr: true,
expErrMsg: "constitution amendment quorum too large",
},
{
name: "empty constitution amendment threshold",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.ConstitutionAmendmentThreshold = ""

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "invalid constitution amendment threshold string: decimal string cannot be empty",
},
{
name: "negative constitution amendment threshold",
input: func() *v1.MsgUpdateParams {
Expand Down Expand Up @@ -1205,6 +1233,97 @@ func (suite *KeeperTestSuite) TestMsgUpdateParams() {
}
},
},
{
name: "empty burnDepositNoThreshold",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.BurnDepositNoThreshold = ""

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "invalid burnDepositNoThreshold string: decimal string cannot be empty",
},
{
name: "invalid burnDepositNoThreshold",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.BurnDepositNoThreshold = "abc"

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "invalid burnDepositNoThreshold string",
},
{
name: "burnDepositNoThreshold <= 1 - amendmentThreshold",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.LawThreshold = "0.8"
params1.ConstitutionAmendmentThreshold = "0.8"
params1.BurnDepositNoThreshold = "0.199"

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "burnDepositNoThreshold cannot be lower than 1-amendmentThreshold",
},
{
name: "burnDepositNoThreshold <= 1 - lawThreshold",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.ConstitutionAmendmentThreshold = "0.9"
params1.LawThreshold = "0.8"
params1.BurnDepositNoThreshold = "0.199"

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "burnDepositNoThreshold cannot be lower than 1-lawThreshold",
},
{
name: "burnDepositNoThreshold <= 1 - threshold",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.ConstitutionAmendmentThreshold = "0.8"
params1.LawThreshold = "0.8"
params1.Threshold = "0.6"
params1.BurnDepositNoThreshold = "0.399"

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "burnDepositNoThreshold cannot be lower than 1-threshold",
},
{
name: "burnDepositNoThreshold > 1",
input: func() *v1.MsgUpdateParams {
params1 := params
params1.BurnDepositNoThreshold = "2"

return &v1.MsgUpdateParams{
Authority: authority,
Params: params1,
}
},
expErr: true,
expErrMsg: "burnDepositNoThreshold too large",
},
}

for _, tc := range testCases {
Expand Down
Loading
Loading